简介
世界上数以百万计的交易者使用 MetaQuotes
Software Corp 开发的交易平台。导致成功的关键因素是以多年经验为基础的技术卓越性和最佳软件解决方案。
很多人已经用新的 MQL5 语言预测新的机
会。其关键特点是高性能和使用面向对象编程的可能性。此外,随着在 MetaTrader 5
客户端中出现多货币策略测试程序,很多交易者已经获得独特的工具来开发、学习和使用复杂的交易系统。
Automated
Trading Championship 2010(2010 年自动交易锦标赛)将在今年秋天开赛;数以千计采用 MQL5
编写的交易机器人将参赛。在比赛期间获得最高利润的 EA 交易程序将获胜。但是哪种策略会是最有效的一个呢?
MetaTrader 5
客户端的策略测试程序允许查找一组最佳参数,系统使用这些参数能够在指定时间段内赚取最大利润。但是在实时交易中行吗?在”Contest
of Expert Advisors inside an Expert Advisor”(在一个 EA 交易程序内的若干
EA 交易程序的竞争)一文中考虑了使用几个策略的虚拟交易的想法,该文包含这个想法在 MQL4 中的实施。
在本文中,我们将说明,由于使用面向对象编程、
标准库中用于处理数据的类和交
易类,自适应策略的创建和分析在 MQL5 已经容易多了。
1. 自适应交易策略
市场恒变。交易策略需要适应当前市场条件。
无需通过依序更改参数及分析测试结果进行优化就可以找到使
策略具有最大利润的参数的值。
图 1 说明十个 EA
交易程序的资产净值曲线(MA_3、…MA_93);每个都是按照移动平均线的策略交易的,但具有不同的时间段(3、13、…
93)。测试是在 EURUSD H1 上进行的,测试期为 2010 年 1 月 4 日至 2010 年 8 月 20 日。
图
1. 十个 EA 交易程序的帐户资产净值曲线图
如您所见,在图 1 中,EA
交易程序在前两个工作周期间具有几乎相同的结果,但是之后它们的盈利开始显著分化。在测试期结束时,时间段为 63、53 和 43 的 EA
交易程序显示出最佳交易结果。
市场选择最佳的一个。为什么我们不遵循其选择呢?如果我们
将所有十个策略整合在一个EA交易程序中,为每个策略提供“虚拟”交易的可能性,并且定期(例如,在每个新的指
标柱开始的时候“确定真实交易的最佳策略,并依据其信号进行交易会怎么样呢?
获得的自适应策略的结果显示在图 2
中。采用自适应交易的帐户的资产净值曲线以红色显示。注意,在多于半个周期的时间里,自适应策略的资产净值曲线的形态与 MA_63
策略的形态相同,该策略是最终的胜利者。
图
2. 运用使用 10 个交易系统的信号的自适应策略的帐户资产净值曲线
平衡曲线具有类似的动态(图 3):
图
3. 运用使用 10 个交易系统的信号的自适应策略的平衡曲线
如果在某时刻没有一个策略是盈利的,则自适应系统不应执行
交易操作。图 4 显示了此类情形的例子(2010 年 1 月 4 日至 2010 年 1 月 22 日)。
图
4. 自适应策略因为缺乏盈利策略而停止建立新仓位的时间
从 2010 年 1 月开始, MA_3
策略显示出最佳效率。因为 MA_3(蓝色)在该时刻赚最多的钱,自适应策略(红色)遵循其信号。在 1 月 8 日至 20
日的时间里,所有考虑的策略都有负面结果,这是为什么自适应策略不建立新交易仓位的原因。
如果所有策略都有负面结果,最好远离交易。这是非常重要的
事情,让您能够停止不赚钱的交易,并保持资金安全。
2. 自适应交易策略的实施
在这一部分,我们将考虑自适应策略的结构,该策略同时使用
几个交易策略进行“虚拟”交易,并且依据其信号选择最赚钱的策略进行真实交易。注意,使用面向对象的方法使
此问题的解决非常容易。
首先,我们将调查自适应 EA
交易程序的代码,接着我们将详细查看在其中实施自适应系统功能的CAdaptiveStrategy,然后我们将
显示CSampleStrategy 类的结构 – 在其中实施虚拟交易功能的交易策略的基类。
此外,我们将考虑其两个子项 – CStrategyMA
和 CStrategyStoch
类的代码,这两个类代表按移动平均线和随机振荡指标进行交易的策略。在分析它们的结构之后,您将能够轻松地编写和添加用于实现您的策略的您自己的类。
2.1. EA 交易程序的代码
EA 交易程序的代码看起来非常简单:
//+------------------------------------------------------------------+ //| Adaptive_Expert_Sample.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include <CAdaptiveStrategy.mqh> CAdaptiveStrategy Adaptive_Expert; //+------------------------------------------------------------------+ //| EA初始化函数 | //+------------------------------------------------------------------+ int OnInit() { return(Adaptive_Expert.Expert_OnInit()); } //+------------------------------------------------------------------+ //| EA去初始化函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Adaptive_Expert.Expert_OnDeInit(reason); } //+------------------------------------------------------------------+ //| EA订单函数 | //+------------------------------------------------------------------+ void OnTick() { Adaptive_Expert.Expert_OnTick(); } //+------------------------------------------------------------------+
前三行定义程
序的属性,接着是#include 指令,告诉预处理程序包含
CAdaptiveStrategy.mqh 文件。尖括号指定文件应来自标准目录(通常为
terminal_folder/MQL5/Include)。
下一行包含 Adaptive_Expert
对象(CAdaptiveStrategy 类的实例)的声明;EA 交易程序的OnInit、OnDeinit
和 OnTick
函数的代码包含相应函数Expert_OnInit、Expert_OnDeInit和Expert_OnTick以及Adaptive_Expert
对象的调用。
2.2.
CAdaptiveStrategy 类
自适应 EA
交易程序的类(CAdaptiveStrategy 类)位于 CAdaptiveStrategy.mqh 文件中。让我们以包含文件开始:
#include <Arrays/ArrayObj.mqh> #include <Trade/PositionInfo.mqh> #include <Trade/Trade.mqh> #include <CStrategyMA.mqh> #include <CStrategyStoch.mqh>
我们包含 ArrayObj.mqh
文件的原因是便于处理不同策略的类,这些策略使用CArrayObj 类的对象,表示指向由基类CObject
及其子类派生的类实例的指针的动态数组。此对象是m_all_strategies
数组,它将用作交易策略的一个“容器”。
每个策略被表示为一个类。在此示例中,我们已经包括含有CStrategyMA和CStrategyStoch类
的文件,这些类表示按移动平均线和随机振荡指标进行交易的策略。
要请求当前仓位的属性以及执行交易操作,我们将使用标准库
中的CPositionInfo和CTrade类,这是为什么我们包含PositionInfo.mqh
和 Trade.mqh 文件的原因之所在。
让我们看一看CAdaptiveStrategy类
的结构。
//+------------------------------------------------------------------+ //| CAdaptiveStrategy类 | //+------------------------------------------------------------------+ class CAdaptiveStrategy { protected: CArrayObj *m_all_strategies; // 交易策略对象 void ProceedSignalReal(int state,double trade_volume); int RealPositionDirection(); public: // 初始化自适应策略 int Expert_OnInit(); // 去初始化自适应策略 int Expert_OnDeInit(const int reason); // 检查交易条件建立虚拟仓位 void Expert_OnTick(); };
为了对不同类的对象实施统一的方法,交易策略(或者它们的
类的实例)存储在动态数组m_all_strategies中(CArrayObj
类型),该数组用作策略类的“容器”。这是交易策略SampleStrategy类从CObject
类派生的原因。
ProceedSignalReal
函数用指定方向和数量实施真实仓位的方向和数量的“同步”:
//+------------------------------------------------------------------+ //| 此方法用于将真实交易仓位和状态 | //| 值进行同步 | //+------------------------------------------------------------------+ void CAdaptiveStrategy::ProceedSignalReal(int state,double trade_volume) { CPositionInfo posinfo; CTrade trade; bool buy_opened=false; bool sell_opened=false; if(posinfo.Select(_Symbol)) // 如果有持仓 { if(posinfo.Type()==POSITION_TYPE_BUY) buy_opened=true; // 有买入仓位 if(posinfo.Type()==POSITION_TYPE_SELL) sell_opened=true; // 有卖出仓位 // 如果 state = 0, 我们需要关闭仓位 if((state==POSITION_NEUTRAL) && (buy_opened || sell_opened)) { if(!trade.PositionClose(_Symbol,200)) Print(trade.ResultRetcodeDescription()); } //反转: 关闭买入仓位开启卖出仓位 if((state==POSITION_SHORT) && (buy_opened)) { if(!trade.PositionClose(_Symbol,200)) Print(trade.ResultRetcodeDescription()); if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,trade_volume,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0)) Print(trade.ResultRetcodeDescription()); } //反转: 关闭卖出仓位开启买入仓位 if(((state==POSITION_LONG) && (sell_opened))) { if(!trade.PositionClose(_Symbol,200)) Print(trade.ResultRetcodeDescription()); if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,trade_volume,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0)) Print(trade.ResultRetcodeDescription()); } } else // 如果没有开启的仓位 { // 开启买入仓位 if(state==POSITION_LONG) { if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,0.1,SymbolInfoDouble(_Symbol,SYMBOL_ASK),0,0)) Print(trade.ResultRetcodeDescription()); } // 开启卖出仓位 if(state==POSITION_SHORT) { if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,0.1,SymbolInfoDouble(_Symbol,SYMBOL_BID),0,0)) Print(trade.ResultRetcodeDescription()); } } }
注意,使用交
易类处理交易仓位更加容易。我们使用CPositionInfo和CTrade类的对象分别请求市场仓位的属性以及执行交易操作。
RealPositionDirection
函数请求真实建仓的参数,并返回其方向:
//+------------------------------------------------------------------+ //| 返回当前真实仓位的方向 (0,+1,-1) | //+------------------------------------------------------------------+ int CAdaptiveStrategy::RealPositionDirection() { int direction=POSITION_NEUTRAL; CPositionInfo posinfo; if(posinfo.Select(_Symbol)) // 如果有开启的仓位 { if(posinfo.Type()==POSITION_TYPE_BUY) direction=POSITION_LONG; // 开启了买入仓位 if(posinfo.Type()==POSITION_TYPE_SELL) direction=POSITION_SHORT; // 开启了卖出仓位 } return(direction); }
现在,我们将查看 СAdaptiveStrategy
类的主要函数。
让我们以Expert_OnInit
函数开始:
//+------------------------------------------------------------------+ //| 初始化自适应EA的函数 | //+------------------------------------------------------------------+ int CAdaptiveStrategy::Expert_OnInit() { //--- 创建 m_all_strategies 对象数组 //--- 我们会把策略对象放置其中 m_all_strategies=new CArrayObj; if(m_all_strategies==NULL) { Print("创建 m_all_strategies 对象失败"); return(-1); } // 创建10个 CStrategyMA (根据移动平均交易) 类型的交易策略 // 初始化它们,设置参数 // 并把它们加入到 m_all_strategies 容器中 for(int i=0; i<10; i++) { CStrategyMA *t_StrategyMA; t_StrategyMA=new CStrategyMA; if(t_StrategyMA==NULL) { delete m_all_strategies; Print("创建 CStrategyMA 类型对象错误"); return(-1); } //给每个策略设置周期 int period=3+i*10; // 初始化策略 t_StrategyMA.Initialization(period,true); // 设置策略详细信息 t_StrategyMA.SetStrategyInfo(_Symbol,"[MA_"+IntegerToString(period)+"]",period,"Moving Averages "+IntegerToString(period)); //t_StrategyMA.Set_Stops(3500,1000); //把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyMA); } for(int i=0; i<m_all_strategies.Total(); i++) { CSampleStrategy *t_SampleStrategy; t_SampleStrategy=m_all_strategies.At(i); Print(i," 策略名称:",t_SampleStrategy.StrategyName(), " 策略编号:",t_SampleStrategy.StrategyID(), " 虚拟交易:",t_SampleStrategy.IsVirtualTradeAllowed()); } //--- return(0); }
在 Expert_OnInit
函数中准备了一组交易策略。首先,创建m_all_strategies动态数组的对象。
在此示例中,我们创建CStrategyMA类
的十个实例。每一个实例都用Initialization
函数进行初始化(在此示例中,我们设置不同的时间段并允许“虚拟”交易)。
然后,使用SetStrategyInfo函
数设置金融工具、策略名称和注释。
如有必要,使用Set_Stops(TP,SL)
函数指定将在“虚拟”交易期间执行的Take Profit(止盈)和Stop
Loss(止损)的值(点)。我们对此行添加了注释(运行时不执行此行)。
一旦创建并调整好策略类,我们将其添加到
m_all_strategies 容器中。
所有交易策略类都应有用于检查交易条件的 CheckTradeConditions()
函数。在自适应策略的类中,此函数在每个新指标柱开始时调用,因此我们使策略能够检查指标的值并进行“虚拟”交
易操作。
我们可以添加数以百计的移动平均线(或者实例,如果是 CStrategyMA
类),而不是十个指定的移动平均线 (3, 13, 23…93):
for(int i=0; i<100; i++) { CStrategyMA *t_StrategyMA; t_StrategyMA=new CStrategyMA; if(t_StrategyMA==NULL) { delete m_all_strategies; Print("创建 CStrategyMA 类型对象错误"); return(-1); } //设置每个策略的周期 int period=3+i*10; // 初始化策略 t_StrategyMA.Initialization(period,true); // 设置策略详细内容 t_StrategyMA.SetStrategyInfo(_Symbol,"[MA_"+IntegerToString(period)+"]",period,"Moving Averages "+IntegerToString(period)); //把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyMA); }
或者,我们可以添加按随机动量指标(CStrategyStoch
类的实例)的信号进行工作的策略的类。
for(int i=0; i<5; i++) { CStrategyStoch *t_StrategyStoch; t_StrategyStoch=new CStrategyStoch; if(t_StrategyStoch==NULL) { delete m_all_strategies; printf("创建 CStrategyStoch 类型对象错误"); return(-1); } //设置每个策略的周期 int Kperiod=2+i*5; int Dperiod=2+i*5; int Slowing=3+i; // 初始化策略 t_StrategyStoch.Initialization(Kperiod,Dperiod,Slowing,true); // 设置策略详细内容 string s=IntegerToString(Kperiod)+"/"+IntegerToString(Dperiod)+"/"+IntegerToString(Slowing); t_StrategyStoch.SetStrategyInfo(_Symbol,"[Stoch_"+s+"]",100+i," Stochastic "+s); //把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyStoch); }
在此示例中,容器包含移动平均线的 10
个策略和随机动量指标的 5 个策略。
交易策略的类的实例应该是CObject
类的子项并且应包含CheckTradeConditions() 函数。最好从CSampleStrategy
类继承它们。实施交易策略的类可能不同,并且它们的数量不受限制。
Expert_OnInit
函数以存在于m_all_strategies 容器中的策略的列表结束。注意,容器中的所有策略都被视为 CSampleStrategy
类的子项。交易策略 CStrategyMA 和 CStrategyStoch 的类也是其子项。
在Expert_OnDeInit
函数中使用相同的技巧。在容器中,我们为每个策略调用SaveVirtualDeals
函数;它存储所执行虚拟交易的历史记录。
我们使用策略的名称作为文件名,该名称作为一个参数传递。
然后我们通过调用Deinitialization() 函数并删除 m_all_strategies
容器,对策略进行去初始化:
//+------------------------------------------------------------------+ //| 自适应EA的去初始化函数 | //+------------------------------------------------------------------+ int CAdaptiveStrategy::Expert_OnDeInit(const int reason) { // 去初始化所有的策略 for(int i=0; i<m_all_strategies.Total(); i++) { CSampleStrategy *t_Strategy; t_Strategy=m_all_strategies.At(i); t_Strategy.SaveVirtualDeals(t_Strategy.StrategyName()+"_deals.txt"); t_Strategy.Deinitialization(); } //删除策略对象数组 delete m_all_strategies; return(0); }
如果您不需要知道策略执行的虚拟交易,则删除在其中调用
tStrategy.SaveVirtualDeals 的行。注意,当使用策略测试程序时,文件被保存到
/tester_directory/Files/ 目录中。
让我们考虑一下在每次新的价格变动出现时要调用的
CAdaptiveStrategy 类的 Expert_OnTick 函数:
//+------------------------------------------------------------------+ //| 自适应策略的订单处理函数 | //+------------------------------------------------------------------+ void CAdaptiveStrategy::Expert_OnTick() { CSampleStrategy *t_Strategy; // 重新计算策略的仓位信息 for(int i=0; i<m_all_strategies.Total(); i++) { t_Strategy=m_all_strategies.At(i); t_Strategy.UpdatePositionData(); } // EA应该只有在新柱出现的时候才检查进行交易操作的条件 if(IsNewBar()==false) { return; } // 检查所有策略的交易条件 for(int i=0; i<m_all_strategies.Total(); i++) { t_Strategy=m_all_strategies.At(i); t_Strategy.CheckTradeConditions(); } //寻找最佳仓位 //准备 performance[] 数组 double performance[]; ArrayResize(performance,m_all_strategies.Total()); //请求每个策略的效率值, //每个策略在 Strategyperformance() 函数中返回它 for(int i=0; i<m_all_strategies.Total(); i++) { t_Strategy=m_all_strategies.At(i); performance[i]=t_Strategy.StrategyPerformance(); } //寻找策略 (或者说寻找它在 m_all_strategies 容器中的索引) //根据 Strategyperformance() 返回的最大值 int best_strategy_index=ArrayMaximum(performance,0,WHOLE_ARRAY); //这个策略是 - t_Strategy t_Strategy=m_all_strategies.At(best_strategy_index); //请求仓位方向 int best_direction=t_Strategy.PositionDirection(); string s=s+" "+t_Strategy.StrategyName()+" "+DoubleToString(t_Strategy.GetVirtualEquity())+" "+IntegerToString(best_direction); Print(TimeCurrent()," 策略总数=",m_all_strategies.Total(), " 最佳索引=",best_strategy_index, " 最佳策略="," ",t_Strategy.StrategyName(), " 最佳成绩=",performance[best_strategy_index]," =", " 最佳方向=",best_direction, " 成绩=",t_Strategy.StrategyPerformance()); //如果最佳策略结果为负并且没有开启的仓位,最好不要进行交易 if((performance[best_strategy_index]<0) && (RealPositionDirection()==POSITION_NEUTRAL)) {return;} if(best_direction!=RealPositionDirection()) { ProceedSignalReal(best_direction,t_Strategy.GetCurrentLotSize()); } }
代码非常简单。容器中的每个策略都必须能够使用当前价重新
计算其虚拟仓位的当前财务结果。这通过调用 UpdatePositionData()
函数来完成。在这里,我们再一次称策略为 CSampleStrategy 类的子项。
所有交易操作都在新指标柱开始时执行(IsNewBar
() 函数允许确定该时刻和检查新指标柱的 其他方法)。在此示例中,指标柱形成的结束意味着以前的指标柱的所有数据
(价格和指标值)将不再改变,因此可以依据交易条件对其进行分析。对于所有策略,我们通过调用它们的 CheckTradeConditions
函数来提供执行这种检查和虚拟交易操作的机会。
现在,我们应在 m_all_strategies
数组的所有策略中查找最成功的策略。为此,我们使用 Performance[] 数组,每个策略的StrategyPerformance()
函数返回的值被置于该数组中。基类CSampleStrategy
包含此函数,以“虚拟”资产净值和平衡的当前值之差来计算。
使用ArrayMaximum
函数搜索最成功的策略的索引。如果最佳策略在当时具有负盈利,并且没有真实建仓,则最好不进行交易,这是我们退出函数的原因(请参阅第 1 部分)。
此外,我们请求此策略的虚拟仓位的方向
(best_direction)。如果它与真实仓位的当前方向不同,则将依据 best_direction 方向纠正真实仓位的当前方向(使用
ProceedSignalReal 函数)。
2.3.
CSampleStrategy 类
m_all_strategies
容器中的所有策略都被视为CSampleStrategy 类的子项。
该类是交易策略的基类;它包含虚拟交易的实施。在本文中,
我们将考虑一个虚拟交易实施的简化例子,并不考虑交换。交易策略的类应继承自 CSampleStrategy
类。
让我们显示此类的结构。
//+------------------------------------------------------------------+ //| CSampleStrategy.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #include <Object.mqh> #define POSITION_NEUTRAL 0 // 没有仓位 #define POSITION_LONG 1 // 买入仓位 #define POSITION_SHORT -1 // 卖出仓位 #define SIGNAL_OPEN_LONG 10 // 开启买入仓位的信号 #define SIGNAL_OPEN_SHORT -10 // 开启卖出仓位的信号 #define SIGNAL_CLOSE_LONG -1 // 关闭买入仓位的信号 #define SIGNAL_CLOSE_SHORT 1 // 关闭卖出仓位的信号 //+------------------------------------------------------------------+ //| 保存虚拟仓位参数的结构 | //+------------------------------------------------------------------+ struct virtual_position { string symbol; // 交易品种 int direction; // 虚拟仓位方向 (0-没有仓位,+1 买入,-1 卖出) double volume; // 仓位交易量 double profit; // 当前虚拟仓位的利润点数 double stop_loss; // 虚拟仓位的止损价位 double take_profit; // 虚拟仓位的获利价位 datetime time_open; // 虚拟仓位的开启时间 datetime time_close; // 虚拟仓位的关闭时间 double price_open; // 虚拟仓位的建仓价位 double price_close; // 虚拟仓位的平仓价位 double price_highest; // 虚拟仓位生命周期中的最高价位 double price_lowest; // 虚拟仓位生命周期中的最低价位 double entry_eff; // 进入效率 double exit_eff; // 退出效率 double trade_eff; // 交易效率 }; //+------------------------------------------------------------------+ //| CSampleStrategy 类 | //+------------------------------------------------------------------+ class CSampleStrategy: public CObject { protected: int m_strategy_id; // 策略编号 string m_strategy_symbol; // 交易品种 string m_strategy_name; // 策略名称 string m_strategy_comment; // 注释 MqlTick m_price_last; // 最后价位 MqlRates m_rates[]; // 当前报价的数组 bool m_virtual_trade_allowed; // 允许虚拟交易的标志 int m_current_signal_state; // 当前策略的状态 double m_current_trade_volume; // 交易手数 double m_initial_balance; // 初始余额 (在构造函数中设定, 默认值为 10000) int m_sl_points; // 止损点数 int m_tp_points; // 获利点数 virtual_position m_position; // 虚拟仓位 virtual_position m_deals_history[]; // 交易数组 int m_virtual_deals_total; // 交易总数 double m_virtual_balance; // 虚拟余额 double m_virtual_equity; // 虚拟净值 double m_virtual_cumulative_profit; // 累计虚拟利润 double m_virtual_profit; // 当前开启的虚拟仓位的利润 //检查并且如有必要根据止损价位关闭虚拟仓位 bool CheckVirtual_Stops(virtual_position &position); // 重新计算仓位和余额 void RecalcPositionProperties(virtual_position &position); // 重新根据现有价位计算虚拟建仓信息 void Position_RefreshInfo(virtual_position &position); // 开启虚拟卖出仓位 void Position_OpenShort(virtual_position &position); // 关闭虚拟卖出仓位 void Position_CloseShort(virtual_position &position); // 开启虚拟买入仓位 void Position_OpenLong(virtual_position &position); // 关闭虚拟买入仓位 void Position_CloseLong(virtual_position &position); // 关闭开启的虚拟仓位 void Position_CloseOpenedPosition(virtual_position &position); // 把关闭的仓位添加到 m_deals_history[] 数组中 (交易历史) void AddDealToHistory(virtual_position &position); //计算并且返回将用于交易的推荐的交易量 virtual double MoneyManagement_CalculateLots(double trade_volume); public: // 构造函数 void CSampleStrategy(); // 析构函数 void ~CSampleStrategy(); //返回当前虚拟余额 double GetVirtualBalance() { return(m_virtual_balance); } //返回当前虚拟净值 double GetVirtualEquity() { return(m_virtual_equity); } //返回当前虚拟持仓利润 double GetVirtualProfit() { return(m_virtual_profit); } //设置止损获利点数 void Set_Stops(int tp,int sl) {m_tp_points=tp; m_sl_points=sl;}; //设置当前交易手数 void SetLots(double trade_volume) {m_current_trade_volume=trade_volume;}; //返回当前交易手数 double GetCurrentLots() { return(m_current_trade_volume); } // 返回策略名称 string StrategyName() { return(m_strategy_name); } // 返回策略编号 int StrategyID() { return(m_strategy_id); } // 返回策略注释 string StrategyComment() { return(m_strategy_comment); } // 返回策略详细信息 (策略的交易品种,名称,编号) void SetStrategyInfo(string symbol,string name,int id,string comment); // 设置虚拟交易标志 (是否允许虚拟交易) void SetVirtualTradeFlag(bool pFlag) { m_virtual_trade_allowed=pFlag; }; // 返回是否允许虚拟交易的标志 bool IsVirtualTradeAllowed() { return(m_virtual_trade_allowed); }; // 返回策略的当前状态 int GetSignalState(); // 设置策略的当前状态 (如有必要修改虚拟仓位) void SetSignalState(int state); // 根据状态修改虚拟仓位 void ProceedSignalState(virtual_position &position); // 设置累计虚拟利润值 void SetVirtualCumulativeProfit(double cumulative_profit) { m_virtual_cumulative_profit=cumulative_profit; }; //返回策略效率 double StrategyPerformance(); //更新仓位数据 void UpdatePositionData(); //关闭开启的仓位 void CloseVirtualPosition(); //返回当前虚拟仓位的方向 int PositionDirection(); //初始化虚拟函数 virtual int Initialization() {return(0);}; //检查交易条件的虚拟函数 virtual bool CheckTradeConditions() {return(false);}; //去初始化的虚拟函数 virtual int Deinitialization() {return(0);}; //把虚拟交易保存到文件中 void SaveVirtualDeals(string file_name); };
我们不会分析其详细说明;可以在
CSampleStrategy.mqh文件中找到其他信息。在那里,您也可以找到检查新指标柱的函数 – IsNewBar。
3. 交易策略的类
这部分专注于在自适应EA交易程序中使用的交易策略的类的
结构。
3.1. CStrategyMA
类 – 按移动平均线交易的策略
//+------------------------------------------------------------------+ //| CStrategyMA.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #include <CSampleStrategy.mqh> //+------------------------------------------------------------------+ //| CStrategyMA 类实现了根据移动平均策略 | //| 进行虚拟交易 | //+------------------------------------------------------------------+ class CStrategyMA : public CSampleStrategy { protected: int m_handle; // 移动平均 (iMA) 指标的句柄 int m_period; // 移动平均指标的周期 double m_values[]; // 保存指标值的数组 public: // 初始化策略 int Initialization(int period,bool virtual_trade_flag); // 去初始化策略 int Deinitialization(); // 检查交易条件并开启虚拟仓位 bool CheckTradeConditions(); };
CStrategyMA
类是在其中实施虚拟交易的整个功能的CSampleStrategy类的子类。
protected(受保护)部分包含将在策略的类使用的
内部变量。这些变量如下:m_handle – iMA 指标的处理程序;m_period –
移动平均线的时间段;m_values[] – 将在CheckTradeConditions 函数中用于获取
指标当前值的数组。
public(公共)部分包含用于实施交易策略的三个函
数。
- Initialization 函数。在这里对策略进行初始化。如果您需要创建指标,则
在这里创建它们。 - Deinitialization 函数。在
这里对策略进行去初始化。在这里释放指标的处理程序。 - СheckTradeConditions 函数。在
这里,策略检查交易条件并生成用于虚拟交易的交易信号。为了执行虚拟交易操作,调用CStrategy父类的SetSignalState函数;以下四种
交易信号之一被传送到该函数:
- 用于建买入持仓的信号 (SIGNAL_OPEN_LONG)
- 用于建卖出持仓的信号 (SIGNAL_OPEN_SHORT)
- 用于平买入持仓的信号 (SIGNAL_CLOSE_LONG)
- 用于平卖出持仓的信号 (SIGNAL_CLOSE_SHORT)
//+------------------------------------------------------------------+ //| 策略初始化方法 | //+------------------------------------------------------------------+ int CStrategyMA::Initialization(int period,bool virtual_trade_flag) { // 设置移动平均周期 m_period=period; // 设置虚拟交易标志 SetVirtualTradeFlag(virtual_trade_flag); //设置数组索引方式为时间序列 ArraySetAsSeries(m_rates,true); ArraySetAsSeries(m_values,true); //创建指标获得句柄 m_handle=iMA(_Symbol,_Period,m_period,0,MODE_EMA,PRICE_CLOSE); if(m_handle<0) { Alert("创建 MA 指标错误 - 错误代码: ",GetLastError(),"!!"); return(-1); } return(0); } //+------------------------------------------------------------------+ //| 策略去初始化方法 | //+------------------------------------------------------------------+ int CStrategyMA::Deinitialization() { Position_CloseOpenedPosition(m_position); IndicatorRelease(m_handle); return(0); }; //+------------------------------------------------------------------+ //| 检查交易条件开启虚拟仓位 | //+------------------------------------------------------------------+ bool CStrategyMA::CheckTradeConditions() { RecalcPositionProperties(m_position); double p_close; // 取得最后三个柱的历史数据 if(CopyRates(_Symbol,_Period,0,3,m_rates)<0) { Alert("复制历史数据错误 - 错误:",GetLastError(),"!!"); return(false); } // 复制前一个柱的收盘价(柱1) p_close=m_rates[1].close; // 前一个柱的收盘价 if(CopyBuffer(m_handle,0,0,3,m_values)<0) { Alert("复制移动平均指标缓冲区错误 - 错误代码:",GetLastError()); return(false); } // 买入条件 1: MA 上升 bool buy_condition_1=(m_values[0]>m_values[1]) && (m_values[1]>m_values[2]); // 买入条件 2: 前一价格比MA高 bool buy_condition_2=(p_close>m_values[1]); // 卖出条件 1: MA 下降 bool sell_condition_1=(m_values[0]<m_values[1]) && (m_values[1]<m_values[2]); // 卖出条件 2: 前一价格比MA低 bool sell_condition_2=(p_close<m_values[1]); int new_state=0; if(buy_condition_1 && buy_condition_2) new_state=SIGNAL_OPEN_LONG; if(sell_condition_1 && sell_condition_2) new_state=SIGNAL_OPEN_SHORT; if((GetSignalState()==SIGNAL_OPEN_SHORT) && (buy_condition_1 || buy_condition_2)) new_state=SIGNAL_CLOSE_SHORT; if((GetSignalState()==SIGNAL_OPEN_LONG) && (sell_condition_1 || sell_condition_2)) new_state=SIGNAL_CLOSE_LONG; if(GetSignalState()!=new_state) { SetSignalState(new_state); } return(true); };
概念很简单 – 依据指标状态和价格确定信号类型
(new_state),然后请求虚拟交易的当前状态(使用 GetSignalState
函数);并且如果它们不相同,则调用SetSignalState
函数以“纠正”虚拟仓位。
3.2.
CStrategyStoch 类 – 按随机振荡指标交易的策略
以下给出依据 iStochastic
随机振荡指标的主线和信号线的相交执行交易的类的代码:
//+------------------------------------------------------------------+ //| CStrategyStoch.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #include <CSampleStrategy.mqh> //+------------------------------------------------------------------+ //| CStrategyStoch 根据随机振荡指标的交叉线 | //| 策略进行虚拟交易 | //+------------------------------------------------------------------+ class CStrategyStoch : public CSampleStrategy { protected: int m_handle; // 随机振荡指标的句柄 (iStochastic) int m_period_k; // K-周期 (用于计算的柱数) int m_period_d; // D-周期 (首要平滑周期) int m_period_slowing; // 最终平滑 double m_main_line[]; // 保存指标值的数组 double m_signal_line[]; // 保存指标值的数组 public: // 初始化策略 int Initialization(int period_k,int period_d,int period_slowing,bool virtual_trade_flag); // 去初始化策略 int Deinitialization(); // 检查交易策略,开启虚拟仓位 bool CheckTradeConditions(); }; //+------------------------------------------------------------------+ //| 策略初始化方法 | //+------------------------------------------------------------------+ int CStrategyStoch::Initialization(int period_k,int period_d,int period_slowing,bool virtual_trade_flag) { // 设置振荡指标周期 m_period_k=period_k; m_period_d=period_d; m_period_slowing=period_slowing; // 设置虚拟交易标志 SetVirtualTradeFlag(virtual_trade_flag); // 设置数组索引方式为时间序列 ArraySetAsSeries(m_rates,true); ArraySetAsSeries(m_main_line,true); ArraySetAsSeries(m_signal_line,true); // 创建指标获得句柄 m_handle=iStochastic(_Symbol,_Period,m_period_k,m_period_d,m_period_slowing,MODE_SMA,STO_LOWHIGH); if(m_handle<0) { Alert("创建随机振荡指标错误 - 错误代码: ",GetLastError(),"!!"); return(-1); } return(0); } //+------------------------------------------------------------------+ //| 策略去初始化方法 | //+------------------------------------------------------------------+ int CStrategyStoch::Deinitialization() { // 关闭所有开启仓位 Position_CloseOpenedPosition(m_position); // 释放指标句柄 IndicatorRelease(m_handle); return(0); }; //+------------------------------------------------------------------+ //| 检查交易条件开启虚拟仓位 | //+------------------------------------------------------------------+ bool CStrategyStoch::CheckTradeConditions() { // 调用函数重新计算仓位属性 RecalcPositionProperties(m_position); double p_close; // 获得最后3个柱的历史数据 if(CopyRates(_Symbol,_Period,0,3,m_rates)<0) { Alert("复制历史数据错误 - 错误:",GetLastError(),"!!"); return(false); } // 复制前一个柱的收盘价 (柱 1) p_close=m_rates[1].close; // 前1柱的收盘价 if((CopyBuffer(m_handle,0,0,3,m_main_line)<3) || (CopyBuffer(m_handle,1,0,3,m_signal_line)<3)) { Alert("复制随机振荡指标缓冲区错误 - 错误代码:",GetLastError()); return(false); } // 买入条件: 从下往上主线穿过信号线 bool buy_condition=((m_signal_line[2]<m_main_line[2]) && (m_signal_line[1]>m_main_line[1])); // 卖出条件: 从上往下主线穿过信号线 bool sell_condition=((m_signal_line[2]>m_main_line[2]) && (m_signal_line[1]<m_main_line[1])); int new_state=0; if(buy_condition) new_state=SIGNAL_OPEN_LONG; if(sell_condition) new_state=SIGNAL_OPEN_SHORT; if((GetSignalState()==SIGNAL_OPEN_SHORT) && (buy_condition)) new_state=SIGNAL_CLOSE_SHORT; if((GetSignalState()==SIGNAL_OPEN_LONG) && (sell_condition)) new_state=SIGNAL_CLOSE_LONG; if(GetSignalState()!=new_state) { SetSignalState(new_state); } return(true); };
如您所见, 在CStrategyStoch类
的结构和CStrategyMA类的结构之间的唯一差异是初始化函数(不同的参数)、所用指标的类型和交易信号。
因此,要在自适应EA交易程序中使用您的策略,您应以这种
类型的类的形式重写它们,并将它们载入m_all_strategies 容器。
4. 自适应交易策略的分析结果
在这一部分,我们将讨论自适应策略的具体应用的几个方面以
及改进它们的方法。
4.1.
通过使用反转信号的策略改进系统
当没有趋势时,移动平均线并不好。我们已经满足此类情形
– 在图 3 中,您可以看到在从 1 月 8 日至 1 月 20 日的时间里并不存在趋势;因此在交易中使用移动平均线的所有 10
个策略均有虚拟损失。作为缺少盈利的策略的结果,自适应系统停止了交易。有任何方法来避免此类负面效果吗?
让我们添加我们的 10 个策略 (MA_3,
MA_13, …MA_93),另外10 个CStrategyMAinv
类,它们的交易信号是反转的(条件相同,但是 SIGNAL_OPEN_LONG/SIGNAL_OPEN_SHORT 和
SIGNAL_CLOSE_LONG/SIGNAL_CLOSE_SHORT 交换了它们的位置)。因此,除了十个趋势策略(CStrategyMA
类的实例)以外,我们还有另外十个反趋势策略( CStrategyMAinv 类的实例)。
图 5 显示了使用包含二十个策略的自适应系统的结果。
图
5. 运用使用 20 个交易信号(10 个移动平均线 CAdaptiveMA 和 10
个“镜像”移动平均线 CAdaptiveMAinv)
的自适应策略的帐户资产净值图
如您在图 5 中所见,在所有 CAdaptiveMA
策略具有负面结果的时间内,以下 CAdaptiveMAinv 策略允许 EA
交易程序在交易刚开始避免不希望的亏损。
图
6. 自适应策略使用“反趋势”CAdaptiveMAinv 策略信号的时间
此类方法看起来可能是不可接受的,因为使用反趋势策略时损
失仅仅是一个时间问题。然而在我们的例子中,我们并不限于一个策略。市场更知道哪些策略在当时是有效的。
自适应系统的优势在于市场自我建议应使用哪个策略,以及在
什么时候使用它。
它使从策略逻辑中进行抽取成为可能 –
如果某个策略是有效的,则该策略的工作方式并不重要。自适应方法使用唯一的策略成功标准 – 其有效性。
4.2.
反转最差策略的信号是否值得?
以上显示的反转技巧导致一种关于使用最差策略的信号的潜在
可能性的想法。如果一个策略不盈利(并且是最差的一个),则我们可以通过反转操作来盈利?
我们能够通过简单地改变其信号,将损失策略转变为盈利策
略?为了回答此问题,我们需要在 CAdaptiveStrategy 类的 Expert_OnTick() 函数中用 ArrayMinimum
代替 ArrayMaximum,
并且通过将变量 BestDirection 的值乘以 -1 来实施方向的改变。
此外,我们需要注释掉负面效果时对虚拟交易的限制(因为我
们要分析最差策略的结果):
//if((Performance[BestStrategyIndex]<0) && (RealPositionDirection()==0)) {return;}
图 7 显示了使用最差策略的反转信号的自适应 EA
交易程序的资产净值图:
图
7. 运用十个策略和使用最差系统的反转信号的自适应系统的帐户的资产净值图
在此示例中,大多数时间里最差的策略是依据时间段 3
统计的移动平均线 (MA_3) 的相交进行交易的策略。如您在图 7
中所见,MA_3(蓝色)和自适应策略(红色)之间存在反转关系,但是自适应系统的财务结果并不让人印象深刻。
复制(并反转)最差策略的信号并没有导致交易有效性的改
进。
4.2.
为什么很多移动平均线并没有像其看起来的有效呢?
您可以通过将另外数以百计具有不同时间段的
CStrategyMA 策略添加到 m_all_strategies 容器来大量使用移动平均线(而不是 10 个)。
为此,稍微更改一下
CAdaptiveStrategy 类中的代码:
for(int i=0; i<100; i++) { CStrategyMA *t_StrategyMA; t_StrategyMA=new CStrategyMA; if(t_StrategyMA==NULL) { delete m_all_strategies; Print("创建 CStrategyMA 类型对象错误"); return(-1); } //设置每个策略的周期 int period=3+i*10; // 初始化策略 t_StrategyMA.Initialization(period,true); // 设置策略详细内容 t_StrategyMA.SetStrategyInfo(_Symbol,"[MA_"+IntegerToString(period)+"]",period,"Moving Averages "+IntegerToString(period)); //把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyMA); }
但是,您应该理解,闭合移动平均线将不可避免地相交;领先
者会不断改变;并且自适应系统将不必要地频繁更改其状态和建仓/平仓。结果,自适应系统的特征将变差。您可以通过比较系统的统计特征(策略测试程序的
“结果”选项卡)确信这一点。
最好不要基于含有平仓参数的多种策略创建自适应系统。
5. 应考虑什么?
m_all_strategies
容器可以包含数以千计的建议策略的实例,您甚至还可以添加所有具有不同参数的策略;然而,要赢得2010
年自动交易锦标赛,您需要开发先进的资金管理系统。注意,我们使用等于 0.1 手的交易量测试历史数据(以及在类的代码中使用)。
5.1 如何增大自适应 EA
交易程序的盈利?
CSampleStrategy 类具有虚拟函数 MoneyManagement_CalculateLots:
//+------------------------------------------------------------------+ //| 此函数返回策略推荐的交易量 | //| 当前交易量作为参数传入 | //| 交易量可能依赖于: | //| current m_virtual_balance 和 m_virtual_equity | //| 当前交易的统计 (位于 m_deals_history 中) | //| 或者您想要的任何其他事情 | //| 如果一个策略不需要修改交易量 | //| 您可以返回传入的交易量: return(trade_volume); | //+------------------------------------------------------------------+ double CSampleStrategy::MoneyManagement_CalculateLots(double trade_volume) { //返回我们收到的值 return(trade_volume); }
要管理交易量,您可以使用有关虚拟交易的结果和特征的统计
信息,这些信息记录在m_deals_history[] 数组中。
如果您需要增大交易量(例如,如果
m_deals_history[] 中的上一次虚拟交易是盈利的,则加倍;或减小交易量),您应按对应的方式更改返回值。
5.2
使用成交统计数据来计算策略性能
在 CSampleStrategy
类中实施的 StrategyPerformance() 函数用于计算策略性能。
//+-----------------------------------------------------------------------+ //| StrategyPerformance 函数- 策略效率的函数 | //+-----------------------------------------------------------------------+ double CSampleStrategy::StrategyPerformance() { //返回策略效率 //本案中是当前时刻净值和初始余额 //之间的数量差异, //也就是通过策略获得的资产 double performance=(m_virtual_equity-m_initial_balance); return(performance); }
一个策略的效率计算公式可以更复杂,例如,可以包含进入效
率、退出效率、成交效率、盈利、亏损等。
进入效率、退出效率、成交效率
(m_deals_history[] 结构数组的 entry_eff、exit_eff 和 trade_eff
字段)的计算是在虚拟交易期间自动执行的(请参阅 CSampeStrategy 类)。这些统计信息可用于创建您自己的、更加复杂的策略效率评估。
例如,作为效率的一个特征,您可以使用最近三次成交的盈利
(使用来自交易存档 m_deals_history[] 的 pos_Profit 字段):
double CSampleStrategy::StrategyPerformance() { //如果有交易,把最后三次结果相乘 if(m_virtual_deals_total>0) { int avdeals=MathRound(MathMin(3,m_virtual_deals_total)); double sumprofit=0; for(int j=0; j<avdeals; j++) { sumprofit+=m_deals_history[m_virtual_deals_total-1-j].profit; } double performance=sumprofit/avdeals; } return(performance); }
如果您想更改此函数,则仅在
CSampleStrategy
类中更改,因为对于自适应系统的所有交易策略,它必须是相同的。但是您应记住,资产净值和余额之间的差异也是效率的一个优秀因子。
5.3 使用止盈和止损
您可以通过设置固定止损水平来更改交易系统的效率(可以通
过调用 Set_Stops 函数来进行;该函数用于设置虚拟交易的止损点)。如果指定了止损点,则会自动执行虚拟仓位的平仓;此功能在 CSampleStrategy
类中实施。
在我们的例子中(请参阅
2.2“使用移动平均线的类的函数”),设置止损水平的函数被注释掉。
5.4. 累积虚拟盈利的定期归零
自适应方法具有与普通策略一样的缺点。如果领先策略开始亏
损,则自适应系统也开始亏损。这是有时候您需要将所有策略的工作结果“归零”并平它们的所有虚拟仓位的原因。
为此,在 CSampleStrategy
类中实施了以下函数:
// 设置累计"虚拟利润"的值 void SetVirtualCumulativeProfit(double cumulative_profit) { m_virtual_cumulative_profit=cumulative_perofit; }; //关闭所有的虚拟持仓 void CloseVirtualPosition();
可以不时地使用此类检查点,例如在每个 N 指标柱之后。
5.5. 没有奇迹
您应记住,自适应系统并不是长期以来梦寐以求的东西
(USDJPY H1,2010 年 1 月 4 日至 2010 年 8 月 20 日):
图
8. 使用 10 个策略中的最佳信号的自适应系统的金额和资产净值曲线 (USDJPY H1)
图 9 显示了所有策略的资产净值曲线。
图
9. 运用基于 10 个策略的自适应系统的帐户资产净值曲线 (USDJPY
H1)
如果在自适应系统中没有可盈利的策略,则使用它们就没有效
果。使用可盈利的策略。
我们应考虑其他重要且感兴趣的事情。注意交易刚开始时自适
应策略的行为:
图
10. 运用使用 10 个策略的自适应策略的帐户资产净值曲线
首先,所有策略都有负面结果,并且自适应策略停止交易;接
着,它开始在具有正面结果的策略之间切换;再接着,所有策略再次变得不盈利。
所有策略在开始时都有相同的余额。仅过一段时间,一个或其
他策略成为领先者;因此,建议在自适应策略中设置一个限制以避免在最前面几个指标柱上交易。为此,用一个变量补充 CAdaptiveStrategy
类的 Expert_OnTick 函数,该变量的值在每次出现新的指标柱时增大。
从开始,直到市场选择最佳策略为止,您应避免真实交易。
总结
在本文中,我们考虑了一种由多种策略组成的自适应系统的例
子,其中每种策略执行其自己的“虚拟”交易操作。实际交易依据当时最赚钱策略的信号进行。
归功于使用面向对象的方
法、标准库中用于处理数据的类和交
易类,系统的架构看起来很简单并且可扩展;现在,您可以轻松地创建和分析包含数以百计的交易策略的自适应系统。
注:为了便于分析自适应系统的行为,附加了
CSampleStrategy 类的调试版本( adaptive-systems-mql5-sources-debug-en.zip
压缩文件)。此版本的差别之处在于其工作期间的文本文件的创建;它们包含有关系统所含策略的虚拟余额/资产净值的动态变化的汇总报告。
本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/143
(274.09 KB)
(12.78 KB)
(12.38 KB)
MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。