自适应交易系统以及它们在 MetaTrader 5 客户端中的运用·进阶篇
批量挂策略对象的坑与周期排布
在 MT5 里用 CArrayObj 管理多策略时,常见写法是循环 new 出策略对象再 Add 进数组。下面这段把 MA 策略按 3+10*i 的步长铺了 100 个,周期从 3 一路走到 993,回测时内存里会同时驻留上百个 CStrategyMA 实例,EA 初始化耗时可能明显拉长。 对象创建失败必须立刻 delete 已挂的数组并 return(-1),否则 m_all_strategies 里会留半截脏数据,后续 At(i) 取指针容易踩空。注意原文两处循环都用了同样的判空回收逻辑,但第二处把 Print 换成了 printf,编译都能过,只是日志风格不统一。 Stoch 策略那组只跑了 5 个,K/D 周期用 2+5*i、Slowing 用 3+i,也就是 K/D 从 2 到 22、Slowing 从 3 到 7。外汇与贵金属杠杆高,这类多周期堆叠策略组合在历史回测中表现可能与实盘偏离,参数组合爆炸前先想清楚样本量。 别把正态当圣经:周期线性递增不代表覆盖市场状态,993 期 MA 在 M15 上已经跨到十几天,和短期 Stoch 同框跑,信号频率差可能差出两个数量级,组合权重不调就等于瞎加权。
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">//t_StrategyMA.Set_Stops(class="num">3500,class="num">1000); class=class="str">"cmt">//把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyMA); } for(class="type">int i=class="num">0; i<m_all_strategies.Total(); i++) { CSampleStrategy *t_SampleStrategy; t_SampleStrategy=m_all_strategies.At(i); Print(i," 策略名称:",t_SampleStrategy.StrategyName(), " 策略编号:",t_SampleStrategy.StrategyID(), " 虚拟交易:",t_SampleStrategy.IsVirtualTradeAllowed()); } class=class="str">"cmt">//--- class="kw">return(class="num">0); } 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); } for(class="type">int i=class="num">0; i<class="num">5; i++) { CStrategyStoch *t_StrategyStoch; t_StrategyStoch=new CStrategyStoch; if(t_StrategyStoch==NULL) { class="kw">delete m_all_strategies; printf("创建 CStrategyStoch 类型对象错误"); class="kw">return(-class="num">1); } class=class="str">"cmt">//设置每个策略的周期 class="type">int Kperiod=class="num">2+i*class="num">5; class="type">int Dperiod=class="num">2+i*class="num">5; class="type">int Slowing=class="num">3+i; class=class="str">"cmt">// 初始化策略 t_StrategyStoch.Initialization(Kperiod,Dperiod,Slowing,true); class=class="str">"cmt">// 设置策略详细内容
「自适应EA如何在新柱挑出最优策略」
自适应EA的核心调度逻辑放在 OnTick 里:每跳先让容器内所有策略对象刷新各自持仓数据,再用 IsNewBar() 拦截,只在出新柱时才跑交易条件检查。这样能把非必要计算压到最低,MT5 实盘里 CPU 占用倾向更平稳。 挑最优策略靠的是效率数组。代码里先 ArrayResize 出与策略总数等长的 performance[],逐个调 StrategyPerformance() 填值,再用 ArrayMaximum 拿到最大效率对应的索引。若你挂了 5 套随机参数 Stoch 策略,日志会打印「策略总数=5 最佳索引=3」这类数据,直接可抄去观察哪套参数在当前品种更扛打。 去初始化时别漏了虚拟成交落地。DeInit 里对每个策略先 SaveVirtualDeals 写 txt,再调 Deinitialization,最后 delete 数组对象——外汇与贵金属杠杆高,回测虚拟权益和实盘滑点可能偏差明显,拿这套做仓位切换前务必先跑几天模拟盘验证。
class="type">class="kw">string s=IntegerToString(Kperiod)+"/"+IntegerToString(Dperiod)+"/"+IntegerToString(Slowing); t_StrategyStoch.SetStrategyInfo(_Symbol,"[Stoch_"+s+"]",class="num">100+i," Stochastic "+s); class=class="str">"cmt">//把策略对象添加到 m_all_strategies 数组中 m_all_strategies.Add(t_StrategyStoch); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自适应EA的去初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CAdaptiveStrategy::Expert_OnDeInit(const class="type">int reason) { class=class="str">"cmt">// 去初始化所有的策略 for(class="type">int i=class="num">0; i<m_all_strategies.Total(); i++) { CSampleStrategy *t_Strategy; t_Strategy=m_all_strategies.At(i); t_Strategy.SaveVirtualDeals(t_Strategy.StrategyName()+"_deals.txt"); t_Strategy.Deinitialization(); } class=class="str">"cmt">//删除策略对象数组 class="kw">delete m_all_strategies; class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自适应策略的订单处理函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CAdaptiveStrategy::Expert_OnTick() { CSampleStrategy *t_Strategy; class=class="str">"cmt">// 重新计算策略的仓位信息 for(class="type">int i=class="num">0; i<m_all_strategies.Total(); i++) { t_Strategy=m_all_strategies.At(i); t_Strategy.UpdatePositionData(); } class=class="str">"cmt">// EA应该只有在新柱出现的时候才检查进行交易操作的条件 if(IsNewBar()==false) { class="kw">return; } class=class="str">"cmt">// 检查所有策略的交易条件 for(class="type">int i=class="num">0; i<m_all_strategies.Total(); i++) { t_Strategy=m_all_strategies.At(i); t_Strategy.CheckTradeConditions(); } class=class="str">"cmt">//寻找最佳仓位 class=class="str">"cmt">//准备 performance[] 数组 class="type">class="kw">double performance[]; ArrayResize(performance,m_all_strategies.Total()); class=class="str">"cmt">//请求每个策略的效率值, class=class="str">"cmt">//每个策略在 Strategyperformance() 函数中返回它 for(class="type">int i=class="num">0; i<m_all_strategies.Total(); i++) { t_Strategy=m_all_strategies.At(i); performance[i]=t_Strategy.StrategyPerformance(); } class=class="str">"cmt">//寻找策略 (或者说寻找它在 m_all_strategies 容器中的索引) class=class="str">"cmt">//根据 Strategyperformance() 返回的最大值 class="type">int best_strategy_index=ArrayMaximum(performance,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">//这个策略是 - t_Strategy t_Strategy=m_all_strategies.At(best_strategy_index); class=class="str">"cmt">//请求仓位方向 class="type">int best_direction=t_Strategy.PositionDirection(); class="type">class="kw">string s=s+" "+t_Strategy.StrategyName()+" "+DoubleToString(t_Strategy.GetVirtualEquity())+" "+IntegerToString(best_direction); Print(TimeCurrent()," 策略总数=",m_all_strategies.Total(), " 最佳索引=",best_strategy_index, " 最佳策略="," ",t_Strategy.StrategyName(), " 最佳成绩=",performance[best_strategy_index]," ="
◍ 用虚拟仓位隔离策略与实盘风险
这段逻辑先把最佳策略的方向和成绩打印出来,供调试时肉眼核对。关键一句:若 best_strategy_index 对应的绩效为负,且 RealPositionDirection() 返回 POSITION_NEUTRAL(即 0,无实仓),函数直接 return,不下达任何订单——这是用代码强制空仓的硬开关。 当 best_direction 与当前实盘方向不一致时,才调用 ProceedSignalReal(best_direction, t_Strategy.GetCurrentLotSize()) 去触发真实信号。也就是说,策略层算出的最佳方向不会每次都覆盖实盘,只在方向冲突时动手。 下方头文件里用宏把仓位状态固化成数字:POSITION_NEUTRAL=0、POSITION_LONG=1、POSITION_SHORT=-1;信号侧则用 SIGNAL_OPEN_LONG=10、SIGNAL_OPEN_SHORT=-10 等区分开仓与平仓意图。虚拟仓位结构 virtual_position 单独存 symbol、direction、volume、profit、stop_loss、take_profit 及开平时间价格,实盘与回测可共用同一套字段。 外汇与贵金属杠杆高,虚拟仓位即便算错也只丢点数不丢本金;但把虚拟信号接进实盘前,务必在 MT5 策略测试器跑通 direction 与 performance 的符号校验,避免负收益策略误发单。
" 最佳方向=",best_direction,
" 成绩=",t_Strategy.StrategyPerformance());
class=class="str">"cmt">//如果最佳策略结果为负并且没有开启的仓位,最好不要进行交易
if((performance[best_strategy_index]<class="num">0) && (RealPositionDirection()==POSITION_NEUTRAL)) {class="kw">return;}
if(best_direction!=RealPositionDirection())
{
ProceedSignalReal(best_direction,t_Strategy.GetCurrentLotSize());
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CSampleStrategy.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 <Object.mqh>
class="macro">#define POSITION_NEUTRAL class="num">0 class=class="str">"cmt">// 没有仓位
class="macro">#define POSITION_LONG class="num">1 class=class="str">"cmt">// 买入仓位
class="macro">#define POSITION_SHORT -class="num">1 class=class="str">"cmt">// 卖出仓位
class="macro">#define SIGNAL_OPEN_LONG class="num">10 class=class="str">"cmt">// 开启买入仓位的信号
class="macro">#define SIGNAL_OPEN_SHORT -class="num">10 class=class="str">"cmt">// 开启卖出仓位的信号
class="macro">#define SIGNAL_CLOSE_LONG -class="num">1 class=class="str">"cmt">// 关闭买入仓位的信号
class="macro">#define SIGNAL_CLOSE_SHORT class="num">1 class=class="str">"cmt">// 关闭卖出仓位的信号
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 保存虚拟仓位参数的结构 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct virtual_position
{
class="type">class="kw">string symbol; class=class="str">"cmt">// 交易品种
class="type">int direction; class=class="str">"cmt">// 虚拟仓位方向 (class="num">0-没有仓位,+class="num">1 买入,-class="num">1 卖出)
class="type">class="kw">double volume; class=class="str">"cmt">// 仓位交易量
class="type">class="kw">double profit; class=class="str">"cmt">// 当前虚拟仓位的利润点数
class="type">class="kw">double stop_loss; class=class="str">"cmt">// 虚拟仓位的止损价位
class="type">class="kw">double take_profit; class=class="str">"cmt">// 虚拟仓位的获利价位
class="type">class="kw">datetime time_open; class=class="str">"cmt">// 虚拟仓位的开启时间
class="type">class="kw">datetime time_close; class=class="str">"cmt">// 虚拟仓位的关闭时间
class="type">class="kw">double price_open; class=class="str">"cmt">// 虚拟仓位的建仓价位策略类的虚拟仓位结构怎么摆
在 MT5 里做无风险预演,核心是把真实下单逻辑换成一套虚拟持仓结构。下面这段类声明把每笔模拟交易的元数据全摊开了:平仓价、生命周期高低点、进出场效率都单独成字段,方便后面回测时直接算滑点损耗。 初始余额在构造函数里默认给 10000,止损止盈以点数(m_sl_points / m_tp_points)形式存,不绑定具体点值,换品种时不用改结构。虚拟余额和净值是分开维护的,这样能看出浮盈回撤和真实权益的偏离。 开 MT5 新建 EA 时,直接照这个 protected 成员清单抄一遍,先把 m_virtual_trade_allowed 置 true,就能在不碰实盘的情况下跑通信号 state 机。外汇和贵金属杠杆高,虚拟测试过的参数上线后仍可能因点差跳变失效,需小仓验证。
class="type">class="kw">double price_close; class=class="str">"cmt">// 虚拟仓位的平仓价位 class="type">class="kw">double price_highest; class=class="str">"cmt">// 虚拟仓位生命周期中的最高价位 class="type">class="kw">double price_lowest; class=class="str">"cmt">// 虚拟仓位生命周期中的最低价位 class="type">class="kw">double entry_eff; class=class="str">"cmt">// 进入效率 class="type">class="kw">double exit_eff; class=class="str">"cmt">// 退出效率 class="type">class="kw">double trade_eff; class=class="str">"cmt">// 交易效率 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CSampleStrategy 类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSampleStrategy: class="kw">public CObject { class="kw">protected: class="type">int m_strategy_id; class=class="str">"cmt">// 策略编号 class="type">class="kw">string m_strategy_symbol; class=class="str">"cmt">// 交易品种 class="type">class="kw">string m_strategy_name; class=class="str">"cmt">// 策略名称 class="type">class="kw">string m_strategy_comment; class=class="str">"cmt">// 注释 class="type">MqlTick m_price_last; class=class="str">"cmt">// 最后价位 class="type">MqlRates m_rates[]; class=class="str">"cmt">// 当前报价的数组 class="type">bool m_virtual_trade_allowed;class=class="str">"cmt">// 允许虚拟交易的标志 class="type">int m_current_signal_state; class=class="str">"cmt">// 当前策略的状态 class="type">class="kw">double m_current_trade_volume; class=class="str">"cmt">// 交易手数 class="type">class="kw">double m_initial_balance; class=class="str">"cmt">// 初始余额 (在构造函数中设定, 默认值为 class="num">10000) class="type">int m_sl_points; class=class="str">"cmt">// 止损点数 class="type">int m_tp_points; class=class="str">"cmt">// 获利点数 virtual_position m_position; class=class="str">"cmt">// 虚拟仓位 virtual_position m_deals_history[]; class=class="str">"cmt">// 交易数组 class="type">int m_virtual_deals_total; class=class="str">"cmt">// 交易总数 class="type">class="kw">double m_virtual_balance; class=class="str">"cmt">// 虚拟余额 class="type">class="kw">double m_virtual_equity; class=class="str">"cmt">// 虚拟净值 class="type">class="kw">double m_virtual_cumulative_profit; class=class="str">"cmt">// 累计虚拟利润
「虚拟持仓与风控接口的底层声明」
在 MT5 里做策略回测或影子账户,最怕真实下单把本金打穿。把仓位逻辑抽象成 virtual_position 结构,用一套独立函数管理虚拟开平,就能在不开真实单的前提下观察权益曲线。 double m_virtual_profit; // 当前开启的虚拟仓位的利润 bool CheckVirtual_Stops(virtual_position &position); // 检查并且如有必要根据止损价位关闭虚拟仓位 void RecalcPositionProperties(virtual_position &position); // 重新计算仓位和余额 void Position_RefreshInfo(virtual_position &position); // 重新根据现有价位计算虚拟建仓信息 void Position_OpenShort(virtual_position &position); // 开启虚拟卖出仓位 void Position_CloseShort(virtual_position &position); // 关闭虚拟卖出仓位 void Position_OpenLong(virtual_position &position); // 开启虚拟买入仓位 void Position_CloseLong(virtual_position &position); // 关闭虚拟买入仓位 void Position_CloseOpenedPosition(virtual_position &position); // 关闭开启的虚拟仓位 void AddDealToHistory(virtual_position &position); // 把关闭的仓位添加到 m_deals_history[] 数组中 (交易历史) virtual double MoneyManagement_CalculateLots(double trade_volume); // 计算并且返回将用于交易的推荐的交易量 上面这组声明里,CheckVirtual_Stops 是风控闸门,Position_OpenShort / OpenLong 决定方向,AddDealToHistory 把每笔闭环写入本地历史数组。外汇与贵金属波动剧烈、杠杆高危,实盘前先用这套虚拟接口跑至少 3 个月 tick 数据,才可能看出止损点数是不是设得太紧。 对外暴露的 public 接口更轻量:GetVirtualBalance 返回虚拟余额,Set_Stops(int tp,int sl) 直接写止损止盈点数,SetLots 设定手数。StrategyID 和 StrategyName 用来在多策略并行时区分实例,避免日志串号。
class="type">class="kw">double m_virtual_profit; class=class="str">"cmt">// 当前开启的虚拟仓位的利润 class=class="str">"cmt">//检查并且如有必要根据止损价位关闭虚拟仓位 class="type">bool CheckVirtual_Stops(virtual_position &position); class=class="str">"cmt">// 重新计算仓位和余额 class="type">void RecalcPositionProperties(virtual_position &position); class=class="str">"cmt">// 重新根据现有价位计算虚拟建仓信息 class="type">void Position_RefreshInfo(virtual_position &position); class=class="str">"cmt">// 开启虚拟卖出仓位 class="type">void Position_OpenShort(virtual_position &position); class=class="str">"cmt">// 关闭虚拟卖出仓位 class="type">void Position_CloseShort(virtual_position &position); class=class="str">"cmt">// 开启虚拟买入仓位 class="type">void Position_OpenLong(virtual_position &position); class=class="str">"cmt">// 关闭虚拟买入仓位 class="type">void Position_CloseLong(virtual_position &position); class=class="str">"cmt">// 关闭开启的虚拟仓位 class="type">void Position_CloseOpenedPosition(virtual_position &position); class=class="str">"cmt">// 把关闭的仓位添加到 m_deals_history[] 数组中 (交易历史) class="type">void AddDealToHistory(virtual_position &position); class=class="str">"cmt">//计算并且返回将用于交易的推荐的交易量 class="kw">virtual class="type">class="kw">double MoneyManagement_CalculateLots(class="type">class="kw">double trade_volume); class="kw">public: class=class="str">"cmt">// 构造函数 class="type">void CSampleStrategy(); class=class="str">"cmt">// 析构函数 class="type">void ~CSampleStrategy(); class=class="str">"cmt">//返回当前虚拟余额 class="type">class="kw">double GetVirtualBalance() { class="kw">return(m_virtual_balance); } class=class="str">"cmt">//返回当前虚拟净值 class="type">class="kw">double GetVirtualEquity() { class="kw">return(m_virtual_equity); } class=class="str">"cmt">//返回当前虚拟持仓利润 class="type">class="kw">double GetVirtualProfit() { class="kw">return(m_virtual_profit); } class=class="str">"cmt">//设置止损获利点数 class="type">void Set_Stops(class="type">int tp,class="type">int sl) {m_tp_points=tp; m_sl_points=sl;}; class=class="str">"cmt">//设置当前交易手数 class="type">void SetLots(class="type">class="kw">double trade_volume) {m_current_trade_volume=trade_volume;}; class=class="str">"cmt">//返回当前交易手数 class="type">class="kw">double GetCurrentLots() { class="kw">return(m_current_trade_volume); } class=class="str">"cmt">// 返回策略名称 class="type">class="kw">string StrategyName() { class="kw">return(m_strategy_name); } class=class="str">"cmt">// 返回策略编号 class="type">int StrategyID() { class="kw">return(m_strategy_id); } class=class="str">"cmt">// 返回策略注释 class="type">class="kw">string StrategyComment() { class="kw">return(m_strategy_comment); } class=class="str">"cmt">// 返回策略详细信息 (策略的交易品种,名称,编号) class="type">void SetStrategyInfo(class="type">class="kw">string symbol,class="type">class="kw">string name,class="type">int id,class="type">class="kw">string comment); class=class="str">"cmt">// 设置虚拟交易标志 (是否允许虚拟交易)
◍ 虚拟交易类的接口与虚函数落点
上面这段是策略基类的尾部声明,把虚拟交易相关的读写接口和可供派生的虚函数一次性摊开。实盘跑 EA 前,先在 MT5 里把这些成员函数对照看一遍,能少踩一半继承坑。 SetVirtualTradeFlag 用 bool 开关控制是否允许虚拟成交,IsVirtualTradeAllowed 直接返回该标志位;这两个成对出现,是用来在回测和实盘之间切阀的。GetSignalState 与 SetSignalState 负责读取和写入策略状态,后者会顺带按需要改虚拟仓位。 ProceedSignalState 接收 virtual_position 引用去改虚拟仓位,SetVirtualCumulativeProfit 写累计虚拟利润,StrategyPerformance 回吐策略效率值——这几个没有内联实现,说明具体算法留给子类。UpdatePositionData、CloseVirtualPosition、PositionDirection、SaveVirtualDeals 则处理仓位数据维护、平仓、方向读取和成交落盘。 Initialization、CheckTradeConditions、Deinitialization 被标成 virtual,默认分别返回 0、false、0。你要接自己的逻辑,就 override 这三个,其余接口保持原签名,MT5 编译器才不会报派生不匹配。
class="type">void SetVirtualTradeFlag(class="type">bool pFlag) { m_virtual_trade_allowed=pFlag; }; class=class="str">"cmt">// 返回是否允许虚拟交易的标志 class="type">bool IsVirtualTradeAllowed() { class="kw">return(m_virtual_trade_allowed); }; class=class="str">"cmt">// 返回策略的当前状态 class="type">int GetSignalState(); class=class="str">"cmt">// 设置策略的当前状态 (如有必要修改虚拟仓位) class="type">void SetSignalState(class="type">int state); class=class="str">"cmt">// 根据状态修改虚拟仓位 class="type">void ProceedSignalState(virtual_position &position); class=class="str">"cmt">// 设置累计虚拟利润值 class="type">void SetVirtualCumulativeProfit(class="type">class="kw">double cumulative_profit) { m_virtual_cumulative_profit=cumulative_profit; }; class=class="str">"cmt">//返回策略效率 class="type">class="kw">double StrategyPerformance(); class=class="str">"cmt">//更新仓位数据 class="type">void UpdatePositionData(); class=class="str">"cmt">//关闭开启的仓位 class="type">void CloseVirtualPosition(); class=class="str">"cmt">//返回当前虚拟仓位的方向 class="type">int PositionDirection(); class=class="str">"cmt">//初始化虚拟函数 class="kw">virtual class="type">int Initialization() {class="kw">return(class="num">0);}; class=class="str">"cmt">//检查交易条件的虚拟函数 class="kw">virtual class="type">bool CheckTradeConditions() {class="kw">return(false);}; class=class="str">"cmt">//去初始化的虚拟函数 class="kw">virtual class="type">int Deinitialization() {class="kw">return(class="num">0);}; class=class="str">"cmt">//把虚拟交易保存到文件中 class="type">void SaveVirtualDeals(class="type">class="kw">string file_name); };