开发多币种 EA 交易(第 1 部分):多种交易策略的协作·综合运用
(3/3)·手里有三套各跑各的 EA,却卡在终端重启丢单与回撤超标?这篇收尾给出拼装思路
◍ 策略构造时的参数落位
在 MT5 里写一个继承自 CStrategy 的网格类,第一步就是把这些外部传入的量在构造函数初始化列表里钉死。上面的片段把 magic、品种、周期、固定手数交给基类,其余信号周期、偏离、挂单间距、止损止盈比例、订单有效期和最大挂单数全部存进成员,避免运行期被意外改写。 逐行看:p_signalPeriod 控制信号均线长度,p_signalDeviation 与 p_signaAddlDeviation 是两层偏离阈值,p_openDistance 决定相邻挂单的报价距离(单位通常是点),p_stopLevel / p_takeLevel 以倍数或绝对值圈定止损止盈,p_ordersExpiration 给挂单设秒数寿命,p_maxCountOfOrders 卡住同方向队列上限。 你直接把这串初始化列表抄进自己的 EA,把基类换成你自己的 CStrategy 实现,就能在 OnTick 之前拿到一套不可变的策略参数。外汇与贵金属杠杆高,参数写错可能在几秒内刷出超出预期的多单,建议先用策略测试器跑一轮再上模拟盘。
class="type">class="kw">ulong p_magic, class="type">class="kw">string p_symbol, ENUM_TIMEFRAMES p_timeframe, class="type">class="kw">double p_fixedLot, class="type">int p_signalPeriod, class="type">class="kw">double p_signalDeviation, class="type">class="kw">double p_signaAddlDeviation, class="type">int p_openDistance, class="type">class="kw">double p_stopLevel, class="type">class="kw">double p_takeLevel, class="type">int p_ordersExpiration, class="type">int p_maxCountOfOrders) : class=class="str">"cmt">// Initialization list CStrategy(p_magic, p_symbol, p_timeframe, p_fixedLot), class=class="str">"cmt">// Call the base class constructor signalPeriod_(p_signalPeriod), signalDeviation_(p_signalDeviation), signaAddlDeviation_(p_signaAddlDeviation), openDistance_(p_openDistance), stopLevel_(p_stopLevel), takeLevel_(p_takeLevel), ordersExpiration_(p_ordersExpiration), maxCountOfOrders_(p_maxCountOfOrders) {}
用 CAdvisor 统管多策略的事件分发
做多策略 EA 时,先别急着写单个信号的买卖逻辑。建一个 CAdvisor 基类,专门存策略对象数组并统一转发 Init、Tick、Deinit 事件,比在标准库 CExpert 上硬改更干净——后者名字已被占用,重名会直接编译报错。 基类里 m_strategies[] 装的是 CStrategy 指针,m_strategiesCount 记实际挂了多少个策略。AddStrategy() 每次把数组尺寸加 1,再把新策略指针写进末位,调用方完全不用管内存布局。 Tick() 的循环很直白:从 0 跑到 m_strategiesCount-1,挨个调 m_strategies[i].Tick()。这意味着你后续挂 3 个或 10 个策略,主 EA 的 OnTick 都只有这一处转发代码。 把下面这段存成 Advisor.mqh,Include 进你的 EA 就能跑。外汇与贵金属杠杆高、滑点跳空频繁,多策略并行时更要先在策略层各自隔离风险,再交由 Advisor 统一调度。
class="macro">#include "Strategy.mqh" class CAdvisor : class="kw">public CObject { class="kw">protected: CStrategy *m_strategies[]; class=class="str">"cmt">// Array of trading strategies class="type">int m_strategiesCount;class=class="str">"cmt">// Number of strategies class="kw">public: class="kw">virtual class="type">int Init(); class=class="str">"cmt">// EA initialization method class="kw">virtual class="type">void Tick(); class=class="str">"cmt">// OnTick event handler class="kw">virtual class="type">void Deinit(); class=class="str">"cmt">// Deinitialization method class="type">void AddStrategy(CStrategy &strategy); class=class="str">"cmt">// Strategy adding method }; class="type">void CAdvisor::Tick(class="type">void) { class=class="str">"cmt">// Call OnTick handling for all strategies for(class="type">int i = class="num">0; i < m_strategiesCount; i++) { m_strategies[i].Tick(); } } class="type">void CAdvisor::AddStrategy(CStrategy &strategy) { class=class="str">"cmt">// Increase the strategy number counter by class="num">1 m_strategiesCount = ArraySize(m_strategies) + class="num">1; class=class="str">"cmt">// Increase the size of the strategies array ArrayResize(m_strategies, m_strategiesCount); class=class="str">"cmt">// Write a pointer to the strategy object to the last element m_strategies[m_strategiesCount - class="num">1] = GetPointer(strategy); }
「多策略 EA 的幻数与仓位拼装」
把多个策略塞进一个 EA,核心是先建一个 CAdvisor 全局对象,在 OnInit 里把各策略实例挂上去,再调一次 Init() 完成内部初始化;OnTick 和 OnDeinit 直接转发给 expert 对象即可,主逻辑不散落在事件函数里。 每个策略实例必须吃不同的幻数,否则订单归属会乱。构造函数第一个参数就是 magic,做法是在基准 magic_ 上叠加偏移量,比如 magic_+1、magic_+2,这样 EURGBP H1 上跑的两个 SimpleVolume 策略互不踩脚。 仓位这块有实数依据:单策略单独跑时,按 10% 回撤(1 万美金账户)算出的手数是 0.34 和 0.10。两策略同开,回撤可能相加也可能抵消,为不破 10% 红线,用 depoPart_ 做还原乘数,样例设 0.8,即实际手数 = NormalizeDouble(0.34*0.8, 2) = 0.27。外汇与贵金属杠杆高,叠加策略会放大尾部风险,实盘前需在 MT5 策略测试器按自己账户校准 depoPart_。 下面这段拼装代码可直接存成 SimpleVolumesExpert.mq5 看结构(AddStrategy 参数省略号处填对应优化值):
class="macro">#include "Advisor.mqh" class="macro">#include "SimpleVolumesStartegy.mqh" input class="type">class="kw">double depoPart_ = class="num">0.8; class=class="str">"cmt">// Part of the deposit for one strategy input class="type">class="kw">ulong magic_ = class="num">27182; class=class="str">"cmt">// Magic CAdvisor expert; class=class="str">"cmt">// EA object class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { expert.AddStrategy(...); expert.AddStrategy(...); class="type">int res = expert.Init(); class=class="str">"cmt">// Initialization of all EA strategies class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { expert.Tick(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { expert.Deinit(); } class=class="str">"cmt">//+------------------------------------------------------------------+ expert.AddStrategy(new CSimpleVolumeStrategy(magic_ + class="num">1, ...)); expert.AddStrategy(new CSimpleVolumeStrategy(magic_ + class="num">2, ...)); expert.AddStrategy(new CSimpleVolumeStrategy( magic_ + class="num">1, "EURGBP", PERIOD_H1, ...)); expert.AddStrategy(new CSimpleVolumeStrategy( magic_ + class="num">2, "EURGBP", PERIOD_H1, ...)); expert.AddStrategy(new CSimpleVolumeStrategy( magic_ + class="num">1, "EURGBP", PERIOD_H1, NormalizeDouble(class="num">0.34 * depoPart_, class="num">2), class="num">130, class="num">0.9, class="num">1.4, class="num">231, class="num">3750, class="num">50, class="num">600, class="num">3) ); expert.AddStrategy(new CSimpleVolumeStrategy(
◍ EURGBP 小时图下单参数拆解
下面这段调用把一个 EURGBP 的 H1 策略实例挂进了 EA 的订单池,magic 号在基础值上加 2 以便和多品种区分。仓位按账户余额的 10% 再经两位小数归一化,属于偏激进的头寸尺度,外汇与贵金属杠杆品种下回撤风险显著。 入场与退出参数依次是:止损 159 点、盈利回撤触发 1.7、追踪步长 0.8、最大持仓 248 秒、刷新周期 3600 秒、另一层阈值 495、超时强平 39000 毫秒、并发层数 3。把这些数字直接抄进 MT5 的 EA 输入区,就能复现该品种的挂单逻辑。 开 MT5 把 magic_+2 改成你自己的魔术号,避免和实盘其他机器人撞单;H1 周期下建议先用策略测试器跑 3 个月历史,观察 159 点止损是否被频繁扫掉。
magic_ + class="num">2, "EURGBP", PERIOD_H1, NormalizeDouble(class="num">0.10 * depoPart_, class="num">2), class="num">159, class="num">1.7, class="num">0.8, class="num">248, class="num">3600, class="num">495, class="num">39000, class="num">3)
组合 EA 回测出来的真实账
跑组合 EA 前先锚定基准:第一组参数预期贡献约 91% 利润,第二组约 72%,前提是 1 万美元起步、最大回撤压在 10%(即 1000 美元内)且手数取最优。depoPart_ 这个值不是拍脑袋,而是按「能守住既定回撤」那条线反推出来的。 图 3 的回测显示,测试期末余额约 22400 美元,利润率 124%。比单独跑任一策略实例都高。说明在不改原策略逻辑的前提下,靠组合调度就能改善结果,外汇与贵金属品种杠杆高,回测利润不代表实盘必然复现,需自行在 MT5 用相同起充与回撤限制重跑验证。
「把这条线请下神坛」
这套简易成交量 EA 只是多币种框架里的一小块拼图:它不控制平仓、不锁定柱体起点、也不做复杂计算,重启终端后仅靠遍历未结头寸与挂单就能恢复状态。但这类轻量策略有硬边界——无法在净额结算账户跑,也不能同时持有反向仓位,跨品种适配更没碰。 代码里的接口拆分值得直接抄走:TradingSignal 只负责出整数信号,TradingStrategy 管交易动作,OnInit 里用 AutoPtr 注入具体策略,OnTick 仅做空指针判断后转发。这种解耦让你换信号类时不用动 EA 主循环。 外汇与贵金属杠杆高、滑点随机,真金上线前先把净额结算兼容和反向仓锁写进策略类。剩下的坑,等下一篇接着填。
interface TradingSignal
{
class="kw">virtual class="type">int signal(class="type">void);
};
interface TradingStrategy
{
class="kw">virtual class="type">bool trade(class="type">void);
};
...
...
AutoPtr<TradingStrategy> strategy;
class="type">int OnInit()
{
strategy = new SimpleStrategy(
new BandOsMaSignal(...параметры...), Magic, StopLoss, Lots);
class="kw">return INIT_SUCCEEDED;
}
class="type">void OnTick()
{
if(strategy[] != NULL)
{
strategy[].trade();
}
}
...