您应当知道的 MQL5 向导技术(第 19 部分):贝叶斯(Bayesian)推理·进阶篇
「多头信号的后验概率触发逻辑」
这段 MQL5 实现把贝叶斯分类器落到了多单条件判断上:先用最近两根 K 线的第 8 类费率差分构造特征向量,再算它属于多头簇的后验概率。 后验值乘 100 后若超过 50.0,才认为价格倾向上涨;此时返回强度 = 2 × (概率-50),最高可到 100。低于阈值直接返回 0,不报任何偏向。 外汇与贵金属波动受杠杆与消息面放大,这套条件只给出概率倾向,实盘前请在 MT5 策略测试器用历史数据回测确认阈值是否适配你的品种周期。
class="type">int CSignalBAYES::LongCondition(class="type">void) { class="type">int result = class="num">0; vector _s_new, _s_old, _s; _s_new.CopyRates(m_symbol.Name(), m_period, class="num">8, class="num">0, m_series_size); _s_old.CopyRates(m_symbol.Name(), m_period, class="num">8, class="num">1, m_series_size); _s = _s_new - _s_old; matrix _series; _series.Init(_s.Size(), class="num">2); for(class="type">int i = class="num">0; i < class="type">int(_s.Size()); i++) { _series[i][class="num">0] = _s[i]; } SetCluster(_series); class="type">class="kw">double _cond = GetPosterior(m_long_cluster, _series); _cond *= class="num">100.0; class=class="str">"cmt">//printf(__FUNCSIG__ + " cond: %.2f", _cond); class=class="str">"cmt">//class="kw">return(result); if(_cond > class="num">50.0) { result = class="type">int(class="num">2.0 * (_cond - class="num">50.0)); } class="kw">return(result); }
◍ 用贝叶斯思路改写手数缩放
在单品种智能系统里,资金管理可以脱离固定规则,改用历史成交序列做基础。原生的「规模已优化」类按连续亏损次数反比缩手数,我们在此之上加一个聚集索引判断:当首选索引的似然低于可优化参数 m_condition 时,才触发缩手,而不是无脑递减。 所谓聚集索引,本质是净值曲线类型的简化标识——因为只做一个品种,赢、输(零利润也占一个索引但权重极低)就够描述结果分布。若按可用保证金比例跑出来的结果盈利,就回头看前一笔的零索引结果,估算下一笔再盈利的概率。 这里要引入第二个可优化参数:概率阈值。后验似然没过线,仓位规模就按亏损计数等比降,和原始类一致;过了线则按保证金比例自由缩放。减少因子、已投入保证金百分比等其余参数保持原样。 拿凯利准则 K = W – ((1 - W) / R) 对照,它看胜率 W 和盈利因子 R,有长期资本视角,但不随近期表现更新,且默许忽略交易成本与滑点。在代客理财或外部资本场景下,短期回撤敏感度往往决定委托存续,这是贝叶斯式 MM 相对 KC 的实操优势。外汇与贵金属杠杆高,任何 MM 类都只是控制回撤概率,不消除爆仓风险。 下面这段 CMoneyBAYES::Optimize 是核心:从历史里抓本品种成交,按 m_series_size 截序列,数连续亏损。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Optimizing lot size for open. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CMoneyBAYES::Optimize(class="type">int Type, class="type">class="kw">double lots) { class="type">class="kw">double lot = lots; class=class="str">"cmt">//--- calculate number of losses orders without a break if(m_decrease_factor > class="num">0) { class=class="str">"cmt">//--- select history for access HistorySelect(class="num">0, TimeCurrent()); class=class="str">"cmt">//--- class="type">int orders = HistoryDealsTotal(); class=class="str">"cmt">// total history deals class="type">int losses=class="num">0; class=class="str">"cmt">// number of consequent losing orders class=class="str">"cmt">//-- class="type">int size=class="num">0; matrix series; series.Init(fmin(m_series_size,orders), class="num">2); series.Fill(class="num">0.0); class=class="str">"cmt">//-- CDealInfo deal; class=class="str">"cmt">//--- for(class="type">int i = orders - class="num">1; i >= class="num">0; i--) { deal.Ticket(HistoryDealGetTicket(i)); if(deal.Ticket() == class="num">0) { Print("CMoneySizeOptimized::Optimize: HistoryDealGetTicket failed, no trade history"); break; } class=class="str">"cmt">//--- check symbol if(deal.Symbol() != m_symbol.Name()) class="kw">continue; class=class="str">"cmt">//--- check profit class="type">class="kw">double profit = deal.Profit(); class=class="str">"cmt">//-- series[size][class="num">0] = profit; size++; class=class="str">"cmt">//-- if(size >= m_series_size) break; if(profit<class="num">0.0) losses++; } class=class="str">"cmt">//-- series.Resize(size,class="num">2); SetCluster(series);
亏损后怎么砍仓位
上面这段 MT5 头寸管理逻辑,核心就一句话:当贝叶斯后验值 _cond 低于阈值 m_condition 时,按累计亏损比例缩减下单手数。
具体砍仓幅度由 losses / m_decrease_factor 决定,比如 m_decrease_factor=10、losses=3 时,手数直接乘以 0.7 再两位小数归一。外汇与贵金属杠杆高,这种递减机制可能降低连亏期的回撤速度,但无法消除爆仓风险。
代码里 NormalizeDouble(...,2) 锁死两位小数,避免经纪商最小手数规则外的脏值。开 MT5 把这段塞进 EA 的仓位函数,改 m_decrease_factor 跑一下历史回测,能直观看到不同因子下权益曲线的陡峭程度差异。
class="type">class="kw">double _cond = GetPosterior(Type, series); class=class="str">"cmt">//-- class=class="str">"cmt">//--- if(_cond < m_condition) lot = NormalizeDouble(lot - lot * losses / m_decrease_factor, class="num">2); } class=class="str">"cmt">//--- normalize and check limits ... class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- ... class=class="str">"cmt">//--- class="kw">return(lot); }
「用柱线幅度驱动尾随止损」
尾随破位类的核心,是把价格柱线的范围(最高减最低)当作波动性代理量。比起只盯收盘价位移,柱线幅度更能反映当前市场噪声级别,从而决定止损该挪多少才不至于被正常回扫洗掉。 贝叶斯尾随的思路是:取最近 m_series_size 根 K 线,分别拉出当前与上一根的高、低序列,算每根的范围差(_s = (H新−L新) − (H旧−L旧))。这组差值的分布被送进聚类,得到多头侧的后验概率 _cond。 实盘里若 _cond 大于 0.5 且现价相对基准浮盈已覆盖 delta(delta 设为最新一根幅度的一半),就把多单止损推到 price−delta。外汇与贵金属杠杆高,这种尾随仍可能在跳空时失效,概率上只是降低无效止损,不保证持仓一定走到止盈。 下面这段是多头尾随检查的 MQL5 实现骨架,重点看向量与矩阵的取数逻辑: //+------------------------------------------------------------------+
| // | Checking trailing stop and/or profit for long position. |
|---|
//+------------------------------------------------------------------+ bool CTrailingBAYES::CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp) { //--- check ... //--- ... //--- sl=EMPTY_VALUE; tp=EMPTY_VALUE; // vector _h_new, _h_old, _l_new, _l_old, _s; _h_new.CopyRates(m_symbol.Name(), m_period, COPY_RATES_HIGH, 0, m_series_size); // 取最新 m_series_size 根最高价 _h_old.CopyRates(m_symbol.Name(), m_period, COPY_RATES_HIGH, 1, m_series_size); // 取偏移1根的最高价序列 _l_new.CopyRates(m_symbol.Name(), m_period, COPY_RATES_LOW, 0, m_series_size); // 最新最低价序列 _l_old.CopyRates(m_symbol.Name(), m_period, COPY_RATES_LOW, 1, m_series_size); // 偏移1根最低价序列 _s = (_h_new - _l_new) - (_h_old - _l_old); // 逐根算范围差,得到波动变化向量 matrix _series; _series.Init(_s.Size(), 2); // 初始化两列矩阵 for(int i = 0; i < int(_s.Size()); i++) { _series[i][0] = _s[i]; // 把范围差填入第一列 } SetCluster(_series); // 聚类 double _cond = GetPosterior(m_long_cluster, _series); // 取多头聚类后验 // delta=0.5*(_h_new[0] - _l_new[0]); // 最新一根幅度的一半 if(_cond>0.5&&price-base>delta) // 后验过半且浮盈覆盖 delta { sl=price-delta; // 推止损 } //--- return(sl!=EMPTY_VALUE); } 把 m_series_size 从默认改到 20 或 50,在 MT5 策略测试器里跑一遍 EURUSD 15M,能直接看到止损触发频率随聚类阈值变化的差异。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">long position. |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">bool</span> CTrailingBAYES::CheckTrailingStopLong(CPositionInfo *position,<span class="keyword">class="type">class="kw">double</span> &sl,<span class="keyword">class="type">class="kw">double</span> &tp) { <span class="comment">class=class="str">"cmt">//--- check</span> ... <span class="comment">class=class="str">"cmt">//---</span> ... <span class="comment">class=class="str">"cmt">//---</span> sl=<span class="macro">EMPTY_VALUE</span>; tp=<span class="macro">EMPTY_VALUE</span>; <span class="comment">class=class="str">"cmt">//</span> <span class="keyword">vector</span> _h_new, _h_old, _l_new, _l_old, _s; _h_new.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="macro">COPY_RATES_HIGH</span>, <span class="number">class="num">0</span>, m_series_size); _h_old.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="macro">COPY_RATES_HIGH</span>, <span class="number">class="num">1</span>, m_series_size); _l_new.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="macro">COPY_RATES_LOW</span>, <span class="number">class="num">0</span>, m_series_size); _l_old.<span class="functions">CopyRates</span>(m_symbol.Name(), m_period, <span class="macro">COPY_RATES_LOW</span>, <span class="number">class="num">1</span>, m_series_size); _s = (_h_new - _l_new) - (_h_old - _l_old); <span class="keyword">matrix</span> _series; _series.Init(_s.Size(), <span class="number">class="num">2</span>); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < <span class="keyword">class="type">int</span>(_s.Size()); i++) { _series[i][<span class="number">class="num">0</span>] = _s[i]; } SetCluster(_series); <span class="keyword">class="type">class="kw">double</span> _cond = GetPosterior(m_long_cluster, _series); <span class="comment">class=class="str">"cmt">//</span> delta=<span class="number">class="num">0.5</span>*(_h_new[<span class="number">class="num">0</span>] - _l_new[<span class="number">class="num">0</span>]); <span class="keyword">if</span>(_cond><span class="number">class="num">0.5</span>&&price-base>delta) { sl=price-delta; } <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">return</span>(sl!=<span class="macro">EMPTY_VALUE</span>); }
◍ 三层类叠加的实盘回测对照
在 EURJPY 的 2022 年 H4 周期上跑了一套递进式验证:先用纯 BI 信号类(固定手数、无尾随),再叠上自编资金管理类,最后把信号、资金管理和尾随破位三个类全装上。三套均由向导式 EA 组装,退出完全靠开平仓信号,没有预设价格目标。 净值曲线显示,从单信号到「信号+MM」再到「信号+MM+尾随」的递进组合,整体表现呈正相关倾向。测试基于真实报价完成,但样本仅限 2022 单年 H4,更长周期独立回测才更具参考性,这点读者自己跑时得留神。 为做对照,另用函数库里的振荡器信号、优化后的 MM 类和均线尾随类组了「控制 EA」。控制组前两次跑输 BI 系统,仅第三次全类组合勉强追上。替代类的选取本身大幅左右了结果,但核心结论清晰:BI 体系与标准库类在性能上存在可观测差异,外汇与贵金属品种波动剧烈,此类差异不代表任何稳健盈利承诺,开 MT5 用策略测试器复跑 2022 H4 即可核对。
把工具请下神坛
前面几节把贝叶斯推理塞进了 MQL5 向导生成的三类支柱系统里,本质只是搭了个能跑的原型。它没碰多维数据聚集、也没上复杂核函数,留出的口子很大。 想继续挖,就得拉长时间窗、用高质量 tick 数据回测,才可能拿到一点边际优势。外汇和贵金属杠杆高,原型思路不等于实盘胜率,验证前先想清楚爆仓成本。 向导生成的 EA 仍是试错 cheapest 的沙盒,别把它当圣杯;跑通、改参、再看样本外表现,才是正经路数。