序言
很久以前,有一个论坛 (MQL5) 发布了两篇文章:"《遗传算法 - 很简单!》(作者 joo)及《Dr. Tradelove...》(作者是我)。在第一篇文章中,作者为我们提供了一款优化您所需任何内容的强大工具,其中就包括交易策略 - 一种通过 MQL5 语言实施的遗传算法。
利用此算法,我在第二篇文章中就尝试着根据它来开发一个自优化的 EA 交易。此文以下述任务的公式化描述作为结尾:要创建不仅可以选择某特定交易系统的最佳参数、亦可选择所有已制定策略中最佳策略的 EA 交易(当然是自优化型)。我们来看看是否可能,如果可能,又该怎样做到。
交易机器人相关纪事
首先,我们制定自优化 EA 交易的一般要求。
它要能够(基于历史数据):
而且,在真实交易中,它还要能够:
下图所示为提议 EA 交易的原理图。
带有界限的详图,位于随附文件 Scheme_en 中。
要记住,掌握无限是不可能的,我们在 EA 交易逻辑中引入限制。我们认为(重要):
现在,要求已规定,限制亦已选定,您可以查看实施所有这些内容的代码了。
我们从一切得以运行的函数开始。
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 交易的下一代会是什么样?制定策略的 EA 交易以及其中选择最佳策略。而且更进一步,它还可以管理资金、购买更强大的硬件、通道等等...
风险警告:
如此简短的陈述,不足以完整揭示基于预付款的外汇货币交易的所有风险及其它重大内容。您要清楚交易的本质,以及您暴露于风险的程度。鉴于您的经验、目标、金融资源及其它相关环境,您要认真考虑交易是否适合于您。
外汇市场中不仅有利可图,也存在着巨大的风险。就预付款交易而言,相对较小的汇价波动可对交易者的账户造成重大影响,进而导致初始存款以及为维持开仓而存入账户的任何款项全部损失。您不要存入自己不能接受其损失的款额。决定交易之前,请确保您了解所有风险,并考虑了自己的经验水平。如有必要,请寻求独立顾问建议。
许可:
UGAlib.mqh 模块由 Andrey Dik aka joo 根据 BSD 许可研制和发布。
本文随附的 EA 交易与辅助模块,均由作者 Roman Rich 根据 BSD 许可创建和发布。许可文本请见 Lic.txt 文件。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程