开发多币种 EA 交易(第 1 部分):多种交易策略的协作·综合运用
🧩

开发多币种 EA 交易(第 1 部分):多种交易策略的协作·综合运用

(3/3)·手里有三套各跑各的 EA,却卡在终端重启丢单与回撤超标?这篇收尾给出拼装思路

案例拆解新手友好 第 3/3 篇
很多交易者把几套盈利思路硬塞进同一账户,却从没算过叠加头寸会不会击穿最大回撤。终端一重启,挂单消失、比例错位,回测好看实盘崩。先定清楚「想要什么、有什么」,比急着写代码更省时间。

◍ 策略构造时的参数落位

在 MT5 里写一个继承自 CStrategy 的网格类,第一步就是把这些外部传入的量在构造函数初始化列表里钉死。上面的片段把 magic、品种、周期、固定手数交给基类,其余信号周期、偏离、挂单间距、止损止盈比例、订单有效期和最大挂单数全部存进成员,避免运行期被意外改写。 逐行看:p_signalPeriod 控制信号均线长度,p_signalDeviation 与 p_signaAddlDeviation 是两层偏离阈值,p_openDistance 决定相邻挂单的报价距离(单位通常是点),p_stopLevel / p_takeLevel 以倍数或绝对值圈定止损止盈,p_ordersExpiration 给挂单设秒数寿命,p_maxCountOfOrders 卡住同方向队列上限。 你直接把这串初始化列表抄进自己的 EA,把基类换成你自己的 CStrategy 实现,就能在 OnTick 之前拿到一套不可变的策略参数。外汇与贵金属杠杆高,参数写错可能在几秒内刷出超出预期的多单,建议先用策略测试器跑一轮再上模拟盘。

MQL5 / C++
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 统一调度。

MQL5 / C++
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 参数省略号处填对应优化值):

MQL5 / C++
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 点止损是否被频繁扫掉。

MQL5 / C++
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 主循环。 外汇与贵金属杠杆高、滑点随机,真金上线前先把净额结算兼容和反向仓锁写进策略类。剩下的坑,等下一篇接着填。

MQL5 / C++
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();
   }
}
...
让小布替你跑这套诊断
多策略同账户下的回撤占用与重启丢单隐患,小布盯盘的 AIGC 已内置扫描,打开对应品种页即可看到实时诊断,你只管决定留哪套思路。

常见问题

倾向在初始化阶段读入配置比例,按当前权益动态换算每策略可开手数上限,避免静态切分在浮亏扩大时越权。
可以,小布盯盘的品种页会汇总同账户下多策略的开仓占比与回撤进度,异常时给提示,省去自己写日志解析。
可能在 OnInit 里重建此前应存在但未触发的挂单,并以烛形平均分时量为基准重新判定交易强度,防止重复发单。
不同品种活跃度差异大,单一水平会漏掉冷门币种的密集信号或误触发热门品种,概率上更稳的是用局部均值动态标定。
本系列基于 MQL5 与 MT5 标准测试器,MT4 需改写类结构与测试接口,完整跨平台讨论见《开发多币种 EA 交易·基础篇》。