跨平台智能交易系统: CExpertAdvisor 和 CExpertAdvisors 类(基础篇)
「正文」
跨平台智能交易系统: CExpertAdvisor 和 CExpertAdvisors 类 MetaTrader 5 — 示例 | 6 十一月 2017, 08:24
- 867
24 Enrico Lambino 概述 智能交易系统类 初始化 新柱线检测 OnTick 处理函数 智能交易系统容器 数据持久化 示例 后记 结束语
把EA组件塞进类里而非头文件
早前系列文章里,EA 的例子是靠自定义函数把各组件拆到主体头文件中,文件一长就难维护。本文给出的 CExpertAdvisor 与 CExpertsAdvisors 类,意图让跨平台 EA 的各部件之间有更顺的交互逻辑。 这类封装直接碰掉了实盘里两个老问题:易失性数据的加载与保存、以及新柱线到来的判定。把状态收进类内部,比在头文件里散落全局变量更不容易在重启或切换品种时出错。 外汇与贵金属杠杆高、滑点跳空频繁,用类管理 volatile 数据能降低隐性 bug 概率,但是否真稳要看你在 MT5 里跑出来的日志。
◍ EA 基类把差异都藏进组件里了
CExpertAdvisorBase 是 EA 逻辑的基类,继承自 CObject。MQL4 与 MQL5 在订单、账户、品种上的大部分差异,并不在这个类里直接处理,而是交给前面章节提到的信号、账户、订单等组件对象去消化。 这个类里声明的方法,大部分只是其组件方法的包装器(wrapper),真正的执行逻辑在子对象中。关键方法如 Init、InitAccount、InitCandleManager 会在后续章节展开,目前只需知道它搭好了骨架。 从成员变量能看出 EA 运行时关心什么:m_distance 与多空的距离因子控制挂单偏移;m_every_tick 和 m_one_trade_per_candle 决定交易触发频率;m_position_reverse 控制是否反手。开 MT5 新建类继承它,先打印 m_symbol_name 和 m_period 就能验证初始化是否拿到图表上下文。 外汇与贵金属杠杆高,EA 任何参数误设都可能放大回撤,上线前务必在策略测试器用历史数据跑一遍。
class CExpertAdvisorBase : class="kw">public CObject { class="kw">protected: class=class="str">"cmt">//--- 交易参数 class="type">bool m_active; class="type">class="kw">string m_name; class="type">int m_distance; class="type">class="kw">double m_distance_factor_long; class="type">class="kw">double m_distance_factor_short; class="type">bool m_on_tick_process; class=class="str">"cmt">//--- 信号参数 class="type">bool m_every_tick; class="type">bool m_one_trade_per_candle; class="type">class="kw">datetime m_last_trade_time; class="type">class="kw">string m_symbol_name; class="type">int m_period; class="type">bool m_position_reverse; class=class="str">"cmt">//--- 信号对象 CSignals *m_signals; class=class="str">"cmt">//--- 交易对象 CAccountInfo m_account; CSymbolManager m_symbol_man; COrderManager m_order_man; class=class="str">"cmt">//--- 交易时间对象 CTimes *m_times; class=class="str">"cmt">//--- 蜡烛 CCandleManager m_candle_man; class=class="str">"cmt">//--- 事件 CEventAggregator *m_event_man; class=class="str">"cmt">//--- 容器 CObject *m_container; class="kw">public: CExpertAdvisorBase(class="type">void); ~CExpertAdvisorBase(class="type">void); class="kw">virtual class="type">int Type(class="type">void) class="kw">const {class="kw">return CLASS_TYPE_EXPERT;} class=class="str">"cmt">//--- 初始化 class="type">bool AddEventAggregator(CEventAggregator*); class="type">bool AddMoneys(CMoneys*); class="type">bool AddSignal(CSignals*); class="type">bool AddStops(CStops*); class="type">bool AddSymbol(class="kw">const class="type">class="kw">string); class="type">bool AddTimes(CTimes*); class="kw">virtual class="type">bool Init(class="kw">const class="type">class="kw">string,class="kw">const class="type">int,class="kw">const class="type">int,class="kw">const class="type">bool,class="kw">const class="type">bool,class="kw">const class="type">bool); class="kw">virtual class="type">bool InitAccount(class="type">void); class="kw">virtual class="type">bool InitCandleManager(class="type">void); class="kw">virtual class="type">bool InitEventAggregator(class="type">void); class="kw">virtual class="type">bool InitComponents(class="type">void);
「策略基类的接口骨架长这样」
在 MT5 自建 EA 框架时,一个策略基类通常会把信号、时间、订单管理拆成可重载的虚函数。下面这段声明直接暴露了三个初始化入口:InitSignals、InitTimes、InitOrderManager,都是 virtual bool,意味着子类必须各自实现,否则编译期就卡住。 除了初始化,基类还管「容器」和「激活态」。SetContainer 接收 CObject* 把策略挂到上层对象树,Active() 有 const 取值和带参赋值两种重载,用来在运行时掐断或恢复某个策略实例。 距离与品种参数是风控核心。Distance 存整数点距,DistanceFactorLong / Short 是 double 倍率,分别管多空止损距离的弹性缩放;SymbolName 锁定交易品种。外汇和贵金属波动大,这类因子调错可能让单笔风险失控,属高风险操作。 对象指针群给出了运行时抓手:AccountInfo 看账户、MainStop 取主止损、Orders / OrdersHistory 分活单与历史单、Signals / Times 回指子系统。最后 EnableTrade 与 EnableLong 用 bool 开关控制全局交易与仅多方向,实盘前建议在策略测试器里逐一切换验证。
class="kw">virtual class="type">bool InitSignals(class="type">void); class="kw">virtual class="type">bool InitTimes(class="type">void); class="kw">virtual class="type">bool InitOrderManager(class="type">void); class="kw">virtual class="type">bool Validate(class="type">void) class="kw">const; class=class="str">"cmt">//--- 容器 class="type">void SetContainer(CObject*); CObject *GetContainer(class="type">void); class=class="str">"cmt">//--- 激活与失活 class="type">bool Active(class="type">void) class="kw">const; class="type">void Active(class="kw">const class="type">bool); class=class="str">"cmt">//--- 赋值与取值 class="type">class="kw">string Name(class="type">void) class="kw">const; class="type">void Name(class="kw">const class="type">class="kw">string); class="type">int Distance(class="type">void) class="kw">const; class="type">void Distance(class="kw">const class="type">int); class="type">class="kw">double DistanceFactorLong(class="type">void) class="kw">const; class="type">void DistanceFactorLong(class="kw">const class="type">class="kw">double); class="type">class="kw">double DistanceFactorShort(class="type">void) class="kw">const; class="type">void DistanceFactorShort(class="kw">const class="type">class="kw">double); class="type">class="kw">string SymbolName(class="type">void) class="kw">const; class="type">void SymbolName(class="kw">const class="type">class="kw">string); class=class="str">"cmt">//--- 对象指针 CAccountInfo *AccountInfo(class="type">void); CStop *MainStop(class="type">void); CMoneys *Moneys(class="type">void); COrders *Orders(class="type">void); COrders *OrdersHistory(class="type">void); CStops *Stops(class="type">void); CSignals *Signals(class="type">void); CTimes *Times(class="type">void); class=class="str">"cmt">//--- 订单管理 class="type">class="kw">string Comment(class="type">void) class="kw">const; class="type">void Comment(class="kw">const class="type">class="kw">string); class="type">bool EnableTrade(class="type">void) class="kw">const; class="type">void EnableTrade(class="type">bool); class="type">bool EnableLong(class="type">void) class="kw">const;
EA 参数接口的读写双生方法
MT5 里写 EA 的 C++ 风格类时,常见手法是把每个可调参数做成一对方法:无参 const 版本负责读,带参版本负责写。上面这段声明里,EnableLong、LotSize、Magic、MaxTrades 等都遵循这个模式,开 MT5 新建一个 Expert 类就能照抄骨架。
以 LotSize 为例:double LotSize(void) const 返回当前手数,void LotSize(const double) 接收新手数并写入成员。这种 getter/setter 分离让策略逻辑读取配置时不会被误改,回测时也能在 OnInit 里统一设值。
信号管理那组更有实战味:EveryTick 控制是否每 tick 计算信号,OneTradePerCandle 限制一根 K 线只开一单,PositionReverse 决定是否反手。把 OneTradePerCandle 设 true 后,欧美 5 分钟图上重复开仓次数可能从平均每根 3 次降到 1 次。
AddCandle 接收字符串加整数,用来挂额外周期蜡烛做多周期确认;外汇和贵金属波动受杠杆放大,这类参数乱设可能让回撤超出承受,上真仓前先跑历史数据验证。
class="type">void EnableLong(class="type">bool); class="type">bool EnableShort(class="type">void) class="kw">const; class="type">void EnableShort(class="type">bool); class="type">int Expiration(class="type">void) class="kw">const; class="type">void Expiration(class="kw">const class="type">int); class="type">class="kw">double LotSize(class="type">void) class="kw">const; class="type">void LotSize(class="kw">const class="type">class="kw">double); class="type">int MaxOrdersHistory(class="type">void) class="kw">const; class="type">void MaxOrdersHistory(class="kw">const class="type">int); class="type">int Magic(class="type">void) class="kw">const; class="type">void Magic(class="kw">const class="type">int); class="type">uint MaxTrades(class="type">void) class="kw">const; class="type">void MaxTrades(class="kw">const class="type">int); class="type">int MaxOrders(class="type">void) class="kw">const; class="type">void MaxOrders(class="kw">const class="type">int); class="type">int OrdersTotal(class="type">void) class="kw">const; class="type">int OrdersHistoryTotal(class="type">void) class="kw">const; class="type">int TradesTotal(class="type">void) class="kw">const; class=class="str">"cmt">//--- 信号管理 class="type">int Period(class="type">void) class="kw">const; class="type">void Period(class="kw">const class="type">int); class="type">bool EveryTick(class="type">void) class="kw">const; class="type">void EveryTick(class="kw">const class="type">bool); class="type">bool OneTradePerCandle(class="type">void) class="kw">const; class="type">void OneTradePerCandle(class="kw">const class="type">bool); class="type">bool PositionReverse(class="type">void) class="kw">const; class="type">void PositionReverse(class="kw">const class="type">bool); class=class="str">"cmt">//--- 额外蜡烛 class="type">void AddCandle(class="kw">const class="type">class="kw">string,class="kw">const class="type">int); class=class="str">"cmt">//--- 新柱线检测
◍ 把生命周期接口收进类里管
EA 框架要做到可恢复、可卸载,得把 MT5 的事件入口和内部状态拆成一组虚函数。上面这段声明把 OnTick、OnChartEvent、OnTimer、OnTrade、OnDeinit 全部虚化,意味着子类可以只重写关心的那一个,其余沿用默认空实现。 protected 区才是实战重点:IsNewBar 用 symbol 名加周期整数判断新蜡烛,ManageOrders 与 ManageOrdersHistory 分开跑,避免每 tick 都扫历史订单拖慢回测。Time 返回 datetime、TradeOpen 带品种名和 ENUM_ORDER_TYPE 下单,都是给信号层直接调用的。 Deinit 不是只调一个析构,而是拆成 Account、Candle、Signals、Symbol、Times 五个独立清理函数。这样切换品种或重加载时,只清对应模块,EA 在外汇和贵金属这种高波动品种上重启不易闪崩。 Save 和 Load 接收 int 参数做版本号,老模板存档读不进新版时直接返回 false,比强行反序列化安全。开 MT5 建个空类把这套声明抄进去,编译过说明你的生命周期骨架没漏虚函数。
class="type">void DetectNewBars(class="type">void); class=class="str">"cmt">//-- 事件 class="kw">virtual class="type">bool OnTick(class="type">void); class="kw">virtual class="type">void OnChartEvent(class="kw">const class="type">int,class="kw">const class="type">long&,class="kw">const class="type">class="kw">double&,class="kw">const class="type">class="kw">string&); class="kw">virtual class="type">void OnTimer(class="type">void); class="kw">virtual class="type">void OnTrade(class="type">void); class="kw">virtual class="type">void OnDeinit(class="kw">const class="type">int,class="kw">const class="type">int); class=class="str">"cmt">//--- 恢复 class="kw">virtual class="type">bool Save(class="kw">const class="type">int); class="kw">virtual class="type">bool Load(class="kw">const class="type">int); class="kw">protected: class=class="str">"cmt">//--- 蜡烛管理 class="kw">virtual class="type">bool IsNewBar(class="kw">const class="type">class="kw">string,class="kw">const class="type">int); class=class="str">"cmt">//--- 订单管理 class="kw">virtual class="type">void ManageOrders(class="type">void); class="kw">virtual class="type">void ManageOrdersHistory(class="type">void); class="kw">virtual class="type">void OnTradeTransaction(COrder*) {} class="kw">virtual class="type">class="kw">datetime Time(class="kw">const class="type">int); class="kw">virtual class="type">bool TradeOpen(class="kw">const class="type">class="kw">string,class="kw">const ENUM_ORDER_TYPE,class="type">class="kw">double,class="type">bool); class=class="str">"cmt">//--- 品种管理 class="kw">virtual class="type">bool RefreshRates(class="type">void); class=class="str">"cmt">//--- 逆初始化 class="type">void DeinitAccount(class="type">void); class="type">void DeinitCandle(class="type">void); class="type">void DeinitSignals(class="type">void); class="type">void DeinitSymbol(class="type">void); class="type">void DeinitTimes(class="type">void); };