经典策略重构(第14部分):多策略分析·综合运用
「交叉信号触发后的下单与收尾」
当均线交叉模块给出买入信号时,引擎直接用账户允许的最小手数吃进多单,卖价取自实时 Ask,止损止盈先留空,注释字段也不填,随后立即 return 退出本次检查作用域。 空单分支逻辑对称:若 MACross.SellSignal() 成立,则按最小手数做空,Bid 价来自 TradeInformation.GetBid(),同样零止损零止盈。 信号判断作用域结束之后,代码用 #undef 把 MA_SHIFT、RSI_PRICE、MA_TYPE、ONNX_INPUTS、ONNX_OUTPUTS、HORIZON 这批宏全部撤销定义,避免污染后续编译单元。 外汇与贵金属杠杆高,最小手数试单也只能看作信号验证,不代表方向概率占优,实盘前请在 MT5 策略测试器跑通这段代码。
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 可直接复现上述三张图,把你的 data 和 HORIZON 替换进去就能在本地验证。
<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 前先拿脚本跑一遍再信。