Trademinator 3:交易机器的崛起(基础篇)
◍ 自优化EA的骨架与一次实盘回测
一套自优化 EA 的逻辑起点,是先框死「能在历史数据上做什么、能在真实盘上做什么」。历史侧要覆盖四件事:从候选对象里挑最佳策略、挑最佳品种、按杠杆校正存款敞口、搜指标最优参数;实盘侧则只负责建平仓、定仓位、以及判断何时该重新跑优化。 作者给这套逻辑加了硬限制:决策只在新柱触发,平仓只看指标信号而不挂止盈止损;待优化指标缓冲参数锁死在 5 个以内,因为内置指标缓冲上限摆在那,想用多缓冲自定义指标就得去 main.mq5 里改 OptParamCount 变量。重新优化的触发条件他个人设为「余额亏损超过初始化预设值」,适应度函数则是「模拟余额最大化且回撤低于某阈值」——这两处你都能换成自己的规则。 跑 2011 年 H1、初始入金 10000 美元、杠杆 1:100 的回测,EA 先选了 USDCHF 上 SAR 突破、占用 28% 存款,亏超 20% 后重优化;第二次切 USDJPY 用满 98% 存款,一天内又亏到第三次优化;第三次回到 USDCHF 做 MA 金死叉,空仓熬到 10 月 20 日才抓到死叉,单笔盈利 3535.51 美元,把前期亏损捞回大半,年末权益 9461.26 美元、总净利润 -538.74 美元。 外汇与贵金属保证金交易高风险,小波动就可能吞掉初始存款乃至追加资金,上面这套回测结果不代表实盘倾向盈利,仅说明遗传自优化框架在极端年份也可能长时间不进场。
EA 里用回撤触发遗传重优化的开关逻辑
这段 OnTick 骨架展示了一个自适应性 EA 的常见做法:不在每根 K 线无脑交易,而是先判新柱,再按 strat 变量分流到不同平仓 / 开仓判定函数。strat 支持 0/1/2 三种策略分支,分别对应 MA、SAR、Stochastic 的 NeedClose / NeedOpen 系列函数,default 兜底回 MA。 真正值得抄的是回撤保护那段:当 GetRelDD() 超过 maxDD 时,才调用 GA() 做遗传优化、拉取新参数,并把 maxBalance 重置为当前权益,后续回撤从新基准算。这样能在浮亏扩大时倾向自动重训,而不是死扛原参数。外汇与贵金属杠杆高,这类自动重优化若参数空间设错,可能加速亏损。 下面把代码逐行拆一下,方便你直接粘进 MT5 改条件验证。 void OnTick() 定义每次报价触发的主函数。 if(isNewBars()==true) 只在出现新柱时跑逻辑,避免同根 K 线重复计算。 trig=false; 先清零触发标志。 switch(strat) 按策略编号选择平仓条件函数,case 0/1/2 分别调 NeedCloseMA/SAR/Stoch,default 回 MA。 if(trig==true) 若平仓条件满足,进入下一步。 if(GetRelDD()>maxDD) 实时回撤超阈值才动手。 GA(); 启动遗传优化。 GetTrainResults(); 取回优化后的参数。 maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); 把回撤基准切成当前余额。 第二个 switch(strat) 同理判开仓函数 NeedOpen*。 Print(...) 打日志:时间、标记、当前 strat,便于观察分流。
<span class="keyword">class="type">void</span> <span class="functions">OnTick</span>() { <span class="keyword">if</span>(isNewBars()==<span class="keyword">true</span>) { trig=<span class="keyword">false</span>; <span class="keyword">class="kw">switch</span>(strat) { <span class="keyword">case</span> <span class="number">class="num">0</span>: {trig=NeedCloseMA() ; <span class="keyword">class="kw">break</span>;}; <span class="comment">class=class="str">"cmt">//The number of case strings must be equal to the number of strategies</span> <span class="keyword">case</span> <span class="number">class="num">1</span>: {trig=NeedCloseSAR() ; <span class="keyword">class="kw">break</span>;}; <span class="keyword">case</span> <span class="number">class="num">2</span>: {trig=NeedCloseStoch(); <span class="keyword">class="kw">break</span>;}; <span class="keyword">class="kw">default</span>: {trig=NeedCloseMA() ; <span class="keyword">class="kw">break</span>;}; } <span class="keyword">if</span>(trig==<span class="keyword">true</span>) { <span class="keyword">if</span>(GetRelDD()>maxDD) <span class="comment">class=class="str">"cmt">//If a balance drawdown is above the max allowed value:</span> { GA(); <span class="comment">class=class="str">"cmt">//Call the genetic optimization function</span> GetTrainResults(); <span class="comment">class=class="str">"cmt">//Get the optimized parameters</span> maxBalance=<span class="functions">AccountInfoDouble</span>(<span class="keyword">ACCOUNT_BALANCE</span>); <span class="comment">class=class="str">"cmt">//Now count the drawdown not from the balance maximum...</span> <span class="comment">class=class="str">"cmt">//...but from the current balance</span> } } <span class="keyword">class="kw">switch</span>(strat) { <span class="keyword">case</span> <span class="number">class="num">0</span>: {trig=NeedOpenMA() ; <span class="keyword">class="kw">break</span>;}; <span class="comment">class=class="str">"cmt">//The number of case strings must be equal to the number of strategies</span> <span class="keyword">case</span> <span class="number">class="num">1</span>: {trig=NeedOpenSAR() ; <span class="keyword">class="kw">break</span>;}; <span class="keyword">case</span> <span class="number">class="num">2</span>: {trig=NeedOpenStoch(); <span class="keyword">class="kw">break</span>;}; <span class="keyword">class="kw">default</span>: {trig=NeedOpenMA() ; <span class="keyword">class="kw">break</span>;}; } <span class="functions">Print</span>(<span class="functions">TimeToString</span>(<span class="functions">TimeCurrent</span>()),<span class="class="type">class="kw">string">";"</span>,<span class="class="type">class="kw">string">"Main:OnTick:isNewBars(true)"</span>, <span class="class="type">class="kw">string">";"</span>,<span class="class="type">class="kw">string">"strat="</span>,strat); } }
「SAR翻穿开盘价就平掉现有仓位」
下面这段逻辑专门处理抛物线转向指标(SAR)与K线开盘价的相对位置突变。当最新一根K线的SAR从上穿开盘价变成下穿,或反过来,就认为趋势方向可能翻转,若有持仓则直接平仓。 CopyBuffer把SAR指标缓冲区的count根数值读进SARBuffer,CopyOpen把对应周期s、tf下的开盘价读进数组o。Print那行把当前时间、SAR[0]/[1]和Open[0]/[1]打到日志,方便你手动核对翻转信号有没有误触发。
| 核心判断在if((SARBuffer[0]>o[0]&&SARBuffer[1]<o[1]) | (SARBuffer[0]<o[0]&&SARBuffer[1]>o[1])):两根K线内SAR与开盘价的上下关系对调,即视为触发。此时若PositionsTotal()>0,调用ClosePosition()平掉并return(true)。外汇与贵金属波动大,这种翻转平仓只能降低被套概率,不保证避开反向行情。 |
|---|
另一段残片显示,当trig为true且GetRelDD()超过maxDD(实时回撤超阈值),会跑GA()做遗传优化、GetTrainResults()取新参数,并把maxBalance重置为当前权益,相当于从亏损坑里重新算回撤基准。FitnessFunction则是给优化器用的适应度函数,可挑策略、品种、仓位比和指标缓冲参数——具体能优化什么,看你EA里塞了哪些基因位。
class="type">bool NeedCloseSAR() { CopyBuffer(SAR,class="num">0,class="num">0,count,SARBuffer); CopyOpen(s,tf,class="num">0,count,o); Print(TimeToString(TimeCurrent()),";","StrategySAR:NeedCloseSAR", ";","SAR[class="num">0]=",SARBuffer[class="num">0],";","SAR[class="num">1]=",SARBuffer[class="num">1],";","Open[class="num">0]=",o[class="num">0],";","Open[class="num">1]=",o[class="num">1]); if((SARBuffer[class="num">0]>o[class="num">0]&&SARBuffer[class="num">1]<o[class="num">1])|| (SARBuffer[class="num">0]<o[class="num">0]&&SARBuffer[class="num">1]>o[class="num">1])) { if(PositionsTotal()>class="num">0) { ClosePosition(); class="kw">return(true); } } class="kw">return(false); } if(trig==true) { if(GetRelDD()>maxDD) class=class="str">"cmt">//If the balance drawdown is above the max allowed one: { GA(); class=class="str">"cmt">//Call the genetic optimization function GetTrainResults(); class=class="str">"cmt">//Get optimized parameters maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); class=class="str">"cmt">//Now count the drawdown not from the balance maximum... class=class="str">"cmt">//...but from the current balance } } class="type">void FitnessFunction(class="type">int chromos) class=class="str">"cmt">//A fitness function for the genetic optimizer:... class=class="str">"cmt">//...selects a strategy, symbol, deposit share,... class=class="str">"cmt">//...parameters of indicator buffers;... class=class="str">"cmt">//...you can optimize whatever you need, but...
◍ 遗传算法怎么挑品种和策略
这段代码片段展示了用遗传算法(GA)在 MT5 里动态选定交易品种、策略与仓位系数的核心逻辑。注意 Colony 是二维种群矩阵,chromos 是当前染色体索引,GeneCount 是基因总数。 double ff=0.0; 先声明适应度函数变量,GA 靠它评判个体优劣。strat 用 Colony[GeneCount-2] 乘 StratCount 再四舍五入,决定调用哪套策略;z 用 Colony[GeneCount-1] 乘 3 取整,把 0~3 映射到 EURUSD、GBPUSD、USDCHF、USDJPY 四个直盘,默认落回 EURUSD。外汇与贵金属属高风险品种,实盘切换前务必在策略测试器跑过。 optF=Colony[GeneCount][chromos] 取出该个体分配的资金比例系数。随后 switch(strat) 分流:case 0 跑 FFMA、case 1 跑 FFSAR、case 2 跑 FFStoch,每个函数吃 Colony[1]~Colony[5] 五个基因当参数。注释里点明 case 数量必须和策略总数一致,否则会漏算适应度。 测试模式写死四个符号方便回测;真要挂实盘,把前面 switch(z) 整段注释掉,改用 SymbolsTotal(true) 从 MarketWatch 窗口已勾选品种里随机抽一个 SymbolName(z,true)。改完编译进 MT5,开个 EURUSD 的 2020—2023 年 M15 回测,看 GA 是否倾向把 optF 压在 0.1~0.3 区间来控回撤。
class="type">class="kw">double ff=class="num">0.0; class=class="str">"cmt">//The fitness function strat=(class="type">int)MathRound(Colony[GeneCount-class="num">2][chromos]*StratCount); class=class="str">"cmt">//GA selects a strategy class=class="str">"cmt">//For EA testing mode use the following code... z=(class="type">int)MathRound(Colony[GeneCount-class="num">1][chromos]*class="num">3); class=class="str">"cmt">//GA selects a symbol class="kw">switch(z) { case class="num">0: {s="EURUSD"; class="kw">break;}; case class="num">1: {s="GBPUSD"; class="kw">break;}; case class="num">2: {s="USDCHF"; class="kw">break;}; case class="num">3: {s="USDJPY"; class="kw">break;}; class="kw">default: {s="EURUSD"; class="kw">break;}; } class=class="str">"cmt">//..for real mode, comment the previous code and uncomment the following one(symbols are selected in the MarketWatch window) class=class="str">"cmt">/* z=(class="type">int)MathRound(Colony[GeneCount-class="num">1][chromos]*(SymbolsTotal(true)-class="num">1));class=class="str">"cmt">//GA selects a symbol s=SymbolName(z,true); */ optF=Colony[GeneCount][chromos]; class=class="str">"cmt">//GA selects a deposit share class="kw">switch(strat) { case class="num">0: {ff=FFMA( Colony[class="num">1][chromos], class=class="str">"cmt">//The number of case strings must be equal to the number of strategies Colony[class="num">2][chromos], Colony[class="num">3][chromos], Colony[class="num">4][chromos], Colony[class="num">5][chromos]); class="kw">break;}; case class="num">1: {ff=FFSAR( Colony[class="num">1][chromos], Colony[class="num">2][chromos], Colony[class="num">3][chromos], Colony[class="num">4][chromos], Colony[class="num">5][chromos]); class="kw">break;}; case class="num">2: {ff=FFStoch(Colony[class="num">1][chromos], Colony[class="num">2][chromos], Colony[class="num">3][chromos],