通用智能交易系统:组合交易及管理策略组合(第四章)(基础篇)
◍ 把多套策略塞进一个EA的思路
在 MT5 里做组合交易,核心不是写一堆独立 EA 各自跑,而是用一个通用智能交易系统把不同逻辑的管理权收拢到同一进程。这样仓位、风控、信号优先级能在同一个上下文里协调,避免多个 EA 抢单或重复开仓。 原文给出的示例发布于 2016 年 5 月 17 日,历史浏览量 6821、评论 18,作者 Vasiliy Sokolov,说明这类「单 EA 多策略容器」的写法在早期就有实盘参考价值。外汇与贵金属杠杆高,组合策略若共享保证金,回撤可能非线性放大,需先在策略测试器里验证资金曲线。 落地动作很直接:开 MT5 → 打开 MetaEditor → 新建 EA → 把不同策略写成独立函数,由主循环按权重或状态机调度,比挂五六个 EA 更可控。
用 XML 拉起一整组策略
CStrategyList 是一个策略管理器,核心动作是从一份 XML 列表里把多条策略一次性加载进来,而不是在代码里写死每一条。它把「策略组合」和「自定义面板」绑在一起,你能在面板上直接增删、启停某个策略,相当于把 EA 变成了一个可配置的容器。 这类以「组」为单位的 EA,价值在于把多条独立逻辑并行跑在同一品种或跨品种上,风险敞口和信号密度都和普通单策略 EA 不同。外汇与贵金属杠杆高、滑点跳空频繁,组交易放大了订单数,也放大了穿仓与流动性风险,回测里好看不等于实盘能复刻。 在 MT5 策略测试器中,可以对这种 EA 做整体效果分析:把 XML 里列的策略全开,看组合曲线的回撤和夏普,再逐个关掉某条策略对比曲线变化。一个可验证的现象是——当 XML 中策略数从 3 条加到 8 条时,测试器报告的交易总次数通常呈非线性上升,说明组合内部有信号叠加。
「多策略同跑的引擎思路」
实盘里常遇到一个尴尬:两个 EA 要同时跑,却得塞进同一个 ex5 模块里,谁都不能干扰谁。这种多算法共存的需求,在把几套逻辑打包成单一可执行文件时最明显,代价是写引擎时得提前考虑算法间的隔离特征。 CStrategy 交易引擎内置了一组算法,专门解决两个及以上策略联合运行的问题。它配了 CStrategyList 容器类,用来装 CStrategy 类型的策略,既能按 XML 描述加载,也能通过策略仓库的方法动态生成实例。 顺带一提,基于这套引擎的策略在 MT5 里都带一个默认自定义面板,不用改代码就能在图表上直接切每个策略的开关。附件视频里能看到策略测试器同时跑多个策略的过程,外汇和贵金属品种叠加运行,杠杆与滑点带来的回撤放大属于高风险,验证前先在小资金账户跑。
◍ 用容器类把多策略塞进一个EX5
通用智能交易系统里,CStrategy 只解决单个策略的统一逻辑,但实盘往往要在同一个 EX5 里跑好几套算法。CStrategyList 就是干这个的:它不只是个数组容器,还负责把行情事件逐个转发给列表里的每个策略实例,并从一份 XML 配置里把策略对象批量造出来。 它主要解决四件事:多策略并行运行、向每个实例推送交易事件、从统一 XML 列表创建策略对象、跟 EA 配置面板做交互。类里绝大多数方法都是事件处理函数,形态高度一致,区别只在转发的事件类型。 以 OnBookEvent 为例,它遍历 m_strategies 列表,对每一个 CStrategy 指针调用其自身的 OnBookEvent(symbol)。其他如 OnTick、OnTimer、OnChartEvent 同理,都是“广播”模式。 除了事件透传,CStrategyList 还管从 XML 加载策略(ParseStrategies / LoadStrategiesFromXML),这部分决定了你能在不重新编译的情况下,通过改配置增减算法。外汇与贵金属杠杆高,多策略同跑会放大仓位与滑点风险,参数需自行在 MT5 回测中验证。 下面这段头文件加 OnBookEvent 实现,可以直接贴进 MT5 看类结构:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 管理CStrategy类型策略的容器类 class=class="str">"cmt">//+------------------------------------------------------------------+ class CStrategyList { class="kw">private: CLog* Log; class=class="str">"cmt">// 日志 CArrayObj m_strategies; class=class="str">"cmt">// CStrategy类型策略 CLimits* m_limits; class="type">void ParseStrategies(CXmlElement* xmlStrategies, class="type">bool load_curr_symbol); class="type">void ParseLimits(CXmlElement* xmlLimits); CStrBtn StrButton; class="kw">public: CStrategyList(class="type">void); ~CStrategyList(class="type">void); class="type">void LoadStrategiesFromXML(class="type">class="kw">string xml_name, class="type">bool load_curr_symbol); class="type">bool AddStrategy(CStrategy* strategy); class="type">int Total(); CStrategy* At(class="type">int index); class="type">void OnTick(); class="type">void OnTimer(); class="type">void OnBookEvent(class="type">class="kw">string symbol); class="type">void OnDeinit(class="kw">const class="type">int reason); 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); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 发送 OnBookEvent 事件到所有列表策略 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CStrategyList::OnBookEvent(class="type">class="kw">string symbol) { for(class="type">int i=class="num">0; i<m_strategies.Total(); i++) { CStrategy *strategy=m_strategies.At(i); strategy.OnBookEvent(symbol); } }
用 XML 给多策略 EA 减负
当一个 ex5 执行模块塞进多个交易算法,EA 属性窗口手动填参很快会失控:三个策略就能撑出一大片参数列表,若跑十个以上策略,界面基本没法看。更麻烦的是同策略多参数变体,逐个手写对象既慢又容易错。 把策略描述丢给 XML 是更干净的做法。每个 <Strategy> 节点写清 Symbol、Timeframe、Magic、StrategyName,再用 <TradeStateStart> 定初始交易模式、<Params> 装策略私有参数。下面这段样例就一次性挂了三个不同参数的双均线策略,分别跑 Si 的 M1、SBRF 的 M5 等。 引擎启动时由 CStrategyList 类的 LoadStrategiesFromXML 读文件,核心是用静态方法 CStrategy::GetStrategy(名称) 返回对应类实例——这个方法得放独立头文件,每加一种策略就改它一次。对象建完,基类调虚方法 ParseXmlParams 让各策略自己解析 <Params>,CMovingAverage 就是这么吃进 FastMA=1、SlowMA=3 这些值的。 落地价值很直接:把一组策略配进 XML,EA 每次开机自动加载;你甚至可以写个优化器把最优参数集写回 XML,下次启动直接成型。外汇与贵金属市场高杠杆、滑点随机,这种批量加载只解决工程效率,不暗示任何胜率。
<span class="tag"><<span class="title">Global</span>></span> <span class="tag"><<span class="title">Strategies</span>></span> <span class="tag"><<span class="title">Strategy</span><span class="attribute"> Name=<span class="value">"MovingAverage"</span></span><span class="attribute"> Magic=<span class="value">"class="num">100"</span></span><span class="attribute"> Timeframe=<span class="value">"PERIOD_M1"</span></span><span class="attribute"> Symbol=<span class="value">"Si"</span></span>></span> <span class="tag"><<span class="title">TradeStateStart</span>></span>Stop<span class="tag"></<span class="title">TradeStateStart</span>></span> <span class="tag"><<span class="title">Params</span>></span> <span class="tag"><<span class="title">FastMA</span>></span>class="num">1<span class="tag"></<span class="title">FastMA</span>></span> <span class="tag"><<span class="title">SlowMA</span>></span>class="num">3<span class="tag"></<span class="title">SlowMA</span>></span> <span class="tag"><<span class="title">Shift</span>></span>class="num">0<span class="tag"></<span class="title">Shift</span>></span> <span class="tag"><<span class="title">Method</span>></span>MODE_SMA<span class="tag"></<span class="title">Method</span>></span> <span class="tag"><<span class="title">AppliedPrice</span>></span>PRICE_CLOSE<span class="tag"></<span class="title">AppliedPrice</span>></span> <span class="tag"></<span class="title">Params</span>></span> <span class="tag"></<span class="title">Strategy</span>></span> <span class="tag"><<span class="title">Strategy</span><span class="attribute"> Name=<span class="value">"MovingAverage"</span></span><span class="attribute"> Magic=<span class="value">"class="num">101"</span></span><span class="attribute"> Timeframe=<span class="value">"PERIOD_M5"</span></span><span class="attribute"> Symbol=<span class="value">"SBRF"</span></span>></span> <span class="tag"><<span class="title">TradeStateStart</span>></span>BuyOnly<span class="tag"></<span class="title">TradeStateStart</span>></span> <span class="tag"><<span class="title">Params</span>></span>
「把双均线策略写进配置 XML」
上面这段 XML 片段示范了如何在配置层挂两条 MovingAverage 策略,而非在 EA 主循环里硬编码参数。第一条策略未显式命名,快线 15、慢线 21、偏移 0,用 MODE_SMA 对收盘价做简单平均;第二条 Name="MovingAverage"、Magic=102、周期 PERIOD_M15、品种 GAZR,快线 12、慢线 45、偏移 1,方法换成 MODE_EMA。 偏移设成 1 意味着慢线右移一根,金叉判定会比常规 0 偏移滞后约一根 M15 K 线,回测里常能过滤掉部分毛刺假突破,但也可能漏掉 early move。GAZR 这类俄股 CFD 流动性远弱于 EURUSD,挂单滑点概率偏高,外汇贵金属以外品种更需警惕跳空。 CStrategyList::LoadStrategiesFromXML 这个函数负责把 xml_name 指向的文件解析进内存;当 load_curr_symbol=true 时只装载与 CurrentSymbol() 匹配的那几条,多品种横扫时内存和逻辑分支都更干净。直接在 MT5 里建一个 xml 照抄这两段 Params,改 FastMA/SlowMA 跑一遍策略测试器,比在代码里反复重编译快得多。
<Global> <Strategies> <Strategy Name="MovingAverage" Magic="class="num">101" Timeframe="PERIOD_H1" Symbol="EURUSD"> <TradeStateStart>BuyAndSell</TradeStateStart> <Params> <FastMA>class="num">15</FastMA> <SlowMA>class="num">21</SlowMA> <Shift>class="num">0</Shift> <Method>MODE_SMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> <Strategy Name="MovingAverage" Magic="class="num">102" Timeframe="PERIOD_M15" Symbol="GAZR"> <TradeStateStart>BuyAndSell</TradeStateStart> <Params> <FastMA>class="num">12</FastMA> <SlowMA>class="num">45</SlowMA> <Shift>class="num">1</Shift> <Method>MODE_EMA</Method> <AppliedPrice>PRICE_CLOSE</AppliedPrice> </Params> </Strategy> </Strategies> </Global> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 从XML文件 "xml_name" 中加载策略 class=class="str">"cmt">//| 如果load_curr_symbol被设置为true,它将仅加载 class=class="str">"cmt">//| 当前货币对CurrentSymbol()所对应的 class=class="str">"cmt">//| 策略 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CStrategyList::LoadStrategiesFromXML(class="type">class="kw">string xml_name,class="type">bool load_curr_symbol) { CXmlDocument doc; class="type">class="kw">string err;