利用解析入场点为指标的技术创建新的交易策略·进阶篇
🧪

利用解析入场点为指标的技术创建新的交易策略·进阶篇

(2/3)· 100% 交易者都想稳定盈利但仅 5% 做到,把入场信号翻成指标来反向验证或许是破局切口

偏理论 第 2/3 篇
很多人直接拿现成指标信号开仓,却从没问过「这个信号在历史同条件下到底赚不赚」。把指标当真理用,账户迟早被样本外的波动洗掉。本篇接上篇,继续深挖如何把入场点解析成指标来做可控的离线推演。

◍ 多周期指标的参数入口怎么摆

把鳄鱼、CCI、Chaikin、Force 这几类指标直接做成 input 参数块,开 MT5 挂 EA 时就能在属性框里实时改周期,不用回编译器重编。 CCI 这里给了三档周期:14、28、56,价格源用 PRICE_TYPICAL(典型价)。Chaikin 则是快/慢线各三套:3/14、6/28、12/56,均线方法锁 MODE_EMA,成交量取 VOLUME_TICK。 下面这段就是参数声明原文,逐行看:input ENUM_APPLIED_PRICE 定义鳄鱼用的价格(中位数价);CCI_Period1~3 是三周期;CCI_Price 指定典型价。Chaikin 的 Ch_Fast/Slow_Period 对应三组合;Ch_Method 与 Ch_Volume 固定算法与量源。Force 只露了 Period1=14、Period2=28 两档,后续小节会接上取值逻辑。 外汇与贵金属杠杆高、滑点跳空频繁,参数只是过滤器,信号失效时止损要先于调参。

MQL5 / C++
input ENUM_APPLIED_PRICE Alligator_Price    = PRICE_MEDIAN      ;
input class="type">class="kw">string                s5                = "CCI"             ; class=class="str">"cmt">//---
input class="type">uint                  CCI_Period1       = class="num">14                ;
input class="type">uint                  CCI_Period2       = class="num">28                ;
input class="type">uint                  CCI_Period3       = class="num">56                ;
input ENUM_APPLIED_PRICE    CCI_Price         = PRICE_TYPICAL     ;
input class="type">class="kw">string                s6                = "Chaikin"         ; class=class="str">"cmt">//---
input class="type">uint                  Ch_Fast_Period1   = class="num">3                 ;
input class="type">uint                  Ch_Slow_Period1   = class="num">14                ;
input class="type">uint                  Ch_Fast_Period2   = class="num">6                 ;
input class="type">uint                  Ch_Slow_Period2   = class="num">28                ;
input class="type">uint                  Ch_Fast_Period3   = class="num">12                ;
input class="type">uint                  Ch_Slow_Period3   = class="num">56                ;
input ENUM_MA_METHOD        Ch_Method         = MODE_EMA          ;
input ENUM_APPLIED_VOLUME   Ch_Volume         = VOLUME_TICK       ;
input class="type">class="kw">string                s7                = "Force Index"     ; class=class="str">"cmt">//---
input class="type">uint                  Force_Period1     = class="num">14                ;
input class="type">uint                  Force_Period2     = class="num">28                ;

「双指标参数与初始化骨架」

这段声明把力道指数(Force Index)和三档 MACD 的输入参数直接摊开。Force_Period3 取 56,均线算法用 SMA,成交量源锁定 VOLUME_TICK;MACD 则铺了三组快慢线——最慢一档慢线周期到 104、信号线 36,明显是想在日线级别以外叠一层更钝的动量滤波。 OnInit 里先做两个状态归零:last_bar 和 last_closed_deal 都置 0,避免挂上 EA 时沿用上一次运行的残留计数。随后 new 出 CArrayObj 与 CDealsToIndicators 两个对象,并用 CheckPointer 判空,任一指针无效就回 INIT_FAILED,这是 MT5 里防止后续裸指针调用的标准写法。 atr 句柄用 iATR(_Symbol, TimeFrame1, ATR_Period) 取,若返回 INVALID_HANDLE 同样走失败分支。开 MT5 把这段贴进 EA 模板,改 TimeFrame1 和 ATR_Period 就能验证不同周期下句柄获取是否稳定,外汇与贵金属品种波动大,参数误配可能让 ATR 滤波失效,实盘前务必在策略测试器跑一轮。

MQL5 / C++
input class="type">uint              Force_Period3      =  class="num">56               ;
input ENUM_MA_METHOD    Force_Method       =  MODE_SMA          ;
input ENUM_APPLIED_VOLUME Force_Volume      =  VOLUME_TICK       ;
input class="type">class="kw">string            s8                 =  "MACD"            ;   class=class="str">"cmt">//---
input class="type">uint              MACD_Fast1         =  class="num">12               ;
input class="type">uint              MACD_Slow1         =  class="num">26               ;
input class="type">uint              MACD_Signal1       =  class="num">9                ;
input class="type">uint              MACD_Fast2         =  class="num">24               ;
input class="type">uint              MACD_Slow2         =  class="num">52               ;
input class="type">uint              MACD_Signal2       =  class="num">18               ;
input class="type">uint              MACD_Fast3         =  class="num">48               ;
input class="type">uint              MACD_Slow3         =  class="num">104              ;
input class="type">uint              MACD_Signal3       =  class="num">36               ;
input ENUM_APPLIED_PRICE MACD_Price         =  PRICE_CLOSE      ;
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   last_bar=class="num">0;
   last_closed_deal=class="num">0;
class=class="str">"cmt">//---
   Deals =  new CArrayObj();
   if(CheckPointer(Deals)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
   IndicatorsStatic  =  new CDealsToIndicators();
   if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
      class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
   atr=iATR(_Symbol,TimeFrame1,ATR_Period);
   if(atr==INVALID_HANDLE)

多周期指标批量挂载的实现细节

这段逻辑出现在 EA 初始化尾部,先尝试返回失败状态,随后对三个时间周期分别调用 AddIndicators,最后返回 INIT_SUCCEEDED。也就是说,指标容器若在任一周期挂载出错,初始化就会中断,MT5 终端会在日志抛出对应 handle 错误。 AddIndicators 函数内部先用 CheckPointer 判断 IndicatorsStatic 是否为空指针,无效时才 new 一个 CDealsToIndicators 实例;若二次分配仍无效直接 return false。接着用 StringSubstr(EnumToString(timeframe),7) 截取周期名(如 M15 取自 PERIOD_M15 的第 7 字符起),拼到每个指标名后面做可视化区分。 每个周期里实际挂了 3 组 ADX(周期参数 ADX_Period1/2/3)、3 组 Alligator(颌/齿/唇三组不同周期与位移)、2 组 MACD(快慢与信号线参数各两套)。任何一组 AddXXX 返回 false,函数即退出,EA 不会带残缺指标运行。 开 MT5 把这段塞进 OnInit 之后的私有方法,调 ADX_Period1 等外部输入,就能在 EURUSD 的 M5/M15/H1 上验证多周期指标是否齐刷刷出现在子窗口。外汇与贵金属杠杆高,指标齐了也不代表信号可靠,仍需结合价格行为判断。

MQL5 / C++
   class="kw">return INIT_FAILED;
class=class="str">"cmt">//---
   AddIndicators(TimeFrame1);
   AddIndicators(TimeFrame2);
   AddIndicators(TimeFrame3);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
class="type">bool AddIndicators(ENUM_TIMEFRAMES timeframe)
  {
  if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
     {
       IndicatorsStatic  =  new CDealsToIndicators();
       if(CheckPointer(IndicatorsStatic)==POINTER_INVALID)
         class="kw">return false;
     }
   class="type">class="kw">string tf_name=StringSubstr(EnumToString(timeframe),class="num">7);
   class="type">class="kw">string name="ADX("+IntegerToString(ADX_Period1)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period1, name))
      class="kw">return false;
   name="ADX("+IntegerToString(ADX_Period2)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period2, name))
      class="kw">return false;
   name="ADX("+IntegerToString(ADX_Period3)+") "+tf_name;
   if(!IndicatorsStatic.AddADX(_Symbol, timeframe, ADX_Period3, name))
      class="kw">return false;
   name="Alligator("+IntegerToString(JAW_Period1)+","+IntegerToString(TEETH_Period1)+","+IntegerToString(LIPS_Period1)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period1, JAW_Shift1, TEETH_Period1, TEETH_Shift1, LIPS_Period1, LIPS_Shift1, Alligator_Method, Alligator_Price, name))
      class="kw">return false;
   name="Alligator("+IntegerToString(JAW_Period2)+","+IntegerToString(TEETH_Period2)+","+IntegerToString(LIPS_Period2)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period2, JAW_Shift2, TEETH_Period2, TEETH_Shift2, LIPS_Period2, LIPS_Shift2, Alligator_Method, Alligator_Price, name))
      class="kw">return false;
   name="Alligator("+IntegerToString(JAW_Period3)+","+IntegerToString(TEETH_Period3)+","+IntegerToString(LIPS_Period3)+") "+tf_name;
   if(!IndicatorsStatic.AddAlligator(_Symbol, timeframe, JAW_Period3, JAW_Shift3, TEETH_Period3, TEETH_Shift3, LIPS_Period3, LIPS_Shift3, Alligator_Method, Alligator_Price, name))
      class="kw">return false;
   name="MACD("+IntegerToString(MACD_Fast1)+","+IntegerToString(MACD_Slow1)+","+IntegerToString(MACD_Signal1)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast1, MACD_Slow1, MACD_Signal1, MACD_Price, name))
      class="kw">return false;
   name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name;
   if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name))
      class="kw">return false;

◍ 多周期指标句柄的批量注册写法

这段逻辑在一个时间框架下把 MACD、CCI、Force、Chaikin 四个系列的指标句柄统一挂进静态指标容器,任一创建失败就直接 return false,保证后续引用不会拿到野指针。 MACD 走的是封装好的 AddMACD,名字拼成 "MACD(f,s,sig) TF";CCI 用 iCCI 拿 handle 后交给 AddOneBuffer,周期参数 CCI_Period1/2/3 各开一条缓冲,默认价格类型由 CCI_Price 决定。 Force 指标连续建了三个周期(Force_Period1~3),方法 Force_Method 与成交量类型 Force_Volume 复用同一组入参;Chaikin 则把快/慢周期反过来拼进名字 "CHO(slow,fast) TF",同样三档周期各注册一次。 外汇与贵金属杠杆高,这类多指标堆叠在秒级周期上可能拖慢 MT5 报价响应,实盘前建议在策略测试器里用 2023 年 XAUUSD 的 M15 跑一遍句柄数量压力检查。

MQL5 / C++
  name="MACD("+IntegerToString(MACD_Fast3)+","+IntegerToString(MACD_Slow3)+","+IntegerToString(MACD_Signal3)+") "+tf_name;
  if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast3, MACD_Slow3, MACD_Signal3, MACD_Price, name))
      class="kw">return false;
  name="CCI("+IntegerToString(CCI_Period1)+") "+tf_name;
  class="type">int handle = iCCI(_Symbol, timeframe, CCI_Period1, CCI_Price);
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  name="CCI("+IntegerToString(CCI_Period2)+") "+tf_name;
  handle = iCCI(_Symbol, timeframe, CCI_Period2, CCI_Price);
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  handle = iCCI(_Symbol, timeframe, CCI_Period3, CCI_Price);
  name="CCI("+IntegerToString(CCI_Period3)+") "+tf_name;
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  handle = iForce(_Symbol, timeframe, Force_Period1, Force_Method, Force_Volume);
  name="Force("+IntegerToString(Force_Period1)+") "+tf_name;
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  handle = iForce(_Symbol, timeframe, Force_Period2, Force_Method, Force_Volume);
  name="Force("+IntegerToString(Force_Period2)+") "+tf_name;
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume);
  name="Force("+IntegerToString(Force_Period3)+") "+tf_name;
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  name="CHO("+IntegerToString(Ch_Slow_Period1)+","+IntegerToString(Ch_Fast_Period1)+") "+tf_name;
  handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period1, Ch_Slow_Period1, Ch_Method, Ch_Volume);
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period2, Ch_Slow_Period2, Ch_Method, Ch_Volume);
  name="CHO("+IntegerToString(Ch_Slow_Period2)+","+IntegerToString(Ch_Fast_Period2)+") "+tf_name;
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  handle = iChaikin(_Symbol, timeframe, Ch_Fast_Period3, Ch_Slow_Period3, Ch_Method, Ch_Volume);
  name="CHO("+IntegerToString(Ch_Slow_Period3)+","+IntegerToString(Ch_Fast_Period3)+") "+tf_name;
  if(handle<class="num">0 || !IndicatorsStatic.AddOneBuffer(handle, name) )
      class="kw">return false;
  class="kw">return true;
}

「用成交记录驱动双向挂单的 OnTick 逻辑」

这段 OnTick 的核心不是每次报价都下单,而是先扫一遍 Deals 容器里的新增成交,把已平仓的索引跳过,只对未平仓的 deal 调用 Tick() 更新状态。变量 last_closed_deal 记录了上次已处理到的平仓位置,循环从它往后走,遇到 IsClosed() 就前移索引并 continue,遇到未平仓才把 found 置真并继续刷新。 扫完成交后,代码取当前图表最后一根 K 线的开盘时间 cur_bar,以及服务器时间 cur_time。若 cur_bar 等于上一根记录的 last_bar,或者距新 K 线已过去超过 10 秒((cur_time-cur_bar)>10),直接 return,避免在同一根 bar 内重复发单。这个 10 秒阈值是个实打实的可调参数,你可以按品种波动节奏改小或改大。 接着用 CopyBuffer 从 atr 指标句柄取 1 根历史 ATR 值(偏移 1、取 1 个)。若返回值 <=0 就退出。止损距离 stops 取 2 倍 ATR 与经纪商 SYMBOL_TRADE_STOPS_LEVEL 点数限制中的较大者,再按 _Digits 规范化成 sl。止盈 tp 则按 Reward_Risk 倍数乘(stops+点差)得出,外汇与贵金属杠杆高、滑点跳空频繁,实际成交距离可能偏离计算值,需先在策略测试器跑一遍。 最后分别 new 一个 BUY 和一个 SELL 的 CDeal 对象,买单价位为 ask、SL=bid-sl、TP=ask+tp;卖单对称。指针有效就加入 Deals 并调用 IndicatorsStatic.SaveNewValues 按 Total()-1 存指标快照。把这段代码贴进你的 EA 的 OnTick,改一下 Reward_Risk 和 ATR 周期,就能在 MT5 里观察双向预设单如何随每根新 K 线重生。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   class="type">int total=Deals.Total();
   CDeal *deal;
   class="type">bool found=false;
   for(class="type">int i=last_closed_deal;i<total;i++)
     {
       deal  =  Deals.At(i);
       if(CheckPointer(deal)==POINTER_INVALID)
         class="kw">continue;
       if(!found)
         {
          if(deal.IsClosed())
            {
             last_closed_deal=i;
             class="kw">continue;
            }
          else
             found=true;
         }
       deal.Tick();
     }
class=class="str">"cmt">//---
   class="type">class="kw">datetime cur_bar=(class="type">class="kw">datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   class="type">class="kw">datetime cur_time=TimeCurrent();
   if(cur_bar==last_bar || (cur_time-cur_bar)>class="num">10)
      class="kw">return;
   class="type">class="kw">double atrs[];
   if(CopyBuffer(atr,class="num">0,class="num">1,class="num">1,atrs)<=class="num">0)
      class="kw">return;
   last_bar=cur_bar;
   class="type">class="kw">double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   class="type">class="kw">double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
   class="type">class="kw">double stops=MathMax(class="num">2*atrs[class="num">0],SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point);
   class="type">class="kw">double sl=NormalizeDouble(stops,_Digits);
   class="type">class="kw">double tp=NormalizeDouble(Reward_Risk*(stops+ask-bid),_Digits);
   deal  =  new CDeal(_Symbol,POSITION_TYPE_BUY,TimeCurrent(),ask,bid-sl,ask+tp);
   if(CheckPointer(deal)!=POINTER_INVALID)
      if(Deals.Add(deal))
         IndicatorsStatic.SaveNewValues(Deals.Total()-class="num">1);
   deal  =  new CDeal(_Symbol,POSITION_TYPE_SELL,TimeCurrent(),bid,ask+sl,bid-tp);
   if(CheckPointer(deal)!=POINTER_INVALID)
      if(Deals.Add(deal))
         IndicatorsStatic.SaveNewValues(Deals.Total()-class="num">1);
   class="kw">return;
  }

四轮滤波把EURUSD区间榨出盈利区

回测区间选得够长才有客观性。我拿 EURUSD 从 2016-01-01 到 2017-01-10 这段跑, 里面既有单向趋势也有双向摆动和横盘, 这样模板在任意走势里都可能出利润。每阶段都用利润/风险 1:1 和 15:1 各跑一次, 前者看方向概率, 后者看运动力度, 参数存成 set 文件反复加载。 第一阶段 M5 的力度指数在 -0.01 到 0.01 之间时, 买卖交易利润都贴着零轴下滑, 所有参数组合都复现了这个现象, 大概率是因为这区段波动性缺失。EA 里直接加全局句柄并在 OnTick 舍入后拦截: 力度的归一值绝对值 ≤0.01 就 return 不开单。 第二阶段 MACD 冒出盈利区, 15:1 图更明显, 说明那一段信号有潜力; 第三阶段 D1 的 Chaikin 振荡器显示数值降多头利润增、值增空头利润增; 第四阶段 D1 的 CCI 周期 14 时多空利润随指标同向增长最猛, 三个子区间都见到。这四层过滤依次挂进全局变量、AddIndicators 和 OnTick。 外汇和贵金属杠杆高, 上述依赖关系只是样本内统计倾向, 实盘前请在 MT5 用同区间重跑验证。下面这段是阶段四落地的核心片段, 注意力度过滤的舍入和 MACD 区间判断写法。

MQL5 / C++
class="type">int                 force;
   handle = iForce(_Symbol, timeframe, Force_Period3, Force_Method, Force_Volume);
   if(timeframe==TimeFrame1)
      force=handle;
   class="type">class="kw">double atrs[];
   class="type">class="kw">double force_data[];
   if(CopyBuffer(atr,class="num">0,class="num">1,class="num">1,atrs)<=class="num">0 || CopyBuffer(force,class="num">0,class="num">1,class="num">1,force_data)<=class="num">0)
      class="kw">return;      class=class="str">"cmt">// 加载指标数据时的一些错误
   last_bar=cur_bar;
   class="type">class="kw">double d_Step=_Point*class="num">1000;
   if(MathAbs(NormalizeDouble(force_data[class="num">0]/d_Step,class="num">0)*d_Step)<=class="num">0.01)
      class="kw">return;      class=class="str">"cmt">// 力度指标过滤
class="type">int                 macd;
   name="MACD("+IntegerToString(MACD_Fast2)+","+IntegerToString(MACD_Slow2)+","+IntegerToString(MACD_Signal2)+") "+tf_name;
   if(timeframe==TimeFrame1)
    {
      if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name, macd))
         class="kw">return false;
    }
   else
    {
      if(!IndicatorsStatic.AddMACD(_Symbol, timeframe, MACD_Fast2, MACD_Slow2, MACD_Signal2, MACD_Price, name))
         class="kw">return false;
    }
   class="type">class="kw">double macd_data[];
   if(CopyBuffer(atr,class="num">0,class="num">1,class="num">1,atrs)<=class="num">0 || CopyBuffer(force,class="num">0,class="num">1,class="num">1,force_data)<=class="num">0 || CopyBuffer(macd,class="num">0,class="num">1,class="num">1,macd_data)<=class="num">0)
      class="kw">return;
   class="type">class="kw">double macd_Step=_Point*class="num">50;
   macd_data[class="num">0]=NormalizeDouble(macd_data[class="num">0]/macd_Step,class="num">0)*macd_Step;
   if(macd_data[class="num">0]>=class="num">0.0015 && macd_data[class="num">0]<=class="num">0.0035)
    {
把回测脏活交给小布盯盘
这些虚拟订单与指标类的批量统计,小布盯盘的 AIGC 已内置,打开对应品种页即可看到信号分布,你只管挑条件、做决策。

常见问题

终端不跟踪就自己写虚拟订单类,只记品名、方向、开价、止损止盈和时间,用点数算盈亏,避开仓位冲突。
重点是价格走势与信号条件的关系,仓量不影响盈利因子对指标值的依赖形态,省略能降低模型复杂度。
它把每笔虚拟订单的 outcome 和对应指标值绑定,方便后续按指标区间切分统计盈利因子。
可以,内置的 AIGC 诊断能按品种页展示信号历史分布,省去自己写虚拟订单类的重复劳动。
失效多在参数与样本外环境,用解析技术重看旧指标常能挖出被忽略的条件依赖,不必全盘否定。