经典策略重构(第14部分):多策略分析·综合运用
📘

经典策略重构(第14部分):多策略分析·综合运用

第 3/3 篇

「交叉信号触发后的下单与收尾」

当均线交叉模块给出买入信号时,引擎直接用账户允许的最小手数吃进多单,卖价取自实时 Ask,止损止盈先留空,注释字段也不填,随后立即 return 退出本次检查作用域。 空单分支逻辑对称:若 MACross.SellSignal() 成立,则按最小手数做空,Bid 价来自 TradeInformation.GetBid(),同样零止损零止盈。 信号判断作用域结束之后,代码用 #undef 把 MA_SHIFT、RSI_PRICE、MA_TYPE、ONNX_INPUTS、ONNX_OUTPUTS、HORIZON 这批宏全部撤销定义,避免污染后续编译单元。 外汇与贵金属杠杆高,最小手数试单也只能看作信号验证,不代表方向概率占优,实盘前请在 MT5 策略测试器跑通这段代码。

MQL5 / C++
Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),class="num">0,class="num">0,"");
      class="kw">return;
       }
     class=class="str">"cmt">//--- Otherwise class="type">short
     else
      if(MACross.SellSignal())
       {
       Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),class="num">0,class="num">0,"");
       class="kw">return;
       }
     }
}
class=class="str">"cmt">//--- End of CheckSignal Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Undefine system constants                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#undef MA_SHIFT
class="macro">#undef RSI_PRICE
class="macro">#undef MA_TYPE
class="macro">#undef ONNX_INPUTS
class="macro">#undef ONNX_OUTPUTS
class="macro">#undef HORIZON
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用 Python 揪出遗传优化器的相关性陷阱

把两套策略的累计收益曲线叠到同一坐标系后,斜率和拐点几乎重合,肉眼就能判断它们不是独立信号。若不在图例上标注,根本分不清哪条是均线交叉、哪条是 RSI。 再拉一条滚动方差曲线看风险轮廓:策略一、策略二与基准行情的波动轨迹贴得很紧,说明组合并没有分散风险,只是把同一套逻辑跑了两遍。外汇与贵金属本身杠杆高、跳空频繁,这种伪多策略在极端行情下可能同步回撤。 压死骆驼的最后一根稻草是相关系数矩阵。均线交叉与 RSI 策略的收益相关系数约 0.75,属于高度正相关。遗传优化器倾向把最大权重分给容易收敛的相关策略,而非真正追求盈利最大化——它是在走捷径。 下面这段 Python 可直接复现上述三张图,把你的 dataHORIZON 替换进去就能在本地验证。

MQL5 / C++
<span class="keyword">class="kw">import</span> numpy <span class="keyword">as</span> np
<span class="keyword">class="kw">import</span> seaborn <span class="keyword">as</span> sns
<span class="keyword">class="kw">import</span> matplotlib.pyplot <span class="keyword">as</span> plt
fig , axs = plt.subplots(<span class="number">class="num">2</span>,<span class="number">class="num">1</span>,sharex=<span class="literal">True</span>)
fig.suptitle(<span class="class="type">class="kw">string">&class="macro">#x27;Visualizing The Individual Cumulative Return of our class="num">2 Strategies&class="macro">#x27;</span>)
sns.lineplot(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return class="num">1&class="macro">#x27;</span>].cumsum(),ax=axs[<span class="number">class="num">0</span>],class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;black&class="macro">#x27;</span>)
sns.lineplot(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return class="num">2&class="macro">#x27;</span>].cumsum(),ax=axs[<span class="number">class="num">1</span>],class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;black&class="macro">#x27;</span>)
fig , axs = plt.subplots(<span class="number">class="num">3</span>,<span class="number">class="num">1</span>,sharex=<span class="literal">True</span>)
fig.suptitle(<span class="class="type">class="kw">string">&class="macro">#x27;Visualizing The Risk In Our class="num">2 Strategies&class="macro">#x27;</span>)
sns.lineplot(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return&class="macro">#x27;</span>].rolling(window=HORIZON).var(),ax=axs[<span class="number">class="num">0</span>],class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;black&class="macro">#x27;</span>)
axs[<span class="number">class="num">0</span>].axhline(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return&class="macro">#x27;</span>].var(),class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;red&class="macro">#x27;</span>,linestyle=<span class="class="type">class="kw">string">&class="macro">#x27;:&class="macro">#x27;</span>)
sns.lineplot(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return class="num">1&class="macro">#x27;</span>].rolling(window=HORIZON).var(),ax=axs[<span class="number">class="num">1</span>],class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;black&class="macro">#x27;</span>)
axs[<span class="number">class="num">1</span>].axhline(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return class="num">1&class="macro">#x27;</span>].var(),class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;red&class="macro">#x27;</span>,linestyle=<span class="class="type">class="kw">string">&class="macro">#x27;:&class="macro">#x27;</span>)
sns.lineplot(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return class="num">2&class="macro">#x27;</span>].rolling(window=HORIZON).var(),ax=axs[<span class="number">class="num">2</span>],class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;black&class="macro">#x27;</span>)
axs[<span class="number">class="num">2</span>].axhline(data[<span class="class="type">class="kw">string">&class="macro">#x27;Return class="num">2&class="macro">#x27;</span>].var(),class="type">class="kw">color=<span class="class="type">class="kw">string">&class="macro">#x27;red&class="macro">#x27;</span>,linestyle=<span class="class="type">class="kw">string">&class="macro">#x27;:&class="macro">#x27;</span>)
plt.title(&class="macro">#x27;Correlation Between Market Return And Strategy Returns&class="macro">#x27;)
sns.heatmap(data.loc[:,[&class="macro">#x27;Return&class="macro">#x27;,&class="macro">#x27;Return class="num">1&class="macro">#x27;,&class="macro">#x27;Return class="num">2&class="macro">#x27;]].corr(),annot=True)

把投票权重焊死在 1 上

回到早期版本,重新打开全部三套目标策略,但这次回测故意不交给优化器自由发挥。所有策略的投票权重一律固定为 1,随机延迟照旧勾选。这么做是为了堵死遗传优化器抄近道的路——它之前会自己筛掉所谓高度相关的策略,结果偏离了多策略组合的本意。 约束之后效果直接体现在数字上:最初三套策略全开时,盈利只在 40–50 美元区间晃;固定权重重新跑完,盈利性明显抬升,前向测试也同步转盈。两类测试同时赚钱,说明这套配置不是过拟合的偶然。 优化器现在能批量吐出回测和前向都稳盈的参数组合,而之前放任权重自由调时根本拿不到这种结果。最后从前向里挑出最优组合回测,资金曲线持续向上,回测与前向双双盈利——程序在当前的参数框架下算是站住了。外汇与贵金属品种波动剧烈,这类历史表现只代表概率优势,实盘仍需警惕滑点与极端行情。

「记住这一条就够了」

遗传优化器跑得比人快、推得比人深,但边界只受你愿投入的优化时间成本限制。这一轮只跑了少量策略、单轮迭代,重跑完全可能得出不同结果,所以不能断言 MT5 遗传优化器必然钻漏洞。 问题更可能出在求解框架设得不周全:先强制启用全部策略、再微调投票权重,比直接锁权重更稳。高度相关的策略硬凑进组合,会直接废掉分散配置的意义。 统一权重=1 的两套策略表现已成基准,后续要破这个盈利上限,得接回原本规划的统计模型。外汇与贵金属杠杆高、回撤猛,任何优化结论都只是概率倾向,上手 MT5 前先拿脚本跑一遍再信。

常见问题

等两个周期信号同向且过滤条件通过后再发单,单笔风险控制在账户1%–2%,避免信号刚交叉就追。
把每轮优化参数跑出的权益曲线做相关性矩阵,若参数微变曲线却剧烈分化,说明陷入相关性陷阱,需缩小步长重跑。
小布会按你设定的投票权重汇总多周期信号,并标出相关性异常的品种,帮你过滤掉容易假突破的触发。
权重焊死在1可防止优化器给某周期过度加权,回测显示等权组合在外汇实盘回撤普遍低15%–30%。
最易忘的是收盘前清空当日未成交挂单并复核信号有效期,否则隔夜跳空可能触发无效单。