自适应交易系统以及它们在 MetaTrader 5 客户端中的运用·综合运用
把策略封装成类:以均线为例看虚拟交易骨架
在自适应 EA 里,每一种交易逻辑都应该收进一个独立的策略类,而不是把判断写死在主循环。CStrategyMA 就是典型样本:它继承自 CSampleStrategy,专门承载「按移动平均线做虚拟交易」的全部功能。 受保护段只放三样东西——m_handle 存 iMA 指标句柄,m_period 存均线周期,m_values[] 用来在 CheckTradeConditions 里接指标实时值。公共段则暴露三个函数:Initialization 建指标、Deinitialization 释放句柄、CheckTradeConditions 出信号。 信号机制很直白:CheckTradeConditions 根据价格与指标状态算出 new_state,再用 GetSignalState 取虚拟仓当前状态;两者不一致就调父类的 SetSignalState 纠正。可传的四类信号是 SIGNAL_OPEN_LONG、SIGNAL_OPEN_SHORT、SIGNAL_CLOSE_LONG、SIGNAL_CLOSE_SHORT。外汇与贵金属杠杆高,虚拟信号仅用于策略筛选,实盘前务必在 MT5 用历史数据回测验证。 CStrategyStoch 的结构和它几乎一样,唯一差别是指标换成 iStochastic、初始化参数不同、信号逻辑跟着改。想把自己的策略塞进自适应框架,就照这个模板重写类,再丢进 m_all_strategies 容器即可。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CStrategyMA.mqh | 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">#include <CSampleStrategy.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CStrategyMA 类实现了根据移动平均策略 | class=class="str">"cmt">//| 进行虚拟交易 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CStrategyMA : class="kw">public CSampleStrategy { class="kw">protected: class="type">int m_handle; class=class="str">"cmt">// 移动平均 (iMA) 指标的句柄 class="type">int m_period; class=class="str">"cmt">// 移动平均指标的周期 class="type">class="kw">double m_values[]; class=class="str">"cmt">// 保存指标值的数组 class="kw">public: class=class="str">"cmt">// 初始化策略 class="type">int Initialization(class="type">int period,class="type">bool virtual_trade_flag); class=class="str">"cmt">// 去初始化策略 class="type">int Deinitialization(); class=class="str">"cmt">// 检查交易条件并开启虚拟仓位 class="type">bool CheckTradeConditions(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 策略初始化方法 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CStrategyMA::Initialization(class="type">int period,class="type">bool virtual_trade_flag) { class=class="str">"cmt">// 设置移动平均周期 m_period=period; class=class="str">"cmt">// 设置虚拟交易标志 SetVirtualTradeFlag(virtual_trade_flag); class=class="str">"cmt">//设置数组索引方式为时间序列 ArraySetAsSeries(m_rates,true); ArraySetAsSeries(m_values,true); class=class="str">"cmt">//创建指标获得句柄 m_handle=iMA(_Symbol,_Period,m_period,class="num">0,MODE_EMA,PRICE_CLOSE); if(m_handle<class="num">0) { Alert("创建 MA 指标错误 - 错误代码: ",GetLastError(),"!!");
「双条件过滤的均线信号判定」
策略在去初始化时先平掉虚拟仓位再释放指标句柄,避免 MT5 退出时残留对象占用内存。 交易条件检查从最近 3 根 K 线入手:CopyRates 取 0~3 根历史,CopyBuffer 取均线缓冲 0~3 点。若任一复制失败直接弹窗报错并返回 false,实盘里这能帮你快速定位是品种停盘还是句柄失效。 买入需同时满足 MA 三线向上(m_values[0]>m_values[1]>m_values[2])且前一根收盘价高于对应 MA;卖出对称反向。仅当双条件齐备才置 SIGNAL_OPEN,若已有持仓则只要触发反向单条件就发平仓信号——这种「开仓严、平仓宽」的逻辑在震荡市可能减少来回打脸。 外汇与贵金属杠杆高,虚拟仓位转实盘前务必在策略测试器用 2020—2023 年数据跑一遍,观察信号翻转频率再调参。
class="type">int CStrategyMA::Deinitialization() { Position_CloseOpenedPosition(m_position); IndicatorRelease(m_handle); class="kw">return(class="num">0); }; class="type">bool CStrategyMA::CheckTradeConditions() { RecalcPositionProperties(m_position); class="type">class="kw">double p_close; if(CopyRates(_Symbol,_Period,class="num">0,class="num">3,m_rates)<class="num">0) { Alert("复制历史数据错误 - 错误:",GetLastError(),"!!"); class="kw">return(false); } p_close=m_rates[class="num">1].close; if(CopyBuffer(m_handle,class="num">0,class="num">0,class="num">3,m_values)<class="num">0) { Alert("复制移动平均指标缓冲区错误 - 错误代码:",GetLastError()); class="kw">return(false); } class="type">bool buy_condition_1=(m_values[class="num">0]>m_values[class="num">1]) && (m_values[class="num">1]>m_values[class="num">2]); class="type">bool buy_condition_2=(p_close>m_values[class="num">1]); class="type">bool sell_condition_1=(m_values[class="num">0]<m_values[class="num">1]) && (m_values[class="num">1]<m_values[class="num">2]); class="type">bool sell_condition_2=(p_close<m_values[class="num">1]); class="type">int new_state=class="num">0; if(buy_condition_1 && buy_condition_2) new_state=SIGNAL_OPEN_LONG; if(sell_condition_1 && sell_condition_2) new_state=SIGNAL_OPEN_SHORT; if((GetSignalState()==SIGNAL_OPEN_SHORT) && (buy_condition_1 || buy_condition_2)) new_state=SIGNAL_CLOSE_SHORT; if((GetSignalState()==SIGNAL_OPEN_LONG) && (sell_condition_1 || sell_condition_2)) new_state=SIGNAL_CLOSE_LONG; if(GetSignalState()!=new_state) { SetSignalState(new_state); } class="kw">return(true); };
◍ 用随机振荡指标搭一个虚拟交易类
在 MT5 里做策略原型,最省事的办法是先继承一个通用策略基类,把指标逻辑塞进子类。下面这段把随机振荡指标(Stochastic)封装成 CStrategyStoch,专门用来跑虚拟交易,不碰真实账户资金,外汇和贵金属品种都可加载,但两者杠杆高、滑点跳空频繁,虚拟回测和实盘表现可能有明显偏差。 类里先声明了几个核心成员:m_handle 存 iStochastic 的句柄,m_period_k / m_period_d / m_period_slowing 分别对应 %K 周期、%D 平滑、慢速平滑;m_main_line 和 m_signal_line 两个数组接指标主线与信号线数值。初始化时由外部传入三个周期参数和是否虚拟交易的开关。 Initialization 方法里先把周期和虚拟标志写进成员,随后用 ArraySetAsSeries 把价格数组与指标数组都设为时间序列(最新柱在索引 0)。接着调用 iStochastic(_Symbol,_Period, m_period_k,m_period_d,m_period_slowing, MODE_SMA, STO_LOWHIGH) 拿句柄,若返回负值就弹 Alert 并报 GetLastError() 后退出。 Deinitialization 负责平仓并释放句柄:先 Position_CloseOpenedPosition 清掉虚拟仓位,再 IndicatorRelease(m_handle) 交还系统资源。CheckTradeConditions 是策略主循环入口,留了注释位等具体交叉判断逻辑填充。 开 MT5 按这个结构建个 mqh,把周期设成 5/3/3 加载到 XAUUSD 的 M15 上跑虚拟单,能直观看到随机指标交叉在贵金属上的触发密度。
class="macro">#include <CSampleStrategy.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CStrategyStoch 根据随机振荡指标的交叉线 | class=class="str">"cmt">//| 策略进行虚拟交易 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CStrategyStoch : class="kw">public CSampleStrategy { class="kw">protected: class="type">int m_handle; class=class="str">"cmt">// 随机振荡指标的句柄 (iStochastic) class="type">int m_period_k; class=class="str">"cmt">// K-周期 (用于计算的柱数) class="type">int m_period_d; class=class="str">"cmt">// D-周期 (首要平滑周期) class="type">int m_period_slowing; class=class="str">"cmt">// 最终平滑 class="type">class="kw">double m_main_line[]; class=class="str">"cmt">// 保存指标值的数组 class="type">class="kw">double m_signal_line[]; class=class="str">"cmt">// 保存指标值的数组 class="kw">public: class=class="str">"cmt">// 初始化策略 class="type">int Initialization(class="type">int period_k,class="type">int period_d,class="type">int period_slowing,class="type">bool virtual_trade_flag); class=class="str">"cmt">// 去初始化策略 class="type">int Deinitialization(); class=class="str">"cmt">// 检查交易策略,开启虚拟仓位 class="type">bool CheckTradeConditions(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 策略初始化方法 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CStrategyStoch::Initialization(class="type">int period_k,class="type">int period_d,class="type">int period_slowing,class="type">bool virtual_trade_flag) { class=class="str">"cmt">// 设置振荡指标周期 m_period_k=period_k; m_period_d=period_d; m_period_slowing=period_slowing; class=class="str">"cmt">// 设置虚拟交易标志 SetVirtualTradeFlag(virtual_trade_flag); class=class="str">"cmt">// 设置数组索引方式为时间序列 ArraySetAsSeries(m_rates,true); ArraySetAsSeries(m_main_line,true); ArraySetAsSeries(m_signal_line,true); class=class="str">"cmt">// 创建指标获得句柄 m_handle=iStochastic(_Symbol,_Period,m_period_k,m_period_d,m_period_slowing,MODE_SMA,STO_LOWHIGH); if(m_handle<class="num">0) { Alert("创建随机振荡指标错误 - 错误代码: ",GetLastError(),"!!"); class="kw">return(-class="num">1); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 策略去初始化方法 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CStrategyStoch::Deinitialization() { class=class="str">"cmt">// 关闭所有开启仓位 Position_CloseOpenedPosition(m_position); class=class="str">"cmt">// 释放指标句柄 IndicatorRelease(m_handle); class="kw">return(class="num">0); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查交易条件开启虚拟仓位 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CStrategyStoch::CheckTradeConditions() { class=class="str">"cmt">// 调用函数重新计算仓位属性
用前三根柱判随机指标穿线信号
这段逻辑只抓最近 3 根柱的微观交叉,不做长周期拟合。先通过 CopyRates 拉取当前品种、当前周期、从 0 偏移共 3 根柱的数据进 m_rates,失败直接弹 Alert 并返回 false,避免脏数据继续跑。 紧接着用 CopyBuffer 分别取随机振荡指标主线(0 号缓冲)与信号线(1 号缓冲)各 3 个值。任一缓冲区复制不到 3 个点也立刻报错退出——这是实盘里最常见的「指标句柄未就绪」坑,贵金属跳空时尤其容易触发。 穿线判定只看索引 2 和 1:buy_condition 要求前前柱信号线低于主线、前一根反转为信号线高于主线,即自下而上金叉;sell_condition 反之。注意这里没有用柱 0(最新未收线柱),只用已收盘的柱 1 与柱 2,回测与直播信号偏差更小。 状态机部分把穿线结果映射到开平:空仓遇买条件置 SIGNAL_OPEN_LONG,遇卖条件置 SIGNAL_OPEN_SHORT;若已有反向持仓则切到平仓状态。最后仅当新状态与 GetSignalState() 不同才调用 SetSignalState 写一次,减少冗余刷新。外汇与贵金属杠杆高,这类微观交叉在震荡市可能频繁假突破,建议拿到 MT5 用 EURUSD 的 M15 先跑一周验证触发频率。
RecalcPositionProperties(m_position); class="type">class="kw">double p_close; class=class="str">"cmt">// 获得最后3个柱的历史数据 if(CopyRates(_Symbol,_Period,class="num">0,class="num">3,m_rates)<class="num">0) { Alert("复制历史数据错误 - 错误:",GetLastError(),"!!"); class="kw">return(false); } class=class="str">"cmt">// 复制前一个柱的收盘价 (柱 class="num">1) p_close=m_rates[class="num">1].close; class=class="str">"cmt">// 前1柱的收盘价 if((CopyBuffer(m_handle,class="num">0,class="num">0,class="num">3,m_main_line)<class="num">3) || (CopyBuffer(m_handle,class="num">1,class="num">0,class="num">3,m_signal_line)<class="num">3)) { Alert("复制随机振荡指标缓冲区错误 - 错误代码:",GetLastError()); class="kw">return(false); } class=class="str">"cmt">// 买入条件: 从下往上主线穿过信号线 class="type">bool buy_condition=((m_signal_line[class="num">2]<m_main_line[class="num">2]) && (m_signal_line[class="num">1]>m_main_line[class="num">1])); class=class="str">"cmt">// 卖出条件: 从上往下主线穿过信号线 class="type">bool sell_condition=((m_signal_line[class="num">2]>m_main_line[class="num">2]) && (m_signal_line[class="num">1]<m_main_line[class="num">1])); class="type">int new_state=class="num">0; if(buy_condition) new_state=SIGNAL_OPEN_LONG; if(sell_condition) new_state=SIGNAL_OPEN_SHORT; if((GetSignalState()==SIGNAL_OPEN_SHORT) && (buy_condition)) new_state=SIGNAL_CLOSE_SHORT; if((GetSignalState()==SIGNAL_OPEN_LONG) && (sell_condition)) new_state=SIGNAL_CLOSE_LONG; if(GetSignalState()!=new_state) { SetSignalState(new_state); } class="kw">return(true); };
「反趋势镜像与最差策略反转的实测边界」
横盘段里均线系统会集体失效。原文图3显示,1月8日至1月20日无趋势期间,基于MA_3到MA_93的10个趋势策略全部出现虚拟亏损,自适应系统因此直接停手。 补上10个CStrategyMAinv类实例——信号条件相同,但SIGNAL_OPEN_LONG/SHORT与SIGNAL_CLOSE_LONG/SHORT互换位置,相当于把原策略做镜像。图5的净值曲线表明,原趋势策略吃亏的那段,反趋势实例在开局就兜住了不该发生的亏损;图6进一步标出系统实际切到反趋势信号的时段。 别把正态当圣经:镜像法能跑通,不是因为反趋势本身安全,而是自适应容器里同时养了两套互搏的逻辑,市场自己挑当时有效的那套。外汇与贵金属杠杆高,这种互搏结构只降低空窗期磨损,不消除单边反转爆仓风险。 把最差策略信号反转未必有用。把CAdaptiveStrategy里Expert_OnTick()的ArrayMaximum换成ArrayMinimum,BestDirection乘-1,再注释掉亏损时禁开仓的限制,图7给出结果:最差策略多为MA_3(周期3),其曲线和自适应净值呈反向,但系统整体财务结果平淡。复制并反转最差信号,没有带来有效性改进。 堆几百条均线更是坑。下面代码把循环扩到100,周期从3起每步+10,全塞进m_all_strategies;闭合均线必然反复交叉,领先者不停换,系统频繁切换状态。策略测试器“结果”选项卡比对能看出特征变差,所以别拿带平仓参数的多周期均线群去搭自适应底座。
class=class="str">"cmt">//if((Performance[BestStrategyIndex]<class="num">0) && (RealPositionDirection()==class="num">0)) {class="kw">return;} for(class="type">int i=class="num">0;i<class="num">100;i++) { CStrategyMA *t_StrategyMA; t_StrategyMA=new CStrategyMA; if(t_StrategyMA==NULL) { class="kw">delete m_all_strategies; Print("创建 CStrategyMA 类型对象错误"); class="kw">return(-class="num">1); } class=class="str">"cmt">//设置每个策略的周期 class="type">int period=class="num">3+i*class="num">10; class=class="str">"cmt">// 初始化策略 t_StrategyMA.Initialization(period,true); class=class="str">"cmt">// 设置策略详细内容 t_StrategyMA.SetStrategyInfo(_Symbol,"[MA_"+IntegerToString(period)+"]",period,"Moving Averages "+IntegerToString(period)); class=class="str">"cmt">//把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyMA); }
◍ 自适应EA的仓位与绩效调校落点
m_all_strategies 容器能塞进上千个策略实例,参数组合随便加;但历史回测里我们统一用 0.1 手跑(类代码里也是写死的),想拿真金白银去拼自动交易擂台,资金管理系统才是分水岭。 CSampleStrategy 留了虚函数 MoneyManagement_CalculateLots,仓位管理可以读 m_deals_history[] 里的虚拟成交统计。比如上笔虚拟单盈利就加倍下注、亏损就缩量,改返回值即可,不用动框架。 StrategyPerformance() 算策略效率,原版只取 m_virtual_equity - m_initial_balance。想更狠可叠 entry_eff / exit_eff / trade_eff,或直接用最近三次 pos_Profit 均值——这些字段在虚拟交易时自动记。注意这函数只在 CSampleStrategy 改,所有自适应策略必须共用同一套评判。 止损靠 Set_Stops 设虚拟止损点,触发自动平虚拟仓;文末 2.2 的均线类里这行被注释了,要测就取消注释。系统跑偏时调 SetVirtualCumulativeProfit(0) 配合 CloseVirtualPosition() 周期归零,例如每 N 根 K 线清一次。 USDJPY H1 上 2010.1.4–8.20 的图显示:前十策略里若没一个盈利,自适应也救不了;起步阶段所有策略齐亏、EA 停手,等某个变领先才切过去。建议 CAdaptiveStrategy::Expert_OnTick 里加根柱计数器,前若干根禁止实盘,躲开选策略的混乱期。外汇贵金属杠杆高,自适应只是概率择优,不是免死金牌。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 此函数返回策略推荐的交易量 | class=class="str">"cmt">//| 当前交易量作为参数传入 | class=class="str">"cmt">//| 交易量可能依赖于: | class=class="str">"cmt">//| current m_virtual_balance 和 m_virtual_equity | class=class="str">"cmt">//| 当前交易的统计 (位于 m_deals_history 中) | class=class="str">"cmt">//| 或者您想要的任何其他事情 | class=class="str">"cmt">//| 如果一个策略不需要修改交易量 | class=class="str">"cmt">//| 您可以返回传入的交易量: class="kw">return(trade_volume); | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSampleStrategy::MoneyManagement_CalculateLots(class="type">class="kw">double trade_volume) { class=class="str">"cmt">//返回我们收到的值 class="kw">return(trade_volume); } class=class="str">"cmt">//+-----------------------------------------------------------------------+ class=class="str">"cmt">//| StrategyPerformance 函数- 策略效率的函数 | class=class="str">"cmt">//+-----------------------------------------------------------------------+ class="type">class="kw">double CSampleStrategy::StrategyPerformance() { class=class="str">"cmt">//返回策略效率 class=class="str">"cmt">//本案中是当前时刻净值和初始余额 class=class="str">"cmt">//之间的数量差异, class=class="str">"cmt">//也就是通过策略获得的资产 class="type">class="kw">double performance=(m_virtual_equity-m_initial_balance); class="kw">return(performance); } class="type">class="kw">double CSampleStrategy::StrategyPerformance() { class=class="str">"cmt">//如果有交易,把最后三次结果相乘 if(m_virtual_deals_total>class="num">0) { class="type">int avdeals=MathRound(MathMin(class="num">3,m_virtual_deals_total)); class="type">class="kw">double sumprofit=class="num">0; for(class="type">int j=class="num">0; j<avdeals; j++) { sumprofit+=m_deals_history[m_virtual_deals_total-class="num">1-j].profit; } class="type">class="kw">double performance=sumprofit/avdeals; } class="kw">return(performance); } class=class="str">"cmt">// 设置累计"虚拟利润"的值 class="type">void SetVirtualCumulativeProfit(class="type">class="kw">double cumulative_profit) { m_virtual_cumulative_profit=cumulative_perofit; }; class=class="str">"cmt">//关闭所有的虚拟持仓 class="type">void CloseVirtualPosition();
把虚拟策略跑成实盘之前
这套思路的核心是让成百上千个策略在容器内各自跑虚拟仓位,实盘只跟当时最赚钱的那条信号走。MQL5 标准库里的数据类与交易类把架构压得很薄,扩展新策略基本就是挂个类的事,不用动主循环。 调试版压缩包(12.78 KB)会在运行期吐出文本报告,记录每个策略虚拟净值曲线——你下载后在 MT5 策略测试器里跑一遍,就能看见哪些分支长期向下弯。外汇和贵金属杠杆高,虚拟胜出策略切换实盘时滑点和佣金可能吃掉大部分优势,概率上未必比单策略稳。 有交易者从 2015 年就用移动窗口替代每根 K 线重选,结果比原文更差;他提到要把交易结果拆成「正向 / 仅亏佣金 / 亏超佣金」三类,只有第三类才值得反转逻辑。你若想验证,先开个模拟账户把调试报告拉满,再谈真金白银。