自适应交易系统以及它们在 MetaTrader 5 客户端中的运用(基础篇)
📘

自适应交易系统以及它们在 MetaTrader 5 客户端中的运用(基础篇)

第 1/3 篇

自适应系统在 MT5 里的落地起点

自适应交易系统的核心,是让参数随市场状态漂移,而不是写死一组固定阈值。在 MetaTrader 5 中,这类系统通常借由 OnTick / OnTimer 事件驱动,配合指标缓冲区的重算来完成状态切换。 据官方社区 2013-09-12 发布的统计,该主题发布后累计阅读 5 813 次、评论 39 条,说明当时手动调参的交易者已明显在找“让 EA 自己适应环境”的出路。 开 MT5 后新建 EA,先别急着写信号逻辑,把自适应开关(如波动率分级变量)立起来,后面接策略才不会越写越僵。外汇与贵金属杠杆高,自适应不等于免亏,参数漂移本身也会引入过拟合概率。

◍ 为什么 MQL5 让策略实验门槛骤降

MetaQuotes 的交易平台在全球有数百万使用者,真正拉开差距的往往不是指标花哨与否,而是软件层能不能支撑复杂逻辑。MQL5 相对旧版最关键的两点:一是执行性能更高,二是原生支持面向对象编程(OOP),这让多策略组合不再停留在脚本拼凑阶段。 MT5 客户端自带的多货币策略测试器,给交易者一个以前稀缺的能力——在同一历史段里对复杂系统做参数寻优。2010 年自动交易锦标赛(ATC 2010)秋季开赛,上千个 MQL5 写的 EA 同场跑,比的是指定周期内谁的利润曲线更靠前;但历史回测里赚最多的参数,实盘未必扛得住滑点和流动性断裂,这是外汇/贵金属高杠杆环境里必须自己验证的事。 早先有篇文章在 MQL4 里塞了「EA 套 EA」的虚拟竞赛思路,用来对比几个子策略谁更强。到了 MQL5,靠标准库里的数据类、交易类加 OOP 写法,自适应策略从建模到回测分析的代码量明显压缩,你开 MT5 新建一个 EA 模板就能体会到这套类库的直接可用。

「让市场替你挑均线周期」

市场状态一直在换,写死的均线周期迟早失效。与其手动逐个改参数回测,不如把多个周期策略塞进同一个 EA,让账户曲线自己投票。 我们在 EURUSD H1 上做了一次实跑对比:用 MA 周期 3、13…93 共十个 EA,测试段为 2010.01.04–2010.08.20。前两周十个净值曲线几乎重叠,之后明显分化,期末跑赢的是周期 63、53、43 的那三个。 把十个策略合成一个自适应 EA,每根新 K 线评估谁当前最赚,只跟它的信号下单。红色净值曲线在超过一半测试时间里和最终的赢家 MA_63 长一样。 关键防线在亏钱时段:2010.01.04–01.22,十个策略集体负收益,自适应系统直接不建新仓。外汇和贵金属杠杆高,这种“全绿转全红就缩手”的机制,比硬扛更能保住本金。

用容器装策略,让EA自己挑最赚钱的那个

自适应 EA 的思路不复杂:同时跑多个策略做“虚拟交易”,只在每根新柱结束时挑出阶段性最赚钱的那个,把真实仓位方向同步过去。面向对象写法下,这事比想象中轻——所有策略实例塞进一个动态数组当容器,统一调接口就行。 EA 主文件极简:前三行是程序属性,#include 尖括号指向终端目录 MQL5\Include 下的 CAdaptiveStrategy.mqh;声明一个 CAdaptiveStrategy 实例 Adaptive_Expert,OnInit / OnDeinit / OnTick 全部转发给该对象的对应方法。 容器用的是标准库 CArrayObj,存的是 CObject 派生类的指针。示例里往里塞了 10 个 CStrategyMA(不同周期)和 5 个 CStrategyStoch 实例,总数不限。Expert_OnInit 里对每个实例调 Initialization 设周期、开虚拟交易,再用 SetStrategyInfo 写品种和名称,必要时 Set_Stops 设点数的 TP/SL。 每根新柱调用一次 CheckTradeConditions 做虚拟成交;Expert_OnTick 里先 UpdatePositionData 刷新所有虚拟仓位浮盈,再用 StrategyPerformance 算差值填进 Performance[],ArrayMaximum 挑最优索引。若最佳策略盈利为负且真实仓空着,直接退出不交易;否则取 best_direction,和真实仓方向不同就靠 ProceedSignalReal 纠正。外汇与贵金属杠杆高,虚拟筛选≠实盘稳健,参数乱加可能放大回撤。 去初始化时遍历容器调 SaveVirtualDeals 把虚拟成交存成以策略名为文件名的记录(测试器下落在 /tester/Files/),不需要可删那行。基类 CSampleStrategy 已含 IsNewBar 与虚拟交易骨架,自己写策略继承它就够。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     Adaptive_Expert_Sample.mq5 |
class=class="str">"cmt">//|    Copyright class="num">2010, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <CAdaptiveStrategy.mqh>
CAdaptiveStrategy Adaptive_Expert;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class="kw">return(Adaptive_Expert.Expert_OnInit());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA去初始化函数                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 自适应策略的清仓与反转触发

EA 在 OnDeinit 和 OnTick 里只做薄薄一层转发,真正逻辑落在 CAdaptiveStrategy 类。该类用 m_all_strategies 指针数组挂多套子策略(如 MA、Stoch),对外暴露 Expert_OnInit / Expert_OnDeInit / Expert_OnTick 三个接口,由父级 Expert 统一调用。 ProceedSignalReal 是真实账户同步核心。它先 CPositionInfo::Select(_Symbol) 抓当前品种持仓,标记 buy_opened / sell_opened;随后按 state 决策:state=POSITION_NEUTRAL 且有仓,就 trade.PositionClose(_Symbol,200) 平掉,200 是平仓超时毫秒数;state=POSITION_SHORT 且持多,同样先平多再准备反手空。 外汇与贵金属杠杆高,PositionClose 返回 false 时务必 Print(trade.ResultRetcodeDescription()) 看终端报错码,别让反转单卡在残留仓位上。开 MT5 把这段贴进 EA,改一下 state 枚举值就能复现中性清仓路径。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
  Adaptive_Expert.Expert_OnDeInit(reason);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA订单函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  Adaptive_Expert.Expert_OnTick();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Arrays\ArrayObj.mqh>
class="macro">#include <Trade\PositionInfo.mqh>
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <CStrategyMA.mqh>
class="macro">#include <CStrategyStoch.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CAdaptiveStrategy类                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CAdaptiveStrategy
  {
class="kw">protected:
  CArrayObj         *m_all_strategies;   class=class="str">"cmt">// 交易策略对象
  class="type">void              ProceedSignalReal(class="type">int state,class="type">class="kw">double trade_volume);
  class="type">int               RealPositionDirection();
class="kw">public:
  class=class="str">"cmt">// 初始化自适应策略
  class="type">int               Expert_OnInit();
  class=class="str">"cmt">// 去初始化自适应策略
  class="type">int               Expert_OnDeInit(const class="type">int reason);
  class=class="str">"cmt">// 检查交易条件建立虚拟仓位
  class="type">void              Expert_OnTick();
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 此方法用于将真实交易仓位和状态值进行同步                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CAdaptiveStrategy::ProceedSignalReal(class="type">int state,class="type">class="kw">double trade_volume)
  {
  CPositionInfo posinfo;
  CTrade trade;
  class="type">bool buy_opened=false;
  class="type">bool sell_opened=false;
  if(posinfo.Select(_Symbol)) class=class="str">"cmt">// 如果有持仓
    {
    if(posinfo.Type()==POSITION_TYPE_BUY) buy_opened=true;    class=class="str">"cmt">// 有买入仓位
    if(posinfo.Type()==POSITION_TYPE_SELL) sell_opened=true;  class=class="str">"cmt">// 有卖出仓位
    class=class="str">"cmt">// 如果 state = class="num">0, 我们需要关闭仓位
    if((state==POSITION_NEUTRAL) && (buy_opened || sell_opened))
      {
      if(!trade.PositionClose(_Symbol,class="num">200))
        Print(trade.ResultRetcodeDescription());
      }
    class=class="str">"cmt">//反转: 关闭买入仓位开启卖出仓位
    if((state==POSITION_SHORT) && (buy_opened))
      {
      if(!trade.PositionClose(_Symbol,class="num">200))
        Print(trade.ResultRetcodeDescription());

「仓位翻转与自适应策略初始化」

当账户里已有反向仓位时,EA 会先平后开完成翻转:比如当前持多单且空方信号触发,先用 PositionClose(_Symbol,200) 尝试平仓(200 为超时毫秒数),失败就打印返回码描述;紧接着以 SYMBOL_ASK 现价挂多单进场,完成多空切换。 若当前无任何持仓,则按 state 状态直接开仓:state==POSITION_LONG 时以 0.1 手吃进市价多单,state==POSITION_SHORT 时以 0.1 手吃进市价空单,滑点参数均置 0。外汇与贵金属杠杆高,这类市价翻转在重大数据行情中可能触发滑点扩大或拒绝成交。 RealPositionDirection() 用 CPositionInfo 读取当前品种真实方向,返回 0(中性)、+1(多)、-1(空),是上面翻转判断的底層依据。 初始化阶段 Expert_OnInit() 先 new 一个 CArrayObj 容器 m_all_strategies,失败直接返回 -1 并报警;随后 for 循环 i 从 0 到 9 创建 10 个 CStrategyMA 均线策略对象并塞进容器,这是自适应 EA 同时跑多套参数的起点。

MQL5 / C++
if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,trade_volume,SymbolInfoDouble(_Symbol,SYMBOL_BID),class="num">0,class="num">0))
      Print(trade.ResultRetcodeDescription());
    }
    class=class="str">"cmt">//反转: 关闭卖出仓位开启买入仓位
    if(((state==POSITION_LONG) && (sell_opened)))
      {
       if(!trade.PositionClose(_Symbol,class="num">200))
          Print(trade.ResultRetcodeDescription());
       if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,trade_volume,SymbolInfoDouble(_Symbol,SYMBOL_ASK),class="num">0,class="num">0))
          Print(trade.ResultRetcodeDescription());
      }
   }
  else class=class="str">"cmt">// 如果没有开启的仓位
   {
   class=class="str">"cmt">// 开启买入仓位
   if(state==POSITION_LONG)
     {
      if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,class="num">0.1,SymbolInfoDouble(_Symbol,SYMBOL_ASK),class="num">0,class="num">0))
         Print(trade.ResultRetcodeDescription());
     }
   class=class="str">"cmt">// 开启卖出仓位
   if(state==POSITION_SHORT)
     {
      if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,class="num">0.1,SymbolInfoDouble(_Symbol,SYMBOL_BID),class="num">0,class="num">0))
         Print(trade.ResultRetcodeDescription());
     }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回当前真实仓位的方向 (class="num">0,+class="num">1,-class="num">1)                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CAdaptiveStrategy::RealPositionDirection()
  {
   class="type">int direction=POSITION_NEUTRAL;
   CPositionInfo posinfo;
   if(posinfo.Select(_Symbol)) class=class="str">"cmt">// 如果有开启的仓位
     {
      if(posinfo.Type()==POSITION_TYPE_BUY) direction=POSITION_LONG;    class=class="str">"cmt">// 开启了买入仓位
      if(posinfo.Type()==POSITION_TYPE_SELL) direction=POSITION_SHORT;  class=class="str">"cmt">// 开启了卖出仓位
     }
   class="kw">return(direction);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 初始化自适应EA的函数            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CAdaptiveStrategy::Expert_OnInit()
  {
class=class="str">"cmt">//--- 创建 m_all_strategies 对象数组
class=class="str">"cmt">//--- 我们会把策略对象放置其中
   m_all_strategies=new CArrayObj;
   if(m_all_strategies==NULL)
     {
      Print("创建 m_all_strategies 对象失败"); class="kw">return(-class="num">1);
     }
class=class="str">"cmt">// 创建10个 CStrategyMA(根据移动平均交易) 类型的交易策略
class=class="str">"cmt">// 初始化它们,设置参数
class=class="str">"cmt">// 并把它们加入到 m_all_strategies 容器中
   for(class="type">int i=class="num">0; i<class="num">10; i++)
     {
      CStrategyMA *t_StrategyMA;
      t_StrategyMA=new CStrategyMA;

常见问题

先建一个最小可运行的逻辑容器,把进出场规则写成函数,再用历史数据做初步回测,确认无报错再上模拟盘。
可以写一段循环评估不同周期在近段时间的表现,让系统每过一段时间挑出收益倾向更优的周期,避免人为滞后。
小布可读取品种页的波动与胜率诊断,提示近期更适配的均线区间与清仓阈值,你再据此微调EA参数即可。
在初始化里设定单笔风险上限与连续亏损暂停规则,反转触发后只按固定比例复原仓位,不自动加倍。
贵金属波动概率上更剧,建议把反转触发阈值放宽一档,并点明高风险,先用小仓验证再逐步放大。