本文包含对通用交易引擎CStrategy的进一步描述。在第一片文章通用智能交易系统:交易策略的模式(第一部分)中,我们详细讨论了要实现的交易模型及函数。我们已经分析过一个由四种方法组成的通用EA框架,两个方法用于开新仓其他两个用于平仓。不同的方法调用组合可以定义一个特定的交易模型。例如,只允许EA卖或者买,可以管理未平仓头寸或等待。使用这些方法,一个EA可以根据交易时间或者日期进行灵活的设置。
然而,交易模型并不是一个EA的全部。在第二部分我们将讨论基于事件集中处理的CStrategy交易模型的事件模型。提出的事件处理方案和所有事件聚集在一处的系统事件不同。这样实现的好处在后面会讲到。
此外,本文还介绍两个重要的交易引擎类 — CStrategy 和 CPosition。第一个是整个EA交易逻辑的核心,它将事件和模型结合起来成为一个灵活的框架,自定义EA可以直接继承它。第二个类是通用交易操作的基类。它包含针对未平仓头寸的操作(如平仓或者修改止赢止损)。这样可以构成所有的交易操作并且使得它们独立于平台。
请注意,你必须遵守交易引擎具体功能的使用规则,这都是为你了保护你的利益而创建的。例如,策略不提供通过头寸搜索以及访问系统事件的功能。因此,没有必要为操作执行的顺序和使用哪个处理器来处理事件而担心。相反,CStrategy承担了这些及其他许多操作的实现,使得自定义EA能专注于它的交易逻辑。
如我们介绍的交易引擎,它是为方便普通用户使用想要的功能而设计。你无须探究所述算法的细节。你只需要知道总体规则和CStrategy的功能函数。因此,如果你发现本文有些部分比较难理解,跳过它们就行。
MetaTrader 5 提供了许多事件。这些事件包括市场价格变化通知(NewTick,BookEvent)以及系统事件如Timer或 TradeTransaction。对于每一个事件都有一个带有On*前缀的系统同名函数。此函数是该事件的处理者。例如,如果你想要处理到来的新的报价tick,则向OnTick函数中添加一系列合适的处理函数。
//+------------------------------------------------------------------+ //| EA的tick函数 | //+------------------------------------------------------------------+ void OnTick() { // 处理新的报价到来事件 // ... }
当OnBookEvent发生时,应该调用处理订单簿(市场深度)价格变化的函数:
void OnBookEvent (const string& symbol) { // 这里我们处理定单簿价格变化 // ... }
通过这个方法,我们的EA处理的事件越多其逻辑就越分散。开仓和平仓的逻辑可以分别放在不同的单元中。从编程的角度看,将逻辑分离成不同的单元可以是一个不错的解决方案,但是在我们的情况下这种方式并不好。相反,将交易逻辑集中在一个特定的地方集中处理更好。
另外,有些事件MetaTrader 5并不支持。这种情况下,EA要自己来检测事件的发生。例如,MetaTrader 5没有检测新柱形到来的事件处理函数。经常是由EA自身来执行检测。因此,交易我们所述的引擎的事件处理模型,不仅支持系统事件还支持自定义事件(别和图表用户事件混淆),这样大大简化了EA的开发。例如,这类事件之一的图表新柱形创建事件。
要理解所提出的事件模型,让我们使用特定的枚举值ENUM_MARKET_EVENT_TYPE来描述同价格或时间变化相关的事件:
//+------------------------------------------------------------------+ //| 确定市场事件类型 | //+------------------------------------------------------------------+ enum ENUM_MARKET_EVENT_TYPE { MARKET_EVENT_TICK, // 新报价到来 MARKET_EVENT_BAR_OPEN, // 新柱形开始 MARKET_EVENT_TIMER, // 激活计时器 MARKET_EVENT_BOOK_EVENT // 市场深度变化(包括新报价到来) };
如你所见,枚举值包括系统事件和MetaTrader 5中不直接支持的事件(MARKET_EVENT_BAR_OPEN — EA所在图表新柱形的开始)。
假设我们的通用EA有四种交易逻辑:InitBuy,InitSell,SupportBuy,SupportSell。我们已经在系类文章的第一部分“通用智能交易系统”中介绍过这些方法了。如果这些方法使用其中一个枚举值作为参数,EA的处理逻辑可以基于调用的方法,随时检测事件。让我们看一个简化的EA方案:
//+------------------------------------------------------------------+ //| ExampExp.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\Series.mqh> ulong ExpertMagic=12345; // EA的编号 //+------------------------------------------------------------------+ //| 确定市场事件类型 | //+------------------------------------------------------------------+ enum ENUM_MARKET_EVENT_TYPE { MARKET_EVENT_TICK, //当前货币对的新报价到来 MARKET_EVENT_BAR_OPEN, // 当前货币对新柱形开始 MARKET_EVENT_TIMER, // 激活计时器 MARKET_EVENT_BOOK_EVENT // 市场变化深度(包括报价到来) }; //+------------------------------------------------------------------+ //| 通用EA的原型 | //+------------------------------------------------------------------+ class CExpert { public: void InitBuy(ENUM_MARKET_EVENT_TYPE &event_id); void InitSell(ENUM_MARKET_EVENT_TYPE &event_id); void SupportBuy(ENUM_MARKET_EVENT_TYPE &event_id); void SupportSell(ENUM_MARKET_EVENT_TYPE &event_id); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::InitBuy(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::InitSell(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::SupportBuy(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::SupportSell(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } ENUM_MARKET_EVENT_TYPE event_type; CExpert Expert; datetime last_time; CTime Time; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| EA的tick函数 | //+------------------------------------------------------------------+ void OnTick() { event_type=MARKET_EVENT_TICK; CallExpertLogic(event_type); if(last_time!=Time[0]) { event_type=MARKET_EVENT_BAR_OPEN; CallExpertLogic(event_type); last_time=Time[0]; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnTimer() { event_type=MARKET_EVENT_TIMER; CallExpertLogic(event_type); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { event_type=MARKET_EVENT_TIMER; CallExpertLogic(event_type); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CallExpertLogic(ENUM_MARKET_EVENT_TYPE &event) { Expert.InitBuy(event); Expert.InitSell(event); Expert.SupportBuy(event); Expert.SupportSell(event); } //+------------------------------------------------------------------+
标示市场价格变化的事件调用CallExpertMagic函数,当前事件作为参数传入。这个函数依次调用CExpert四种方法。当被调用,这些方法中的每一个都会打印自身名称以及引起调用的事件标识符。如果你在图标上运行这个EA,标识被处理事件ID的相关记录之后将显示出来:
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:40.015 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_TICK 2015.11.30 14:13:40.015 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_TICK
当设计一个分析多货币对交易系统时,你需要创建一个可以追踪多货币对价格变化的机制。然而,标准的OnTick函数仅在EA运行货币对上新报价到来时被激活。另一方面,交易系统开发者可以使用OnBookEvent函数,它响应订单簿(市场深度)中的价格变化。和OnTick不同,OnBookEvent在订单簿中的任一对象发生价格变化时被调用,你可以使用MarketBookAdd 函数来订阅订单簿。
订单簿的改变非常频繁,这也是为何监控该事件是非常耗费资源的。一般,对于EA来说监控所需货币的报价变化足够了。另一方面,订单簿变化事件也包含了新报价的到来。除了OnBookEvent,你可以设置以一定的时间间隔调用OnTimer,并在其中分析多货币对的价格变化。
因此,在对NewTick,BookEvent 和 Timer 事件做出响应的系统函数中,你可以添加对某些中间件 的调用(我们称之为事件处理器EventProcessor),它将同时分析多个货币对的价格变化并产生与之对应的事件。每一个事件将有一个结构体形式的统一描述,并将由策略的控制函数传递。接收到一个结构体的事件后,策略将对此做出相应或者忽略它。在这种情况下,为EA最终实际产生事件的系统函数将不可知。
事实上,如果EA接收到一个新报价事件通知,至于通知通过OnTick, OnTimer 还是 OnBookEvent来接收是无关紧要的。重要的是指定货币对上有新的报价到来。一个事件处理器可以给很多策略使用。例如,如果每个策略由一个自定义类代表,这些类的实例可以被存储在特定的策略列表中。在这种情况下,列表中的任一策略将能够接收到由EventProcessor产生的新事件。下面的图表显示了事件是如何产生和发送的:
图 1. 事件产生和发送图
现在让我们来考虑将被作为一个事件传入EA的实际结构体。结构体被称为MarketEvent,它的定义如下:
//+------------------------------------------------------------------+ //|结构体定义事件类型,事件发生的图表对象及时间框架(用于BarOpen事件) | //| | //+------------------------------------------------------------------+ struct MarketEvent { ENUM_MARKET_EVENT_TYPE type; // 事件类型 ENUM_TIMEFRAMES period; // 事件发生所在图表的时间框架(仅用于MARKET_EVENT_BAR_OPEN) string symbol; // 事件发生所在的货币对名称 };
通过引用接收到结构体实例后,做交易决定的方法将能够对其进行分析,并且基于下面的信息来做出正确的决定:
如果对于一个事件(如,NewTick 或 Timer)分析图表时间框架没有用处,那么MarketEvent结构体的period字段将始终用PERIOD_CURRENT的值填充。
为了追踪多货币对上新报价和新柱形到来的信息,我们要写一个恰当的模型类来实现这个任务。这些模型是CStrategy类的内部部分,用户不直接接触它们。第一个要考虑的模型是CTickDetector类。这个类的源代码如下:
//+------------------------------------------------------------------+ //| NewTickDetector.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, MetaQuotes Software Corp." #property link "https://www.mql5.com" #include <Object.mqh> //+------------------------------------------------------------------+ //| 新报价检测器 | //+------------------------------------------------------------------+ class CTickDetector : public CObject { private: string m_symbol; // 追踪该货币的新报价 MqlTick m_last_tick; // 最近一次报价 public: CTickDetector(void); CTickDetector(string symbol); string Symbol(void); void Symbol(string symbol); bool IsNewTick(void); }; //+------------------------------------------------------------------+ //| 构造函数默认使用当前时间框架 | //| 和货币对进行设置 | //+------------------------------------------------------------------+ CTickDetector::CTickDetector(void) { m_symbol=_Symbol; } //+------------------------------------------------------------------+ //| 用预设的货币对和时间框架创建一个对象 | //+------------------------------------------------------------------+ CTickDetector::CTickDetector(string symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+ //| 设置需要追踪新报价的货币对名称 | //| | //+------------------------------------------------------------------+ void CTickDetector::Symbol(string symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+ //| 返回追踪的新报价出现的货币对名称 | //| | //+------------------------------------------------------------------+ string CTickDetector::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| 如果给定货币对和时间框架下出现新的报价, | //| 返回 true | //+------------------------------------------------------------------+ bool CTickDetector::IsNewTick(void) { MqlTick tick; SymbolInfoTick(m_symbol,tick); if(tick.last!=m_last_tick.last || tick.time!=m_last_tick.time) { m_last_tick=tick; return true; } return false; }
IsNewTick是我们主要使用的方法。如果被监控的交易对象上出现新的报价,则返回true。要监控的交易对象通过Symbol方法来设置。CTickDetector类从CObject派生而来。因此,它可以被添加为CArrayObj中的一个元素。这正是我们需要的。例如,我们可以创建CTickDetect的10个副本,每一个监控一个货币对。通过不断的引用CArrayObj类型的类集,你可以迅速的找到出现新报价的货币对,然后生成响应事件将其传入EA。
正如已经提到的,除了新的报价事件,还需要经常检测新柱形的出现。最好把这个任务交给CBarDetector类来处理。它的工作原理和CTickDetector类似。CBarDetector的主要方法是IsNewBar — 如果货币对上出现新的柱形,方法返回 true,货币对使用Symbol方法指定。源代码如下:
//+------------------------------------------------------------------+ //| NewBarDetecter.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "http://www.mql5.com" #include <Object.mqh> //+------------------------------------------------------------------+ //| 检测指定货币对和时间框架下新柱形出现的类 | //| | //+------------------------------------------------------------------+ class CBarDetector : public CObject { private: ENUM_TIMEFRAMES m_timeframe; // 追踪新柱形形成的时间框架 string m_symbol; // 新柱形形成所在的货币对 datetime m_last_time; // 最近一个柱形的开始时间 public: CBarDetector(void); CBarDetector(string symbol,ENUM_TIMEFRAMES timeframe); void Timeframe(ENUM_TIMEFRAMES tf); ENUM_TIMEFRAMES Timeframe(void); void Symbol(string symbol); string Symbol(void); bool IsNewBar(void); }; //+------------------------------------------------------------------+ //| 构造函数默认使用当前时间框架 | //| 和货币对进行设置 | //+------------------------------------------------------------------+ CBarDetector::CBarDetector(void) { m_symbol=_Symbol; m_timeframe=Period(); } //+------------------------------------------------------------------+ //| 用预设的货币对和时间框架创建一个对象 | //+------------------------------------------------------------------+ CBarDetector::CBarDetector(string symbol,ENUM_TIMEFRAMES tf) { m_symbol=symbol; m_timeframe=tf; } //+------------------------------------------------------------------+ //| 设置你想追踪的新柱形出现的时间框架 | //| | //+------------------------------------------------------------------+ void CBarDetector::Timeframe(ENUM_TIMEFRAMES tf) { m_timeframe=tf; } //+------------------------------------------------------------------+ //| 返回你所追踪的新柱形出现 | //| 的时间框架 | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES CBarDetector::Timeframe(void) { return m_timeframe; } //+------------------------------------------------------------------+ //| 设置想要追踪的货币对名称 | //| | //+------------------------------------------------------------------+ void CBarDetector::Symbol(string symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+ //| 返回新柱形出现的货币对名称 | //| | //+------------------------------------------------------------------+ string CBarDetector::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| 如果给定货币对和时间框架下出现新的报价, | //| 的时间框架 | //+------------------------------------------------------------------+ bool CBarDetector::IsNewBar(void) { datetime time[]; if(CopyTime(m_symbol, m_timeframe, 0, 1, time) < 1)return false; if(time[0] == m_last_time)return false; return m_last_time = time[0]; }
现在是时候分析通用EA操作中最重要的类之一了。这个类包含了 MetaTrader 5 中处理头寸的方法。从技术角度来看,它很简单。它是一个封装类,作为EA和 MetaTrader 5 系统函数 — PositionSelect 和 PositionGet之间的中间件。然而,它实现了给出类的一个重要的功能 — 独立于平台的。
通过仔细分析上述所有模块,你可以得出结论,它们没有使用仅适用于一种平台(MetaTrader 4 或 MetaTrader 5)的函数。因为事实上,当前的MQL4和MQL5是同一种语言使用了不同的函数集而已。两个平台的特别函数集主要是和交易头寸管理相关。
一般来说,所有的EA都会用到头寸管理函数。然而,如果一个EA使用一个单一的,头寸管理类形式的抽象接口来取代这些函数,那就有可能(至少理论上)开发一个兼容MetaTrader 4 和 MetaTrader 5 的自动交易系统,而无需改变其源代码。我们所要做的是开发几个用来操作头寸的替代类。一个类使用 MetaTrader 4 的函数,另一个将使用来自 MetaTrader 5 的函数。然而,这些类都将为最终的EA提供同样的方法集来实现“独立于平台”的能力。
实际上,创建一个独立于平台的EA有些复杂,需要单独论述。这就将是非常长的文章了,因此我们不打算在本文中讨论,将在其他文章里考虑。
第二个支持用特定的类来代表策略的每一个头寸的理由是,每个头寸都要被独立的管理。交易引擎遍历头寸列表并将每一个头寸传入EA的处理逻辑中。因此,我们获得了极大的灵活性:EA独立管理每一个头寸,因此就有可能构建管理多个头寸的策略。当然,在MetaTrader 5中你只能有一个持仓头寸。但是如果我们将交易引擎导入MetaTrader 4平台,这一特性就非常重要了。
这里是这个类的源代码。它简单且直观:
//+------------------------------------------------------------------+ //| PositionMT5.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "http://www.mql5.com" #include <Object.mqh> #include "Logs.mqh" #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| 典型策略的持仓头寸类 | //+------------------------------------------------------------------+ class CPositionMT5 : public CObject { private: ulong m_id; // 持仓头寸唯一标识 uint m_magic; // 头寸所属EA的唯一标识 ENUM_POSITION_TYPE m_direction; // 方向 double m_entry_price; // 开仓价 string m_symbol; // 持仓头寸的品种 datetime m_time_open; // 开仓时间 string m_entry_comment; // 注释 bool m_is_closed; // 如果头寸被平仓则 True CLog* Log; // 日志 CTrade m_trade; // 交易模型 public: CPositionMT5(void); uint ExpertMagic(void); ulong ID(void); ENUM_POSITION_TYPE Direction(void); double EntryPrice(void); string EntryComment(void); double Profit(void); double Volume(void); string Symbol(void); datetime TimeOpen(void); bool CloseAtMarket(string comment=""); double StopLossValue(void); bool StopLossValue(double sl); double TakeProfitValue(void); bool TakeProfitValue(double tp); }; //+------------------------------------------------------------------+ //| 初始化一个持仓头寸的基本属性 | //+------------------------------------------------------------------+ void CPositionMT5::CPositionMT5(void) : m_id(0), m_entry_price(0.0), m_symbol(""), m_time_open(0) { m_id=PositionGetInteger(POSITION_IDENTIFIER); m_magic=(uint)PositionGetInteger(POSITION_MAGIC); m_direction=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); m_entry_price=PositionGetDouble(POSITION_PRICE_OPEN); m_symbol=PositionGetString(POSITION_SYMBOL); m_time_open=(datetime)PositionGetInteger(POSITION_TIME); m_entry_comment=PositionGetString(POSITION_COMMENT); m_trade.SetExpertMagicNumber(m_magic); } //+------------------------------------------------------------------+ //| 返回持仓方向 | //+------------------------------------------------------------------+ ENUM_POSITION_TYPE CPositionMT5::Direction(void) { return m_direction; } //+------------------------------------------------------------------+ //| 返回当前持仓所属 | //| EA的唯一标识 | //+------------------------------------------------------------------+ uint CPositionMT5::ExpertMagic(void) { return m_magic; } //+------------------------------------------------------------------+ //| 返回持仓头寸唯一标识符 | //+------------------------------------------------------------------+ ulong CPositionMT5::ID(void) { return m_id; } //+------------------------------------------------------------------+ //| 返回开仓价格 | //+------------------------------------------------------------------+ double CPositionMT5::EntryPrice(void) { return m_entry_price; } //+------------------------------------------------------------------+ //| 返回激活持仓的注释 | //+------------------------------------------------------------------+ string CPositionMT5::EntryComment(void) { return m_entry_comment; } //+------------------------------------------------------------------+ //| 返回当前有未平仓头寸的货币对名称 | //+------------------------------------------------------------------+ string CPositionMT5::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| 返回开仓时间 | //+------------------------------------------------------------------+ datetime CPositionMT5::TimeOpen(void) { return m_time_open; } //+------------------------------------------------------------------+ //| 返回当前头寸的止赢价格 | //| 如果止损未设置,返回0.0 | //+------------------------------------------------------------------+ double CPositionMT5::StopLossValue(void) { if(!PositionSelect(m_symbol)) return 0.0; return PositionGetDouble(POSITION_SL); } //+------------------------------------------------------------------+ //| 设置止赢价格 | //+------------------------------------------------------------------+ bool CPositionMT5::StopLossValue(double sl) { if(!PositionSelect(m_symbol)) return false; return m_trade.Buy(0.0, m_symbol, 0.0, sl, TakeProfitValue(), NULL); } //+------------------------------------------------------------------+ //| 返回当前头寸的止赢价格 | //| 如果止损未设置,返回0.0 | //+------------------------------------------------------------------+ double CPositionMT5::TakeProfitValue(void) { if(!PositionSelect(m_symbol)) return 0.0; return PositionGetDouble(POSITION_TP); } //+------------------------------------------------------------------+ //| 设置止赢价格 | //+------------------------------------------------------------------+ bool CPositionMT5::TakeProfitValue(double tp) { if(!PositionSelect(m_symbol)) return false; return m_trade.Buy(0.0, m_symbol, 0.0, StopLossValue(), tp, NULL); } //+------------------------------------------------------------------+ //| 按当前市价平仓并设置一个平仓备注 | //| 'comment' | //+------------------------------------------------------------------+ bool CPositionMT5::CloseAtMarket(string comment="") { if(!PositionSelect(m_symbol)) return false; return m_trade.PositionClose(m_symbol); } //+------------------------------------------------------------------+ //| 返回当前头寸大小 | //+------------------------------------------------------------------+ double CPositionMT5::Volume(void) { if(!PositionSelect(m_symbol)) return false; return PositionGetDouble(POSITION_VOLUME); } //+------------------------------------------------------------------+ //| 返回以存款货币计算的当前持仓头寸的获利 | //+------------------------------------------------------------------+ double CPositionMT5::Profit(void) { if(!PositionSelect(m_symbol)) return false; return PositionGetDouble(POSITION_PROFIT); }
如你所见,这个类的主要功能是返回当前未平仓头寸的特定属性。同时,类还提供了一些管理持仓头寸的方法:平仓,改变止赢止损。
我们已经考虑了不少执行公共任务的模块。我们还分析了事件模型,决策模型,以及一个典型的EA交易算法。现在我们需要将这一切组合到EA的一个独立模块中 — CStrategy类。它要执行多种任务。下面是其中的一些:
CStrategy是一个非常大的类,因此我们不打算在此列出它的所有源码。取而代之的是我们将聚焦于这个类的最重要的部分。首先我们将要考虑的是它的事件模型。我们已经对此有所了解了。现在我们仅需要考虑一个接受事件的处理过程以及将它们传递给某一策略。CStrategy 是我们未来策略的一个父类。因此,我们熟悉它的收到相应事件通知的方法。
//+------------------------------------------------------------------+ //| 策略的基类 | //+------------------------------------------------------------------+ class CStrategy : public CObject { //... void OnTick(void); void OnTimer(void); void OnBookEvent(string symbol); virtual void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result); //... };
请注意所有的事件处理函数除了OnTradeTransaction外都不是虚函数。这意味着Tick,Timer 或者 BookEvent不能直接在策略层进行处理。相反,它们由CStrategy进行处理。TradeTransaction事件未在此类中使用,因为它是虚函数。
下面是OnTick,OnTimer 和 OnBookEvent的内容:
//+------------------------------------------------------------------+ //| 对系统事件“OnBookEvent” | //| 调用策略的处理函数 | //+------------------------------------------------------------------+ void CStrategy::OnTick(void) { NewTickDetect(); NewBarsDetect(); } //+------------------------------------------------------------------+ //| 对系统事件“OnBookEvent” | //| 调用策略的处理函数 | //+------------------------------------------------------------------+ void CStrategy::OnTimer(void) { m_event.symbol=Symbol(); m_event.type=MARKET_EVENT_TIMER; m_event.period=(ENUM_TIMEFRAMES)Period(); CallSupport(m_event); CallInit(m_event); NewTickDetect(); NewBarsDetect(); } //+------------------------------------------------------------------+ //| 对系统事件“OnBookEvent” | //| 调用策略的处理函数 | //+------------------------------------------------------------------+ void CStrategy::OnBookEvent(string symbol) { m_event.symbol=symbol; m_event.type=MARKET_EVENT_BOOK_EVENT; m_event.period=PERIOD_CURRENT; CallSupport(m_event); CallInit(m_event); NewTickDetect(); NewBarsDetect(); }
如上所述,NewTick系统事件仅对当前正在运行EA的图表对象起作用。如果我们想让NewTick成为一个多货币对事件,我们应该使用一个特殊的新报价处理函数并且追踪新柱形的出现。这个任务交给相应的类似方法:NewBarDetect 和 NewTickDetect。下面是它们的源代码:
//+------------------------------------------------------------------+ //| 检测一个新柱形的出现并 | //| 为EA产生一个与之对应的件 | //+------------------------------------------------------------------+ void CStrategy::NewBarsDetect(void) { if(m_bars_detecors.Total()==0) AddBarOpenEvent(ExpertSymbol(),Timeframe()); for(int i=0; i<m_bars_detecors.Total(); i++) { CBarDetector *bar=m_bars_detecors.At(i); if(bar.IsNewBar()) { m_event.period = bar.Timeframe(); m_event.symbol = bar.Symbol(); m_event.type=MARKET_EVENT_BAR_OPEN; CallSupport(m_event); CallInit(m_event); } } } //+------------------------------------------------------------------+ //| 检测多个货币对上新报价的到来 | //+------------------------------------------------------------------+ void CStrategy::NewTickDetect(void) { if(m_ticks_detectors.Total()==0) AddTickEvent(ExpertSymbol()); for(int i=0; i<m_ticks_detectors.Total(); i++) { CTickDetector *tick=m_ticks_detectors.At(i); if(tick.IsNewTick()) { m_event.period=PERIOD_CURRENT; m_event.type=MARKET_EVENT_TICK; m_event.symbol=tick.Symbol(); CallSupport(m_event); CallInit(m_event); } } }
此方法实际上检测新报价和柱形探测器(上面所述的CTickDetector 和 CBarDetector类)。每个探测器最初是为了检测EA所运行于的交易对象而配置的。如果一个新的报价或柱形出现,特定的CallSupport和CallInit方法被调用,然后调用特定策略的交易函数。
事件处理程序的一般算法,包括NewBarsDetect 和 NewTickDetect,如下所述:
让了解控制是如何被传入一个特定策略的,我们要研究 CallInit 和 CallSupport 方法。这里是CallInit方法的源码:
//+------------------------------------------------------------------+ //| 如果交易状态没有明确限制的话, | //| 调用开仓逻辑。 | //+------------------------------------------------------------------+ void CStrategy::CallInit(const MarketEvent &event) { m_trade_state=m_state.GetTradeState(); if(m_trade_state == TRADE_STOP)return; if(m_trade_state == TRADE_WAIT)return; if(m_trade_state == TRADE_NO_NEW_ENTRY)return; SpyEnvironment(); if(m_trade_state==TRADE_BUY_AND_SELL || m_trade_state==TRADE_BUY_ONLY) InitBuy(event); if(m_trade_state==TRADE_BUY_AND_SELL || m_trade_state==TRADE_SELL_ONLY) InitSell(event); }
该方法从m_state 模块接收交易状态(第一篇文章中的CTradeState类),并确定针对给出的交易状态是否要调用新头寸的初始化方法。如果交易状态禁止交易,这些方法不会被调用。否则,它们会调用起一个事件指示,它会引起对方法CallInit的调用。
CallSupport的工作原理类似,但是逻辑上有些不同。这里是它的源代码:
//+------------------------------------------------------------------+ //| 如果交易状态不是TRADE_WAIT, | //| 调用持仓保持逻辑 | //+------------------------------------------------------------------+ void CStrategy::CallSupport(const MarketEvent &event) { m_trade_state=m_state.GetTradeState(); if(m_trade_state == TRADE_WAIT)return; SpyEnvironment(); for(int i=ActivePositions.Total()-1; i>=0; i--) { CPosition *pos=ActivePositions.At(i); if(pos.ExpertMagic()!=m_expert_magic)continue; if(pos.Direction()==POSITION_TYPE_BUY) SupportBuy(event,pos); else SupportSell(event,pos); if(m_trade_state==TRADE_STOP && pos.IsActive()) ExitByStopRegim(pos); } }
以类似的方式,改方法接收EA的当前交易状态。若将它移到持仓管理中,这个方法将开始遍历所有当前未平仓头寸。这些持仓头寸被 CPosition 类所表征。能够访问每一个持仓头寸后,改方法将其magic编号和当前EA的magic编号作对比。一旦匹配,相应的头寸管理方法被调用:多单调用SupportBuy,空单调用SupportSell。
我们已经检查了CStrategy类的主要方法,它为自定义策略提供了广泛的功能。一个从它派生而来的类能够对价格行情和交易事件进行响应。同时,策略获得从CStrategy继承而来的统一的交易行为序列。这样,策略可以作为独立模块开发,仅需要描述交易逻辑和规则。这一解决方案大大简化了交易算法的开发过程。
在下一篇文章“通用智能交易系统:自定义策略和辅助交易类(第三部分)”中,我们将讨论基于已论述算法的策略开发过程。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程