Dr. Tradelove 或我如何不再担忧并创建一个自训练 EA 交易·进阶篇
用空闲保证金倒推可下手数
EA 里最容易被忽略的一步,是根据账户当前空闲保证金反算能开多大仓位,而不是写死 volume。下面这段 GetPossibleLots 就是干这个的:先把请求手数暂设为 1.0,按买卖方向取 BID 或 ASK 填充价格,再用 OrderCheck 拿到这笔 1 手占用的保证金 check.margin。 最后用空闲保证金除以单手保证金,NormalizeDouble 到 2 位小数返回。你在 MT5 里把这段贴进 EA,改一下返回精度,就能直观看到「当前环境最多扛几手」——外汇和贵金属杠杆浮动时,这个数每小时都可能变。 相对回撤的统计藏在 InitRelDD 和 GetRelDD 里。InitRelDD 从 2000 年拉一遍历史成交,把每笔 profit 累加到余额上,顺手记录历史最高余额 maxBalance;GetRelDD 则用 (maxBalance-当前余额)/maxBalance 给出相对回撤比例,数值落在 0~1 之间。 别把正态当圣经 这段初始化把基准时间写死成 D'2000.01.01 00:00:00',实盘换经纪商时历史可能没这么长,HistorySelect 返回空不会报错但 maxBalance 就只剩当前余额,回撤计算会失真。开 MT5 跑一遍,确认自己的账户历史起点再改这个常量。
class="type">class="kw">double GetPossibleLots() { request.volume=class="num">1.0; if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); else request.price=SymbolInfoDouble(s,SYMBOL_ASK); OrderCheck(request,check); class="kw">return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,class="num">2)); } class="type">void InitRelDD() { class="type">ulong DealTicket; class="type">class="kw">double curBalance; prevBT[class="num">0]=D&class="macro">#x27;class="num">2000.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;; TimeToStruct(prevBT[class="num">0],prevT); curBalance=AccountInfoDouble(ACCOUNT_BALANCE); maxBalance=curBalance; HistorySelect(D&class="macro">#x27;class="num">2000.01.class="num">01 class="num">00:class="num">00:class="num">00&class="macro">#x27;,TimeCurrent()); for(class="type">int i=HistoryDealsTotal();i>class="num">0;i--) { DealTicket=HistoryDealGetTicket(i); curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT); if(curBalance>maxBalance) maxBalance=curBalance; } } class="type">class="kw">double GetRelDD() { if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); class="kw">return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance); }
◍ 双均线交叉与回撤触发的再优化逻辑
这段初始化把历史深度 depth 写死成 10000 根 K 线、单次拷贝 count 设成 2,意味着遗传优化只基于万根历史与最近两根均线值做决策,参数若不动,EA 在 MT5 回测里就会按这个窗口跑。 OnTick 里先靠 isNewBars() 拦截新柱,再用 CopyBuffer 把短、长均线各取 2 个点进 ShortBuffer / LongBuffer。多头翻转条件是 LongBuffer[0]>LongBuffer[1] 且 ShortBuffer[0]>LongBuffer[0] 且 ShortBuffer[1]<LongBuffer[1]——即长均线走平向上、短均线从下穿上并确认。 若已有多单且触发翻转,ClosePosition() 平掉并置 trig=true;空头对称处理。trig 成立后再查 GetRelDD()>maxDD,也就是实时回撤超阈值才调用 GA() 重新跑神经网络遗传优化。外汇与贵金属杠杆高,这种自动重优化可能频繁交易,实盘前务必用策略测试器验证触发频率。
TimeToStruct(prevBT[class="num">0],prevT); class=class="str">"cmt">//--- historical depth(should be set since the optimisation is based on historical data) depth=class="num">10000; class=class="str">"cmt">//--- copies at a time(should be set since the optimisation is based on historical data) count=class="num">2; ArrayResize(LongBuffer,count); ArrayResize(ShortBuffer,count); ArrayInitialize(LongBuffer,class="num">0); ArrayInitialize(ShortBuffer,class="num">0); class=class="str">"cmt">//--- calling the neural network genetic optimisation function GA(); class=class="str">"cmt">//--- getting the optimised neural network parameters and other variables GetTrainResults(); class=class="str">"cmt">//--- getting the account drawdown InitRelDD(); class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if(isNewBars()==true) { class="type">bool trig=false; CopyBuffer(MAshort,class="num">0,class="num">0,count,ShortBuffer); CopyBuffer(MAlong,class="num">0,class="num">0,count,LongBuffer); 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]) { if(PositionsTotal()>class="num">0) { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { ClosePosition(); trig=true; } } } 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]) { if(PositionsTotal()>class="num">0) { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { ClosePosition(); trig=true; } } } if(trig==true) { class=class="str">"cmt">//--- if the account drawdown has exceeded the allowable value: if(GetRelDD()>maxDD) { class=class="str">"cmt">//--- calling the neural network genetic optimisation function GA();
「均线交叉触发与遗传优化器参数」
双均线通道的入场判定直接写在缓冲区比较里:当长期均线走平向上(LongBuffer[0]>LongBuffer[1])、短期均线已上穿长期均线且前一根还未穿(ShortBuffer[0]>LongBuffer[0] 且 ShortBuffer[1]<LongBuffer[1])时,挂空单;反向条件对称则挂多单。外汇与贵金属杠杆高,这种交叉信号在历史回测中胜率随品种波动,实盘触发前建议在 MT5 策略测试器跑一遍 EURUSD 的 M15 数据。 遗传优化器 GA() 的群体规模由基因数决定:GeneCount = OptParamCount+2,染色体数固定为 GeneCount*11。搜索域锁死在 0.0–1.0,步长 0.0001,最大搜索模式 OptimizeMethod=2,连续 100 个 epoch 无改善才停。 UGA() 里七项概率分别是复制100%、自然突变1%、人工突变1%、基因借用1%、交叉1%、边界位移0.5%、单基因突变1%。想加快收敛就把 Epoch 降到 50,但过拟合风险会明显上升。
class=class="str">"cmt">//--- getting the optimised neural network parameters and other variables GetTrainResults(); class=class="str">"cmt">//--- readings of the drawdown will from now on be based on the current balance instead of the maximum balance maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); } } CopyBuffer(MAshort,class="num">0,class="num">0,count,ShortBuffer); CopyBuffer(MAlong,class="num">0,class="num">0,count,LongBuffer); 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(); } 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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Preparing and calling the genetic optimizer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void GA() { class=class="str">"cmt">//--- number of genes(equal to the number of optimised variables), class=class="str">"cmt">//--- all of them should be specified in the FitnessFunction()) GeneCount =OptParamCount+class="num">2; class=class="str">"cmt">//--- number of chromosomes in a colony ChromosomeCount=GeneCount*class="num">11; class=class="str">"cmt">//--- minimum search range RangeMinimum =class="num">0.0; class=class="str">"cmt">//--- maximum search range RangeMaximum =class="num">1.0; class=class="str">"cmt">//--- search pitch Precision =class="num">0.0001; class=class="str">"cmt">//--- class="num">1 is a minimum, anything else is a maximum OptimizeMethod =class="num">2; ArrayResize(Chromosome,GeneCount+class="num">1); ArrayInitialize(Chromosome,class="num">0); class=class="str">"cmt">//--- number of epochs without any improvement Epoch =class="num">100; class=class="str">"cmt">//--- ratio of replication, natural mutation, artificial mutation, gene borrowing, class=class="str">"cmt">//--- crossingover, interval boundary displacement ratio, every gene mutation probabilty, % UGA(class="num">100.0,class="num">1.0,class="num">1.0,class="num">1.0,class="num">1.0,class="num">0.5,class="num">1.0); }
遗传算法里怎么挑货币对和均线周期
在 MT5 用遗传算法跑 EA 优化时,染色体最后一列基因被用来选交易品种,映射逻辑是把该基因值乘以 12 后四舍五入,得到 0~12 的整数再 switch 到具体符号。注意 case 0 和 case 1 都指向 AUDUSD,等于变相提高了澳美被选中的概率,这种冗余映射在回测里可能让品种分布失真。 短长均线周期则由前两根基因决定:MAshort 取 Colony[1][chromos]*MaxMAPeriod 四舍五入加 1,MAlong 取 Colony[2][chromos]*MaxMAPeriod 加 1,都走 iMA 的 MODE_SMA + PRICE_OPEN。dig 用 SymbolInfoInteger 拿品种小数位再算 10 的幂,后续处理价格精度会用到。 别把正态当圣经 品种选择写死在代码里意味着算法自由度和你预设的映射强相关。改 case 分支或调整乘數,回测中品种偏好可能完全变样,外汇与贵金属杠杆高、滑点随机,实盘前务必在策略测试器里重跑几代基因验证分布。
class=class="str">"cmt">//| anything can be optimised but it is necessary | class=class="str">"cmt">//| to carefully monitor the number of genes | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void FitnessFunction(class="type">int chromos) { class="type">int b; class=class="str">"cmt">//--- is there an open position? class="type">bool trig=false; class=class="str">"cmt">//--- direction of an open position class="type">class="kw">string dir=""; class=class="str">"cmt">//--- opening price class="type">class="kw">double OpenPrice=class="num">0; class=class="str">"cmt">//--- intermediary between a gene colony and optimised parameters class="type">int z; class=class="str">"cmt">//--- current balance class="type">class="kw">double t=cap; class=class="str">"cmt">//--- maximum balance class="type">class="kw">double maxt=t; class=class="str">"cmt">//--- absolute drawdown class="type">class="kw">double aDD=class="num">0; class=class="str">"cmt">//--- relative drawdown class="type">class="kw">double rDD=class="num">0.000001; class=class="str">"cmt">//--- fitness function proper class="type">class="kw">double ff=class="num">0; class=class="str">"cmt">//--- GA is selecting a pair z=(class="type">int)MathRound(Colony[GeneCount-class="num">1][chromos]*class="num">12); class="kw">switch(z) { case class="num">0: {s="AUDUSD"; class="kw">break;}; case class="num">1: {s="AUDUSD"; class="kw">break;}; case class="num">2: {s="EURAUD"; class="kw">break;}; case class="num">3: {s="EURCHF"; class="kw">break;}; case class="num">4: {s="EURGBP"; class="kw">break;}; case class="num">5: {s="EURJPY"; class="kw">break;}; case class="num">6: {s="EURUSD"; class="kw">break;}; case class="num">7: {s="GBPCHF"; class="kw">break;}; case class="num">8: {s="GBPJPY"; class="kw">break;}; case class="num">9: {s="GBPUSD"; class="kw">break;}; case class="num">10: {s="USDCAD"; class="kw">break;}; case class="num">11: {s="USDCHF"; class="kw">break;}; case class="num">12: {s="USDJPY"; class="kw">break;}; class="kw">default: {s="EURUSD"; class="kw">break;}; } MAshort=iMA(s,tf,(class="type">int)MathRound(Colony[class="num">1][chromos]*MaxMAPeriod)+class="num">1,class="num">0,MODE_SMA,PRICE_OPEN); MAlong =iMA(s,tf,(class="type">int)MathRound(Colony[class="num">2][chromos]*MaxMAPeriod)+class="num">1,class="num">0,MODE_SMA,PRICE_OPEN); dig=MathPow(class="num">10.0,(class="type">class="kw">double)SymbolInfoInteger(s,SYMBOL_DIGITS)); class=class="str">"cmt">//--- GA is selecting the optimal F
◍ 双均线交叉的历史回测记账逻辑
这段逻辑把遗传算法挑出的 optF(最优适应度)和账户杠杆、合约大小一起抓出来,作为历史回测的盈亏计算基底。leverage 取自 AccountInfoInteger(ACCOUNT_LEVERAGE),contractSize 用 SymbolInfoDouble 拿 SYMBOL_TRADE_CONTRACT_SIZE,外汇与贵金属品种这两值差异很大,回测前必须确认。 核心循环从 b 根历史 bar 往前扫,b 由 Bars(s,tf)-1-count-MaxMAPeriod 与 depth 取小得到,保证均线缓冲不越界。每一根都先 CopyBuffer 把长短均线最近 count 根拷进来,再用 LongBuffer[0]>LongBuffer[1] 且 ShortBuffer[0]>LongBuffer[0] 且 ShortBuffer[1]<LongBuffer[1] 判定短上穿长的做空触发。 触发后若 trig 为 false 就记 OpenPrice=o[1]、dir="SELL" 并置 trig;若已有多头持仓则平多转空,用 t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize 累加净值,同时跟踪 maxt 与 aDD,rDD 取最大回撤比例。反向 LongBuffer[0]<LongBuffer[1] 且短下穿长则对称处理 BUY。 直接在 MT5 策略测试器里把这段嵌进 EA 的 OnTester 类函数,把 depth 设成 5000、count 设成 30,跑 EURUSD 的 H1 就可能看到 rDD 落在 0.2~0.4 区间;杠杆若从 100 调到 500,t 的波动幅度会明显放大,但最大回撤比例未必线性增长,值得手动验证。
optF=Colony[GeneCount][chromos]; leverage=AccountInfoInteger(ACCOUNT_LEVERAGE); contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE); b=MathMin(Bars(s,tf)-class="num">1-count-MaxMAPeriod,depth); class=class="str">"cmt">//--- for a neural network using historical data - where the data is copied from for(from=b;from>=class="num">1;from--) { CopyBuffer(MAshort,class="num">0,from,count,ShortBuffer); CopyBuffer(MAlong,class="num">0,from,count,LongBuffer); 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]) { if(trig==false) { CopyOpen(s,tf,from,count,o); OpenPrice=o[class="num">1]; dir="SELL"; trig=true; } else { 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; OpenPrice=o[class="num">1]; dir="SELL"; trig=true; } } } 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]) { if(trig==false) { CopyOpen(s,tf,from,count,o); OpenPrice=o[class="num">1]; dir="BUY"; trig=true; } else { if(dir=="SELL") { CopyOpen(s,tf,from,count,o);