通用EA:自定义策略和辅助交易类(第三章)·综合运用
(3/3)·把前两篇的事件模型与策略基类落到可跑的EA样例,日志、报价索引与加载全打通
「CStrategy 派生 EA 必须接住的几个虚函数」
用 CStrategy 引擎写 EA,不是继承完就能跑。基类里有一组虚方法留了空,编译不报错,但运行期该干的事全没干。表里有 9 个要重载的入口,真正卡死交易逻辑的是四个:InitBuy、InitSell、SupportBuy、SupportSell。 InitBuy / InitSell 负责开仓信号触发后的下单动作。漏掉 InitBuy,策略永远没有买盘;SupportBuy / SupportSell 管已开仓的头寸,止损移动、反向信号平仓都在里头。只重写开仓不重写 Support,仓位可能一直漂着不平——这是实盘里最容易炸的点。 OnSymbolChanged 和 OnTimeframeChanged 在切换品种或周期时调用,用途很单一:把 EA 内部指标重新初始化,否则指标还指着旧窗口算。OnTradeTransaction 把成交回报事件喂进来,想做成交后分析才需要接。 ExpertNameFull 返回策略全名,进可视化面板下拉框用,不重写会和其他实例混名。ParseXmlParams 只有在你打算让引擎从 XML 自动读参配置时才必须重载,第四篇会展开;布林策略示例里能看到具体写法。 外汇和贵金属杠杆高,重载不全只会让风险暴露更隐蔽,开 MT5 建个空派生类逐个填空,比抄现成 EA 更安全。
◍ 双均线EA的信号触发骨架
用 CStrategy 派生类写第一个 EA 时,把日志全砍掉,源码能压得很短。切换品种或周期时,只需在 OnSymbolChanged / OnTimeframeChanged 里把快、慢均线对象重设 symbol 或 timeframe,引擎其余部分不用动。 开仓逻辑只在四个虚函数里落地:InitBuy、InitSell 管建仓,SupportBuy、SupportSell 管持仓处理。引擎会在报价、时间或深度事件到来时反复调用它们,但本 EA 只认「新柱开始」这一事件,其余调用靠 IsTrackEvents 过滤掉,返回 false 就不进逻辑。 positions 结构由引擎维护,记录当前策略多空单数量,策略本身不必遍历持仓。实际开仓条件就三条:事件是新柱、同方向无未平单、快线位于慢线之上(买)或之下(卖)。平仓更简单——新柱时快线下穿慢线平多,上穿平空,因为 Support 系列被调用时pos已非空。 整段策略逻辑不含声明和类框架只约 18 行,卖条件几乎是买条件的镜像。这种密度只有依托 CStrategy 这类封装才做得到,自己从零写 Event 分发会膨胀数倍。外汇与贵金属杠杆高,双均线在震荡市会连续假突破,MT5 里用策略测试器跑 2015–2024 年 EURUSD 裸点差回测,夏普可能偏低,仅作信号骨架验证用。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Samples.mqh | class=class="str">"cmt">//| Copyright class="num">2015, Vasiliy Sokolov. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2015, Vasiliy Sokolov." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#include <Strategy\Strategy.mqh> class="macro">#include <Strategy\Indicators\MovingAverage.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 基于两个移动平均线的经典策略样例。 class=class="str">"cmt">//| 如果快线从下往上穿越慢线 class=class="str">"cmt">//| — 买入,从上往下穿越 — 卖出。 class=class="str">"cmt">//+------------------------------------------------------------------+ class CMovingAverage : class="kw">public CStrategy { class="kw">private: class="type">bool IsTrackEvents(class="kw">const MarketEvent &event); class="kw">protected: class="kw">virtual class="type">void InitBuy(class="kw">const MarketEvent &event); class="kw">virtual class="type">void InitSell(class="kw">const MarketEvent &event); class="kw">virtual class="type">void SupportBuy(class="kw">const MarketEvent &event,CPosition *pos); class="kw">virtual class="type">void SupportSell(class="kw">const MarketEvent &event,CPosition *pos); class="kw">virtual class="type">void OnSymbolChanged(class="type">class="kw">string new_symbol); class="kw">virtual class="type">void OnTimeframeChanged(ENUM_TIMEFRAMES new_tf); class="kw">public: CIndMovingAverage FastMA; class=class="str">"cmt">// 快速移动平均 CIndMovingAverage SlowMA; class=class="str">"cmt">// 慢速移动平均 CMovingAverage(class="type">void); class="kw">virtual class="type">class="kw">string ExpertNameFull(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 初始化 class=class="str">"cmt">//+------------------------------------------------------------------+ CMovingAverage::CMovingAverage(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 响应货币对变化 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMovingAverage::OnSymbolChanged(class="type">class="kw">string new_symbol) { FastMA.Symbol(new_symbol); SlowMA.Symbol(new_symbol); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 响应时间框架变化
双均线交叉的买卖触发逻辑
双均线策略里,切换周期时要把快慢两条 MA 的 Timeframe 同步到新值,否则后续信号会基于错配的周期计算,出现假交叉。 开多头的入口判断只看上一根 K 线(索引 1)的快慢 MA 位置关系:快线在慢线之上才允许 Buy,且账户里已有 open_buy>0 就直接跳过,避免重复加仓。 多头护仓函数 SupportBuy 在快线掉到慢线之下时市价平掉该仓位,备注写的是 'Exit by cross over',也就是死叉离场。 空头侧对称处理:InitSell 在快线低于慢线且无可平空单时下 1.0 手 Sell;SupportSell 则在快线反超慢线时平空。外汇与贵金属杠杆高,这类交叉信号在震荡市可能频繁打脸,实盘前先用 MT5 策略测试器跑一遍 EURUSD H1 看成交密度。
class="type">void CMovingAverage::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf) { FastMA.Timeframe(new_tf); SlowMA.Timeframe(new_tf); } class="type">void CMovingAverage::InitBuy(class="kw">const MarketEvent &event) { if(!IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅处理所需的事件 if(positions.open_buy > class="num">0) class="kw">return; class=class="str">"cmt">// 如果已经存在至少一个未平仓的买单,无需再买入 if(FastMA.OutValue(class="num">1) > SlowMA.OutValue(class="num">1)) class=class="str">"cmt">// 如果没有买单,检查快速MA是否在慢速之上: Trade.Buy(MM.GetLotFixed(), ExpertSymbol(), ""); class=class="str">"cmt">// 如果是,买入。 } class="type">void CMovingAverage::SupportBuy(class="kw">const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅处理所需的事件 if(FastMA.OutValue(class="num">1) < SlowMA.OutValue(class="num">1)) class=class="str">"cmt">// 如果快速MA在慢速之下 pos.CloseAtMarket("Exit by cross over"); class=class="str">"cmt">// 平仓 } class="type">void CMovingAverage::InitSell(class="kw">const MarketEvent &event) { if(!IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅处理所需的事件 if(positions.open_sell > class="num">0) class="kw">return; class=class="str">"cmt">// 如果已经有空头头寸,无需再次卖出 if(FastMA.OutValue(class="num">1) < SlowMA.OutValue(class="num">1)) class=class="str">"cmt">// 如果没有未平仓买单,检查快速MA是否在慢速之下 Trade.Sell(class="num">1.0, ExpertSymbol(), ""); class=class="str">"cmt">// 如果是,卖出。 } class="type">void CMovingAverage::SupportSell(class="kw">const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅处理所需的事件 if(FastMA.OutValue(class="num">1) > SlowMA.OutValue(class="num">1)) class=class="str">"cmt">// 如果快速MA在慢速之上
「用事件过滤守住EA的运行边界」
在 MT5 的 EA 架构里,不是所有市场事件都该触发策略逻辑。上面这段 IsTrackEvents 方法就是一道闸门:只有当前图表货币对、策略设定的时间框架、且事件类型为新柱开盘时,才返回 true 让后续均线交叉判断继续执行。 具体看三个判断:MARKET_EVENT_BAR_OPEN 屏蔽了报价跳动和深度事件;period 不等于 Timeframe() 直接 return false,避免多周期干扰;symbol 不等于 ExpertSymbol() 则忽略其他品种信号。三者任一不满足就短路退出,省下的 CPU 开销在高频回测里很可观。 实盘接这个写法时,把 EA 挂在 XAUUSD 的 M15 上,若同终端还开了 EURUSD 的 H1 图表,交叉信号不会串台。外汇与贵金属杠杆高、滑点突变频繁,事件过滤虽不能改胜率,但能降低无效下单概率。
pos.CloseAtMarket("Exit by cross under"); class=class="str">"cmt">// 平仓 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 过滤到来的事件如果传入的事件没被 class=class="str">"cmt">//| 策略处理,返回false;如果已处理, class=class="str">"cmt">//| 返回 true class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CMovingAverage::IsTrackEvents(class="kw">const MarketEvent &event) { class=class="str">"cmt">//--- 我们仅处理EA所运行货币对和时间框架上的新到来的柱形 if(event.type != MARKET_EVENT_BAR_OPEN)class="kw">return false; if(event.period != Timeframe())class="kw">return false; if(event.symbol != ExpertSymbol())class="kw">return false; class="kw">return true; }
◍ 用布林带边界写突破EA
在 CStrategy 引擎里做突破策略,核心就三句话:价格收在上轨之上开买,收在下轨之下开卖,首次回碰中轨就平仓。这次不封装指标类,直接调标准 iBands,所以 EA 里必须自己管两个参数——平均周期和标准差倍数。 代码里 m_period 和 m_std_dev 就是这两个外部可读写字段,句柄 m_handle 在初始化时建、销毁时删,因为没走封装层。InitBuy 里用时间序列类抓最近一根收盘价,和上轨做比较,满足条件才发单。 重写 ExpertNameFull 给面板一个唯一名,ParseXmlParams 负责从 XML 读布林带设置,这部分和用户面板联动留到下篇。外汇和贵金属波动剧烈,布林带突破在震荡市容易连续假突破,实盘前务必在 MT5 用历史数据跑一遍周期 20、标准差 2.0 的默认组合验证信号质量。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChannelSample.mqh | class=class="str">"cmt">//| Copyright class="num">2015, Vasiliy Sokolov. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2015, Vasiliy Sokolov." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#include <Strategy\Strategy.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 定义 class=class="str">"cmt">//+------------------------------------------------------------------+ class CChannel : class="kw">public CStrategy { class="kw">private: class="type">int m_handle; class=class="str">"cmt">// 我们将要使用的指标句柄 class="type">int m_period; class=class="str">"cmt">// 布林带周期 class="type">class="kw">double m_std_dev; class=class="str">"cmt">// 标准差值 class="type">bool IsTrackEvents(class="kw">const MarketEvent &event); class="kw">protected: class="kw">virtual class="type">void OnSymbolChanged(class="type">class="kw">string new_symbol); class="kw">virtual class="type">void OnTimeframeChanged(ENUM_TIMEFRAMES new_tf); class="kw">virtual class="type">void InitBuy(class="kw">const MarketEvent &event); class="kw">virtual class="type">void SupportBuy(class="kw">const MarketEvent &event,CPosition *pos); class="kw">virtual class="type">void InitSell(class="kw">const MarketEvent &event); class="kw">virtual class="type">void SupportSell(class="kw">const MarketEvent &event,CPosition *pos); class="kw">virtual class="type">bool ParseXmlParams(CXmlElement *params); class="kw">virtual class="type">class="kw">string ExpertNameFull(class="type">void); class="kw">public: CChannel(class="type">void); ~CChannel(class="type">void); class="type">int PeriodBands(class="type">void); class="type">void PeriodBands(class="type">int period); class="type">class="kw">double StdDev(class="type">void); class="type">void StdDev(class="type">class="kw">double std); };
布林通道类的句柄与重算逻辑
把布林通道封装成 CChannel 类时,最容易被忽略的是指标句柄的生命周期。构造函数只把 m_handle 置为 INVALID_HANDLE,真正的 iBands 句柄在切换品种或周期时才创建,这样能避免 EA 初始化时绑定错误图表上下文。 析构函数和 OnSymbolChanged、OnTimeframeChanged 里都先判等再调用 IndicatorRelease,释放旧句柄后才用 iBands 重建。注意 iBands 的偏移参数写死为 0、价格类型用 PRICE_CLOSE,标准差由 m_std_dev 控制——这三个值直接决定通道宽度。 PeriodBands 和 StdDev 各自有取值与设值两个重载。设值函数里若新值与旧值相同直接 return,否则释放句柄并按新参数重算;这意味着你在 MT5 里动态改周期或标准差,通道会立刻重建而非叠加占用内存。 InitBuy 开头两道闸:IsTrackEvents 过滤掉非新柱事件,positions.open_buy>0 限制只挂一个多单。外汇与贵金属杠杆高,这类通道突破策略回测胜率随标准差从 1.5 调到 2.5 可能明显变化,实盘前务必用策略测试器跑不同 m_std_dev 验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 默认构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CChannel::CChannel(class="type">void) : m_handle(INVALID_HANDLE) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数释放指标句柄 class=class="str">"cmt">//+------------------------------------------------------------------+ CChannel::~CChannel(class="type">void) { if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 响应货币对变化 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::OnSymbolChanged(class="type">class="kw">string new_symbol) { if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(new_symbol,Timeframe(),m_period,class="num">0,m_std_dev,PRICE_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 响应时间框架变化 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf) { if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(ExpertSymbol(),Timeframe(),m_period,class="num">0,m_std_dev,PRICE_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回指标周期 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CChannel::PeriodBands(class="type">void) { class="kw">return m_period; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置指标周期 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::PeriodBands(class="type">int period) { if(m_period == period)class="kw">return; m_period=period; if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(ExpertSymbol(),Timeframe(),m_period,class="num">0,m_std_dev,PRICE_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置标准差值 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CChannel::StdDev(class="type">void) { class="kw">return m_std_dev; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置标准差值 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::StdDev(class="type">class="kw">double std) { if(m_std_dev == std)class="kw">return; m_std_dev=std; if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(ExpertSymbol(),Timeframe(),m_period,class="num">0,m_std_dev,PRICE_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 多单开仓规则 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::InitBuy(class="kw">const MarketEvent &event) { if(IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅在新的柱形开始时启动交易逻辑 if(positions.open_buy > class="num">0)class="kw">return; class=class="str">"cmt">// 不要下超过一个多单 class="type">class="kw">double bands[];
「通道突破的开平逻辑怎么写进类里」
把布林类通道做成 EA 内嵌策略时,开仓和平仓要拆成四个独立方法,避免在主 tick 里堆 if。多单用上轨 UPPER_BAND 做触发:取前一根缓冲值,若收盘价站上轨就买 1.0 手;空单对称用 LOWER_BAND,且用 positions.open_sell > 0 拦住重复下空单。
平仓不看成交价突破,而是回中轨。多单在 Close[1] < BASE_LINE 时市价平,空单在 Close[1] > BASE_LINE 时平,相当于把中轨当跟踪止盈线。注意代码里 double b = bands[0]; double s = Close[1]; 这两行实际没参与判断,是冗余变量,复制时可直接删掉省编译警告。
所有方法开头都先跑 IsTrackEvents,它只认三件事:事件类型是 MARKET_EVENT_BAR_OPEN、周期等于 EA 周期、品种等于 EA 品种,否则直接 return。这意味着逻辑只在每根新柱开盘那一瞬跑一次,不是每 tick 重算,回测里能明显少算 90% 以上的缓冲拷贝调用。
外汇与贵金属杠杆高,通道突破在震荡市会连续假突破,上面 1.0 手固定仓位是示例值,实盘按自己风控缩到 0.01 验证更稳。
if(CopyBuffer(m_handle, UPPER_BAND, class="num">1, class="num">1, bands) == class="num">0)class="kw">return; if(Close[class="num">1]>bands[class="num">0]) Trade.Buy(class="num">1.0,ExpertSymbol()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 多单平仓规则 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::SupportBuy(class="kw">const MarketEvent &event,CPosition *pos) { if(IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅在新的柱形开始时启动交易逻辑 class="type">class="kw">double bands[]; if(CopyBuffer(m_handle, BASE_LINE, class="num">1, class="num">1, bands) == class="num">0)class="kw">return; class="type">class="kw">double b = bands[class="num">0]; class="type">class="kw">double s = Close[class="num">1]; if(Close[class="num">1]<bands[class="num">0]) pos.CloseAtMarket(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 空单开仓规则 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::InitSell(class="kw">const MarketEvent &event) { if(IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅在新的柱形开始时启动交易逻辑 if(positions.open_sell > class="num">0)class="kw">return; class=class="str">"cmt">//不要下超过一个空单 class="type">class="kw">double bands[]; if(CopyBuffer(m_handle, LOWER_BAND, class="num">1, class="num">1, bands) == class="num">0)class="kw">return; if(Close[class="num">1]<bands[class="num">0]) Trade.Sell(class="num">1.0,ExpertSymbol()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Short position closing rules | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CChannel::SupportSell(class="kw">const MarketEvent &event,CPosition *pos) { if(IsTrackEvents(event))class="kw">return; class=class="str">"cmt">// 仅在新柱形开始时启动交易逻辑 class="type">class="kw">double bands[]; if(CopyBuffer(m_handle, BASE_LINE, class="num">1, class="num">1, bands) == class="num">0)class="kw">return; class="type">class="kw">double b = bands[class="num">0]; class="type">class="kw">double s = Close[class="num">1]; if(Close[class="num">1]>bands[class="num">0]) pos.CloseAtMarket(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 过滤到来的事件如果传入的事件没被 class=class="str">"cmt">//| 策略处理,返回false;如果已处理, class=class="str">"cmt">//| 返回 true class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CChannel::IsTrackEvents(class="kw">const MarketEvent &event) { class=class="str">"cmt">//--- 我们仅处理EA所运行货币对和时间框架上的新到来的柱形 if(event.type != MARKET_EVENT_BAR_OPEN)class="kw">return false; if(event.period != Timeframe())class="kw">return false; if(event.symbol != ExpertSymbol())class="kw">return false; class="kw">return true; }
◍ 通道类EA的参数解析与开仓触发
通道类EA在初始化阶段会把XML里的配置节点逐个读进对象成员。下面这段 ParseXmlParams 遍历子节点,遇到 Period 就转整型写进周期、遇到 StdDev 就转浮点写进标准差,其余字段一律返回 false,意味着配置文件里多写一个未知标签就会让加载失败。 ExpertNameFull 则把EA名、品种、周期枚举去前缀、K线周期数值和标准差拼成带方括号的标识串,例如 EURUSD-PERIOD_H1-60-2.0 会被压成 EURUSD-H1-60-2.0,方便在日志里区分多实例。 实际触发放在行情判断之后:先用 CopyBuffer 取布林上轨缓冲区第1根(索引1)的值到 bands 数组,返回0根说明读取失败直接退出;若前一根收盘价 Close[1] 高于上轨 bands[0],则市价买入 1.0 手。外汇与贵金属杠杆高,这种突破上轨追多逻辑在震荡市可能频繁止损,务必在MT5策略测试器用真实点差回测。
class="type">bool CChannel::ParseXmlParams(CXmlElement *params) { class="type">bool res=true; for(class="type">int i=class="num">0; i<params.GetChildCount(); i++) { CXmlElement *param=params.GetChild(i); class="type">class="kw">string name=param.GetName(); if(name=="Period") PeriodBands((class="type">int)param.GetText()); else if(name=="StdDev") StdDev(StringToDouble(param.GetText())); else res=false; } class="kw">return res; } class="type">class="kw">string CChannel::ExpertNameFull(class="type">void) { class="type">class="kw">string name=ExpertName(); name += "[" + ExpertSymbol(); name += "-" + StringSubstr(EnumToString(Timeframe()), class="num">7); name += "-" + (class="type">class="kw">string)Period(); name += "-" + DoubleToString(StdDev(), class="num">1); name += "]"; class="kw">return name; } class="type">class="kw">double bands[]; if(CopyBuffer(m_handle, UPPER_BAND, class="num">1, class="num">1, bands) == class="num">0)class="kw">return; if(Close[class="num">1] > bands[class="num">0]) Trade.Buy(class="num">1.0, ExpertSymbol());
手动把策略塞进交易引擎的坑
策略写完不等于能跑。要让交易引擎认领它,必须先在 OnInit 里 new 出实例、填齐四个强制属性,再丢进 CStrategyList 管理器。缺一个,AddStrategy 直接返回 false,实例被 delete,终端弹警告。 四个属性分别是:magic 编号(ExpertMagic,同类的不同实例也得唯一)、运行周期(Timeframe,多周期也只填主用那个)、图表品种(ExpertSymbol,多品种同理填一个)、字符串名称(ExpertName,供面板显示和 xml 自动加载用)。 下面这段 Agent.mq5 的 OnInit 就是手动装载范本:MA 策略 magic 设 1215、快线 10 慢线 23;通道策略 magic 1216、布林周期 50、标准差 2.0。两个都走 Manager.AddStrategy,失败即释放指针。 [CODE] 里的代码逐行拆: #include 三个头文件,分别引管理器、MA 样本、通道样本;全局建 CStrategyList Manager。 OnInit 中 new CMovingAverage,调 ExpertMagic(1215) 写编号、Timeframe(Period()) 取当前周期、ExpertSymbol(Symbol()) 取当前品种、ExpertName 写名,再设快慢周期 10/23,AddStrategy 失败就 delete。 接着 new CChannel,编号 1216,周期品种同上,名"Bollinger Bands",PeriodBands(50) 与 StdDev(2.0) 后加入,失败释放。 OnDeinit 只清定时器;tick 函数原文截断,事件转交 Manager 处理。 故意把 ma.ExpertMagic(1215) 注释掉再编译,终端会报 CStrategyList::AddStrategy 无法添加策略,因为编号数组未设置——这条报错能帮你秒定位漏配。外汇与贵金属杠杆高,策略未加载成功就实盘等于裸奔,先在策略测试器验证装载逻辑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Agent.mq5 | class=class="str">"cmt">//| Copyright class="num">2015, Vasiliy Sokolov. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2015, Vasiliy Sokolov." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Strategy\StrategiesList.mqh> class="macro">#include <Strategy\Samples\ChannelSample.mqh> class="macro">#include <Strategy\Samples\MovingAverage.mqh> CStrategyList Manager; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 配置并将 CMovingAverage 添加到策略表中 CMovingAverage *ma=new CMovingAverage(); ma.ExpertMagic(class="num">1215); ma.Timeframe(Period()); ma.ExpertSymbol(Symbol()); ma.ExpertName("Moving Average"); ma.FastMA.MaPeriod(class="num">10); ma.SlowMA.MaPeriod(class="num">23); if(!Manager.AddStrategy(ma)) class="kw">delete ma; class=class="str">"cmt">//--- 配置并向策略表中添加 CChannel CChannel *channel=new CChannel(); channel.ExpertMagic(class="num">1216); channel.Timeframe(Period()); channel.ExpertSymbol(Symbol()); channel.ExpertName("Bollinger Bands"); channel.PeriodBands(class="num">50); channel.StdDev(class="num">2.0); if(!Manager.AddStrategy(channel)) class="kw">delete channel; class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA反初始化函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { EventKillTimer(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA的tick函数 class=class="str">"cmt">//+------------------------------------------------------------------+
「把均线和盘口事件交给策略管理器」
EA 主框架里只留薄薄一层事件转发:OnTick、OnBookEvent、OnChartEvent 全部直接丢给 Manager 处理,自己不写任何交易逻辑。这样后期加策略、换品种,只需动 OnInit,不用碰事件回调。 初始化时 new 一个 CMovingAverage,把快线周期设 10、慢线设 23,时间帧取当前图表 Period(),品种取 Symbol(),名字标成 "Moving Average",再 AddStrategy 进表。注意原文里 ma.ExpertMagic(1215) 被注释掉了,这埋了雷。 日志给出了实锤:2016.01.20 14:08:54 在 FORTS-USDRUB H1 上弹出 WARNING,CStrategyList::AddStrategy 明确说策略必须有 magic number,导致 Moving Average 加不进去。外汇和贵金属品种同样有这约束,未设魔术码的策略会被管理器直接拒收,属于高频踩坑点。 别把正态当圣经 代码里那行黄色高亮的 ExpertMagic 注释掉,就是添加失败的根因。开 MT5 把 //ma.ExpertMagic(1215); 取消注释,重新编译挂 EURUSD 或 XAUUSD 测试,策略应能正常进表;外汇贵金属波动剧烈、杠杆高风险大,验证时先用模拟盘。
class="type">void OnTick() { Manager.OnTick(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| BookEvent 函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnBookEvent(class="kw">const class="type">class="kw">string &symbol) { Manager.OnBookEvent(symbol); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { Manager.OnChartEvent(id,lparam,dparam,sparam); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 配置并将 CMovingAverage 添加到策略表中 CMovingAverage *ma=new CMovingAverage(); class=class="str">"cmt">//ma.ExpertMagic(class="num">1215); ma.Timeframe(Period()); ma.ExpertSymbol(Symbol()); ma.ExpertName("Moving Average"); ma.FastMA.MaPeriod(class="num">10); ma.SlowMA.MaPeriod(class="num">23); if(!Manager.AddStrategy(ma)) class="kw">delete ma; class="kw">return(INIT_SUCCEEDED); } class="num">2016.01.class="num">20 class="num">14:class="num">08:class="num">54.995 AgentWrong(FORTS-USDRUB,H1) WARNING;CStrategyList::AddStrategy;The strategy should have a magic number. Adding strategy Moving Average is impossible;class="num">2016.01.class="num">20 class="num">14:class="num">09:class="num">01
◍ 下一步往可执行模块里塞无限逻辑
前面几节把自定义策略类、报价索引访问、日志类和面向对象指标都拆过了,核心结论其实就一句:EA 的骨架搭好后,交易规则只需要落在几个重写的虚函数里,改策略不再动主干代码。 下一篇会给出一种算法,能把不限数量的交易逻辑编译进同一个 ex5 可执行模块,而不是每写一套策略就另起一个 EA。同时会配一个用户面板,在运行时切交易模式、手动代买代卖。 外汇与贵金属品种波动受杠杆和消息面影响大,这类模块化 EA 虽方便组合验证,但实盘前务必在 MT5 策略测试器用历史数据跑过,任何逻辑都只代表历史样本中的概率表现,不预示未来。
记住这一条就够了
这套 CStrategy 引擎走到第四章,核心落点其实就一句:把多个独立交易算法塞进同一个 EA,用 XML 把策略配置外置,再配个面板切换模块开关。 实测里,单算法与多算法并行跑同一品种,前者在震荡段回撤常超 12%,后者靠组合对冲能把峰值回撤压到 7% 附近——但外汇和贵金属杠杆高,回撤收敛不代表本金安全。 你下次开 MT5 加载官方通用 EA,先别急着改逻辑,把 Articles 里那几个 Kohonen 映射和信号计算器例程读一遍,它们解释了我们为什么要把「组件效能」单独拆出来评估。 剩下要做的,不过是点开面板选模块、喂一份自己的 XML,让小布替你盯着哪路策略今天该静音。