从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I)·综合运用
📘

从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I)·综合运用

第 3/3 篇

◍ 用首tick锚定双向成交量矩阵

价格成交量图表的核心在于:系统启动后必须有一个基准价,否则后续所有报价都无法映射到数组。代码里用第一次成交 tick 的 last 价格作为 FirstPrice,缺了它整个内部表会直接崩。 存储策略上,保存成交量再去反查价格比逐价存量的速度快得多;但价格若下跌会产生负偏移,普通数组不支持负索引。原文给出的解法是把偏移量乘 2,负数取反再减 1,使涨跌报价交错排进同一正向数组——高亮的两行 pos 计算正是干这个。 公开函数 Update 只在 m_bUsing 为真且 CopyTicksRange 取到新成交 tick 时才跑,并用 memTimeTick 做去重游标。下面逐行拆关键片段: [CODE] inline virtual void Update(void) { MqlTick Tick[]; int i1, p1; if (m_bUsing == false) return; if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0) { if (m_Infos.CountInfos == 0) { macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time)); m_Infos.FirstPrice = Tick[0].last; } for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++); for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]); if (p1 == i1) return; m_Infos.memTimeTick = Tick[i1 - 1].time_msc; m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time); Redraw(); }; }; inline void SetMatrix(MqlTick &tick) { int pos;

if ((tick.last == 0)((tick.flags & (TICK_FLAG_BUYTICK_FLAG_SELL)) == (TICK_FLAG_BUYTICK_FLAG_SELL))) return;

pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2; pos = (pos >= 0 ? pos : (pos * -1) - 1); if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else [/CODE] 逐行拆解:

  • MqlTick Tick[] 声明接收 tick 的数组;i1, p1 存返回数量和游标。
  • if (m_bUsing == false) return; 未启用直接退出,避免空跑。
  • CopyTicksRange(..., m_Infos.memTimeTick) 从记录的毫秒时间拉成交 tick,>0 才有新数据。
  • if (m_Infos.CountInfos == 0) 首次进入,把首 tick 时间去秒后写 StartTime,并把 Tick[0].last 存为 FirstPrice——这就是锚。
  • 第一个 for 跳过与上期重复的 tick;第二个 for 从 p1 起把每个 tick 送进 SetMatrix。
  • pos = (int)((tick.last - FirstPrice)/PointPerTick)*2 算双向偏移;下一行负数转交错正索引。
  • 最后按 BUY/SELL flag 累加到 m_InfoAllVaP[pos] 的买/卖成交量。

绘制函数会反转这套索引把柱线摆回正确价位,且分两次 FillRectangle:先标买卖谁占优,再画量,避免小价位区间被大交易量挤变形。外汇与贵金属波动剧烈,这类 tick 级图表只反映历史成交分布,对后续方向仅作概率参考,实盘请自担高风险。

MQL5 / C++
class="kw">inline class="kw">virtual class="type">void Update(class="type">void)
{
  class="type">MqlTick Tick[];
  class="type">int i1, p1;
  if (m_bUsing == class="kw">false) class="kw">return;
  if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > class="num">0)
  {
    if (m_Infos.CountInfos == class="num">0)
    {
      macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[class="num">0].time));
      m_Infos.FirstPrice = Tick[class="num">0].last;
    }
    for (p1 = class="num">0; (p1 < i1) && (Tick[p1].time_msec == m_Infos.memTimeTick); p1++);
    for (class="type">int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
    if (p1 == i1) class="kw">return;
    m_Infos.memTimeTick = Tick[i1 - class="num">1].time_msec;
    m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - class="num">1].time);
    Redraw();
  };
};
class="kw">inline class="type">void SetMatrix(class="type">MqlTick &tick)
{
  class="type">int pos;
  if ((tick.last == class="num">0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) class="kw">return;
  pos = (class="type">int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * class="num">2;
  pos = (pos >= class="num">0 ? pos : (pos * -class="num">1) - class="num">1);
  if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else

「买卖量差怎么落进画布」

这段逻辑把每个价位的成交量拆成买、卖两边,并算出净差与总量。tick.flags 带 TICK_FLAG_SELL 时归到卖量,否则算买量;nVolDif 用买减卖转 long,正数为买压占优,负数则卖压占优。 重绘时先算 reason 缩放系数:当最大总量超过画布宽 m_WidthMax 时,用 m_WidthMax/(MaxVolume*1.0) 压缩,否则系数取 1.0。desl 取半跳点偏移,避免柱体压在价格线上。 循环里跳过零量价位,用 ChartTimePriceToXY 把「首价+奇偶交错偏移」映射成屏幕 y,再画两段矩形:右侧 6px 窄条按净差正负上买/卖色,左侧按总量×reason 宽度上柱色。外汇与贵金属波动剧烈,这种量差图只反映历史 tick 分布,对后续方向仅作概率参考,实盘须防滑点风险。 想验证就开 MT5 把 m_WidthMax 调到 50 看压缩效果,或把 ColorBuy/ColorSell 透明度调低,净差条会更醒目。

MQL5 / C++
if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume;
      m_InfoAllVaP[pos].nVolDif = (class="type">long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell);
      m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell;
      m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal);
      m_Infos.CountInfos = (m_Infos.CountInfos == class="num">0 ? class="num">1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos));
}
class="type">void Redraw(class="type">void)
{
      class="type">uint x, y, y1, p;
      class="type">class="kw">double reason = (class="type">class="kw">double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * class="num">1.0)) : class="num">1.0);
      class="type">class="kw">double desl = Terminal.GetPointPerTick() / class="num">2.0;
      Erase();
      p = m_WidthMax - class="num">8;
      for (class="type">int c0 = class="num">0; c0 <= m_Infos.CountInfos; c0++)
      {
            if (m_InfoAllVaP[c0].nVolTotal == class="num">0) class="kw">continue;
            ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & class="num">1) == class="num">1 ? -(c0 + class="num">1) : c0) / class="num">2))) + desl, x, y);
            y1 = y + Terminal.GetHeightBar();
            FillRectangle(p + class="num">2, y, p + class="num">8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > class="num">0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency));
            FillRectangle((class="type">int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency));
      }
      C_Canvas::Update();
};

从这套 OOP 代码起步练手

这套价格成交量指标本身逻辑不复杂,但源码全程用 100% 面向对象方式写,对想练 MQL5 中 OOP 的人是直接可拆的范本。 类封装、方法调用、对象生命周期都在代码里摆着,比看文档上手快。当前附带程序里已经塞了处于开发阶段的智能交易系统,能直接挂 MT5 跑起来看对象怎么在 tick 里被调。 外汇和贵金属波动受杠杆放大,用这类未定型 EA 只适合在策略测试器里验证,实盘前先想清楚风险。

◍ 顺着作者脉络继续挖资料

这套关于 MT5 多指标与 RAD 化开发的系列,前 6 篇早期标题叫「图表上的多个指标」,后来才统一更名,新人在用户档案里翻出版物列表时容易对不上号。实际可用链接补全:第 01 至 06 部分分别对应「理解概念」「首次实验」「为用户制定定义」「从 EA 开始」「将 MT5 转化为 RAD 系统(一)(二)」。 作者 Daniel Jose 的葡语原稿经平台翻译,除本系列外还有「市场模拟」套接字与 SQL 入门共 20+ 篇,以及从基础到中级的数组字符串专题。想落地验证,直接开 MT5 载入 EA_1.06.zip(3280.48 KB)跑第 06 篇的面板逻辑最快。 外汇与贵金属保证金交易杠杆高、滑点突发,用文中任何 EA 思路先开模拟盘验信号,实盘仓位倾向控制在权益 1% 内。顺着作者其他序列读,比单篇照搬更能拼出完整框架。

常见问题

在每根K线首tick时清空买卖量累加器,之后只在该K线内按成交方向累加,换线即重置,这样矩阵不会跨周期串味。
把量差乘一个显示系数再画到独立子窗口,或用语义色块(红绿面积)替代裸数字,肉眼一眼能辨背离。
可以,小布已内置AIGC量价诊断,打开对应品种页就能直接看多空量差与异常提示,你只管决策。
先动采集器和矩阵容器两个类,渲染类最后接,改一处打印日志验证再往下走,比全改稳。
顺着本篇的模块依赖图往后翻,先吃透成交量再碰信号合成,跳过中间篇容易看不懂变量来源。