利用指标实时优化智能交易系统(基础篇)
「用实时指标给 EA 做在盘优化」
很多 MT5 使用者只把策略测试器当成离线回放工具,其实它能在历史数据上调用自定义指标,让 EA 在每根 K 线重算时拿到指标新值,从而模拟「边跑边调参」的闭环。2018 年那篇社区文被阅览 5719 次、收藏 3 次,说明这套思路在实战圈一直有人啃。 核心落点是:测试器里挂的指标不是给肉眼看的,而是喂给 EA 的 OnTick / OnCalculate 逻辑。你只要把指标句柄在初始化时建好,回测中就能像实盘一样读取缓冲区的当下值,不必把指标算法定死在 EA 内部。 外汇与贵金属品种波动跳空频繁,这类在盘优化逻辑回测表现好,实盘也可能因滑点和点差走形,属于高风险验证项,结论仅具概率倾向。开 MT5 建个空 EA,用 iCustom 接一个你常用的均线指标,先跑 2023 年 EURUSD H1,看缓冲区取值是否随 K 线推进更新,就能确认管道通不通。
◍ 把优化周期交给指标来驱动
在 MT5 图表挂上 EA 后,参数衰败是常态。历史回测挑出的那组手数、周期、止损,跑三个月可能就因为波动率结构移位而失效,这时就得重跑策略测试器。 人工盯绩效再手动重优化,中断多、容易拖延。已知一种思路是用自定义配置文件拉起终端做程序化控制,但那仍脱离图表本身。 本篇换一条路:把测试器该干的事直接挂到指标上,让指标在图表内触发重优化。外汇与贵金属杠杆高,参数漂移带来的回撤可能放大,自动化前务必在模拟盘验证。
用指标实例替 EA 跑实时虚拟盘
指标不是策略测试器,但能把 EA 的开平仓逻辑搬进指标里,用虚拟成交实时统计盈亏。优化时不再排队跑回测,而是同时挂起多个指标实例,每个分配不同参数,EA 在决策时扫描这些实例挑出当前表现最优的一组参数。 这种思路有两个硬优势:一是基于经纪商实时逐笔报价,接近真实环境;二是指标只重算当前 tick,不像策略测试器每次从历史开头重跑,所以理论上能每根柱线都做一次优化。代价也很直白——你得等足够 tick 累积统计,且 MT5 里同一品种的所有指标共用一个线程,实例挂太多终端会卡。 为压住缺陷,约定历史部分用 M1 的 OHLC 估算:平仓判定先查止损,再按最高/最低价查止盈;订单只在蜡烛开盘时触发。参数筛选上要做剪枝,比如 MACD 里若快线周期 ≥ 慢线周期,直接丢弃这组矛盾组合,并设最小差值过滤假信号,少跑无意义的实例。 实盘外汇和贵金属波动剧烈、杠杆高风险大,这类实时虚拟优化只是辅助,参数优异不代表未来盈利。
「三振荡器横盘过滤的进出场规则」
实测这套方法时,用的是 WPR、RSI、ADX 三个标准指标拼的极简策略。WPR 上穿 -80 超卖线给买讯,但 RSI 必须没进超买区(不高于 70),因为俩都是振荡器,只在横盘里靠谱;ADX 用来卡横盘,值不能过 40,过了就当趋势市不接。 卖讯是镜像:WPR 下破 -20 超买线,RSI 得在 30 超卖线上方,ADX 同样压在 40 以下。信号出现后不在本根蜡烛跑,等下一根新蜡烛才进场,避开毛刺。 离场只靠固定止损或止盈,不拖泥带水。亏损管理上,同一时刻场内只留最多一笔单,不摊平不加码,外汇与贵金属杠杆高,这种单线约束能压住回撤概率。
◍ 把虚拟订单塞进指标里跑回测
想用 MT5 测试器验证策略,第一步是把虚拟成交逻辑搬进自定义指标。原本的虚拟订单类只在实时 tick 上用卖价买价判平,碰到历史数据就失效;改法很直接——给 Tick 方法加 price 和 spread 两个参数,让它脱离实时环境也能回放每根 K 线的 OHLC 来触发止损止盈。 指标端要开 9 个缓冲区跟 EA 交换数据:盈利概率、盈利因子、止损止盈价位、成交总数与盈利数、当前柱的同类计数,以及发给 EA 的下单信号。OnInit 里初始化缓冲区并绑动态数组,OnCalculate 分两条线——新柱时按 M1 历史逐根查信号并挂虚拟单,未成形新柱时每次报价只扫持仓止损。 CheckDeals 函数负责遍历成交数组,用每根分钟 candle 的开高低收去调 Tick。注意买单先判 SL 后判 TP,卖单则先给 price 加 spread*d_Point 再判,顺序反了会低估回撤但能少算未来亏损,属刻意保守。 下面这段是改造后的 Tick 核心,逐行拆一下: bool CDeal::Tick(double price, int spread) —— 返回 bool,表示本单是否已平。 if(d_ClosePrice>0) return true; —— 已平仓就直接返回,不重复计算。 switch(e_Direct) —— 按多空分支。 CASE BUY:若 SL>0 且 SL>=price,以 price 平仓,盈利点数=(平-开)/Point;否则若 TP<=price 才平。 CASE SELL:price+=spread*d_Point 模拟卖价叠加点差;SL<=price 平,盈利=(开-平)/Point;TP>=price 平。 return IsClosed(); —— 交给类内方法确认状态。 开 MT5 把这段贴进虚拟订单类,历史回测里止损触发点会和实时有数点偏差,外汇和贵金属杠杆高,点差放大时回测盈利概率可能倾向偏低,动手前先在自己的品种上跑一遍 M1 验证。
class="type">bool CDeal::Tick(class="type">class="kw">double price, class="type">int spread) { if(d_ClosePrice>class="num">0) class="kw">return true; class=class="str">"cmt">//--- class="kw">switch(e_Direct) { case POSITION_TYPE_BUY: if(d_SL_Price>class="num">0 && d_SL_Price>=price) { d_ClosePrice=price; i_Profit=(class="type">int)((d_ClosePrice-d_OpenPrice)/d_Point); } else { if(d_TP_Price>class="num">0 && d_TP_Price<=price) { d_ClosePrice=price; i_Profit=(class="type">int)((d_ClosePrice-d_OpenPrice)/d_Point); } } break; case POSITION_TYPE_SELL: price+=spread*d_Point; if(d_SL_Price>class="num">0 && d_SL_Price<=price) { d_ClosePrice=price; i_Profit=(class="type">int)((d_OpenPrice-d_ClosePrice)/d_Point); } else { if(d_TP_Price>class="num">0 && d_TP_Price>=price) { d_ClosePrice=price; i_Profit=(class="type">int)((d_OpenPrice-d_ClosePrice)/d_Point); } } break; } class="kw">return IsClosed(); }
把参数和句柄一次性铺开
这套多指标过滤的 EA 骨架,先把可调参数全摊在 input 区,方便在 MT5 里直接改数验证。历史深度取 500 根柱线,止损 200 点、止盈 600 点,盈亏比倾向 3:1;RSI 周期 28、超买超卖区 30,WPR 周期 7、区域同样 30,ADX 周期 11、横盘阈值 40,Direction 设 -1 表示多空都跑。 下方声明的 Buffer_ 数组是用来给可视化或统计模块喂数据的:概率、盈利因子、TP/SL、历史盈利笔数、总笔数,以及当前窗口的计数和交易信号。Deals 用 CArrayObj 承接,last_deal 记最近一单时间,三个指标句柄和 rsi[]/adx[]/wpr[] 缓存数组在 OnInit 里初始化。 OnInit 第一步就是拿 RSI 句柄:iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod,PRICE_CLOSE),若返回 INVALID_HANDLE 就直接中断。外汇和贵金属波动大、杠杆高,参数没跑过历史回测前别真金白银挂实盘,先在策略测试器里用 500 根以上的样本看信号分布。
class="kw">input class="type">int HistoryDepth = class="num">500; class=class="str">"cmt">//历史数据深度(柱线) class="kw">input class="type">int StopLoss = class="num">200; class=class="str">"cmt">//止损(点数) class="kw">input class="type">int TakeProfit = class="num">600; class=class="str">"cmt">//止盈(点数) class=class="str">"cmt">//--- RSI 指标参数 class="kw">input class="type">int RSIPeriod = class="num">28; class=class="str">"cmt">//RSI 周期 class="kw">input class="type">class="kw">double RSITradeZone = class="num">30; class=class="str">"cmt">//超买/超卖区域大小 class=class="str">"cmt">//--- WPR 指标参数 class="kw">input class="type">int WPRPeriod = class="num">7; class=class="str">"cmt">// WPR 周期 class="kw">input class="type">class="kw">double WPRTradeZone = class="num">30; class=class="str">"cmt">//超买/超卖区域大小 class=class="str">"cmt">//--- ADX 指标参数 class="kw">input class="type">int ADXPeriod = class="num">11; class=class="str">"cmt">//ADX 周期 class="kw">input class="type">int ADXLevel = class="num">40; class=class="str">"cmt">//ADX 横盘等级 class=class="str">"cmt">//--- class="kw">input class="type">int Direction = -class="num">1; class=class="str">"cmt">//交易方向 "-class="num">1"-所有, "class="num">0"-买入, "class="num">1"-卖出 class=class="str">"cmt">//--- class="kw">input class="type">int AveragePeriod = class="num">10; class=class="str">"cmt">//均化周期 class="type">class="kw">double Buffer_Probability[]; class="type">class="kw">double Buffer_ProfitFactor[]; class="type">class="kw">double Buffer_TakeProfit[]; class="type">class="kw">double Buffer_StopLoss[]; class="type">class="kw">double Buffer_ProfitCount[]; class="type">class="kw">double Buffer_DealsCount[]; class="type">class="kw">double Buffer_ProfitCountCurrent[]; class="type">class="kw">double Buffer_DealsCountCurrent[]; class="type">class="kw">double Buffer_TradeSignal[]; CArrayObj Deals; class="type">class="kw">datetime last_deal; class="type">int wpr_handle,rsi_handle,adx_handle; class="type">class="kw">double rsi[],adx[],wpr[]; class="type">int OnInit() { class=class="str">"cmt">//--- 获取 RSI 指标句柄 rsi_handle=iRSI(Symbol(),PERIOD_CURRENT,RSIPeriod,PRICE_CLOSE); if(rsi_handle==INVALID_HANDLE) {