
Dr. Tradelove 或我如何不再担忧并创建一个自训练 EA 交易
概念
在创建 EA 交易之后,我们都求助内置的策略测试程序来选择最佳参数。在选择这些参数时,我们运行 EA,并且一旦出现任何重大变化,则 EA 将会停止,并且一次又一次地使用策略测试程序进行优化。
我们能够将再优化决策和再优化作为一个进程分配给 EA 而不需要自然地中断其工作吗?
Quantum 在其《《自适应交易系统以及它们在 MetaTrader 5 客户端中的运用》一文中提出了这个问题的一个解决方案,专门致力于使用真实交易系统以及几个(在数量上没有限制)虚拟交易策略,从中选择一个目前为止具有最大盈利的策略。在超过某个固定的柱值之后,更改交易策略的决定被采纳。
我建议使用 joo 在《《遗传算法 - 很简单!》一文中提出的遗传算法 (GA) 代码。让我们看一看此类 EA 交易的实施(以下例子中的一个是为参加 2011 年自动交易锦标赛而写的 EA)。
正在进行的工作
那么,我们需要定义 EA 交易应能够做什么。首先,不言而喻的是使用选定的策略进行交易。其次,做出决定:是否应该再优化(执行输入参数的新优化)。再其次,利用 GA 进行再优化。首先,我们回顾一下最简单的再优化 - 有一个策略,并且我们只选择新的参数。然后,我们将看看是否能够利用 GA 在变化后的市场环境中选择其他策略,以及如果能够,如何进行。
此外,为了有利于适应度函数中的模拟,我们决定在一个工具上仅交易已完成柱。将不会有添加仓位和部分平仓。对于那些喜欢使用固定止损和获利以及跟踪止损的人,请参阅《MetaTrader 5 策略测试程序中的价格变动生成算法》一文,以在适应度函数中实施止损和获利订单检查。我将展开下面的习语:
在适应度函数中,我模拟了一种测试模式,这种模式在测试程序中被称为 "Open Prices Only"(仅开盘价)。但是!这并不意味着这是适应度函数中唯一可能的测试过程模拟。更加小心谨慎的人可能希望使用 "Every Tick"(每一价格变动)模式实施适应度函数测试。为了不重复劳动或拼凑“每一价格变动”,我愿意提醒他们注意 MetaQuotes 开发的现有算法。换言之,读过本文之后,读者将能够在适应度函数中模拟 "Every Tick"(每一价格变动)模式,这是在适应度函数中正确模拟止损和获利的必要条件。
在继续要点(策略实施)之前,让我们简短地回顾一下定义新柱的开盘以及建仓和平仓的技术性和实施辅助函数。
//+------------------------------------------------------------------+ //| Define whether a new bar has opened | //+------------------------------------------------------------------+ bool isNewBars() { CopyTime(s,tf,0,1,curBT); TimeToStruct(curBT[0],curT); if(tf==PERIOD_M1|| tf==PERIOD_M2|| tf==PERIOD_M3|| tf==PERIOD_M4|| tf==PERIOD_M5|| tf==PERIOD_M6|| tf==PERIOD_M10|| tf==PERIOD_M12|| tf==PERIOD_M15|| tf==PERIOD_M20|| tf==PERIOD_M30) if(curT.min!=prevT.min) { prevBT[0]=curBT[0]; TimeToStruct(prevBT[0],prevT); return(true); }; if(tf==PERIOD_H1|| tf==PERIOD_H2|| tf==PERIOD_H3|| tf==PERIOD_H4|| tf==PERIOD_H6|| tf==PERIOD_H8|| tf==PERIOD_M12) if(curT.hour!=prevT.hour) { prevBT[0]=curBT[0]; TimeToStruct(prevBT[0],prevT); return(true); }; if(tf==PERIOD_D1|| tf==PERIOD_W1) if(curT.day!=prevT.day) { prevBT[0]=curBT[0]; TimeToStruct(prevBT[0],prevT); return(true); }; if(tf==PERIOD_MN1) if(curT.mon!=prevT.mon) { prevBT[0]=curBT[0]; TimeToStruct(prevBT[0],prevT); return(true); }; return(false); } //+------------------------------------------------------------------+ //| ClosePosition | //+------------------------------------------------------------------+ void ClosePosition() { request.action=TRADE_ACTION_DEAL; request.symbol=PositionGetSymbol(0); if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) request.type=ORDER_TYPE_SELL; else request.type=ORDER_TYPE_BUY; request.type_filling=ORDER_FILLING_FOK; if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST|| SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT) { request.sl=NULL; request.tp=NULL; request.deviation=100; } while(PositionsTotal()>0) { request.volume=NormalizeDouble(MathMin(PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(PositionGetSymbol(0),SYMBOL_VOLUME_MAX)),2); if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST|| SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT) { if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); else request.price=SymbolInfoDouble(s,SYMBOL_ASK); } OrderSend(request,result); Sleep(10000); } } //+------------------------------------------------------------------+ //| OpenPosition | //+------------------------------------------------------------------+ void OpenPosition() { double vol; request.action=TRADE_ACTION_DEAL; request.symbol=s; request.type_filling=ORDER_FILLING_FOK; if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST|| SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT) { request.sl=NULL; request.tp=NULL; request.deviation=100; } vol=MathFloor(AccountInfoDouble(ACCOUNT_FREEMARGIN)*optF*AccountInfoInteger(ACCOUNT_LEVERAGE) /(SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP)))*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP); vol=MathMax(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MIN)); vol=MathMin(vol,GetPossibleLots()*0.95); if(SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)!=0) vol=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)),2); request.volume=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2); while(PositionSelect(s)==false) { if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST|| SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT) { if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); else request.price=SymbolInfoDouble(s,SYMBOL_ASK); } OrderSend(request,result); Sleep(10000); PositionSelect(s); } while(PositionGetDouble(POSITION_VOLUME)<vol) { request.volume=NormalizeDouble(MathMin(vol-PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2); if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST|| SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT) { if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); else request.price=SymbolInfoDouble(s,SYMBOL_ASK); } OrderSend(request,result); Sleep(10000); PositionSelect(s); } } //+------------------------------------------------------------------+
在经过仔细考虑之后,您可能注意到建仓函数中的三个重要参数:s 和 optF 变量以及 GetPossibleLots() 函数调用:
- s - 交易工具,GA 优化的变量之一,
- optF - 用于交易的保证金部分(GA 优化的另一个变量),
- GetPossibleLots() 函数 - 返回用于交易的保证金部分:
//+------------------------------------------------------------------+ //| GetPossibleLots | //+------------------------------------------------------------------+ double GetPossibleLots() { request.volume=1.0; if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); else request.price=SymbolInfoDouble(s,SYMBOL_ASK); OrderCheck(request,check); return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,2)); }
稍微打乱一下讲述顺序,我们另外介绍两个所有 EA 都有并且在第二阶段非常重要的函数:
//+------------------------------------------------------------------+ //| InitRelDD | //+------------------------------------------------------------------+ void InitRelDD() { ulong DealTicket; double curBalance; prevBT[0]=D'2000.01.01 00:00:00'; TimeToStruct(prevBT[0],prevT); curBalance=AccountInfoDouble(ACCOUNT_BALANCE); maxBalance=curBalance; HistorySelect(D'2000.01.01 00:00:00',TimeCurrent()); for(int i=HistoryDealsTotal();i>0;i--) { DealTicket=HistoryDealGetTicket(i); curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT); if(curBalance>maxBalance) maxBalance=curBalance; } } //+------------------------------------------------------------------+ //| GetRelDD | //+------------------------------------------------------------------+ double GetRelDD() { if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE); return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance); }
我们在这里看到了什么?第一个函数确定最大帐户余额值,第二个函数计算帐户的相对当前亏损。将在第二阶段的说明中详细列出它们的特性。
- 一个使用移动平均线的相交进行交易(黄金交叉 - 我们买入金融工具,死亡交叉 - 我们卖出);
- 另一个是简单的神经网络,接收最后五个交易会话中 [0..1] 范围内的价格变动。
在算法上,自优化 EA 交易的工作可通过以下例子来说明:
-
EA 交易使用的变量的初始化:定义和初始化指标缓存,或设置神经网络拓扑结构(层数/一层中的神经元数量;以一个简单的神经网络为例,其中神经元的数量在所有层中都是相同的),设置工作时间框架。此外,很可能是最重要的步骤 - 我们调用遗传优化函数,该函数解决最重要的函数 - 适应度函数(以下简称为 FF)。
重要须知!每一个交易策略有一个新的 FF,即每一次都会重新创建一个新的 FF。针对一条移动平均线的 FF 与针对两条移动平均线的 FF 完全不同,并且与神经网络 FF 完全不同。
我的 EA 交易中的 FF 性能结果是最大余额,前提是相对亏损未超过作为外部变量设置的临界值(在我们的例子中为 0.5)。换言之,如果下一次 GA 运行得出 100,000 的余额,而相对余额亏损为 -0.6,则 FF=0.0。亲爱的读者,在您的例子中,FF 结果可能带来完全不同的标准。
收集遗传算法性能结果:对于移动平均线的相交,这些显然是移动平均周期,对于神经网络,有突触权重,它们共有的结果(以及我的其他 EA 的结果)是直到下一次再优化为止要交易的金融工具,以及我们已经熟悉的 optF,即要用于交易的保证金的一部分。由您自主决定向您的 FF 添加优化参数,例如还可以选择时间框架或其他参数。
初始化的最后一步是找出最大帐户余额值。为什么它很重要?因为这是再优化决策的起点。
重要须知!再优化的决策过程:一旦相对余额亏损达到作为外部变量设置的某个临界值(在我们的例子中为 0.2),则我们需要再优化。为了不让 EA 在到达临界亏损时在每一根柱上实施再优化,最大余额值被当前值所代替。
- 正在进行的交易。
- 在每一次平仓时,我们检查余额亏损是否达到临界值。如果达到临界值,我们运行 GA 并收集其性能结果(这就是再优化)!
-
我们要么等待 Forex 的董事打电话来,要求不要破坏世界,或者(更有可能)止损离场、追加保证金或等待救护车。
请在下面找出针对使用移动平均线策略的 EA(提供了源代码)和使用神经网络的 EA 的上述要点的编程实施 - 所有一切都作为源代码提供。
代码是依据 GPL 许可条款与条件提供的。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { tf=Period(); //---for bar-to-bar test... prevBT[0]=D'2001.01.01'; //---... long ago TimeToStruct(prevBT[0],prevT); //--- historical depth (should be set since the optimisation is based on historical data) depth=10000; //--- copies at a time (should be set since the optimisation is based on historical data) count=2; ArrayResize(LongBuffer,count); ArrayResize(ShortBuffer,count); ArrayInitialize(LongBuffer,0); ArrayInitialize(ShortBuffer,0); //--- calling the neural network genetic optimisation function GA(); //--- getting the optimised neural network parameters and other variables GetTrainResults(); //--- getting the account drawdown InitRelDD(); return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(isNewBars()==true) { bool trig=false; CopyBuffer(MAshort,0,0,count,ShortBuffer); CopyBuffer(MAlong,0,0,count,LongBuffer); if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1]) { if(PositionsTotal()>0) { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { ClosePosition(); trig=true; } } } if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1]) { if(PositionsTotal()>0) { if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { ClosePosition(); trig=true; } } } if(trig==true) { //--- if the account drawdown has exceeded the allowable value: if(GetRelDD()>maxDD) { //--- calling the neural network genetic optimisation function GA(); //--- getting the optimised neural network parameters and other variables GetTrainResults(); //--- 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,0,0,count,ShortBuffer); CopyBuffer(MAlong,0,0,count,LongBuffer); if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1]) { request.type=ORDER_TYPE_SELL; OpenPosition(); } if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1]) { request.type=ORDER_TYPE_BUY; OpenPosition(); } }; } //+------------------------------------------------------------------+ //| Preparing and calling the genetic optimizer | //+------------------------------------------------------------------+ void GA() { //--- number of genes (equal to the number of optimised variables), //--- all of them should be specified in the FitnessFunction()) GeneCount =OptParamCount+2; //--- number of chromosomes in a colony ChromosomeCount=GeneCount*11; //--- minimum search range RangeMinimum =0.0; //--- maximum search range RangeMaximum =1.0; //--- search pitch Precision =0.0001; //--- 1 is a minimum, anything else is a maximum OptimizeMethod =2; ArrayResize(Chromosome,GeneCount+1); ArrayInitialize(Chromosome,0); //--- number of epochs without any improvement Epoch =100; //--- ratio of replication, natural mutation, artificial mutation, gene borrowing, //--- crossingover, interval boundary displacement ratio, every gene mutation probabilty, % UGA(100.0,1.0,1.0,1.0,1.0,0.5,1.0); } //+------------------------------------------------------------------+ //| Fitness function for neural network genetic optimizer: | //| selecting a pair, optF, synapse weights; | //| anything can be optimised but it is necessary | //| to carefully monitor the number of genes | //+------------------------------------------------------------------+ void FitnessFunction(int chromos) { int b; //--- is there an open position? bool trig=false; //--- direction of an open position string dir=""; //--- opening price double OpenPrice=0; //--- intermediary between a gene colony and optimised parameters int z; //--- current balance double t=cap; //--- maximum balance double maxt=t; //--- absolute drawdown double aDD=0; //--- relative drawdown double rDD=0.000001; //--- fitness function proper double ff=0; //--- GA is selecting a pair z=(int)MathRound(Colony[GeneCount-1][chromos]*12); switch(z) { case 0: {s="AUDUSD"; break;}; case 1: {s="AUDUSD"; break;}; case 2: {s="EURAUD"; break;}; case 3: {s="EURCHF"; break;}; case 4: {s="EURGBP"; break;}; case 5: {s="EURJPY"; break;}; case 6: {s="EURUSD"; break;}; case 7: {s="GBPCHF"; break;}; case 8: {s="GBPJPY"; break;}; case 9: {s="GBPUSD"; break;}; case 10: {s="USDCAD"; break;}; case 11: {s="USDCHF"; break;}; case 12: {s="USDJPY"; break;}; default: {s="EURUSD"; break;}; } MAshort=iMA(s,tf,(int)MathRound(Colony[1][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN); MAlong =iMA(s,tf,(int)MathRound(Colony[2][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN); dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS)); //--- GA is selecting the optimal F optF=Colony[GeneCount][chromos]; leverage=AccountInfoInteger(ACCOUNT_LEVERAGE); contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE); b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth); //--- for a neural network using historical data - where the data is copied from for(from=b;from>=1;from--) { CopyBuffer(MAshort,0,from,count,ShortBuffer); CopyBuffer(MAlong,0,from,count,LongBuffer); if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1]) { if(trig==false) { CopyOpen(s,tf,from,count,o); OpenPrice=o[1]; dir="SELL"; trig=true; } else { 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; OpenPrice=o[1]; dir="SELL"; trig=true; } } } if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1]) { if(trig==false) { CopyOpen(s,tf,from,count,o); OpenPrice=o[1]; dir="BUY"; trig=true; } else { 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; OpenPrice=o[1]; dir="BUY"; trig=true; } } } } if(rDD<=trainDD) ff=t; else ff=0.0; AmountStartsFF++; Colony[0][chromos]=ff; } //+---------------------------------------------------------------------+ //| getting the optimized neural network parameters and other variables | //| should always be equal to the number of genes | //+---------------------------------------------------------------------+ void GetTrainResults() { //--- intermediary between a gene colony and optimised parameters int z; MAshort=iMA(s,tf,(int)MathRound(Chromosome[1]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN); MAlong =iMA(s,tf,(int)MathRound(Chromosome[2]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN); CopyBuffer(MAshort,0,from,count,ShortBuffer); CopyBuffer(MAlong,0,from,count,LongBuffer); //--- save the best pair z=(int)MathRound(Chromosome[GeneCount-1]*12); switch(z) { case 0: {s="AUDUSD"; break;}; case 1: {s="AUDUSD"; break;}; case 2: {s="EURAUD"; break;}; case 3: {s="EURCHF"; break;}; case 4: {s="EURGBP"; break;}; case 5: {s="EURJPY"; break;}; case 6: {s="EURUSD"; break;}; case 7: {s="GBPCHF"; break;}; case 8: {s="GBPJPY"; break;}; case 9: {s="GBPUSD"; break;}; case 10: {s="USDCAD"; break;}; case 11: {s="USDCHF"; break;}; case 12: {s="USDJPY"; break;}; default: {s="EURUSD"; break;}; } //--- saving the best optimal F optF=Chromosome[GeneCount]; } //+------------------------------------------------------------------+
让我们看一看算法的主函数 - 适应度函数。
自优化 EA 交易的整个理念基于适应度函数中某个时间段(比如 10000 根柱的历史)内的交易过程(与在 MetaQuotes 的标准测试程序中一样)的模拟,该函数从遗传算法(GA 函数)接收优化变量的输入)。如果算法基于移动平均线的相交,则优化变量包括:
- 工具(在 Forex 中为货币对);是的,它是典型的多货币 EA 交易并且遗传算法选择一个工具(因为代码来自为参加锦标赛而编写的 EA 交易,它有与锦标赛的货币对相对应的货币对;一般而言,可以是经纪人报价的任何工具)。
注:很不幸,EA 交易不能在测试模式中从 MarketWatch (市场报价)窗口中获得货币对列表(感谢 MetaQuotes 用户,我们在这里澄清这一点 - 没门!)。因此,如果您想在测试程序中单独为外汇和股票运行 EA 交易,请在 FF 和 GetTrainResults() 函数中指定您自己的工具。
- 用于交易的保证金部分;
- 两条移动平均线的周期。
以下示例说明了用于测试的 EA 交易的一种情形!
可以使用来自 MarketWatch (市场报价)窗口的工具列表显著简化真实交易代码。
为此,在带有注释 "//--- GA is selecting a pair"(GA 正在选择一个货币对) 和 "//--- saving the best pair"(保存最佳货币对) 的 FF 和 GetTrainResults() 函数中,仅仅写为:
//--- GA is selecting a pair z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1)); s=SymbolName(z,true);
这样,在 FF 的开头,我们指定并在必要时初始化用于模拟基于历史的交易的变量。在下一阶段,我们将从遗传算法收集优化变量的不同值,例如从 "optF=Colony[GeneCount][chromos] 一行;保证金部分值从 GA 传输到 FF。
我们进一步检查历史中的可用柱数,我们或从第 10000 根柱开始,或从模拟 "Open prices only"(仅开盘价)模式中接收报价过程的第一根柱开始,以及做出交易决定:
- 将移动平均线的值复制到缓存;
- 检查是否为死亡交叉。
- 如果有死亡交叉,并且没有未平仓位 (if(trig==false)) - 建立一个虚拟卖出仓位(仅仅记住建仓价和方向);
- 如果有死亡交叉,并且有未平买入仓位 (if(dir=="BUY")) - 取柱的开盘价并注意三条非常重要的线,如下所示:
- 模拟平仓和余额变更:当前余额增加当前余额值,乘以要交易的保证金部分,再乘以开盘价和收盘价之差,再乘以价格利润点价格(近似);
- 检查当前余额是否达到交易模拟历史中的最大值;如果未达到,计算资金的最大余额亏损;
- 将先前计算出来的资金亏损转换为相对余额亏损;
- 建立一个虚拟卖出仓位(仅仅记住建仓价和方向);
- 对黄金交叉进行相同的检查和计算。
遍历整个可用历史并模拟虚拟交易之后,计算最终的 FF 值:如果计算出来的相对余额亏损小于为测试设置的亏损,则 FF=余额,否则 FF=0。遗传算法旨在让适应度函数最大化!
最后,给出工具、保证金部分和移动平均线的周期等各个值之后,遗传算法将找出以最小相对亏损(最小值由用户设定)实现最大余额的值。
总结
以下是简要总结:创建一个自训练 EA 交易很容易,困难之处在于找出要输入什么(重要的是想法,实施只是一个技术问题)。
悲观主义者会问 - “它有用吗?”,我的回答 - 有用;对于乐观主义者 - 这不是万能圣杯。
所提议的方法与 Quantum 的方法有什么根本区别?可以通过比较使用移动平均线的 EA 交易来最好地说明这一点:
- 自适应训练系统中移动平均线的周期应在编译和严格编码之前决定,并且只能在这种数量有限的情形之中做出选择;在遗传优化 EA 交易中,我们在编译之前不做任何决定,此决定由 GA 做出,并且情形的数量仅受判断力的限制。
- 自适应交易系统中的虚拟交易是柱到柱;在遗传优化 EA 交易中很少如此 - 并且仅在再优化的条件出现时。受策略、参数、工具数量不断增长影响的计算机性能或许是自适应交易系统的一个限制因素。
附录
以下是在测试程序中用 2010 年 1 月 1 起的日线图数据,在没有经过任何优化的情况下运行神经网络所得到的结果:
策略测试程序报告 |
||||||||||||
MetaQuotes-Demo (Build 523) |
||||||||||||
EA 交易: | ANNExample | |||||||||||
交易品种: | EURUSD | |||||||||||
周期: | 每日 (2010.01.01 - 2011.09.30) | |||||||||||
输入参数: | trainDD=0.9 | |||||||||||
maxDD=0.1 | ||||||||||||
经纪人: | Alpari NZ Limited | |||||||||||
货币: | USD | |||||||||||
初始存入: | 10 000.00 | |||||||||||
杠杆率: | 1:100 | |||||||||||
结果 |
||||||||||||
历史质量: | 100% | |||||||||||
柱数: | 454 | 价格变动: | 2554879 | |||||||||
总净利润: | -9 094.49 | 毛利: | 29 401.09 | 毛损: | -38 495.58 | |||||||
获利系数: | 0.76 | 预计获利: | -20.53 | 保证金水平: | 732.30% | |||||||
回收系数: | -0.76 | 夏普比率: | -0.06 | OnTester 结果: | 0 | |||||||
余额亏损: | ||||||||||||
绝对余额亏损: | 9 102.56 | 最大余额亏损: | 11 464.70 (92.74%) | 相对余额亏损: | 92.74% (11 464.70) | |||||||
市值亏损: | ||||||||||||
绝对市值亏损: | 9 176.99 | 最大市值亏损: | 11 904.00 (93.53%) | 相对市值亏损: | 93.53% (11 904.00) | |||||||
总交易次数: | 443 | 短线交易次数(获利%): | 7 (14.29%) | 长线交易次数(获利%): | 436 (53.44%) | |||||||
总成交次数: | 886 | 盈利交易次数(总交易次数的%): | 234 (52.82%) | 亏损交易次数(总交易次数的%): | 209 (47.18%) | |||||||
最大获利交易: | 1 095.57 | 最大亏损交易: | -1 438.85 | |||||||||
平均获利交易: | 125.65 | 平均亏损交易: | -184.19 | |||||||||
最大连续盈利次数(盈利金额): | 8 (397.45) | 最大连续亏损次数(亏损金额): | 8 (-1 431.44) | |||||||||
最大连续收益金额(盈利次数): | 1 095.57 (1) | 最大连续损失金额(亏损次数): | -3 433.21 (6) | |||||||||
平均连续盈利次数: | 2 | 平均连续亏损次数: | 2 |
以下是供选择的三种再优化情形:
第一种...
时间 | 成交 | 交易品种 | 类型 | 方向 | 交易量 | 价格 | 订单 | 库存费 | 盈利 | 余额 |
2010.01.01 00:00 | 1 | 余额 | 0.00 | 10 000.00 | 10 000.00 | |||||
2010.01.04 00:00 | 2 | AUDUSD | 买 | 入 | 0.90 | 0.89977 | 2 | 0.00 | 0.00 | 10 000.00 |
2010.01.05 00:00 | 3 | AUDUSD | 卖 | 出 | 0.90 | 0.91188 | 3 | 5.67 | 1 089.90 | 11 095.57 |
2010.01.05 00:00 | 4 | AUDUSD | 买 | 入 | 0.99 | 0.91220 | 4 | 0.00 | 0.00 | 11 095.57 |
2010.01.06 00:00 | 5 | AUDUSD | 卖 | 出 | 0.99 | 0.91157 | 5 | 6.24 | -62.37 | 11 039.44 |
2010.01.06 00:00 | 6 | AUDUSD | 买 | 入 | 0.99 | 0.91190 | 6 | 0.00 | 0.00 | 11 039.44 |
2010.01.07 00:00 | 7 | AUDUSD | 卖 | 出 | 0.99 | 0.91924 | 7 | 18.71 | 726.66 | 11 784.81 |
第二种...
时间 | 成交 | 交易品种 | 类型 | 方向 | 交易量 | 价格 | 订单 | 手续费 | 库存费 | 盈利 | 余额 |
2010.05.19 00:00 | 189 | AUDUSD | 卖 | 出 | 0.36 | 0.86110 | 189 | 0.00 | 2.27 | -595.44 | 4 221.30 |
2010.05.19 00:00 | 190 | EURAUD | 卖 | 入 | 0.30 | 1.41280 | 190 | 0.00 | 0.00 | 0.00 | 4 221.30 |
2010.05.20 00:00 | 191 | EURAUD | 买 | 出 | 0.30 | 1.46207 | 191 | 0.00 | 7.43 | -1 273.26 | 2 955.47 |
2010.05.20 00:00 | 192 | AUDUSD | 买 | 入 | 0.21 | 0.84983 | 192 | 0.00 | 0.00 | 0.00 | 2 955.47 |
第三种
时间 | 成交 | 交易品种 | 类型 | 方向 | 交易量 | 价格 | 订单 | 库存费 | 盈利 | 余额 |
2010.06.16 00:00 | 230 | GBPCHF | 买 | 入 | 0.06 | 1.67872 | 230 | 0.00 | 0.00 | 2 128.80 |
2010.06.17 00:00 | 231 | GBPCHF | 卖 | 出 | 0.06 | 1.66547 | 231 | 0.13 | -70.25 | 2 058.68 |
2010.06.17 00:00 | 232 | GBPCHF | 买 | 入 | 0.06 | 1.66635 | 232 | 0.00 | 0.00 | 2 058.68 |
2010.06.18 00:00 | 233 | GBPCHF | 卖 | 出 | 0.06 | 1.64705 | 233 | 0.04 | -104.14 | 1 954.58 |
2010.06.18 00:00 | 234 | AUDUSD | 买 | 入 | 0.09 | 0.86741 | 234 | 0.00 | 0.00 | 1 954.58 |
2010.06.21 00:00 | 235 | AUDUSD | 卖 | 出 | 0.09 | 0.87184 | 235 | 0.57 | 39.87 | 1 995.02 |
2010.06.21 00:00 | 236 | AUDUSD | 买 | 入 | 0.09 | 0.88105 | 236 | 0.00 | 0.00 | 1 995.02 |
2010.06.22 00:00 | 237 | AUDUSD | 卖 | 出 | 0.09 | 0.87606 | 237 | 0.57 | -44.91 | 1 950.68 |
2010.06.22 00:00 | 238 | AUDUSD | 买 | 入 | 0.09 | 0.87637 | 238 | 0.00 | 0.00 | 1 950.68 |
2010.06.23 00:00 | 239 | AUDUSD | 卖 | 出 | 0.09 | 0.87140 | 239 | 0.57 | -44.73 | 1 906.52 |
2010.06.23 00:00 | 240 | AUDUSD | 买 | 入 | 0.08 | 0.87197 | 240 | 0.00 | 0.00 | 1 906.52 |
2010.06.24 00:00 | 241 | AUDUSD | 卖 | 出 | 0.08 | 0.87385 | 241 | 1.51 | 15.04 | 1 923.07 |
2010.06.24 00:00 | 242 | AUDUSD | 买 | 入 | 0.08 | 0.87413 | 242 | 0.00 | 0.00 | 1 923.07 |
2010.06.25 00:00 | 243 | AUDUSD | 卖 | 出 | 0.08 | 0.86632 | 243 | 0.50 | -62.48 | 1 861.09 |
2010.06.25 00:00 | 244 | AUDUSD | 买 | 入 | 0.08 | 0.86663 | 244 | 0.00 | 0.00 | 1 861.09 |
2010.06.28 00:00 | 245 | AUDUSD | 卖 | 出 | 0.08 | 0.87375 | 245 | 0.50 | 56.96 | 1 918.55 |
2010.06.28 00:00 | 246 | AUDUSD | 买 | 入 | 0.08 | 0.87415 | 246 | 0.00 | 0.00 | 1 918.55 |
2010.06.29 00:00 | 247 | AUDUSD | 卖 | 出 | 0.08 | 0.87140 | 247 | 0.50 | -22.00 | 1 897.05 |
2010.06.29 00:00 | 248 | AUDUSD | 买 | 入 | 0.08 | 0.87173 | 248 | 0.00 | 0.00 | 1 897.05 |
2010.07.01 00:00 | 249 | AUDUSD | 卖 | 出 | 0.08 | 0.84053 | 249 | 2.01 | -249.60 | 1 649.46 |
2010.07.01 00:00 | 250 | EURGBP | 卖 | 入 | 0.07 | 0.81841 | 250 | 0.00 | 0.00 | 1 649.46 |
2010.07.02 00:00 | 251 | EURGBP | 买 | 出 | 0.07 | 0.82535 | 251 | -0.04 | -73.69 | 1 575.73 |
2010.07.02 00:00 | 252 | EURGBP | 卖 | 入 | 0.07 | 0.82498 | 252 | 0.00 | 0.00 | 1 575.73 |
2010.07.05 00:00 | 253 | EURGBP | 买 | 出 | 0.07 | 0.82676 | 253 | -0.04 | -18.93 | 1 556.76 |
2010.07.05 00:00 | 254 | EURGBP | 卖 | 入 | 0.06 | 0.82604 | 254 | 0.00 | 0.00 | 1 556.76 |
2010.07.06 00:00 | 255 | EURGBP | 买 | 出 | 0.06 | 0.82862 | 255 | -0.04 | -23.43 | 1 533.29 |
附言:作为一项家庭作业:不仅选择某个系统的参数,还选择在某个时刻最符合市场的系统(提示 - 从系统库中选择)。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/334
注意: 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.



先生们,专业人士!
帮我这个笨蛋把这些东西组装起来,让它发挥作用。
对不起,我问了个愚蠢的问题。:-)
这简直是所有贸易相关出版物中最具创意的标题。贸易自动化的末日装置!
感谢你们努力整合本网站提供的强大概念。对读者来说,最大的障碍可能就是如何正确应用他们的想法。继续撰写此类文章非常重要,因为我们可以在社区已经提出的理念的基础上再接再厉。我毫不怀疑这篇文章能够启发伟大的思想,让他们看到贸易自动化领域的可能性。
称赞
1
MetaQuotes Software Corp:新文章《Dr Tradelove or how I stopped worrying and created an Expert Advisor for auto-training》已发表:
作者:Roman Zamozhnyy
大家下午好!
这篇文章很棒!
阅读您的文章让我大开眼界!
在花了大量时间优化机器人的结果之后,我意识到一个显而易见的问题:只要市场保持稳定,策略就只能在一定时间内有效,但随着时间的推移,市场会发生变化,一个月前还不错的策略现在就不再那么好了,这就迫使我进行新的参数测试,以找到最适应当前市场的周期和其他设置...
我的想法是让机器人能够在一段时间内自行实现更适应市场的新配置,并选择我事先定义的最佳配置。
我的问题是,是否有可能让机器人每隔 xxx 周或 xx 个月进行一次 "自我训练",并使用参数设置中选择的外部变量更新自己选择的最佳值。