
Trademinator 3:交易机器的崛起
序言
很久以前,有一个论坛 (MQL5) 发布了两篇文章:"《遗传算法 - 很简单!》(作者 joo)及《Dr. Tradelove...》(作者是我)。在第一篇文章中,作者为我们提供了一款优化您所需任何内容的强大工具,其中就包括交易策略 - 一种通过 MQL5 语言实施的遗传算法。
利用此算法,我在第二篇文章中就尝试着根据它来开发一个自优化的 EA 交易。此文以下述任务的公式化描述作为结尾:要创建不仅可以选择某特定交易系统的最佳参数、亦可选择所有已制定策略中最佳策略的 EA 交易(当然是自优化型)。我们来看看是否可能,如果可能,又该怎样做到。
交易机器人相关纪事
首先,我们制定自优化 EA 交易的一般要求。
它要能够(基于历史数据):
- 从所述对象中选择最佳策略
- 选择最佳金融工具
- 选择带杠杆率校正的交易的最佳存款数额
- 选择选定策略中指标的最佳参数
而且,在真实交易中,它还要能够:
- 建仓和平仓
- 选择仓位大小
- 就是否需要新的优化做出决定
下图所示为提议 EA 交易的原理图。
带有界限的详图,位于随附文件 Scheme_en 中。
要记住,掌握无限是不可能的,我们在 EA 交易逻辑中引入限制。我们认为(重要):
- EA 交易要在 新柱出现(在我们选择的任何时间表)时做出交易决定。
- 根据但不限于 p.1,该 EA 交易只会根据指标信号关闭交易,而不是利用获利、止损以及相应的追踪止损。
- 开始新优化的条件:余额亏损高于该水平初始化期间的预设值。请注意,这只是我个人的条件,你们当中的每一位都可以选择自己特定的条件。
- 一个适应度函数模型会根据历史情况进行交易并实现模型化余额最大化,前提是模拟交易余额相对亏损低于某特定预设水平。这里也要注意,这只是我个人的适应度函数,而您可以选择自己的特定适应度函数。
- 我们把指标缓冲区待优化参数的数量——但三个常规参数(策略、工具及存款额)除外,限制为 5 个。该限制逻辑上服从于内置技术指标的指标缓冲区最大数量。如果您要对使用带有大量指标缓冲区的自定义指标的策略进行描述,则只需将 main.mq5 文件中的 OptParamCount 变量更改为想要的数量。
现在,要求已规定,限制亦已选定,您可以查看实施所有这些内容的代码了。
我们从一切得以运行的函数开始。
void OnTick() { if(isNewBars()==true) { trig=false; switch(strat) { case 0: {trig=NeedCloseMA() ; break;}; //The number of case strings must be equal to the number of strategies case 1: {trig=NeedCloseSAR() ; break;}; case 2: {trig=NeedCloseStoch(); break;}; default: {trig=NeedCloseMA() ; break;}; } if(trig==true) { if(GetRelDD()>maxDD) //If a balance drawdown is above the max allowed value: { GA(); //Call the genetic optimization function GetTrainResults(); //Get the optimized parameters maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); //Now count the drawdown not from the balance maximum... //...but from the current balance } } switch(strat) { case 0: {trig=NeedOpenMA() ; break;}; //The number of case strings must be equal to the number of strategies case 1: {trig=NeedOpenSAR() ; break;}; case 2: {trig=NeedOpenStoch(); break;}; default: {trig=NeedOpenMA() ; break;}; } Print(TimeToString(TimeCurrent()),";","Main:OnTick:isNewBars(true)", ";","strat=",strat); } }
这里是什么?如图中所示,我们查看每个价格变动,而不管是否存在新柱。如果有新柱,那么,在知道现在选定哪个策略的情况下,我们调用其特定函数,以检查是否有未平仓位,必要时予以平仓。假设现在的最佳突破策略为 SAR,则会分别调用 NeedCloseSAR 函数:
bool NeedCloseSAR() { CopyBuffer(SAR,0,0,count,SARBuffer); CopyOpen(s,tf,0,count,o); Print(TimeToString(TimeCurrent()),";","StrategySAR:NeedCloseSAR", ";","SAR[0]=",SARBuffer[0],";","SAR[1]=",SARBuffer[1],";","Open[0]=",o[0],";","Open[1]=",o[1]); if((SARBuffer[0]>o[0]&&SARBuffer[1]<o[1])|| (SARBuffer[0]<o[0]&&SARBuffer[1]>o[1])) { if(PositionsTotal()>0) { ClosePosition(); return(true); } } return(false); }
任何平仓函数都必须是布尔型,并在平仓时返回 true。这就允许 OnTick() 函数的下一个代码块来决定是否需要新的优化:
if(trig==true) { if(GetRelDD()>maxDD) //If the balance drawdown is above the max allowed one: { GA(); //Call the genetic optimization function GetTrainResults(); //Get optimized parameters maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); //Now count the drawdown not from the balance maximum... //...but from the current balance } }
获取当前余额亏损,并与最大允许亏损进行对比。如果它超过了最大值,则运行新的优化 (GA())。GA() 函数反过来又会调用 EA 交易的核心 - GAModule.mqh 模块的适应度函数 FitnessFunction(int chromos):
void FitnessFunction(int chromos) //A fitness function for the genetic optimizer:... //...selects a strategy, symbol, deposit share,... //...parameters of indicator buffers;... //...you can optimize whatever you need, but... //...watch carefully the number of genes { double ff=0.0; //The fitness function strat=(int)MathRound(Colony[GeneCount-2][chromos]*StratCount); //GA selects a strategy //For EA testing mode use the following code... z=(int)MathRound(Colony[GeneCount-1][chromos]*3); //GA selects a symbol switch(z) { case 0: {s="EURUSD"; break;}; case 1: {s="GBPUSD"; break;}; case 2: {s="USDCHF"; break;}; case 3: {s="USDJPY"; break;}; default: {s="EURUSD"; break;}; } //..for real mode, comment the previous code and uncomment the following one (symbols are selected in the MarketWatch window) /* z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));//GA selects a symbol s=SymbolName(z,true); */ optF=Colony[GeneCount][chromos]; //GA selects a deposit share switch(strat) { case 0: {ff=FFMA( Colony[1][chromos], //The number of case strings must be equal to the number of strategies Colony[2][chromos], Colony[3][chromos], Colony[4][chromos], Colony[5][chromos]); break;}; case 1: {ff=FFSAR( Colony[1][chromos], Colony[2][chromos], Colony[3][chromos], Colony[4][chromos], Colony[5][chromos]); break;}; case 2: {ff=FFStoch(Colony[1][chromos], Colony[2][chromos], Colony[3][chromos], Colony[4][chromos], Colony[5][chromos]); break;}; default: {ff=FFMA( Colony[1][chromos], Colony[2][chromos], Colony[3][chromos], Colony[4][chromos], Colony[5][chromos]); break;}; } AmountStartsFF++; Colony[0][chromos]=ff; Print(TimeToString(TimeCurrent()),";","GAModule:FitnessFunction", ";","strat=",strat,";","s=",s,";","optF=",optF, ";",Colony[1][chromos],";",Colony[2][chromos],";",Colony[3][chromos],";",Colony[4][chromos],";",Colony[5][chromos]); }
根据当前选定的策略,具体到某特定策略的适应度函数计算模块会被调用。比如说,GA 选择了一个随机指标,则 FFStoch () 会被调用,而指标缓冲区的最优化参数会被传递给它:
double FFStoch(double par1,double par2,double par3,double par4,double par5) { int b; bool FFtrig=false; //Is there an open position? string dir=""; //Direction of the open position double OpenPrice; //Position Open price double t=cap; //Current balance double maxt=t; //Maximum balance double aDD=0.0; //Absolute drawdown double rDD=0.000001; //Relative drawdown Stoch=iStochastic(s,tf,(int)MathRound(par1*MaxStochPeriod)+1, (int)MathRound(par2*MaxStochPeriod)+1, (int)MathRound(par3*MaxStochPeriod)+1,MODE_SMA,STO_CLOSECLOSE); StochTopLimit =par4*100.0; StochBottomLimit=par5*100.0; dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS)); leverage=AccountInfoInteger(ACCOUNT_LEVERAGE); contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE); b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth); for(from=b;from>=1;from--) //Where to start copying of history { CopyBuffer(Stoch,0,from,count,StochBufferMain); CopyBuffer(Stoch,1,from,count,StochBufferSignal); if((StochBufferMain[0]>StochBufferSignal[0]&&StochBufferMain[1]<StochBufferSignal[1])|| (StochBufferMain[0]<StochBufferSignal[0]&&StochBufferMain[1]>StochBufferSignal[1])) { if(FFtrig==true) { if(dir=="BUY") { CopyOpen(s,tf,from,count,o); if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0; if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t; if((maxt>0)&&(aDD/maxt>rDD)) rDD=aDD/maxt; } if(dir=="SELL") { CopyOpen(s,tf,from,count,o); if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0; if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t; if((maxt>0)&&(aDD/maxt>rDD)) rDD=aDD/maxt; } FFtrig=false; } } if(StochBufferMain[0]>StochBufferSignal[0]&&StochBufferMain[1]<StochBufferSignal[1]&&StochBufferMain[1]>StochTopLimit) { CopyOpen(s,tf,from,count,o); OpenPrice=o[1]; dir="SELL"; FFtrig=true; } if(StochBufferMain[0]<StochBufferSignal[0]&&StochBufferMain[1]>StochBufferSignal[1]&&StochBufferMain[1]<StochBottomLimit) { CopyOpen(s,tf,from,count,o); OpenPrice=o[1]; dir="BUY"; FFtrig=true; } } Print(TimeToString(TimeCurrent()),";","StrategyStoch:FFStoch", ";","K=",(int)MathRound(par1*MaxStochPeriod)+1,";","D=",(int)MathRound(par2*MaxStochPeriod)+1, ";","Slow=",(int)MathRound(par3*MaxStochPeriod)+1,";","TopLimit=",StochTopLimit,";","BottomLimit=",StochBottomLimit, ";","rDD=",rDD,";","Cap=",t); if(rDD<=trainDD) return(t); else return(0.0); }
该随机指标的适应度函数会向主函数返回一个模拟的余额,而主函数则会将其传递给遗传算法。GA 会在某个时间点决定结束优化,而我们则会利用 GetTrainResults() 函数返回策略的最佳当前值(比如移动平均线)、交易品种,基础程序指标缓冲区的存款份额与参数,以及为进一步真实交易创建指标:
void GetTrainResults() //Get the best parameters { strat=(int)MathRound(Chromosome[GeneCount-2]*StratCount); //Remember the best strategy //For EA testing mode use the following code... z=(int)MathRound(Chromosome[GeneCount-1]*3); //Remember the best symbol switch(z) { case 0: {s="EURUSD"; break;}; case 1: {s="GBPUSD"; break;}; case 2: {s="USDCHF"; break;}; case 3: {s="USDJPY"; break;}; default: {s="EURUSD"; break;}; } //...for real mode, comment the previous code and uncomment the following one (symbols are selected in the MarketWatch window) /* z=(int)MathRound(Chromosome[GeneCount-1]*(SymbolsTotal(true)-1)); //Remember the best symbol s=SymbolName(z,true); */ optF=Chromosome[GeneCount]; //Remember the best deposit share switch(strat) { case 0: {GTRMA( Chromosome[1], //The number of case strings must be equal to the number of strategies Chromosome[2], Chromosome[3], Chromosome[4], Chromosome[5]) ; break;}; case 1: {GTRSAR( Chromosome[1], Chromosome[2], Chromosome[3], Chromosome[4], Chromosome[5]) ; break;}; case 2: {GTRStoch(Chromosome[1], Chromosome[2], Chromosome[3], Chromosome[4], Chromosome[5]) ; break;}; default: {GTRMA( Chromosome[1], Chromosome[2], Chromosome[3], Chromosome[4], Chromosome[5]) ; break;}; } Print(TimeToString(TimeCurrent()),";","GAModule:GetTrainResults", ";","strat=",strat,";","s=",s,";","optF=",optF, ";",Chromosome[1],";",Chromosome[2],";",Chromosome[3],";",Chromosome[4],";",Chromosome[5]); } void GTRMA(double par1,double par2,double par3,double par4,double par5) { MAshort=iMA(s,tf,(int)MathRound(par1*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN); MAlong =iMA(s,tf,(int)MathRound(par2*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN); CopyBuffer(MAshort,0,from,count,ShortBuffer); CopyBuffer(MAlong, 0,from,count,LongBuffer ); Print(TimeToString(TimeCurrent()),";","StrategyMA:GTRMA", ";","MAL=",(int)MathRound(par2*MaxMAPeriod)+1,";","MAS=",(int)MathRound(par1*MaxMAPeriod)+1); }
现在,它完全返回到了一切都运行 (OnTick()) 的地方:在了解现在什么策略最佳的情况下,它会检查是否到了进入市场的时机:
bool NeedOpenMA() { CopyBuffer(MAshort,0,0,count,ShortBuffer); CopyBuffer(MAlong, 0,0,count,LongBuffer ); Print(TimeToString(TimeCurrent()),";","StrategyMA:NeedOpenMA", ";","LB[0]=",LongBuffer[0],";","LB[1]=",LongBuffer[1],";","SB[0]=",ShortBuffer[0],";","SB[1]=",ShortBuffer[1]); if(LongBuffer[0]>LongBuffer[1]&&ShortBuffer[0]>LongBuffer[0]&&ShortBuffer[1]<LongBuffer[1]) { request.type=ORDER_TYPE_SELL; OpenPosition(); return(false); } if(LongBuffer[0]<LongBuffer[1]&&ShortBuffer[0]<LongBuffer[0]&&ShortBuffer[1]>LongBuffer[1]) { request.type=ORDER_TYPE_BUY; OpenPosition(); return(false); } return(true); }
此循环关闭。
我们来看看其工作方式。这里有一份 2011 年有关 1 小时时间表的报告,有 4 个主货币对:EURUSD、GBPUSD、USDCHF、USDJPY:
策略测试报告 |
||||||||||||
InstaForex-Server (Build 567) |
||||||||||||
设置 |
||||||||||||
EA: | 主 | |||||||||||
交易品种: | EURUSD | |||||||||||
期段: | H1 (2011.01.01 - 2011.12.31) | |||||||||||
输入参数: | trainDD=0.50000000 | |||||||||||
maxDD=0.20000000 | ||||||||||||
经纪: | InstaForex Companies Group | |||||||||||
货币: | USD | |||||||||||
初始存入: | 10 000.00 | |||||||||||
杠杆率: | 1:100 | |||||||||||
结果 |
||||||||||||
历史质量: | 100% | |||||||||||
柱: | 6197 | 价格变动: | 1321631 | |||||||||
总净利润: | -538.74 | 毛利: | 3 535.51 | 净损失: | -4 074.25 | |||||||
获利系数: | 0.87 | 预计获利: | -89.79 | 预付款水平: | 85.71% | |||||||
回收系数: | -0.08 | 夏普比率: | 0.07 | OnTester 结果: | 0 | |||||||
余额亏损: | ||||||||||||
余额亏损绝对值: | 4 074.25 | 余额亏损最大值: | 4 074.25 (40.74%) | 余额亏损相对值: | 40.74% (4 074.25) | |||||||
市值亏损: | ||||||||||||
市值亏损绝对值: | 4 889.56 | 市值亏损最大值: | 6 690.90 (50.53%) | 市值亏损相对值: | 50.53% (6 690.90) | |||||||
总交易: | 6 | 短线交易(获利%) | 6 (16.67%) | 长线交易(获利%) | 0 (0.00%) | |||||||
总交易: | 12 | 盈利交易(总交易的%): | 1 (16.67%) | 亏损交易(总交易的%): | 5 (83.33%) | |||||||
最大获利交易: | 3 535.51 | 最大亏损交易: | -1 325.40 | |||||||||
平均获利交易: | 3 535.51 | 平均亏损交易: | -814.85 | |||||||||
最大连续盈利: | 1 (3 535.51) | 最大连续亏损: | 5 (-4 074.25) | |||||||||
最大连续盈利(次数): | 3 535.51 (1) | 最大连续亏损(次数): | -4 074.25 (5) | |||||||||
平均连续盈利: | 1 | 平均连续亏损: | 5 | |||||||||
订单 |
||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
开盘时间 | 订单 | 交易品种 | 类型 | 交易量 | 价格 | S / L | T / P | 时间 | 状态 | 备注 | ||
2011.01.03 01:00 | 2 | USDCHF | 卖 | 28.21 / 28.21 | 0.9321 | 2011.01.03 01:00 | 已填充 | |||||
2011.01.03 03:00 | 3 | USDCHF | 买 | 28.21 / 28.21 | 0.9365 | 2011.01.03 03:00 | 已填充 | |||||
2011.01.03 06:00 | 4 | USDCHF | 卖 | 24.47 / 24.47 | 0.9352 | 2011.01.03 06:00 | 已填充 | |||||
2011.01.03 09:00 | 5 | USDCHF | 买 | 24.47 / 24.47 | 0.9372 | 2011.01.03 09:00 | 已填充 | |||||
2011.01.03 13:00 | 6 | USDCHF | 卖 | 22.99 / 22.99 | 0.9352 | 2011.01.03 13:00 | 已填充 | |||||
2011.01.03 16:00 | 7 | USDCHF | 买 | 22.99 / 22.99 | 0.9375 | 2011.01.03 16:00 | 已填充 | |||||
2011.01.03 18:00 | 8 | USDJPY | 卖 | 72.09 / 72.09 | 81.57 | 2011.01.03 18:00 | 已填充 | |||||
2011.01.03 21:00 | 9 | USDJPY | 买 | 72.09 / 72.09 | 81.66 | 2011.01.03 21:00 | 已填充 | |||||
2011.01.04 01:00 | 10 | USDJPY | 卖 | 64.54 / 64.54 | 81.67 | 2011.01.04 01:00 | 已填充 | |||||
2011.01.04 02:00 | 11 | USDJPY | 买 | 64.54 / 64.54 | 81.78 | 2011.01.04 02:00 | 已填充 | |||||
2011.10.20 21:00 | 12 | USDCHF | 卖 | 56.30 / 56.30 | 0.8964 | 2011.10.20 21:00 | 已填充 | |||||
2011.10.21 12:00 | 13 | USDCHF | 买 | 56.30 / 56.30 | 0.8908 | 2011.10.21 12:00 | 已填充 | |||||
成交 |
||||||||||||
时间 | 成交 | 交易品种 | 类型 | 方向 | 交易量 | 价格 | 订单 | 手续费 | 互换 | 盈利 | 余额 | 备注 |
2011.01.01 00:00 | 1 | 余额 | 0.00 | 0.00 | 10 000.00 | 10 000.00 | ||||||
2011.01.03 01:00 | 2 | USDCHF | 卖 | 入 | 28.21 | 0.9321 | 2 | 0.00 | 0.00 | 0.00 | 10 000.00 | |
2011.01.03 03:00 | 3 | USDCHF | 买 | 出 | 28.21 | 0.9365 | 3 | 0.00 | 0.00 | -1 325.40 | 8 674.60 | |
2011.01.03 06:00 | 4 | USDCHF | 卖 | 入 | 24.47 | 0.9352 | 4 | 0.00 | 0.00 | 0.00 | 8 674.60 | |
2011.01.03 09:00 | 5 | USDCHF | 买 | 出 | 24.47 | 0.9372 | 5 | 0.00 | 0.00 | -522.19 | 8 152.41 | |
2011.01.03 13:00 | 6 | USDCHF | 卖 | 入 | 22.99 | 0.9352 | 6 | 0.00 | 0.00 | 0.00 | 8 152.41 | |
2011.01.03 16:00 | 7 | USDCHF | 买 | 出 | 22.99 | 0.9375 | 7 | 0.00 | 0.00 | -564.02 | 7 588.39 | |
2011.01.03 18:00 | 8 | USDJPY | 卖 | 入 | 72.09 | 81.57 | 8 | 0.00 | 0.00 | 0.00 | 7 588.39 | |
2011.01.03 21:00 | 9 | USDJPY | 买 | 出 | 72.09 | 81.66 | 9 | 0.00 | 0.00 | -794.53 | 6 793.86 | |
2011.01.04 01:00 | 10 | USDJPY | 卖 | 入 | 64.54 | 81.67 | 10 | 0.00 | 0.00 | 0.00 | 6 793.86 | |
2011.01.04 02:00 | 11 | USDJPY | 买 | 出 | 64.54 | 81.78 | 11 | 0.00 | 0.00 | -868.11 | 5 925.75 | |
2011.10.20 21:00 | 12 | USDCHF | 卖 | 入 | 56.30 | 0.8964 | 12 | 0.00 | 0.00 | 0.00 | 5 925.75 | |
2011.10.21 12:00 | 13 | USDCHF | 买 | 出 | 56.30 | 0.8908 | 13 | 0.00 | -3.78 | 3 539.29 | 9 461.26 | |
0.00 | -3.78 | -534.96 | 9 461.26 | |||||||||
Copyright 2001-2011, MetaQuotes Software Corp. |
我们来解释一下图表上标注的区域(阐释节选自日志分析):
- EA 交易启动后,遗传算法选择了突破策略 SAR,条件是基于 USDCHF、交易中的存款份额为 28%。之后,交易至 1 月 3 日晚,亏损余额 20% 以上,并开始重新优化。
- 然后,EA 交易决定基于 USDJPY 突破 SAR 交易,但使用的是全部存款 (98%)。自然,它不能长线交易,也因此在 1 月 4 日上午开始其第三次优化。
- 这一次,它决定基于 USDCHF 再次针对整个存款交易移动平均线的黄金与死亡交叉。而且,它一直等待第一个死亡交叉,直到 10 月 20 日,将其卖出了最高值,并赢回了它的全部损失。从那之后到年底,该 EA 交易未能发现进入市场的有利时机。
后面还有吗?
还有后面吗?EA 交易的下一代会是什么样?制定策略的 EA 交易以及其中选择最佳策略。而且更进一步,它还可以管理资金、购买更强大的硬件、通道等等...
风险警告:
如此简短的陈述,不足以完整揭示基于预付款的外汇货币交易的所有风险及其它重大内容。您要清楚交易的本质,以及您暴露于风险的程度。鉴于您的经验、目标、金融资源及其它相关环境,您要认真考虑交易是否适合于您。
外汇市场中不仅有利可图,也存在着巨大的风险。就预付款交易而言,相对较小的汇价波动可对交易者的账户造成重大影响,进而导致初始存款以及为维持开仓而存入账户的任何款项全部损失。您不要存入自己不能接受其损失的款额。决定交易之前,请确保您了解所有风险,并考虑了自己的经验水平。如有必要,请寻求独立顾问建议。
许可:
UGAlib.mqh 模块由 Andrey Dik aka joo 根据 BSD 许可研制和发布。
本文随附的 EA 交易与辅助模块,均由作者 Roman Rich 根据 BSD 许可创建和发布。许可文本请见 Lic.txt 文件。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/350
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



做得好,您能否将 MQLArticle.pdf 转为 MQLArticle.txt 格式,以便我们将其翻译为任何语言?)
抱歉,您的附件 MQL5Article.zip 不在这里 :(
对不起,我不会说英语...
.Но есливы дадите нам текст pourons формате мыпереводим егос GOOGLE, а не в формате PDF
新文章《Trademinator 3:交易机器的崛起》已发布:
作者:Roman Zamozhnyy