从头开始开发智能交易系统(第 14 部分):添加价格成交量(II)(基础篇)
📘

从头开始开发智能交易系统(第 14 部分):添加价格成交量(II)(基础篇)

第 1/3 篇

用成交量异常给价格行为加一层过滤

在价格行为策略里直接吃裸 K,容易被震荡市的假突破洗出去。MetaTrader 5 的成交量数据来自券商 tick 聚合,虽不是交易所真实成交量,但能反映当前品种的关注度变化,对识别突破有效性有参考价值。 做法是给原有信号加一个成交量阈值:只有当当前 bar 的成交量明显高于近 N 根均值时,才允许触发入场。这样能过滤掉一批低参与度的毛刺信号,外汇与贵金属杠杆高、滑点风险大,这类过滤只能降低概率上的噪音,不保证胜率。 下面这段取近 20 根平均成交量并判断当前是否放量的逻辑,可以直接塞进 EA 的订单判定前: [CODE] double VolMA(int period=20) { double sum=0; for(int i=0;i<period;i++) sum+=iVolume(NULL,0,i); return sum/period; } bool IsVolumeSpike(double mult=1.5) { double cur=iVolume(NULL,0,0); return cur > VolMA(20)*mult; } [/CODE] 逐行看:VolMA 函数用 for 循环把最近 20 根 bar 的 iVolume 累加再除 20,得到平均成交量;IsVolumeSpike 取当前 bar 成交量,若大于均值乘 1.5 倍则返回 true。把 mult 从 1.5 调到 2.0,信号会更稀疏,读者可以在 MT5 策略测试器里换参数跑一遍 EURUSD 的 M15 看信号数变化。

MQL5 / C++
class="type">class="kw">double VolMA(class="type">int period=class="num">20) {
   class="type">class="kw">double sum=class="num">0;
   for(class="type">int i=class="num">0;i<period;i++) sum+=iVolume(NULL,class="num">0,i);
   class="kw">return sum/period;
}
class="type">bool IsVolumeSpike(class="type">class="kw">double mult=class="num">1.5) {
   class="type">class="kw">double cur=iVolume(NULL,class="num">0,class="num">0);
   class="kw">return cur > VolMA(class="num">20)*mult;
}

「EA 界面瑕疵与补强方向」

现有 EA 已经累积了多篇前文加入的辅助交易资源,但在可视化和窗口缩放上仍有硬伤:某些情形下图表元素会错位、重叠,直到手动强制刷新屏幕才恢复,虽不直接影响下单,却容易造成盯盘误读。 还缺几类能直接给决策提供支撑的信息块,比如更清晰的持仓状态与参数反馈。这些单看都是细节,但在连续交易里往往就是漏看一笔的关键。 这一轮改造不碰核心逻辑,只做两件事:修掉渲染层面的小缺陷,把缺失的可视信息以更合理的方式补上。开 MT5 载入当前 EA,先故意拖拽图表窗口大小,就能复现错位现象,作为验证起点。

◍ 给价格成交量指标加时间窗口痕迹

在 60 分钟主图表上,原价格成交量指标只能绑定该时间帧,想看更低周期堆积区的即时变化就得手动切到 5、10 或 30 分钟图去挪分析起点,实战里很不顺手。尤其是止损触发造成的回踩,往往 5 分钟内就完成,图表上拖出长上下影线——这种买方或卖方测试动作,光看总量根本分不清是试探还是真反转。 之前实现的指标靠改分析起点来读相对走势,但期货短线客常用短周期,外汇和贵金属杠杆更高、影线假突破更密,靠切图调点容易漏掉瞬时痕迹。这里的高风险在于:影线测试信号在贵金属跳空时段可能完全失效,必须结合实时盘口验证。 解决办法不是重写指标,而是对原有对象类做一处小修改:让它在给定时间段内直接画出已发生交易的痕迹。这样不用切时间帧,也能在堆积区快速识别是测试还是趋势转折的概率倾向。

用 30 分钟滑动窗捕捉逐分钟成交量

跟踪系统每分钟捕获一次报价,但最长只保留 30 分钟窗口。定义 def_MaxTrailMinutes 30 后,当时间走到第 31 分钟,最早那 1 分钟的成交量数据就被移出数组,始终维持 30 根「分钟柱」在内存里滚动。 高亮的三行是滑动窗核心:macroGetMin(tick.time) 取当前分钟序号,若大于 29 就减去 30 实现回绕;memMomentum 变化时把对应槽位 m_TrailG30[...].nVolume[] 清零,再用 += tick.volume 把该分钟内的逐笔成交量累加进对应价格档。 直接按最大成交量比例缩放绘制,在清淡或急拉急跌时窄带成交量容易看不见。原文试过按时间远近用浅色调,也试过给每个时段分配固定颜色,但活跃时段仍会混淆,所以最终用一条纯净轨迹,配合「时序与交易」和价格行为一起读。 改显示效果只需动 Redraw 里高亮段,其余类成员不用碰。编译挂到 MT5 图表上,就能看到 30 分钟成交量轨迹随行情滚动——外汇和贵金属波动大、杠杆高,滑窗参数请先在模拟盘验证再上实盘。

MQL5 / C++
class="macro">#define def_MaxTrailMinutes    class="num">30
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
        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));
        m_Infos.Momentum = macroGetMin(tick.time);
        m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - class="num">1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
        if (m_Infos.memMomentum != m_Infos.Momentum)
        {
                for (class="type">int c0 = class="num">0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = class="num">0;
                m_Infos.memMomentum = m_Infos.Momentum;
        }
        m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume;
}
m_Infos.Momentum = macroGetMin(tick.time);
m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - class="num">1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
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;
        class="type">ulong       uValue;

「成交量剖面图的右侧绘制落点」

这段逻辑负责把成交量剖面(Volume Profile)画在图表右侧区域,每根价格档位对应一个纵向矩形条。先调用 Erase() 清掉上一帧画布,再把基准 x 坐标 p 设为 m_WidthMax - 8,也就是主图可视宽度右侧留 8 像素边距。 循环遍历 m_Infos.CountInfos 个价格档,若某档总成交量 m_InfoAllVaP[c0].nVolTotal 为 0 直接跳过,避免空档占位。用 ChartTimePriceToXY 把‘首价 + 奇偶交错偏移 + desl’换算成屏幕 x/y,y1 则是 y 加上单根 K 线高度。 画两类矩形:8 像素宽的净买卖色块(买量大于 0 用 ColorBuy,否则 ColorSell),以及按 nVolTotal * reason 缩放的历史总成交量条(ColorBars)。reason 是像素/手数的换算系数,调它能改变剖面厚度。 高亮段单独累加 def_MaxTrailMinutes 根分钟线在该档的成交量 uValue,用 royalBlue 半透明覆盖,等于把近期 30 分钟内的活跃档位标出来。最后 C_Canvas::Update() 提交,MT5 上就能看到右侧动态剖面。外汇与贵金属波动剧烈,这类可视化仅辅助判断筹码分布,不预示方向。

MQL5 / C++
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));
   uValue = class="num">0;
   for (class="type">int c1 = class="num">0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0];
   FillRectangle((class="type">int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency));
}
C_Canvas::Update();

常见问题

在原有形态判定外叠加成交量突变判定,只有放量伴随的突破才视为高概率信号,缩量突破倾向忽略。
检查绘制坐标是否随时间轴重算,并给标签加防重叠偏移;补强方向是分离指标层与界面层逻辑。
可以,小布能按你设的阈值在成交量异动时推送提醒,并把异常时段标到价格图上供回看。
以当前时间回推 30 根分钟 K 线为窗口,每根成交量单独记录,窗口随新 K 线推进而滑动重算。
右侧落点应绑定最新价所在价位而非固定坐标,按成交密集区实时对齐,避免随缩放错位。