概论
赫兹量化 在处理交易请求时使用不同的约定。本文讨论使用类对象来表达由服务器处理的交易的可能性, 目的是让跨平台智能交易程序可以无视交易平台版本和使用模式均可工作。
编辑切换为居中
添加图片注释,不超过 140 字(可选)
赫兹量化 (净持)
在赫兹量化中交易操作处理乍一看将会感觉十分复杂, 但管理比赫兹量化中更简单。至少在交易者 (非程序员) 一侧, 这应该是真实的。
在 赫兹量化中省缺是净持模式。在此模式下, 由服务器处理过的订单结果会合并为单独一笔持仓。这笔特殊持仓的类型以及成交量, 会基于入场订单的类型和交易量随时间而变化。在程序员一侧, 这有一点复杂。不像在 MQL4 里只有一个订单的概念, 程序员不得不与交易中使用的三种不同类型的令牌打交道。下表展示出一些 MQL5 净持模式与 MQL4 大致等效部分之间的比较:
构件 | MQL5 (净持) | MQL4 (大致等效) |
---|---|---|
订单 | 交易请求 (挂单或市价) | 交易请求 (挂单或市价) |
成交 | 成交基于单笔订单完成 (市价单, 或执行的挂单) | 反映在终端里的单笔市价单 |
仓位 | 交易 (合并) | 在交易终端上的所有市价单之合 (适用订单类型) |
在 MQL5 中, 订单一旦被执行, 在客户端不可改变, 而在 MQL4 里, 当一笔订单未平仓时一些属性依然可以改变。这就是, 在前者中, 一笔订单只是一个简单的发送到服务器的交易请求。在后者上, 它可以用来表示交易请求, 以及这个请求的结果。在这个方面, MQL5 使得整个处理过程更复杂, 以便降低歧义, 可在交易请求和交易结果之间进行明显区分。在 MQL4 中一笔订单可依据不同配置入场和离场, 而在 MQL5 中所有交易可向后追踪到订单, 或触发它们的交易请求。
当发送一个交易请求, 只有两个反馈: 已处理或未处理。如果交易未被处理, 则意味着没有交易, 因为交易服务器出于某种原因 (通常是由于错误) 无法处理它。现在, 如果交易在 MQL5 中已被处理, 客户端和服务器之间达成一笔成交。在此情况下, 订单可被完全或部分执行。
MetaTrader 4 没有这个选项, 因为订单只能完全执行或不执行 (填充或放弃)。
在 MetaTrader 5 中此模式的一个显著缺点是不允许对冲。给定品种的仓位类型可以改变。例如, 给定品种有一笔 0.1 手的多头仓位, 一笔 1.0 手交易量的卖单入场将把此仓位转换为该品种的空头仓位, 交易量为 0.9 手。
赫兹量化 (对冲)
对冲模式允许一个品种有一个以上的持仓, 而非把所有已处理交易合并为一笔单一持仓。当交易服务器触发挂单, 或处理市价交易请求后生成一笔持仓。
构件 | MQL5 (净持) | MQL4 (大致等效) |
---|---|---|
订单 | 交易请求 (挂单或市价) | 交易请求 (挂单或市价) |
成交 | 成交基于单笔订单完成 | 市价单反映在终端里 |
仓位 | 由单个交易请求合并而来的交易 | 订单反映在终端里 |
为了让跨平台智能交易系统能够适应这些区别, 一个可能的解决方案是令智能交易系统保存每笔成交的细节。每次交易成功完成, 一份订单的详情拷贝将被保存在 COrder 类的对象里。以下代码显示其基类的声明:
class COrderBase : public CObject { protected: bool m_closed; bool m_suspend; long m_order_flags; int m_magic; double m_price; ulong m_ticket; ENUM_ORDER_TYPE m_type; double m_volume; double m_volume_initial; string m_symbol; public: COrderBase(void); ~COrderBase(void); //--- 取值与赋值 void IsClosed(const bool); bool IsClosed(void) const; void IsSuspended(const bool); bool IsSuspended(void) const; void Magic(const int); int Magic(void) const; void Price(const double); double Price(void) const; void OrderType(const ENUM_ORDER_TYPE); ENUM_ORDER_TYPE OrderType(void) const; void Symbol(const string); string Symbol(void) const; void Ticket(const ulong); ulong Ticket(void) const; void Volume(const double); double Volume(void) const; void VolumeInitial(const double); double VolumeInitial(void) const; //--- 输出 virtual string OrderTypeToString(void) const; //--- 静态方法 static bool IsOrderTypeLong(const ENUM_ORDER_TYPE); static bool IsOrderTypeShort(const ENUM_ORDER_TYPE); };
由于策略能够记住它自己的交易, 它可在运行各种约定的交易平台上更加独立地工作。它的缺点就是要依赖一个事实, 类的实例只能在智能交易系统操作期间维持。如果智能交易系统或交易平台需要重启动, 所有保存的数据将会丢失, 除非有一个中间值来保存和加载信息。
交易标识符 (单号)
使用 COrder 的实例创建跨平台兼容智能交易系统的可能障碍是如何保存订单 (或仓位) 的单号。区别总结在下表:
操作 | MQL4 | MQL5 (净持) | MQL5 (对冲) |
---|---|---|---|
发送订单 | 新订单号 | 新仓位号 (不存在仓位) 或已存在的仓位号 (当存在仓位时) | 新仓位号 |
部分平仓 | 新订单号 | 相同仓位号 (如果有剩余), 否则为空 | 相同仓位号 |
当发送一笔订单, 所有三个版本均有不同方式来表示已入场交易。在 MQL4 里, 当一个交易请求成功时, 将会开新单。此新单将由一个标识符表示 (订单号)。在 MQL5 净持模式里, 每个入场交易的请求标识为一个订单号。然而, 订单号也许并非代表已入场交易的最佳方式, 但结果是仓位本身。其原因是, 与在 MQL4 里不同, 已入场交易产生的结果订单号不能进一步直接用于操作 (但试图通过确定订单得到其结果仓位时订单号是很有用的)。此外, 当存在相同类型的持仓时, 仓位号将会保持相同 (与 MQL4 不同)。另一方面, 在 MQL5 对冲模式里, 每笔新成交生成一个新仓位 (大致等价于 MQL4 的订单号)。然而, 它与 MQL4 的区别在于, 一个单一交易请求总是得到一笔单一订单, 而在 MQL5 (对冲模式) 里, 有可能是一笔订单有一笔以上的成交 (当填充规则被设定为 SYMBOL_FILLING_FOK 以外的情况)。
当部分平单 (MQL4) 或平仓 (MQL5) 时, 还会有其它问题。如早前所言, 在 MQL4 中, 当特定单号以少于总交易量 (OrderLots) 平单时, 单号已平仓交易, 剩余交易量将会被分配一个新单号, 类型与部分平单的那一笔相同。在 MQL5 中略有不同。在净持模式里, 它请求一笔反向交易 (买则卖, 或卖则买), 以便平仓 (部分或全部)。在对冲模式里, 处理更类似于 MQL4 (OrderClose 对决 CTrade 的 PositionClose), 但不像在 MQL4 里, 部分平仓不会触发其所代表的标识符的任何变化。
解决这个问题的一种途径是, 在两个平台上, 分开实现交易标识符的表达逻辑。由于订单号在 MetaTrader 5 中不会被改变, 我们可以简单地为其分配一个典型的数字变量。另一方面, 对于 the MetaTrader 4 版本, 我们将使用一个 CArrayInt 的实例来保存单号。对于 COrderBase (以及 MQL5 COrder 版本的后续), Ticket 方法将使用以下代码:
COrderBase::Ticket(const ulong value) { m_ticket=value; }
在 MQL4 版本上此方法将用以下代码覆盖:
COrder::Ticket(const ulong ticket) { m_ticket_current.InsertSort((int)ticket); }
状态
跨平台智能交易程序中, 订单至少有两种可能的内部状态:
-
已平仓
-
待定
这两种状态非常相似, 但有一个关键的区别。一笔订单的已平仓状态表示此订单已平仓, 智能交易程序应在其内部数据里将此订单存档。在 MQL4 中, 这大致等价于将一笔订单移到历史数据。待定状态, 发生于智能交易程序平单失败, 或订单链接一个停止位时。在此情况下, 智能交易程序将会尝试再次平单 (及其停止项), 直到它完全平仓。
交易量
在 MQL4 中, 交易量的计算是直接的。当智能交易程序发送交易请求时, 交易量也包含在请求里, 它要么被拒绝要么被接受。这等价于 MQL5 中的完全或遗弃保证金规则, 这也是交易对象 (CTrade 和 CExpertTrade) 的默认设置。共同的功能为我们带来完全或遗弃保证金规则。为了在 MQL4 和 MQL5 之间保持交易量处理的一致性, 一种方法是基于交易请求的交易量派生出 COrder 类实例的交易量。然而, 这意味着对于 MQL5 版本, 我们应需要坚持完全或遗弃保证金规则。仍然可以使用其它保证金规则, 但结果会稍有不同 (即相同 EA 的测试, 在 MQL5 上 COrder 实例的计数可能会更大)。
订单容器
如果智能交易程序处理的 COrder 实例多于一个, 则需要一些组织方法。为此提供助力的是订单容器, 或 COrders。这个类扩展自 CArrayObj 并保存 COrder 实例。这将允许智能交易程序轻松保存并恢复已入场交易。用于所述类的基础模板如下所示:
#include <Arrays\ArrayObj.mqh> #include "OrderBase.mqh" class CExpertAdvisor; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class COrdersBase : public CArrayObj { public: COrdersBase(void); ~COrdersBase(void); virtual bool NewOrder(const ulong,const string,const int,const ENUM_ORDER_TYPE,const double,const double); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrdersBase::COrdersBase(void) { if(!IsSorted()) Sort(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrdersBase::~COrdersBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool COrdersBase::NewOrder(const ulong ticket,const string symbol,const int magic,const ENUM_ORDER_TYPE type,const double volume,const double price) { COrder *order=new COrder(ticket,symbol,type,volume,price); if(CheckPointer(order)==POINTER_DYNAMIC) if(InsertSort(GetPointer(order))) order.Magic(magic); return false; } //+------------------------------------------------------------------+ #ifdef __MQL5__ #include "..\..\MQL5\Order\Orders.mqh" #else #include "..\..\MQL4\Order\Orders.mqh" #endif //+------------------------------------------------------------------+