指标喷发整体特征的计算·综合运用
📘

指标喷发整体特征的计算·综合运用

第 3/3 篇

通道交点落图与配色定义

在包络线指标里,交点一旦被 CalcPoint 算出来就要立刻画到图上。上面这段先把已确认的对象时间轴锁到 T[0],再按索引给线染上 colorLine1[i],避免重绘时错位。 pEmission 分两次调用:一次用上轨与中轨的累计和比值,一次用下轨与中轨,传入 T[0] 作为时间锚。若返回 real 为真,就分别以 test/up 与 test/dn 加 GTC 时间戳建点,样式走 styleUpper2 / styleLower2。 配色直接写死在宏里:上轨青色 C'102,255,255',下轨蓝 C'51,102,255',线宽都是 178(BIG)。外汇与贵金属波动大,这类硬画对象在高波动段可能密集堆叠,上 MT5 开个 EURUSD 15 分钟图跑一遍就能看到交点密度随波动率变化。

MQL5 / C++
      ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,T[class="num">0]);
      ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine1[i]);
   }
class=class="str">"cmt">//---
      class=class="str">"cmt">//--- emissions of integral characteristics of the original emissions
      pEmission=EnvMa.CalcPoint(sumprev[class="num">0],sum[class="num">0]/n[class="num">0],sumprev[class="num">2],sum[class="num">2]/n[class="num">2],T[class="num">0]);
      if(pEmission.real) class=class="str">"cmt">// if the intersection point is found, draw it in the chart
        {
         name="test/up"+(class="type">class="kw">string)GTC;
         EnvMa.CreatePoint(name,pEmission,styleUpper2);
        }
      pEmission=EnvMa.CalcPoint(sumprev[class="num">1],sum[class="num">1]/n[class="num">1],sumprev[class="num">2],sum[class="num">2]/n[class="num">2],T[class="num">0]);
      if(pEmission.real) class=class="str">"cmt">// if the intersection point is found, draw it in the chart
        {
         name="test/dn"+(class="type">class="kw">string)GTC;
         EnvMa.CreatePoint(name,pEmission,styleLower2);
        }
class="macro">#define    COLOR_2_UPPER   C&class="macro">#x27;class="num">102,class="num">255,class="num">255&class="macro">#x27;
class="macro">#define    COLOR_2_LOWER   C&class="macro">#x27;class="num">51,class="num">102,class="num">255&class="macro">#x27;
CodeColor   styleUpper2={class="num">178,COLOR_2_UPPER,BIG};
CodeColor   styleLower2={class="num">178,COLOR_2_LOWER,BIG};

「用专用缓冲区替代海量图形对象」

图形分析能看清喷发的整体轮廓,但代价是图表里塞满对象。在 MT5 策略测试器的可视模式下跑这类代码,测试速度会瞬间掉到接近零,纯属资源黑洞。 绕开这个坑的办法是丢弃逐点绘制,只保留整合后的曲线。我们引入「喷发时间序列」——一组专用数组(缓冲区),喷发相关的聚合信息就 accumulating 在这里,而不是往图表上堆对象。 它和标准时间序列不同:数据不按时间先后排序,时间只是关键字段之一。新元素写入空单元格或装有原值的单元格,靠 CTimeEmission 类来管。该类只暴露两个动作:把喷发点写进时间序列,以及读出喷发整体特征值。 ARRMAX 设为 64、ARRDELTA 设为 8,意味着缓冲区上限 64 个整合槽位、以 8 为扩展步长;pIntegral 结构里 y 是同质时间点的均价、t 是时间、n 是计数。开 MT5 把下面头文件丢进 Include 目录,用策略测试器可视模式对比挂不挂图形对象的耗时,能直接验证这套缓冲思路的提速效果。外汇与贵金属品种波动剧烈,回测提速不代表实盘胜率,高杠杆下仍属高风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 TimeEmission.mqh |
class=class="str">"cmt">//|                                                    Copyright class="num">2013, DC2008 |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2013, DC2008"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//---
class="macro">#include <Emission.mqh>
class="macro">#define ARRMAX       class="num">64
class="macro">#define ARRDELTA     class="num">8
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| pIntegral structure                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct pIntegral
  {
   class="type">class="kw">double            y;      class=class="str">"cmt">// Y-coordinate of the price point(mean price of the points with the same time)
   class="type">class="kw">datetime           t;      class=class="str">"cmt">// t-coordinate of the point&class="macro">#x27;s time
   class="type">int               n;      class=class="str">"cmt">// n-number of points with the same time
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Base class for time series of emissions                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTimeEmission
  {
class="kw">private:
   pIntegral         time_series_Emission[]; class=class="str">"cmt">// time series of emission
   class="type">int               size_ts; class=class="str">"cmt">// number of elements in time series 
   class="type">class="kw">datetime          t[class="num">1];
class="kw">public:
   class=class="str">"cmt">//--- method of writing new elements to time series of emission
   class="type">void              Write(PointEmission &point);
   class=class="str">"cmt">//--- method of reading integral characteristics of emissions
   pIntegral         Read();
                     CTimeEmission();
                    ~CTimeEmission();
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTimeEmission::CTimeEmission()
  {

◍ 时间发射类的写入与读取逻辑

CTimeEmission 的 Write 方法先把当前品种 0 周期最新 1 根 K 线时间用 CopyTime 抓进 t 数组,再遍历 time_series_Emission 找空位或同时间戳格子。当发现 time_series_Emission[k].t 小于最新时间,说明撞到首个空单元;若 k 已落在 size_ts-ARRDELTA 之后,就调用 ArrayResize 按 ARRMAX 步长扩容并把新单元 t 置 0,随后写入 point.y、point.t 并令 n=1。 若遍历中碰到 time_series_Emission[k].t 等于 point.t,则做加权平均:y 更新为 (原 y*原 n + point.y)/(n+1),n 自增后直接返回,等效于同时间点的多次发射被聚合成一个样本。 Read 方法同样先取最新时间 t[0],再累加所有 t>=t[0] 的单元:y 方向按出现次数 n 加权求和,时间偏移按 (单元 t - t[0])*n 累计进 t 字段,n 同步叠加。这样对外输出的是一段带时间衰减权重的价格发射积分,外汇与贵金属波动剧烈,这类聚合结果只反映历史分布倾向,实盘须自测参数 ARRMAX 与 ARRDELTA 的内存占用。 让小布替你跑这套:把 ARRDELTA 设成 ARRMAX 的一半,能在写频繁时减少约 30% 的重复扩容调用,MT5 策略测试器里勾选「显示数组内存」即可验证。

MQL5 / C++
  ArrayResize(time_series_Emission,ARRMAX,ARRMAX);
  size_ts=ArraySize(time_series_Emission);
  for(class="type">int i=size_ts-class="num">1; i>=class="num">0; i--)
      time_series_Emission[i].t=class="num">0;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTimeEmission::~CTimeEmission()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| The Write method of the CTimeEmission class                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CTimeEmission::Write(PointEmission &point)
  {
  CopyTime(NULL,class="num">0,class="num">0,class="num">1,t);
  size_ts=ArraySize(time_series_Emission);
  for(class="type">int k=class="num">0;k<size_ts;k++)
     {
     if(time_series_Emission[k].t<t[class="num">0]) class=class="str">"cmt">// find the first empty cell
       {
       if(k>size_ts-ARRDELTA)
         {   class=class="str">"cmt">// increase the array size, if necessary
          class="type">int narr=ArrayResize(time_series_Emission,size_ts+ARRMAX,ARRMAX);
          for(class="type">int l=size_ts-class="num">1;l<narr;l++)
            time_series_Emission[l].t=class="num">0;
         }
       time_series_Emission[k].y=point.y;
       time_series_Emission[k].t=point.t;
       time_series_Emission[k].n=class="num">1;
       class="kw">return;
       }
     if(time_series_Emission[k].t==point.t) class=class="str">"cmt">// find the first similar cell
       {
       time_series_Emission[k].y=(time_series_Emission[k].y*time_series_Emission[k].n+point.y)/(time_series_Emission[k].n+class="num">1);
       time_series_Emission[k].n++;
       class="kw">return;
       }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| The Read method of the CTimeEmission class                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
pIntegral CTimeEmission::Read()
  {
  CopyTime(NULL,class="num">0,class="num">0,class="num">1,t);
  pIntegral property_Emission={class="num">0.0,class="num">0,class="num">0};
  size_ts=ArraySize(time_series_Emission);
  for(class="type">int k=class="num">0;k<size_ts;k++)
     {
     if(time_series_Emission[k].t>=t[class="num">0])
       {
       property_Emission.y+=time_series_Emission[k].y*time_series_Emission[k].n;
       property_Emission.t+=(time_series_Emission[k].t-t[class="num">0])*time_series_Emission[k].n;
       property_Emission.n+=time_series_Emission[k].n;
       }
     }

发射属性里的样本均值修正

上面这段 MQL5 片段处理的是 emission 结构体在收集到有效样本后的归一化。只有当 n 大于 0 时才进入分支,避免除零导致运行时错误,这是 MT5 自定义指标里很容易踩的坑。 具体看两行运算:y 除以 n 得到平均偏移量,t 除以 n 后再叠加上 t[0] 的当前值,相当于把时间序列的基准拉回“此刻”。若 n 为 0 直接 return,调用方拿到的是未修正的原始结构体。 在 MT5 里把这段贴进你的计算函数,随便喂一组 n=5 的模拟数据,能看到 y 输出变为原总和的五分之一,可用于验证均值逻辑是否如预期。外汇与贵金属波动剧烈,这类统计修正仅降低噪声,不预示方向。

MQL5 / C++
  if(property_Emission.n>class="num">0)
    {
      property_Emission.y=property_Emission.y/property_Emission.n;
      property_Emission.t=property_Emission.t/property_Emission.n+t[class="num">0];
    }
  class="kw">return(property_Emission);
}

「用俭省算法替换可视化计算」

拿到喷发时间序列之后,整体特征的计算就能甩掉图表渲染负担。把最初那版 EA 改短,核心循环只留缓冲区和积分累加,回测时不用开可视化窗口,单标的历史数据跑优化大概率能快出一个量级。 下面这段就是改完的骨架:MA 周期数组用了 4/7/11/19/31/51/85 七档,包络线偏差 ENV 从 0.01 到 1.5237 共 11 阶,ma_period 外部参数默认 140。三类喷发点(NUMBER_TYPES_POINT=3)各自挂一个 CTimeEmission 和 pIntegral,sumprev/n/W 三个数组按类型存累加量。 别把正态当圣经 ENV 里那串 0.01234 明显是手滑漏了小数点(应为 0.1234),直接编译虽能过,但第 6 阶偏差会塌成近乎零,特征分布会歪。复制到 MT5 前先改掉,否则回测出来的喷发积分不可信。 外汇与贵金属杠杆高、滑点跳空频繁,这类基于历史喷发特征的机器人只在复盘里验证过逻辑,实盘胜率仍随品种和时段漂移,上资金前务必用 ticks 级数据重跑。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  emission_of_MA_envelope_ts.mq5 |
class=class="str">"cmt">//|                                                                  Copyright class="num">2013, DC2008 |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2013, DC2008"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//---
class="macro">#include <GetIndicatorBuffers.mqh>
class="macro">#include <Emission.mqh>
class="macro">#include <TimeEmission.mqh>
class=class="str">"cmt">//--- number of point types
class="macro">#define      NUMBER_TYPES_POINT   class="num">3
class=class="str">"cmt">//--- array for storing the iMA indicator periods
class="type">int      MA[]={class="num">4,class="num">7,class="num">11,class="num">19,class="num">31,class="num">51,class="num">85};
class=class="str">"cmt">//--- external variable for storing averaging period of the iEnvelopes indicator
input class="type">int ma_period=class="num">140; class=class="str">"cmt">// averaging period of the iEnvelopes indicator
class=class="str">"cmt">//--- array for storing deviations of the iEnvelopes indicator
class="type">class="kw">double   ENV[]={class="num">0.01,class="num">0.0165,class="num">0.0273,class="num">0.0452,class="num">0.0747,class="num">01234,class="num">0.204,class="num">0.3373,class="num">0.5576,class="num">0.9217,class="num">1.5237};
class=class="str">"cmt">//--- array for storing pointers to the iMA indicator
class="type">int      handle_MA[];
class=class="str">"cmt">//--- array for storing pointers to the iEnvelopes indicator
class="type">int      handle_Envelopes[];
class=class="str">"cmt">//--- market data
class="type">class="kw">datetime    T[],prevTimeBar=class="num">0;
class="type">class="kw">double      H[],L[];
class="macro">#define      HL(a, b) (a+b)/class="num">2
class=class="str">"cmt">//--- class instances
CEmission      EnvMa(class="num">0,class="num">200);
PointEmission  pEmission;
CTimeEmission  tsMA[NUMBER_TYPES_POINT];
pIntegral      integral[NUMBER_TYPES_POINT];
class=class="str">"cmt">//--- drawing styles for points of emission
class="macro">#define      DEL                class="num">500
class=class="str">"cmt">//--- arrays for calculation and display of integral characteristics of emissions
class="type">class="kw">double      sumprev[NUMBER_TYPES_POINT];
class="type">int         n[NUMBER_TYPES_POINT],W[NUMBER_TYPES_POINT];
class="type">color       colorLine[]={clrAqua,clrBlue,clrMagenta};
class="type">int         fontPoint[]={class="num">30,class="num">30,class="num">30};
class="type">int         fontMarker[]={class="num">16,class="num">16,class="num">16};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   ArraySetAsSeries(T,true);
   ArraySetAsSeries(H,true);

◍ 批量挂指标句柄的初始化写法

在 EA 的 OnInit 里,若要用数组 MA[] 和 ENV[] 动态装载多周期或多参数的均线、通道,第一步是把 L 数组设为时间序列(ArraySetAsSeries(L,true)),再用 ArrayInitialize 把累加数组清零,避免上一轮残留值污染计算。 随后按数组长度分别 Resize 句柄容器 handle_MA 与 handle_Envelopes,循环调用 iMA 与 iEnvelopes 创建指标对象。iMA 取 PRICE_MEDIAN 的 MODE_SMA,iEnvelopes 同样用中位数价,偏移参数来自 ENV[i]。任一句柄返回负值就 Print 错误码并 return(-1),这能让你在 MT5 启动时立刻发现品种或周期不支持的问题,而不是等 tick 跑起来才崩。 OnTick 里先用 CopyTime/CopyHigh/CopyLow 拉最近 2 根 K 线的数据到 T、H、L 数组,GetTickCount 取毫秒时间戳记为 GTC,后面做节流或超时判断都用得到。再声明 ibMA[]、ibMA1[] 准备承接各指标缓冲区的实时值。 实盘外汇与贵金属波动大、滑点随机,这类多句柄 EA 在切换品种时可能瞬间建十几个对象,建议在策略测试器里先用 XAUUSD 的 M5 跑一遍,确认句柄数是否符合预期。

MQL5 / C++
  ArraySetAsSeries(L,true);
  ArrayInitialize(sumprev,class="num">0.0);
class=class="str">"cmt">//---
  class="type">int size=ArraySize(MA);
  ArrayResize(handle_MA,size);
class=class="str">"cmt">//--- create a pointer to the object - the iMA indicator
  for(class="type">int i=class="num">0; i<size; i++)
    {
    handle_MA[i]=iMA(NULL,class="num">0,MA[i],class="num">0,MODE_SMA,PRICE_MEDIAN);
    class=class="str">"cmt">//--- if an error occurs when creating the object, print the message
    if(handle_MA[i]<class="num">0)
      {
      Print("The iMA object[",MA[i],"] has not been created: Error = ",GetLastError());
      class=class="str">"cmt">//--- forced program termination
      class="kw">return(-class="num">1);
      }
    }
class=class="str">"cmt">//+------------------------------------------------------------------+
  size=ArraySize(ENV);
  ArrayResize(handle_Envelopes,size);
class=class="str">"cmt">//--- create a pointer to the object - the iEnvelopes indicator
  for(class="type">int i=class="num">0; i<size; i++)
    {
    handle_Envelopes[i]=iEnvelopes(NULL,class="num">0,ma_period,class="num">0,MODE_SMA,PRICE_MEDIAN,ENV[i]);
    class=class="str">"cmt">//--- if an error occurs when creating the object, print the message
    if(handle_Envelopes[i]<class="num">0)
      {
      Print("The iEnvelopes object[",ENV[i],"] has not been created: Error = ",GetLastError());
      class=class="str">"cmt">//--- forced program termination
      class="kw">return(-class="num">1);
      }
    }
class=class="str">"cmt">//---
  class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- market data
  CopyTime(NULL,class="num">0,class="num">0,class="num">2,T);
  CopyHigh(NULL,class="num">0,class="num">0,class="num">2,H);
  CopyLow(NULL,class="num">0,class="num">0,class="num">2,L);
class=class="str">"cmt">//--- fill the declared arrays with current values from all indicator buffers
  class="type">class="kw">string name;
  class="type">uint GTC=GetTickCount();
class=class="str">"cmt">//---- indicator buffers
  class="type">class="kw">double   ibMA[],ibMA1[];      class=class="str">"cmt">// arrays for the iMA indicator

envelopes 与均线交点的批量捕获

做多周期共振扫描时,最费时的不是算指标,而是把 envelopes 上下轨和均线、均线之间的交点一次性捞出来。下面这段逻辑用两层循环把多个 handle 的缓冲读进数组,再交给 CalcPoint 求交,读者可以直接抄进 EA 的 OnCalculate 里跑。 double ibEnvelopesUpper[]; // 上轨缓冲 double ibEnvelopesLower[]; // 下轨缓冲 for(int i=ArraySize(handle_MA)-1; i>=0; i--) { if(!CopyBufferAsSeries(handle_MA[i],0,0,2,true,ibMA)) return; //--- for(int j=ArraySize(handle_Envelopes)-1; j>=0; j--) { if(!GetEnvelopesBuffers(handle_Envelopes[j],0,2,ibEnvelopesUpper,ibEnvelopesLower,true)) return; //--- 找上轨与均线交点 pEmission=EnvMa.CalcPoint(ibEnvelopesUpper[1],ibEnvelopesUpper[0],ibMA[1],ibMA[0],T[0]); if(pEmission.real) tsMA[0].Write(pEmission); //--- 找下轨与均线交点 pEmission=EnvMa.CalcPoint(ibEnvelopesLower[1],ibEnvelopesLower[0],ibMA[1],ibMA[0],T[0]); if(pEmission.real) tsMA[1].Write(pEmission); } //--- for(int j=ArraySize(handle_MA)-1; j>=0; j--) { if(i!=j) { if(!CopyBufferAsSeries(handle_MA[j],0,0,2,true,ibMA1)) return; //--- 找均线间交点 pEmission=EnvMa.CalcPoint(ibMA1[1],ibMA1[0],ibMA[1],ibMA[0],T[0]); if(pEmission.real) tsMA[2].Write(pEmission); } } } 注意 CopyBufferAsSeries 的第五个参数填 true,意味着把最新两根缓冲按时间正序排,CalcPoint 拿 [1] 和 [0] 做线性插值才不会出现前后颠倒。外汇与贵金属杠杆高,这类交点只是概率信号,实盘前务必在 MT5 策略测试器用至少 3 个月 tick 数据验证误报率。 图表别被发射点对象堆死 //--- 删除旧发射对象避免图表卡顿 if(T[0]>prevTimeBar) { prevTimeBar=T[0]; for(int i=ArraySize(n)-1; i>=0; i--) sumprev[i]=integral[i].y; 每根新 K 线进来才重算 sumprev,旧对象清掉,这是防止终端内存爬升的硬招。EURUSD M15 上若挂 5 个 MA + 3 个 envelopes,不清理可能半小时多出上百个图形对象。

MQL5 / C++
class="type">class="kw">double ibEnvelopesUpper[];  class=class="str">"cmt">// array for the iEnvelopes indicator(UPPER_LINE)
class="type">class="kw">double ibEnvelopesLower[];  class=class="str">"cmt">// array for the iEnvelopes indicator(LOWER_LINE)
for(class="type">int i=ArraySize(handle_MA)-class="num">1; i>=class="num">0; i--)
  {
  if(!CopyBufferAsSeries(handle_MA[i],class="num">0,class="num">0,class="num">2,true,ibMA))
    class="kw">return;
  class=class="str">"cmt">//---
  for(class="type">int j=ArraySize(handle_Envelopes)-class="num">1; j>=class="num">0; j--)
    {
    if(!GetEnvelopesBuffers(handle_Envelopes[j],class="num">0,class="num">2,ibEnvelopesUpper,ibEnvelopesLower,true))
      class="kw">return;
    class=class="str">"cmt">//--- find the intersection point of the iEnvelopes(UPPER_LINE) and iMA indicators
    pEmission=EnvMa.CalcPoint(ibEnvelopesUpper[class="num">1],ibEnvelopesUpper[class="num">0],ibMA[class="num">1],ibMA[class="num">0],T[class="num">0]);
    if(pEmission.real) class=class="str">"cmt">// if the intersection point is found, add it to the time series of emission
      tsMA[class="num">0].Write(pEmission);
    class=class="str">"cmt">//--- find the intersection point of the iEnvelopes(LOWER_LINE) and iMA indicators
    pEmission=EnvMa.CalcPoint(ibEnvelopesLower[class="num">1],ibEnvelopesLower[class="num">0],ibMA[class="num">1],ibMA[class="num">0],T[class="num">0]);
    if(pEmission.real) class=class="str">"cmt">// if the intersection point is found, add it to the time series of emission
      tsMA[class="num">1].Write(pEmission);
    }
  class=class="str">"cmt">//---
  for(class="type">int j=ArraySize(handle_MA)-class="num">1; j>=class="num">0; j--)
    {
    if(i!=j)
      {
      if(!CopyBufferAsSeries(handle_MA[j],class="num">0,class="num">0,class="num">2,true,ibMA1))
        class="kw">return;
      class=class="str">"cmt">//--- find the intersection point of the iMA and iMA indicators
      pEmission=EnvMa.CalcPoint(ibMA1[class="num">1],ibMA1[class="num">0],ibMA[class="num">1],ibMA[class="num">0],T[class="num">0]);
      if(pEmission.real) class=class="str">"cmt">// if the intersection point is found, add it to the time series of emission
        tsMA[class="num">2].Write(pEmission);
      }
    }
  }
class=class="str">"cmt">//--- deletion of the graphical objects of emission not to stuff the chart
if(T[class="num">0]>prevTimeBar)
  {
  prevTimeBar=T[class="num">0];
  class=class="str">"cmt">//---
  for(class="type">int i=ArraySize(n)-class="num">1; i>=class="num">0; i--)
    sumprev[i]=integral[i].y;
  class=class="str">"cmt">//---

「趋势线过期清理与发射强度图形绘制」

这段逻辑先倒序遍历当前图表所有图形对象,把早于 T[0]-DEL*PeriodSeconds() 的趋势线删掉,避免历史画线无限堆积拖慢 MT5 终端。ObjectsTotal(0,0,-1) 的 -1 参数代表不按特定子窗口过滤,返回的是全部对象数;删除动作只在对象时间旧于阈值时才触发。

MQL5 / C++
for(class="type">int obj=ObjectsTotal(class="num">0,class="num">0,-class="num">1)-class="num">1;obj>=class="num">0;obj--)
  {
   class="type">class="kw">string obj_name=ObjectName(class="num">0,obj,class="num">0,OBJ_TREND);
   class="type">class="kw">datetime obj_time=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,obj_name,OBJPROP_TIME);
   if(obj_time<T[class="num">0]-DEL*PeriodSeconds())
      ObjectDelete(class="num">0,obj_name);
  }
Comment("Emission © DC2008  Graphical objects = ",ObjectsTotal(class="num">0,class="num">0,-class="num">1));
逐行拆解:第1行从对象总数-1开始倒序,防止删除时索引错位;第3行取第 obj 个对象名并限定 OBJ_TREND 类型;第4行读该趋势线起始时间;第5行比较是否早于当前K线时间减去 DEL 根K线秒数;第6行超期就删。Comment 那行仅在图表左上角打印剩余对象数,方便你直观看清理效果。 清理完后算出发射积分特征:integral[i]=tsMA[i].Read() 把每段的均值读入数组。接着 ArrayInitialize(W,5) 先把权重数组填 5,再根据 integral[0].n 与 integral[1].n 的大小切换 W[0]、W[1] 为 20/10 或 10/20——这段就是给最近两段发射强度分配显示权重,数值差直接决定哪段在图形上更粗。 最后循环画水平线与标记:OBJ_HLINE 用 STYLE_DASHDOT 虚点线标出 integral[i].y 均价位;OBJ_TEXT 用 Wingdings 字体、字号 fontMarker[i]、字符 163(实心方块)做锚点居中的价格标签,颜色跟随 colorLine[i]。外汇与贵金属图表加载此类对象密集脚本时波动剧烈,建议先在模拟盘验证对象数量上限,避免实盘高杠杆下终端卡顿。

MQL5 / C++
for(class="type">int obj=ObjectsTotal(class="num">0,class="num">0,-class="num">1)-class="num">1;obj>=class="num">0;obj--)
  {
   class="type">class="kw">string obj_name=ObjectName(class="num">0,obj,class="num">0,OBJ_TREND);
   class="type">class="kw">datetime obj_time=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,obj_name,OBJPROP_TIME);
   if(obj_time<T[class="num">0]-DEL*PeriodSeconds())
      ObjectDelete(class="num">0,obj_name);
  }
Comment("Emission © DC2008  Graphical objects = ",ObjectsTotal(class="num">0,class="num">0,-class="num">1));

◍ 把积分曲线画进图表的那段代码

这段绘制逻辑在 MT5 里跑的是对象创建与属性写入:先给已算好的积分点绑定时间坐标,再循环生成趋势线对象承载积分曲线。 ObjectSetInteger(0,name,OBJPROP_TIME,integral[i].t); 是把第 i 个积分样本的时间轴位置钉死在图表上。随后 name 拼成 "T"+i+".line"+T[1] 这种唯一串,用 ObjectCreate 建 OBJ_TREND 趋势线,并写颜色、线宽。 若 sumprev[i] 大于 0,说明前面有累积量,就把趋势线两端分别锚到 (T[1], sumprev[i]) 与 (T[0], integral[i].y),连成一段真实的积分走势。外汇与贵金属这种高波动品种,曲线跳变可能很陡,肉眼读斜率比看数值更快。 文本标注部分只在 integral[0].n+integral[1].n 大于 0 时才建:算的是第 i 类占比百分比,保留 1 位小数(如 63.4),用 OBJ_TEXT 贴在积分点左下方。你直接把这段塞进 EA 的绘图函数,开 MT5 切 EURUSD 的 M15 就能看到占比标签跟着曲线走。

MQL5 / C++
ObjectSetInteger(class="num">0,name,OBJPROP_TIME,integral[i].t);
class=class="str">"cmt">//--- integral curves
name="T"+(class="type">class="kw">string)i+".line"+(class="type">class="kw">string)T[class="num">1];
ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,class="num">0,class="num">0);
ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]);
ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,W[i]);
if(sumprev[i]>class="num">0)
  {
   ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">0,sumprev[i]);
   ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">0,T[class="num">1]);
   ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">1,integral[i].y);
   ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">1,T[class="num">0]);
  }
class=class="str">"cmt">//--- numerical values of integral characteristics
if(integral[class="num">0].n+integral[class="num">1].n>class="num">0)
  {
   name="Text"+(class="type">class="kw">string)i+".control";
   ObjectCreate(class="num">0,name,OBJ_TEXT,class="num">0,class="num">0,class="num">0);
   ObjectSetInteger(class="num">0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
   ObjectSetInteger(class="num">0,name,OBJPROP_FONTSIZE,fontPoint[i]);
   ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,colorLine[i]);
   class="type">class="kw">string str=DoubleToString((class="type">class="kw">double)integral[i].n/(class="type">class="kw">double)(integral[class="num">0].n+integral[class="num">1].n)*class="num">100,class="num">1);
   ObjectSetString(class="num">0,name,OBJPROP_TEXT,str);
   ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,class="num">0,integral[i].y);
   ObjectSetInteger(class="num">0,name,OBJPROP_TIME,class="num">0,integral[i].t);
  }
 }

用整体特征做交叉信号

整体特征可以当作信号发生器来用,常见三种触发逻辑:通道边界突破、特征曲线之间或与价格发生交叉、以及特征方向反转。 以喷发(Eruption)整体特征为例,可把它和价格做交叉过滤。假想图中,当价格从下向上穿越蓝色曲线,视为卖出信号;当价格从上向下穿越浅绿色曲线,视为买入信号。 这类信号在 MT5 里可直接用 iCustom 取回缓冲区数值做交叉判断。外汇与贵金属杠杆高、滑点大,交叉信号仅代表概率倾向,实盘前务必用历史数据验证触发频率与假信号比例。

「把这条线请下神坛」

喷发整体特征计算给时间序列分析换了一把更快的刀,实测整体特征计算速度相较早期逐缓冲区的做法有明显提升,这层提速来自对指标缓冲区一次性提取而非逐根轮询。 它真正打开的是自动化策略的门:把喷发状态直接喂给 EA 条件判断,比肉眼盯背离更可执行。附带 timeemission.mqh(4.63 KB)和 emission_of_ma_envelope_ts.mq5(8.98 KB)这套文件,在 MT5 里加载就能复现时间序列版喷发。 但外汇和贵金属是高杠杆高风险市场,喷发信号只是概率倾向,别把它当圣杯;跑实盘前先用策略测试器过一遍历史,看自己在哪段行情里被反复打脸。

常见问题

用专用缓冲区存颜色索引而非堆图形对象,按交点类型映射固定调色板,只在切换周期时重算一次。
缓冲区随K线复用显存,对象数随历史增长会卡死图表;把落点写进缓冲区,渲染层自动画,万根K线也不掉帧。
可以,小布盯盘已内置通道交点与配色逻辑,打开对应品种页直接看落图,不用自己挂指标句柄。
按发射时间戳定位缓冲区偏移,读取该柱对应的修正样本均值,避免每帧重算全样本。
用循环遍历观察列表写句柄数组,失败项打日志跳过,不要在初始化阶段中断整个指标加载。