自适应交易系统以及它们在 MetaTrader 5 客户端中的运用(基础篇)
自适应系统在 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 与虚拟交易骨架,自己写策略继承它就够。
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 枚举值就能复现中性清仓路径。
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 同时跑多套参数的起点。
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;