Trademinator 3:交易机器的崛起·进阶篇
◍ 适应度函数里的随机指标封装
这段逻辑把遗传算法里的适应度计算收了尾:先按策略分支调用 FFMA 拿 fitness 值,计数器 AmountStartsFF 自增,再把结果写回 Colony[0][chromos],最后用 Print 把时间、模块名和五个参数打到日志,方便你直接在 MT5 Experts 标签里抓每次迭代的染色体取值。 真正干活的是 FFStoch 函数。它先声明了持仓状态 FFtrig、方向 dir、开仓价 OpenPrice,以及当前资金 t、峰值 maxt、绝对回撤 aDD 和相对回撤 rDD(初始 0.000001 防除零)。外汇与贵金属波动剧烈,这类回撤跟踪必须带下限保护,否则后续比率计算会直接崩。 函数末尾用 iStochastic 建指标句柄,周期参数走 MathRound(par1*MaxStochPeriod)+1——意味着随机指标 K 周期被映射成 par1 乘最大周期再取整加一。你改 MaxStochPeriod 数值,就能让算法在更大范围内搜 Stoch 参数,实盘前建议先用策略测试器跑一遍看收敛速度。
Colony[class="num">4][chromos], Colony[class="num">5][chromos]); class="kw">break;}; class="kw">default: {ff=FFMA( 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;}; } AmountStartsFF++; Colony[class="num">0][chromos]=ff; Print(TimeToString(TimeCurrent()),";","GAModule:FitnessFunction", ";","strat=",strat,";","s=",s,";","optF=",optF, ";",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="type">class="kw">double FFStoch(class="type">class="kw">double par1,class="type">class="kw">double par2,class="type">class="kw">double par3,class="type">class="kw">double par4,class="type">class="kw">double par5) { class="type">int b; class="type">bool FFtrig=false; class=class="str">"cmt">//Is there an open position? class="type">class="kw">string dir=""; class=class="str">"cmt">//Direction of the open position class="type">class="kw">double OpenPrice; class=class="str">"cmt">//Position Open price class="type">class="kw">double t=cap; class=class="str">"cmt">//Current balance class="type">class="kw">double maxt=t; class=class="str">"cmt">//Maximum balance class="type">class="kw">double aDD=class="num">0.0; class=class="str">"cmt">//Absolute drawdown class="type">class="kw">double rDD=class="num">0.000001; class=class="str">"cmt">//Relative drawdown Stoch=iStochastic(s,tf,(class="type">int)MathRound(par1*MaxStochPeriod)+class="num">1,
用随机指标交叉回测权益曲线
这段逻辑把 Stochastic 的主线/信号线交叉当成触发事件,在历史区间里向前滚动复制缓冲,逐根 K 线重算一个模拟权益值 t。注意它并不是真下单,而是用杠杆、合约大小和开盘价差来近似每笔波动的权益增减,外汇和贵金属品种因高杠杆属性,这种近似对回测结果放大效应明显,实盘风险远高于纸面。 核心判定在第一层 if:当主线与信号线发生金叉或死叉时,若上一次触发标记 FFtrig 为真,就按方向用上一根开盘价 o[1] 与开仓价 OpenPrice 的差来更新 t。BUY 方向用 (o[1]-OpenPrice),SELL 方向反过来,再乘上 optF*leverage*dig/contractSize,dig 由 SYMBOL_DIGITS 通过 MathPow(10.0, digits) 算出,保证点位换算到报价精度。 回测里同时维护 maxt(峰值权益)和 aDD(绝对回撤),一旦 (maxt-t) 超过 aDD 就刷新;若 maxt>0 且 aDD/maxt 大于 rDD,则把 rDD 更新为当前回撤比例。也就是说 rDD 最终存的是这段历史里观测到的最大回撤率,是一个可直接拿去横向比较不同参数组的数据点。 触发开仓的方向由第二层 if 决定:只有死叉且前一根主线高于 StochTopLimit(par4*100.0)才记 dir="SELL" 并置 FFtrig=true,等待下一处交叉去结算。想验证就直接把下面代码贴进 MT5 脚本,改 par4 看 rDD 怎么变。
(class="type">int)MathRound(par2*MaxStochPeriod)+class="num">1, (class="type">int)MathRound(par3*MaxStochPeriod)+class="num">1,MODE_SMA,STO_CLOSECLOSE); StochTopLimit =par4*class="num">100.0; StochBottomLimit=par5*class="num">100.0; dig=MathPow(class="num">10.0,(class="type">class="kw">double)SymbolInfoInteger(s,SYMBOL_DIGITS)); leverage=AccountInfoInteger(ACCOUNT_LEVERAGE); contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE); b=MathMin(Bars(s,tf)-class="num">1-count-MaxMAPeriod,depth); for(from=b;from>=class="num">1;from--) class=class="str">"cmt">//Where to start copying of history { CopyBuffer(Stoch,class="num">0,from,count,StochBufferMain); CopyBuffer(Stoch,class="num">1,from,count,StochBufferSignal); if((StochBufferMain[class="num">0]>StochBufferSignal[class="num">0]&&StochBufferMain[class="num">1]<StochBufferSignal[class="num">1])|| (StochBufferMain[class="num">0]<StochBufferSignal[class="num">0]&&StochBufferMain[class="num">1]>StochBufferSignal[class="num">1])) { if(FFtrig==true) { if(dir=="BUY") { CopyOpen(s,tf,from,count,o); if(t>class="num">0) t=t+t*optF*leverage*(o[class="num">1]-OpenPrice)*dig/contractSize; else t=class="num">0; if(t>maxt) {maxt=t; aDD=class="num">0;} else if((maxt-t)>aDD) aDD=maxt-t; if((maxt>class="num">0)&&(aDD/maxt>rDD)) rDD=aDD/maxt; } if(dir=="SELL") { CopyOpen(s,tf,from,count,o); if(t>class="num">0) t=t+t*optF*leverage*(OpenPrice-o[class="num">1])*dig/contractSize; else t=class="num">0; if(t>maxt) {maxt=t; aDD=class="num">0;} else if((maxt-t)>aDD) aDD=maxt-t; if((maxt>class="num">0)&&(aDD/maxt>rDD)) rDD=aDD/maxt; } FFtrig=false; } } if(StochBufferMain[class="num">0]>StochBufferSignal[class="num">0]&&StochBufferMain[class="num">1]<StochBufferSignal[class="num">1]&&StochBufferMain[class="num">1]>StochTopLimit) { CopyOpen(s,tf,from,count,o); OpenPrice=o[class="num">1]; dir="SELL"; FFtrig=true; }
「遗传算法里怎么锁死品种与手数」
这段逻辑出现在策略优化器的参数落库阶段:染色体末位基因决定交易标的,倒数第二位决定策略编号,末位之外的 optF 基因记录最优仓位占比。实盘和回测对品种的处理分两套写法,回测写死 EURUSD / GBPUSD / USDCHF / USDJPY 四选一,实盘则改为从 MarketWatch 已选品种里动态取 SymbolName(z,true)。 z=(int)MathRound(Chromosome[GeneCount-1]*3) 把连续基因映射到 0~3 整数,switch 里 default 兜底回 EURUSD,避免越界基因导致空品种。若你要在 MT5 里跑多品种遗传优化,把注释块里的 SymbolsTotal(true) 写法取消注释、删掉硬编四币种即可,否则永远只能测那四个直盘。 外汇与贵金属杠杆品种回测收益不代表实盘,遗传搜索出的高 optF 在样本外可能快速衰减,切换实盘前建议先用历史中心做 Out-of-sample 验证。
class="type">void GetTrainResults() { strat=(class="type">int)MathRound(Chromosome[GeneCount-class="num">2]*StratCount); z=(class="type">int)MathRound(Chromosome[GeneCount-class="num">1]*class="num">3); 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;}; } optF=Chromosome[GeneCount]; class="kw">switch(strat) { case class="num">0: {GTRMA( Chromosome[class="num">1], Chromosome[class="num">2], Chromosome[class="num">3], Chromosome[class="num">4],
◍ 染色体如何映射到具体策略函数
遗传模块跑完一轮后,会把编码在 Chromosome 数组里的 5 个浮点参数,按 strat 分支派发到对应的策略函数。case 0 走 SAR、case 1 走 SAR 变体、case 2 走 Stochastic,其余一律回落到默认的移动均线逻辑,每个分支末尾都用 break 切断后续判断。 以 GTRMA 为例,par1 与 par2 会先乘上 MaxMAPeriod 再做 MathRound 取整,并强制 +1 作为均线周期。也就是说若 MaxMAPeriod=200、par1=0.05,实际短均线周期就是 11(0.05×200=10,+1 后取整)。这种映射把连续搜索空间压成了整数周期,避免 EA 去试 10.3 这类无意义参数。 NeedOpenMA 里只用 CopyBuffer 把最新 count 根 K 线的 ShortBuffer、LongBuffer 拉回本地,并不重算指标句柄。回测时若 from 与 count 设错,会出现缓冲区错位却无任何报错,建议开 MT5 用 Print 把数组首尾值打出来核对。外汇与贵金属杠杆高,遗传优化出的参数过拟合概率偏大,实盘前务必用 walk-forward 分段验证。
case class="num">1: {GTRSAR( Chromosome[class="num">1], Chromosome[class="num">2], Chromosome[class="num">3], Chromosome[class="num">4], Chromosome[class="num">5]) ; class="kw">break;}; case class="num">2: {GTRStoch(Chromosome[class="num">1], Chromosome[class="num">2], Chromosome[class="num">3], Chromosome[class="num">4], Chromosome[class="num">5]) ; class="kw">break;}; class="kw">default: {GTRMA( Chromosome[class="num">1], Chromosome[class="num">2], Chromosome[class="num">3], Chromosome[class="num">4], Chromosome[class="num">5]) ; class="kw">break;}; } Print(TimeToString(TimeCurrent()),";","GAModule:GetTrainResults", ";","strat=",strat,";","s=",s,";","optF=",optF, ";",Chromosome[class="num">1],";",Chromosome[class="num">2],";",Chromosome[class="num">3],";",Chromosome[class="num">4],";",Chromosome[class="num">5]); } class="type">void GTRMA(class="type">class="kw">double par1,class="type">class="kw">double par2,class="type">class="kw">double par3,class="type">class="kw">double par4,class="type">class="kw">double par5) { MAshort=iMA(s,tf,(class="type">int)MathRound(par1*MaxMAPeriod)+class="num">1,class="num">0,MODE_SMA,PRICE_OPEN); MAlong =iMA(s,tf,(class="type">int)MathRound(par2*MaxMAPeriod)+class="num">1,class="num">0,MODE_SMA,PRICE_OPEN); CopyBuffer(MAshort,class="num">0,from,count,ShortBuffer); CopyBuffer(MAlong, class="num">0,from,count,LongBuffer ); Print(TimeToString(TimeCurrent()),";","StrategyMA:GTRMA", ";","MAL=",(class="type">int)MathRound(par2*MaxMAPeriod)+class="num">1,";","MAS=",(class="type">int)MathRound(par1*MaxMAPeriod)+class="num">1); } class="type">bool NeedOpenMA() { CopyBuffer(MAshort,class="num">0,class="num">0,count,ShortBuffer); CopyBuffer(MAlong, class="num">0,class="num">0,count,LongBuffer );
别急着下结论
上面这段逻辑把双缓冲交叉变成了可执行的市价单触发:当长线缓冲上移且短线缓冲从下往上击穿长线时,挂空单;反向则挂多单。直接在 MT5 的 EA 里把 LongBuffer、ShortBuffer 接上你自己的均线周期,Print 行能让你在专家日志里看到 LB[0]=、SB[1]= 这类实时数值,确认交叉是否成立。 外汇与贵金属杠杆高,这类缓冲交叉在震荡市会连续假突破,实盘前先用策略测试器跑至少 3 个月 Tick 数据看回撤。 参数没调好之前,宁可让 return(true) 空跑几根 K 线,也别一交叉就 OpenPosition。
Print(TimeToString(TimeCurrent()),":","StrategyMA:NeedOpenMA", ",","LB[class="num">0]=",LongBuffer[class="num">0],":","LB[class="num">1]=",LongBuffer[class="num">1],":","SB[class="num">0]=",ShortBuffer[class="num">0],":","SB[class="num">1]=",ShortBuffer[class="num">1]); if(LongBuffer[class="num">0]>LongBuffer[class="num">1]&&ShortBuffer[class="num">0]>LongBuffer[class="num">0]&&ShortBuffer[class="num">1]<LongBuffer[class="num">1]) { request.type=ORDER_TYPE_SELL; OpenPosition(); class="kw">return(false); } if(LongBuffer[class="num">0]<LongBuffer[class="num">1]&&ShortBuffer[class="num">0]<LongBuffer[class="num">0]&&ShortBuffer[class="num">1]>LongBuffer[class="num">1]) { request.type=ORDER_TYPE_BUY; OpenPosition(); class="kw">return(false); } class="kw">return(true); }