
通用智能交易系统:组合交易及管理策略组合(第四章)
内容简介表
简介
经常我们需要创建一个能同另一个算法共存的算法,例如,某算法的执行不能影响另一个同时再运行的算法。这种情况出现在当你需要将多个算法集成到一个可执行ex5模块中的时候。尽管它很简单,但是也有明显的“缺陷” —在创建交易策略的引擎时必须考虑算法的特征。
CStrategy交易引擎包含一组算法,能使两个或多个交易策略联合运行。我们将在本文中详细讨论它们。同时我们也将创建一个交易组合 — 一组同时交易的EA,为了分散交易风险。CStrategyList类 — CStrategy类型策略的容器 — 属于提供同时操作策略的算法。此类允许上传基于XML描述的策略,同时使用相应的方法— 一个策略仓库,动态地创建它们。
附件视频展示了在MetaTrader 5 策略测试器中测试多个策略的过程。基于所描述的交易引擎的所有策略都有一个默认的自定义面板,它可以帮助您轻松的直接从图表上控制每个策略。
CStrategyList 策略管理器
在“通用智能交易系统”系列的第二篇文章中描述了 CStrategy 类和它的主要模块。通过使用这个类以及模块中的功能函数,每一个衍生策略都有一个统一的交易逻辑。然而,使用程序组织一个交易过程不仅仅是执行交易请求。确保策略之间的协作性是非常重要的,其中包括在一个可执行ex5模块中运行多种算法。
CStrategyList类就是用于这个特殊目的的。你可以从它的名字猜到,此类提供一个CStrategy型策略的列表,但是它的运作要比一般的数据容器更为复杂些。该模块解决如下问题:
- 确保多个交易策略同时运行;
- 向每个策略实例发送交易事件;
- 从统一的策略XML列表中创建策略对象;
- 同自定义EA配置面板交互。
这里是 CStrategyList 类的头文件:
//+------------------------------------------------------------------+ //| 管理CStrategy类型策略的容器类 //+------------------------------------------------------------------+ class CStrategyList { private: CLog* Log; // 日志 CArrayObj m_strategies; // CStrategy类型策略 CLimits* m_limits; void ParseStrategies(CXmlElement* xmlStrategies, bool load_curr_symbol); void ParseLimits(CXmlElement* xmlLimits); CStrBtn StrButton; public: CStrategyList(void); ~CStrategyList(void); void LoadStrategiesFromXML(string xml_name, bool load_curr_symbol); bool AddStrategy(CStrategy* strategy); int Total(); CStrategy* At(int index); void OnTick(); void OnTimer(); void OnBookEvent(string symbol); void OnDeinit(const int reason); void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); };
如你所见,大多数方法都是交易事件处理函数。他们有同样类型的内容。让我们分析其中一个,OnBookEvent:
//+------------------------------------------------------------------+ //| 发送 OnBookEvent 事件到所有列表策略 //+------------------------------------------------------------------+ void CStrategyList::OnBookEvent(string symbol) { for(int i=0; i<m_strategies.Total(); i++) { CStrategy *strategy=m_strategies.At(i); strategy.OnBookEvent(symbol); } }
从类的内容来看,它在列表中搜索CStrategy策略并在每个策略中调用一个对应的事件。其他事件的处理方法类似。
除了传递事件外,CStrategyList执行特殊的程序从XML文件中加载策略。关于它运作方式的更多详细信息请读下面的部分。
从一个XML列表中加载策略。一个策略的组合
如果一个ex5执行模块包含多个交易算法,我们需要能够生成策略组合的工具。假设两个具有不同参数的算法在一个执行模块中做交易。如何配置这些参数?最简单的办法就是在EA属性窗口输出每个策略的参数。但是,当使用许多策略,且每个策略都有很多参数时怎么办?这种情况下,不同的调整值,标识,字符串和备注参数列表将会非常庞大。下图就是执行三个交易策略的EA参数窗口:
图 1. 用三种策略交易的EA参数列表
一个EA甚至可以使用更多的策略。这种情况下,参数列表的规模可能难以想象。组合策略交易的第二个重要特征是:流水线化创建策略。假设我们想要运行不同参数的同一个策略。我们该怎么办?显然,除了参数设置不同,这两个策略几乎一样。我们可以把此任务交给一个独立的类,而无需手动创建每个策略。这个类可以自动创建策略对象并正确配置它。
在创建一个策略前,有必要给出它的完整描述。对其的描述需要包含如下几个方面:
- 策略名称;
- 唯一的ID或者编号;
- 策略加载的货币对;
- 时间框架;
- 策略的参数列表(每个策略一个)。
策略描述还可能包含畜类上述之外的属性。最好的方法是使用XML来描述。XML语言是一种特殊的描述工具。他是得描述复杂对象变得容易,因此一个交易策略可以被转化为一个XML文档,一个XML文档能被转化成一个策略。例如,基于一个XML文档,交易引擎能够创建一个策略并正确配置其参数。要直接使用MQL5语言处理这种类型的文档,我们可以使用Code Base中的一个特殊的XML-Parser库来实现。
这里是一个策略组合的XML描述样例,它加载三个不同参数的移动平均策略。
<Global> <Strategies> <Strategy Name="MovingAverage" Magic="100" Timeframe="PERIOD_M1" Symbol="Si"> <TradeStateStart>Stop</TradeStateStart> <Params> <FastMA>1</FastMA> <SlowMA>3</SlowMA> <Shift>0</Shift> <Method>MODE_SMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> <Strategy Name="MovingAverage" Magic="101" Timeframe="PERIOD_M5" Symbol="SBRF"> <TradeStateStart>BuyOnly</TradeStateStart> <Params> <FastMA>15</FastMA> <SlowMA>21</SlowMA> <Shift>0</Shift> <Method>MODE_SMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> <Strategy Name="MovingAverage" Magic="102" Timeframe="PERIOD_M15" Symbol="GAZR"> <TradeStateStart>BuyAndSell</TradeStateStart> <Params> <FastMA>12</FastMA> <SlowMA>45</SlowMA> <Shift>1</Shift> <Method>MODE_EMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> </Strategies> </Global>
每个策略形成<Strategy>单元。下面这些属性在其中指定:Symbol, Timeframe, Magic 和 StrategyName。从上面的例子我们看到三个策略中的每一个都有其自身的货币对,编号和时间框架。除了这些必须的参数,其他策略属性在XML列表中指定。<TradeStateStart>部分用于策略加载时指定交易模式。<Params>部分包含策略的参数。
在开始时,交易引擎尝试从上面的XML文件中加载交易策略。一个策略在LoadStrategiesFromXML方法中基于CStrategyList类的XML文件中,被加载和创建。下面是其方法体以及相关方法:
//+------------------------------------------------------------------+ //| 从XML文件 "xml_name" 中加载策略 //| 如果load_curr_symbol被设置为true,它将仅加载 //| 当前货币对CurrentSymbol()所对应的 //| 策略 //+------------------------------------------------------------------+ void CStrategyList::LoadStrategiesFromXML(string xml_name,bool load_curr_symbol) { CXmlDocument doc; string err; bool res=doc.CreateFromFile(xml_name,err); if(!res) printf(err); CXmlElement *global=GetPointer(doc.FDocumentElement); for(int i=0; i<global.GetChildCount(); i++) { CXmlElement* child = global.GetChild(i); if(child.GetName() == "Strategies") ParseStrategies(child,load_curr_symbol); } } //+------------------------------------------------------------------+ //| 解析 XML 策略 //+------------------------------------------------------------------+ void CStrategyList::ParseStrategies(CXmlElement *xmlStrategies,bool load_curr_symbol) { CParamsBase *params=NULL; for(int i=0; i<xmlStrategies.GetChildCount(); i++) { CXmlElement *xStrategy=xmlStrategies.GetChild(i); if(CheckPointer(params)!=POINTER_INVALID) delete params; params=new CParamsBase(xStrategy); if(!params.IsValid() || (params.Symbol()!=Symbol() && load_curr_symbol)) continue; CStrategy *str=CStrategy::GetStrategy(params.Name()); if(str==NULL) continue; str.ExpertMagic(params.Magic()); str.ExpertSymbol(params.Symbol()); str.Timeframe(params.Timeframe()); str.ExpertName(params.Name()); string name=str.ExpertName(); CXmlElement *xml_params=xStrategy.GetChild("Params"); if(xml_params!=NULL) str.ParseXmlParams(xml_params); CXmlElement *xml_mm=xStrategy.GetChild("MoneyManagment"); if(xml_mm!=NULL) { if(!str.MM.ParseByXml(xml_mm)) { string text="Strategy "+str.ExpertName()+" (Magic: "+(string)str.ExpertMagic()+") load MM from XML failed"; CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); Log.AddMessage(msg); } } CXmlElement *xml_regim=xStrategy.GetChild("TradeStateStart"); if(xml_regim!=NULL) { string regim=xml_regim.GetText(); if(regim=="BuyAndSell") str.TradeState(TRADE_BUY_AND_SELL); else if(regim=="BuyOnly") str.TradeState(TRADE_BUY_ONLY); else if(regim=="SellOnly") str.TradeState(TRADE_SELL_ONLY); else if(regim=="Stop") str.TradeState(TRADE_STOP); else if(regim=="Wait") str.TradeState(TRADE_WAIT); else if(regim=="NoNewEntry") str.TradeState(TRADE_NO_NEW_ENTRY); else { string text="For strategy "+str.ExpertName()+" (Magic: "+(string)str.ExpertMagic()+ ") set not correctly trade state: "+regim; CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); Log.AddMessage(msg); } } AddStrategy(str); } if(CheckPointer(params)!=POINTER_INVALID) delete params; }
此方法最有意思的是,使用特殊的静态方法CStrategy::GetStrategy来创建一个策略。策略的名称应当作为参数传入。方法返回与此名称相对应的一个类的实例。方法被设计为静态的,以便在策略对象创建之前就能够使用。GetStrategy在一个独立的头文件中,因为不同交易引擎的其他部分,你将需要不时的编辑它,向其中添加新的策略。如果你希望从XML中加载策略,它的创建过程必须被直接添加到此方法中。该头文件的源码如下:
//+------------------------------------------------------------------+ //| StrategyFactory.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "http://www.mql5.com" /* GetStrategy是一个策略制造器。它根据特定的名称创建策略对象。 为了自动化,该方法在于一个独立的文件中。 */ #include <Strategy\Strategy.mqh> #include <Strategy\Samples\MovingAverage.mqh> #include <Strategy\Samples\ChannelSample.mqh> CStrategy *CStrategy::GetStrategy(string name) { if(name=="MovingAverage") return new CMovingAverage(); if(name=="BollingerBands") return new CChannel(); CLog *mlog=CLog::GetLog(); string text="Strategy with name "+name+" not defined in GetStrategy method. Please define strategy in 'StrategyFactory.mqh'"; CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text); mlog.AddMessage(msg); return NULL; }
一旦策略被创建,应该改用<Params>中的参数对其进行初始化。因为每个策略的参数都是不同的,就不可能在交易引擎层面对这些参数进行初始化。相反,策略的基类嫩巩固调用虚拟方法ParseXmlParams。如果策略重写此方法并且正确的解析XML节点的参数列表,就将能够确定其自身需要的参数值。作为一个例子,来看看基于两根移动平均线的CMovingAverage策略(算法已在第一章中描述过了)的ParseXmlParams方法。
//+------------------------------------------------------------------+ //| 基于两个移动平均线的经典策略样例。 //| 如果快线从下往上穿越慢线 //| — 买入,从上往下穿越 — 卖出。 //+------------------------------------------------------------------+ class CMovingAverage : public CStrategy { ... public: virtual bool ParseXmlParams(CXmlElement *params); }; //+------------------------------------------------------------------+ //| 策略参数在重写CStrategy中 //| 方法的方法内部解析 //+------------------------------------------------------------------+ bool CMovingAverage::ParseXmlParams(CXmlElement *params) { bool res=true; for(int i=0; i<params.GetChildCount(); i++) { CXmlElement *param=params.GetChild(i); string name=param.GetName(); if(name=="FastMA") { int fastMA=(int)param.GetText(); if(fastMA == 0) { string text="Parameter 'FastMA' must be a number"; CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text); Log.AddMessage(msg); res=false; } else FastMA.MaPeriod(fastMA); } else if(name=="SlowMA") { int slowMA=(int)param.GetText(); if(slowMA == 0) { string text="Parameter 'SlowMA' must be a number"; CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text); Log.AddMessage(msg); res=false; } else SlowMA.MaPeriod(slowMA); } else if(name=="Shift") { FastMA.MaShift((int)param.GetText()); SlowMA.MaShift((int)param.GetText()); } else if(name=="Method") { string smethod=param.GetText(); ENUM_MA_METHOD method=MODE_SMA; if(smethod== "MODE_SMA") method = MODE_SMA; else if(smethod=="MODE_EMA") method=MODE_EMA; else if(smethod=="MODE_SMMA") method=MODE_SMMA; else if(smethod=="MODE_LWMA") method=MODE_LWMA; else { string text="Parameter 'Method' must be type of ENUM_MA_METHOD"; CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text); Log.AddMessage(msg); res=false; } FastMA.MaMethod(method); SlowMA.MaMethod(method); } else if(name=="AppliedPrice") { string price=param.GetText(); ENUM_APPLIED_PRICE a_price=PRICE_CLOSE; if(price=="PRICE_CLOSE") a_price=PRICE_CLOSE; else if(price=="PRICE_OPEN") a_price=PRICE_OPEN; else if(price=="PRICE_HIGH") a_price=PRICE_HIGH; else if(price=="PRICE_LOW") a_price=PRICE_LOW; else if(price=="PRICE_MEDIAN") a_price=PRICE_MEDIAN; else if(price=="PRICE_TYPICAL") a_price=PRICE_TYPICAL; else if(price=="PRICE_WEIGHTED") a_price=PRICE_WEIGHTED; else { string text="Parameter 'AppliedPrice' must be type of ENUM_APPLIED_PRICE"; CMessage *msg=new CMessage(MESSAGE_WARNING,SOURCE,text); Log.AddMessage(msg); res=false; } FastMA.AppliedPrice(a_price); SlowMA.AppliedPrice(a_price); } } return res; }
该策略的细节在本系列的第三篇文章中消息描述过了,其中还包括自定义策略的开发。
使用从文件中创建策略的机制,可以一次性配置一组策略,然后每次从一个文件中加载它。你甚至可以更进一步,写一个能自行优化的算法,将其最优的参数集保存到一个XML文件中。交易引擎在开始运行时读取该文件,并基于此形成一些列策略。
使用自定义面板管理策略
从用户角度来看,应能方便的通过一个特别的自定义面板来控制策略。此面板在EA加载后显示在一个图表上,并将允许对每个交易算法执行简单的操作:
- 改变策略的交易模式;
- 买入或卖出期望的头寸大小,而不是由策略决定。
如果EA由于某些原因没有正确执行恰当的操作,后面一个选项就非常有用了,并且你得将其状态和当前市场情况进行同步。
创建自定义面板和对话框类的描述超出了本文讨论的范围,需要另起一篇文章来描述。我们将仅仅描述和面板连接相关的基本概念。
EA控制面板在一个独立的类CPanel中实现,类中包括各种控件,诸如列表,按钮和文本标签。用于创建GUI的所有类都在<数据文件夹>\MQL5\Include\Panel中。为了确保面板的操作,有必要在EA的mq5文件中直接处理OnChartEvent事件。图表事件处理函数在CStrategyList类中,因此可以在OnChartEvent中调用此函数:
CStrategyList Manager; ... void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { Manager.OnChartEvent(id,lparam,dparam,sparam); }
CStrategyList中的这些事件处理函数将它们直接发送到面板中。
点击面板中的任何一个按钮,定义了执行何种操作并执行之。例如,如果我们从列表中选择了一个策略,当前策略的索引值将等于选中的这个,然后你可以进行后续交易操作了。比如,你可以通过从下拉列表中选择恰当的策略模式选项,来改变选中策略的交易模式。
图 2. 选定策略的模式列表
代表选定策略的买入和卖出执行起来是一样的。策略的指针调用CStrategy基类的Buy和Sell方法。交易量由传入的参数确定。在这种情况下,执行中的magic数字对应于策略的编号,因此无法区分手动交易还是EA交易。
需要注意的是EA的交易逻辑,所有用户的开仓以一般模式被EA所保持。它管理这类头寸如同管理其自动开仓的头寸一样。
以组交易的EA
我们可以创建一个策略组合。策略必须包含解析XML参数的方法,例如,我们要重写 ParseXmlParams 方法。还需要向CStrategy::GetStrategy方法中添加创建正确策略类型的功能。最终我们需要创建一个XML文件,含有策略列表和对应的参数。之后CStrategyList类将创建策略实例并将它们添加到策略列表中。此后自定义面板将显示这些策略。
让我们创建一个包含上述EA的策略组合。解析CMovingAverage和CChannel策略XML设置的例子在3.5和4.3节中提供。
用于创建两个策略的CStrategy::GetStrategy方法的方法体如下:
//+------------------------------------------------------------------+ //| StrategyFactory.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "http://www.mql5.com" /* GetStrategy是一个策略制造器。它根据特定的名称创建策略对象。 为了自动化,该方法在于一个独立的文件中。 */ #include <Strategy\Strategy.mqh> #include <Strategy\Samples\MovingAverage.mqh> #include <Strategy\Samples\ChannelSample.mqh> CStrategy *CStrategy::GetStrategy(string name) { if(name=="MovingAverage") return new CMovingAverage(); if(name=="BollingerBands") return new CChannel(); CLog *mlog=CLog::GetLog(); string text="Strategy with name "+name+" not defined in GetStrategy method. Please define strategy in 'StrategyFactory.mqh'"; CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text); mlog.AddMessage(msg); return NULL; }
最后是重写用于EA全名的函数。为 CMovingAverage 策略进行重写:
//+------------------------------------------------------------------+ //| EA的全称 //+------------------------------------------------------------------+ string CMovingAverage::ExpertNameFull(void) { string name=ExpertName(); name += "[" + ExpertSymbol(); name += "-" + StringSubstr(EnumToString(Timeframe()), 7); name += "-" + (string)FastMA.MaPeriod(); name += "-" + (string)SlowMA.MaPeriod(); name += "-" + StringSubstr(EnumToString(SlowMA.MaMethod()), 5); name += "]"; return name; }
现在一切就绪,可以创建策略组合了。我们的组合将包含四个交易系统。每一个都在其自身加载的货币对上进行交易。两个处理基于移动平均,另外两个基于布林带。这些策略更为详细的描述在先前的文章:“通用智能交易系统:自定义策略和辅助交易类(第三章)”中介绍过了。
我们的XML组合如下:
<Global> <Strategies> <Strategy Name="MovingAverage" Magic="100" Timeframe="PERIOD_M1" Symbol="Si"> <TradeStateStart>Stop</TradeStateStart> <Params> <FastMA>1</FastMA> <SlowMA>3</SlowMA> <Shift>0</Shift> <Method>MODE_SMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> <Strategy Name="MovingAverage" Magic="101" Timeframe="PERIOD_M5" Symbol="SBRF"> <TradeStateStart>BuyAndSell</TradeStateStart> <Params> <FastMA>15</FastMA> <SlowMA>21</SlowMA> <Shift>0</Shift> <Method>MODE_SMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> <Strategy Name="BollingerBands" Magic="102" Timeframe="PERIOD_M15" Symbol="GAZR"> <TradeStateStart>BuyAndSell</TradeStateStart> <Params> <Period>30</Period> <StdDev>1.5</StdDev> </Params> </Strategy> <Strategy Name="BollingerBands" Magic="103" Timeframe="PERIOD_M30" Symbol="ED"> <TradeStateStart>BuyAndSell</TradeStateStart> <Params> <Period>20</Period> <StdDev>2.0</StdDev> </Params> </Strategy> </Strategies> </Global>
此文件Strategies.xml应该保存在MetaTrader平台的公用数据文件夹中。
这里是创建一个EA的mq5模块源代码:
//+------------------------------------------------------------------+ //| Expert.mq5 | //| Copyright 2015, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "http://www.mql5.com" #property version "1.00" #include <Strategy\StrategiesList.mqh> CStrategyList Manager; //+------------------------------------------------------------------+ //| EA初始化函数 //+------------------------------------------------------------------+ int OnInit() { Manager.LoadStrategiesFromXML(StrategiesXMLFile,LoadOnlyCurrentSymbol); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| EA反初始化函数 //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| EA的tick函数 //+------------------------------------------------------------------+ void OnTick() { Manager.OnTick(); } //+------------------------------------------------------------------+ //| BookEvent 函数 //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { Manager.OnBookEvent(symbol); } void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { Manager.OnChartEvent(id,lparam,dparam,sparam); }
自定义变量StrategiesXMLFile 和 LoadOnlyCurrentSymbol在CStrategyList类中定义。它们在这个类中使用,用于指定加载的策略列表以及模式(是否仅允许加载同EA所运行图表名称一致的策略)。还要注意一些事件,诸如 OnBookEvent 和 OnTimer,没有被使用。这意味着它们不会被用于自定义策略。
编译应该能够成功。之后,EA(项目中的名称为 Agent.ex5)就可以使用了。让我们在图标上运行它。在此之前,我们必须确保所有被用到的货币对在 MetaTrader Market Watch 中可用。成功启动之后,EA的图标会出现在图表右上角。另一个按钮被添加到图表的左上角:它用于最大化自定义面板。如果我们在面板上选择EA(Agent)列表,将打开四个EA列表:
图 3. 已加载EA的列表
截图显示了由我们的 Strategies.xml文件创建的EA列表。过一会儿之后,策略开始交易 — 每一个策略独立运行于自己的货币对上。
在策略测试器中分析EA的效果
在生成一个策略组合之后,我们可以在策略测试器中对其运行效果进行测试。无需额外的操作,因为XML策略列表位于全局数据文件夹下,策略测试器能够访问。当在其中加载Agent.ex5 EA 模块后,所需的货币对会被自动加载。每个EA会根据其自身的交易逻辑运行,并且将独立绘制其含有的指标集。下面的视频展示了在四个不同的标的上测试策略组合的例子:
在策略测试器中,基于CStrategy的策略仿真同使用这些策略进行实际交易类似。可视化测试选项使你能够方便的检查策略进场和出场的准确性。
总结
我们已经考虑了可以创建任意交易策略集的算法。有了这些策略集或策略组合,当在同一个执行模块中管理多个交易策略时,你可以灵活有效的控制交易过程。这种算法对于使用多货币对作为交易对象的策略特别有用。使用所提出的方法,创建类似的交易算法就像开发传统交易策略一样简单。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/2179
注意: 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.



仍然无法编译。Error: 'OnChartEvent' - function must have a body Agent.mq5 68 12
注释掉 Agent.mq5 中的OnChartEvent 函数。在当前版本的编译器中,我们将不得不暂时不使用面板和图表事件。
正常运行:
15.04.16 的 MT5 版本 1301
P.S. 提示:对 MM 设置没有反应,我没有指定,总是只交易 1 手,也没有找到关于止损、止盈、止损的任何信息,或者代码中没有?
它可以工作:
15.04.16 的 MT5 版本 1301
P.S. 提示需要挖掘的地方:它对我未指定的 MM 设置没有反应,总是只交易 1 手,也没有找到关于止损、止盈、止损点的任何信息,或者代码中没有?