3D 柱线上的趋势强度和方向指标·综合运用
(3/3)·从张量建模到MT5落地,普通K线如何在三维视角下提前暴露趋势强弱与反转信号
「把趋势强度指标搬进 MT5 的坑与解法」
Python 版趋势强度指标跑通后,下一步自然是塞进 MetaTrader 5 里实时用。MQL5 没有 pandas 和 numpy,滚动窗口那套得自己用循环硬写,活儿不算少但换来的是执行效率——实测该指标在 MT5 里的刷新明显快于 Python 回测脚本。 最棘手的是复刻 pandas 的 rolling 行为:MQL5 里每个柱都得在 OnCalculate 的循环里手动累加、衰减、归一,不能指望向量化。代价是代码啰嗦,收益是单帧计算开销极低,挂实盘不卡。 下面这段代码是雏形框架,定义了 3 个缓冲区和 3 个可调输入:平滑周期默认 20、动量周期 5、信号阈值 6.0。开 MT5 新建指标把这段贴进去,先确认能编译出「Trend Force / Direction / Signal」三条线,再回头填计算逻辑。外汇与贵金属波动剧烈,指标仅作概率参考,实盘请自担高风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TrendForceIndicator.mq5 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Your Name" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">3 class=class="str">"cmt">// Rendering buffers class="type">class="kw">double TrendForceBuffer[]; class="type">class="kw">double DirectionBuffer[]; class="type">class="kw">double SignalBuffer[]; class=class="str">"cmt">// Inputs input class="type">int InpMAPeriod = class="num">20; class=class="str">"cmt">// Smoothing period input class="type">int InpMomentumPeriod = class="num">5; class=class="str">"cmt">// Momentum period input class="type">class="kw">double InpSignalLevel = class="num">6.0; class=class="str">"cmt">// Signal level class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// 绑定三个数据缓冲区到指标索引0/class="num">1/class="num">2 SetIndexBuffer(class="num">0, TrendForceBuffer, INDICATOR_DATA); SetIndexBuffer(class="num">1, DirectionBuffer, INDICATOR_DATA); SetIndexBuffer(class="num">2, SignalBuffer, INDICATOR_DATA); class=class="str">"cmt">// 设置第0条 plots 的标签与蓝色线型(Trend Force) PlotIndexSetString(class="num">0, PLOT_LABEL, "Trend Force"); PlotIndexSetInteger(class="num">0, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(class="num">0, PLOT_LINE_COLOR, clrBlue); class=class="str">"cmt">// 第1条 plots 用直方图画方向 PlotIndexSetString(class="num">1, PLOT_LABEL, "Direction"); PlotIndexSetInteger(class="num">1, PLOT_DRAW_TYPE, DRAW_HISTOGRAM); class=class="str">"cmt">// 第2条 plots 用红线画信号 PlotIndexSetString(class="num">2, PLOT_LABEL, "Signal"); PlotIndexSetInteger(class="num">2, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(class="num">2, PLOT_LINE_COLOR, clrRed); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated,
在OnCalculate里拼出波动、动量、量能三因子
自定义指标的核心计算都发生在 OnCalculate 回调里,MT5 会把每根 K 线的 time、open、high、low、close 以及 tick_volume、volume、spread 以引用数组形式传进来,直接读不让改。 函数开头先拦数据量:rates_total 小于 InpMAPeriod 时直接 return(0),否则前面几根 bar 做均值会越界。start 定位也很讲究——有历史计算量就从上次末尾退一根开始,避免重复算整段。 波动率用 InpMAPeriod 窗口内的收益率平方和开根:先把每根 close[i-j] 对前一根的涨跌幅平方累加,再除以周期取 MathSqrt。动量则拿当前 close 减 InpMomentumPeriod 根之前的 close 做百分比,窗口不够就留 0。 量能趋势是当根 tick_volume 除以 InpMAPeriod 窗口的 tick_volume 均值,除零保护写成三元表达式。最后用 TimeToStruct 拆出小时数丢给 GetSessionCoefficient 拿时段系数,三因子加时段权重就齐了。外汇和贵金属杠杆高,因子仅是概率参考,实盘前请在 MT5 策略测试器跑一遍验证数值。
const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">// Check for data sufficiency if(rates_total < InpMAPeriod) class="kw">return(class="num">0); class=class="str">"cmt">// Calculation of components class="type">int start = (prev_calculated > class="num">0) ? prev_calculated - class="num">1 : class="num">0; for(class="type">int i = start; i < rates_total; i++) { class=class="str">"cmt">// Volatility class="type">class="kw">double volatility = class="num">0.0; if(i >= InpMAPeriod) { class="type">class="kw">double sum = class="num">0.0; for(class="type">int j = class="num">0; j < InpMAPeriod; j++) { class="type">class="kw">double change = (close[i-j] - close[i-j-class="num">1]) / close[i-j-class="num">1]; sum += change * change; } volatility = MathSqrt(sum / InpMAPeriod); } class=class="str">"cmt">// Momentum class="type">class="kw">double momentum = class="num">0.0; if(i >= InpMomentumPeriod) { momentum = (close[i] - close[i-InpMomentumPeriod]) / close[i-InpMomentumPeriod]; } class=class="str">"cmt">// Volume trend class="type">class="kw">double volume_ma = class="num">0.0; if(i >= InpMAPeriod) { for(class="type">int j = class="num">0; j < InpMAPeriod; j++) { volume_ma += tick_volume[i-j]; } volume_ma /= InpMAPeriod; } class="type">class="kw">double volume_trend = volume_ma != class="num">0 ? (class="type">class="kw">double)tick_volume[i] / volume_ma : class="num">0; class=class="str">"cmt">// Session ratio class="type">MqlDateTime dt; TimeToStruct(time[i], dt); class="type">class="kw">double session_coef = GetSessionCoefficient(dt.hour);
◍ 时段系数与趋势力归一化的落地写法
把波动率、动量绝对值和量能趋势乘起来之后,还要乘一个 session_coef 才能写进 TrendForceBuffer。这个系数直接按小时区分,亚盘(0–8点)给 0.7,欧盘(8–16点)给 1.0,美盘(16–24点)给 0.9,等于默认亚盘趋势力打折三成。 DirectionBuffer 的赋值很直白:momentum 大于 0 就取正趋势力,否则取负,方向和下单逻辑直接挂钩。SignalBuffer 则固定为外部输入的 InpSignalLevel,用作阈值线。 NormalizeTrendForce 把原始力压缩到 [3, 9] 区间,经验值 max_force 取 0.01,超出就按上限算。你在 MT5 里把 max_force 调小到 0.005,曲线对弱趋势会更敏感,但假信号可能增多,外汇和贵金属波动大,这种参数改动属于高风险操作,建议先开模拟盘验证。
class=class="str">"cmt">// Trend strength calculation TrendForceBuffer[i] = NormalizeTrendForce(volatility * MathAbs(momentum) * volume_trend) * session_coef; DirectionBuffer[i] = momentum > class="num">0 ? TrendForceBuffer[i] : -TrendForceBuffer[i]; class=class="str">"cmt">// Signal line SignalBuffer[i] = InpSignalLevel; class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get session ratio | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double GetSessionCoefficient(class="type">int hour) { if(hour >= class="num">0 && hour < class="num">8) class="kw">return class="num">0.7; class=class="str">"cmt">// Asian session if(hour >= class="num">8 && hour < class="num">16) class="kw">return class="num">1.0; class=class="str">"cmt">// European session if(hour >= class="num">16 && hour < class="num">24) class="kw">return class="num">0.9; class=class="str">"cmt">// American session class="kw">return class="num">1.0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Normalization of the trend strength indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double NormalizeTrendForce(class="type">class="kw">double force) { class=class="str">"cmt">// Simple normalization to range [class="num">3, class="num">9] class="type">class="kw">double max_force = class="num">0.01; class=class="str">"cmt">// Selected empirically class="kw">return class="num">3.0 + class="num">6.0 * (MathMin(force, max_force) / max_force); }
「别急着下结论」
把原型从 Python 搬到 MQL5 之后,最意外的收获是执行效率反而上来了——原本以为缺现成库是短板,结果手写逻辑让指标几乎实时吃进 tick,信号精度也没掉。作者在回测里压到的最优负担点是:每多一个参数、多一次校验就多一份延迟,最终靠砍掉冗余检查达成了速度与准度的平衡。 有经验的交易者反馈说,这东西帮他们换了个视角看盘,但它绝不是圣杯。外汇和贵金属这类高波动品种,任何技术工具都只是暴露了经典分析里看不见的动力学层面,用错参数照样亏。 后面作者想加自适应参数和信号分拣,甚至塞机器学习进去,但眼下这套已经证明:哪怕技术分析被翻过无数遍,从新角度重做一遍仍有空间。你真要验证,就把 Trend_Force_Reverse.mq5 拖进 MT5 跑一遍即刻报价,别光看讨论区吵架。