利用指标实时优化智能交易系统(基础篇)
📘

利用指标实时优化智能交易系统(基础篇)

第 1/3 篇

「用实时指标给 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 验证。

MQL5 / C++
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 根以上的样本看信号分布。

MQL5 / C++
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)
     {

常见问题

把优化周期交给指标驱动:指标检测到波动 regime 切换就重算输入参数并下发给 EA,避免人工盯盘重启。
用三个振荡器(如 RSI、CCI、Stochastic)同步处于中性区才允许进场,任一出界即判为趋势市,屏蔽横盘信号。
小布可接管指标里的虚拟订单回测与句柄监控,波动异常时推送参数变更建议,你只管确认执行。
把虚拟订单塞进指标里跑实时虚拟盘,用历史 tick 回放检验胜率和回撤,再决定是否实盘加载。
初始化阶段把句柄和参数一次性铺开,避免每 tick 重复申请资源,降低卡顿与重绘引起的信号漂移。