最新的改革·进阶篇
📘

最新的改革·进阶篇

第 2/3 篇

点阵图批量遍历与单品种落盘

这段逻辑解决的是 PNF 点阵图(点数图)在 MT5 里「画谁、怎么画、画完放哪」的问题。核心入口是 OnStart:当全局开关 mw 为 true 时,脚本会扫一遍 SymbolsTotal(true) 返回的全部可见品种,逐个生成点阵图并写文件;mw 为 false 时只处理当前图表品种 Symbol()。 循环里每跑一个品种都先 ArrayFree(pnfArray) 再 ArrayResize(pnfArray,0,0),把承接点阵数据的动态数组彻底清空,避免上一种品种的余柱污染下一品种。随后调用 PNF(symb,...) 计算、pnf2file(...) 落盘,品种游标 mwSymb 自增直到覆盖全部。 文首那两行 RGB 三元组(如 255,255,255 与 230,177,177)是 pic 调色板里的配色序列,对应点阵图里涨柱、跌柱与背景的绘制色,共 24 组数值、覆盖 8 个色块循环。外汇与贵金属品种波动属性差异大,同一 cellsize 在不同品种上生成的列数可能差出数倍,实盘前建议先对 XAUUSD 与 EURUSD 各跑一次比对。 想验证很简单:把 mw 设 false,在 XAUUSD 的 H1 图上挂这段,跑完 Alert("Ok.") 弹窗后即去 MQL5/Files 翻对应 .csv 或位图,看列高是否随近 20 根 K 的箱体收敛而压缩。

MQL5 / C++
 class="num">255,class="num">255,class="num">255,class="num">230,class="num">177,class="num">177,class="num">185,class="num">36,class="num">36,class="num">255,class="num">255,class="num">255,class="num">255,class="num">255,class="num">255,class="num">189,class="num">51,class="num">51,class="num">222,class="num">153,class="num">153,class="num">255,class="num">255,class="num">255,
  class="num">255,class="num">255,class="num">255,class="num">240,class="num">207,class="num">207,class="num">200,class="num">84,class="num">84,class="num">255,class="num">255,class="num">255,class="num">255,class="num">255,class="num">255,class="num">227,class="num">168,class="num">168,class="num">211,class="num">117,class="num">117,class="num">255,class="num">255,class="num">255
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Instrument selection                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">int    mwSymb;
   class="type">class="kw">string symb;
   class="type">int    height=class="num">0,width=class="num">0;
   class="type">class="kw">string pnfArray[];
   if(mw==true)
     {
      mwSymb=class="num">0;
      class="kw">while(mwSymb<SymbolsTotal(true))
        {
         symb=SymbolName(mwSymb,true);
         ArrayFree(pnfArray);
         ArrayResize(pnfArray,class="num">0,class="num">0);
         PNF(symb,pnfArray,height,width,pic,cellsize);
         pnf2file(symb,pnfArray,class="num">0,height);
         mwSymb++;
        };
     }
   else
     {
      symb=Symbol();
      ArrayFree(pnfArray);
      ArrayResize(pnfArray,class="num">0,class="num">0);
      PNF(symb,pnfArray,height,width,pic,cellsize);
      pnf2file(symb,pnfArray,class="num">0,height);
     };
   Alert("Ok.");
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Chart calculation and drawing                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void PNF(class="type">class="kw">string sName,        class=class="str">"cmt">// instrument
         class="type">class="kw">string& array[],     class=class="str">"cmt">// array for the output
         class="type">int& y,              class=class="str">"cmt">// array height
         class="type">int& z,              class=class="str">"cmt">// array width
         class="type">bool toPic,          class=class="str">"cmt">// if true-output and draw
         class="type">int cs)              class=class="str">"cmt">// set the cell size for drawing
  {
   class="type">class="kw">string    s,ps;

「成交量分布图的数组骨架与初始化」

要在 MT5 里手搓一个成交量分布(Volume Profile)矩阵,第一步是把 K 线时间和 OHLCV 拉进一堆动态数组。上面这段声明里,d[] 存时间、o[]/h[]/l[]/c[] 存开高低收、v[] 是 tick 成交量;matrix[] 是二维像素矩阵,VolByPrice[] 与 VolByCol[] 分别按价格档和列统计成交量,HVolumeMax/VVolumeMax 记录横向与纵向最大量。 dateTime 型 DateByCol[] 把每一列对应到具体时间,MqlDateTime 的 bMDT/eMDT 框定起止日期;tMin[]/tMax[] 则缓存每列的最小/最大tick索引。GlobalMin/GlobalMax 等整型负责全局与游标区间的价格映射,height/width 决定画布尺寸,beg/end 控制渲染起止列。 初始化时先按品种小数位定 sd:SymbolInfoInteger 取 SYMBOL_DIGITS,≤3 位(如 XAUUSD 通常 2 位)设 sd=2,否则 sd=4。再用 b=MathMin(Bars(sName,tf),depth) 限制拉取根数,避免深度过大拖垮终端——depth 由外部传入,实操中设 500~2000 都常见。 所有数组先 ArrayFree 释放旧内存再 ArrayResize 到 b,并用 ArrayInitialize(...,NULL) 清场,随后 CopyTime/CopyOpen…CopyTickVolume 一次性灌入。box 参数非 0 时,dBox=box/MathPow(10.0,sd) 把「点数」转成真实价格步长;为 0 则走后续自动计算分支。外汇与贵金属杠杆高、滑点跳空频繁,复制历史数据后务必在策略测试器用不同品种验证数组边界,防止 depth 超 Bars 导致矩阵错位。

MQL5 / C++
  class="type">class="kw">datetime    d[];
  class="type">class="kw">double      o[],h[],l[],c[];
  class="type">long        v[];
  class="type">uchar       matrix[];
  class="type">long        VolByPrice[],VolByCol[],HVolumeMax,VVolumeMax;
  class="type">int         tMin[],tMax[];
  class="type">class="kw">datetime    DateByCol[];
  class="type">MqlDateTime bMDT,eMDT;
  class="type">class="kw">string      strDBC[];
  class="type">uchar       pnf=&class="macro">#x27;.&class="macro">#x27;;
  class="type">int         sd;
  class="type">int         b,i,j,k=class="num">0,m=class="num">0;
  class="type">int         GlobalMin,GlobalMax,StartMin,StartMax,CurMin,CurMax,RevMin,RevMax,ContMin,ContMax;
  class="type">int         height,width,beg=class="num">0,end=class="num">0;
  class="type">class="kw">double      dBox,price;
  class="type">int         thBeg=class="num">1,thEnd=class="num">2,tv=class="num">0;
  class="type">uchar       trend=&class="macro">#x27;.&class="macro">#x27;;
class=class="str">"cmt">// --------------------------------- BMP -----------------------------------------
  class="type">int RowVolWidth=class="num">10*cs;
class=class="str">"cmt">//--- shift for prices
  class="type">int startX=class="num">5*cs;
  class="type">int yshift=cs*class="num">7;
class=class="str">"cmt">// --------------------------------- BMP -----------------------------------------
  if(SymbolInfoInteger(sName,SYMBOL_DIGITS)<=class="num">3) sd=class="num">2; else sd=class="num">4;
  b=MathMin(Bars(sName,tf),depth);
  ArrayFree(d);
  ArrayFree(o);
  ArrayFree(h);
  ArrayFree(l);
  ArrayFree(c);
  ArrayFree(v);
  ArrayFree(matrix);
  ArrayFree(VolByPrice);
  ArrayFree(VolByCol);
  ArrayFree(DateByCol);
  ArrayFree(tMin);
  ArrayFree(tMax);
  ArrayResize(d,b,class="num">0);
  ArrayResize(o,b,class="num">0);
  ArrayResize(h,b,class="num">0);
  ArrayResize(l,b,class="num">0);
  ArrayResize(c,b,class="num">0);
  ArrayResize(v,b,class="num">0);
  ArrayInitialize(d,NULL);
  ArrayInitialize(o,NULL);
  ArrayInitialize(h,NULL);
  ArrayInitialize(l,NULL);
  ArrayInitialize(c,NULL);
  ArrayInitialize(v,NULL);
  CopyTime(sName,tf,class="num">0,b,d);
  CopyOpen(sName,tf,class="num">0,b,o);
  CopyHigh(sName,tf,class="num">0,b,h);
  CopyLow(sName,tf,class="num">0,b,l);
  CopyClose(sName,tf,class="num">0,b,c);
  CopyTickVolume(sName,tf,class="num">0,b,v);
  if(box!=class="num">0)
    {
      dBox=box/MathPow(class="num">10.0,(class="type">class="kw">double)sd);
    }
  else
    {

◍ 点阵图里的坐标归一与转向判定

这段逻辑在干一件事:把高低价数组塞进一个归一化网格,再用点数图(Pnf)规则判断该画 X 还是 O。dBox 先算单格代表的价格跨度,用最高价与最低价差除以 count,再按精度 sd 做十位幂缩放,整个矩阵的高度就是 GlobalMax 减 GlobalMin 的整数差。 归一函数 MathNorm 把真实价格映射到行号:GlobalMin 从下边界减 reverse 取整,GlobalMax 从上边界加 reverse 取整,StartMin / StartMax 则锁定 0 号 K 线的落点。reverse 是转向所需格数,外汇与贵金属波动大,reverse 设太小会频繁假突破,实盘前建议在 MT5 用周线先跑一遍看噪声密度。 数组初始化全是铺垫:matrix 用 '.' 填满,VolByPrice 和 VolByCol 归零,DateByCol 给个 1971 年占位日期,tMin / tMax 清零。之后 for 循环从 i=1 扫到 b-1,每次取 CurMin / CurMax 做归一。 当 pnf 还是 '.' 时,若 CurMax 越过 RevMax 就切 'X' 并向上补一列矩阵;若 CurMin 跌破 RevMin 就切 'O'。这里 RevMax = StartMax - reverse,RevMin = StartMin + reverse,意味着初始方向由两个边界各退 reverse 格来确认,属于典型的三格转向变体。

MQL5 / C++
dBox=MathNorm((h[ArrayMaximum(h,class="num">0,WHOLE_ARRAY)]-l[ArrayMinimum(l,class="num">0,WHOLE_ARRAY)])/count,class="num">1/MathPow(class="num">10.0,(class="type">class="kw">double)sd),true)/MathPow(class="num">10.0,(class="type">class="kw">double)sd);
 };
 GlobalMin=MathNorm(l[ArrayMinimum(l,class="num">0,WHOLE_ARRAY)],dBox,true)-(class="type">int)(reverse);
 GlobalMax=MathNorm(h[ArrayMaximum(h,class="num">0,WHOLE_ARRAY)],dBox,class="kw">false)+(class="type">int)(reverse);
 StartMin=MathNorm(l[class="num">0],dBox,true);
 StartMax=MathNorm(h[class="num">0],dBox,class="kw">false);
 ContMin=(class="type">int)(StartMin-class="num">1);
 ContMax=(class="type">int)(StartMax+class="num">1);
 RevMin=(class="type">int)(StartMax-reverse);
 RevMax=(class="type">int)(StartMin+reverse);
 height=(class="type">int)(GlobalMax-GlobalMin);
 width=class="num">1;
 ArrayResize(matrix,height*width,class="num">0);
 ArrayInitialize(matrix,&class="macro">#x27;.&class="macro">#x27;);
 ArrayResize(VolByPrice,height,class="num">0);
 ArrayInitialize(VolByPrice,class="num">0);
 ArrayResize(VolByCol,width,class="num">0);
 ArrayInitialize(VolByCol,class="num">0);
 ArrayResize(DateByCol,width,class="num">0);
 ArrayInitialize(DateByCol,D&class="macro">#x27;class="num">01.01.class="num">1971&class="macro">#x27;);
 ArrayResize(tMin,width,class="num">0);
 ArrayInitialize(tMin,class="num">0);
 ArrayResize(tMax,width,class="num">0);
 ArrayInitialize(tMax,class="num">0);
 for(i=class="num">1;i<b;i++)
  {
   CurMin=MathNorm(l[i],dBox,true);
   CurMax=MathNorm(h[i],dBox,class="kw">false);
   class="kw">switch(pnf)
     {
      case &class="macro">#x27;.&class="macro">#x27;:
        {
         if(CurMax>=RevMax)
           {
            pnf=&class="macro">#x27;X&class="macro">#x27;;
            ContMax=(class="type">int)(CurMax+class="num">1);
            RevMin=(class="type">int)(CurMax-reverse);
            beg=(class="type">int)(StartMin-GlobalMin-class="num">1);
            end=(class="type">int)(CurMax-GlobalMin-class="num">1);
            SetMatrix(matrix,beg,end,height,(class="type">int)(width-class="num">1),pnf);
            SetVector(VolByPrice,beg,end,v[i]);
            VolByCol[width-class="num">1]=VolByCol[width-class="num">1]+v[i];
            DateByCol[width-class="num">1]=d[i];
            trend=&class="macro">#x27;D&class="macro">#x27;;
            class="kw">break;
           };
         if(CurMin<=RevMin)
           {
            pnf=&class="macro">#x27;O&class="macro">#x27;;

点数图列切换时的矩阵与向量填充

这段逻辑处理点数图(PNF)在 UP 列与 X 列之间的状态切换与数据存储。当趋势标记为 'U' 且当前最小价跌破反转阈值时,代码将趋势改写为 'O',并重新计算 ContMin、RevMax、beg、end 等边界,随后调用 SetMatrix 把该 K 线映射到二维矩阵的对应列高位置,SetVector 则把成交量 v[i] 累加到 VolByPrice 的对应价格区间。 在 'X' 分支里,若 CurMax 继续抬高则只更新 ContMax 与 RevMin,矩阵列宽不变;一旦 CurMin 跌破 RevMin,说明发生向下反转,此时 width++ 并连续对 matrix、VolByCol、DateByCol、tMin、tMax 做 ArrayResize,为新列腾出 height*width 的连续内存。 可直接把这段代码粘进 MT5 自定义指标的 PNF 构建函数里验证:当 reverse 参数设为 3、GlobalMin 取历史最低价归一化值,观察 width 在反转瞬间是否按预期 +1,以及 VolByCol[width-1] 的累加是否与成交量的 Tick 量一致。外汇与贵金属市场波动剧烈,这类基于反转的统计映射仅反映历史概率,实盘需警惕滑点与跳空风险。

MQL5 / C++
ContMin=(class="type">int)(CurMin-class="num">1);
RevMax=(class="type">int)(CurMin+reverse);
beg=(class="type">int)(CurMin-GlobalMin-class="num">1);
end=(class="type">int)(StartMax-GlobalMin-class="num">1);
SetMatrix(matrix,beg,end,height,(class="type">int)(width-class="num">1),pnf);
SetVector(VolByPrice,beg,end,v[i]);
VolByCol[width-class="num">1]=VolByCol[width-class="num">1]+v[i];
DateByCol[width-class="num">1]=d[i];
trend=&class="macro">#x27;U&class="macro">#x27;;
class="kw">break;
};
class="kw">break;
};
case &class="macro">#x27;X&class="macro">#x27;:
{
if(CurMax>=ContMax)
{
pnf=&class="macro">#x27;X&class="macro">#x27;;
ContMax=(class="type">int)(CurMax+class="num">1);
RevMin=(class="type">int)(CurMax-reverse);
end=(class="type">int)(CurMax-GlobalMin-class="num">1);
SetMatrix(matrix,beg,end,height,(class="type">int)(width-class="num">1),pnf);
SetVector(VolByPrice,beg,end,v[i]);
VolByCol[width-class="num">1]=VolByCol[width-class="num">1]+v[i];
DateByCol[width-class="num">1]=d[i];
class="kw">break;
};
if(CurMin<=RevMin)
{
pnf=&class="macro">#x27;O&class="macro">#x27;;
ContMin=(class="type">int)(CurMin-class="num">1);
RevMax=(class="type">int)(CurMin+reverse);
tMin[width-class="num">1]=beg-class="num">1;
tMax[width-class="num">1]=end+class="num">1;
beg=(class="type">int)(CurMin-GlobalMin-class="num">1);
end--;
width++;
ArrayResize(matrix,height*width,class="num">0);
ArrayResize(VolByCol,width,class="num">0);
ArrayResize(DateByCol,width,class="num">0);
ArrayResize(tMin,width,class="num">0);
ArrayResize(tMax,width,class="num">0);

「点数图转向时的矩阵与列数组操作」

在 MQL5 里画点数图(PNF)时,遇到转向信号需要同步重写矩阵和每列附属数组。上面这段逻辑处理的是当前列还没到反转阈值、以及从 O 列翻成 X 列两种情形,核心是把价格坐标映射到二维矩阵的行,再把成交量、时间边界塞进对应列向量。 当 CurMin<=ContMin 时,说明还在原列内延续,代码把 pnf 标记为 'O',并用 SetMatrix 把 beg 到 end 区间的行写为 'O',同时 SetVector 把这段成交量累加进 VolByPrice。此时 VolByCol[width-1] 只做加法,DateByCol 记录当前柱时间,不新增列宽。 一旦 CurMax>=RevMax,意味着 O 列结束、X 列开启。代码先把原列的 tMin/tMax 边界定好,beg 加 1、end 重算为 CurMax-GlobalMin-1,width 自增后立刻对 matrix、VolByCol、DateByCol、tMin、tMax 五个数组调用 ArrayResize。注意 ArrayResize 的预留参数都是 0,代表不保留额外内存余量,实盘若品种跳空大,列数可能短时暴涨,需关注内存重分配频率。 外汇与贵金属市场跳空频繁,这类 PNF 重建逻辑在高波动时段可能每根新柱都触发 width++,建议在 MT5 用 Print(width) 打点观察单次加载的最大列数,再决定是否给 ArrayResize 加缓冲尺寸。

MQL5 / C++
SetMatrix(matrix,class="num">0,(class="type">int)(height-class="num">1),height,(class="type">int)(width-class="num">1),&class="macro">#x27;.&class="macro">#x27;);
SetMatrix(matrix,beg,end,height,(class="type">int)(width-class="num">1),pnf);
SetVector(VolByPrice,beg,end,v[i]);
VolByCol[width-class="num">1]=class="num">0;
VolByCol[width-class="num">1]=VolByCol[width-class="num">1]+v[i];
DateByCol[width-class="num">1]=d[i];
tMin[width-class="num">1]=beg-class="num">1;
tMax[width-class="num">1]=end+class="num">1;
class="kw">break;
};
class="kw">break;
};
case &class="macro">#x27;O&class="macro">#x27;:
{
if(CurMin<=ContMin)
{
pnf=&class="macro">#x27;O&class="macro">#x27;;
ContMin=(class="type">int)(CurMin-class="num">1);
RevMax=(class="type">int)(CurMin+reverse);
beg=(class="type">int)(CurMin-GlobalMin-class="num">1);
SetMatrix(matrix,beg,end,height,(class="type">int)(width-class="num">1),pnf);
SetVector(VolByPrice,beg,end,v[i]);
VolByCol[width-class="num">1]=VolByCol[width-class="num">1]+v[i];
DateByCol[width-class="num">1]=d[i];
class="kw">break;
};
if(CurMax>=RevMax)
{
pnf=&class="macro">#x27;X&class="macro">#x27;;
ContMax=(class="type">int)(CurMax+class="num">1);
RevMin=(class="type">int)(CurMax-reverse);
tMin[width-class="num">1]=beg-class="num">1;
tMax[width-class="num">1]=end+class="num">1;
beg++;
end=(class="type">int)(CurMax-GlobalMin-class="num">1);
width++;
ArrayResize(matrix,height*width,class="num">0);
ArrayResize(VolByCol,width,class="num">0);
ArrayResize(DateByCol,width,class="num">0);
ArrayResize(tMin,width,class="num">0);
ArrayResize(tMax,width,class="num">0);

◍ 把点数图矩阵吐成 BMP 与趋势锚点

这一段收在矩阵填充之后,负责把内存里的 matrix 和每列成交量真正落地成图。若 toPic 开关为 true,先按 cs*width+2*startX+RowVolWidth 算横向像素、cs*height+yshift+70 算纵向像素,用 bmp.Create 建一张白底图;其中 70 是留给底部文字说明的预留高度,调小会导致授权与参数串被截掉。 双层 for 从右下角往左上角刷格子:bmp.Bar 先铺白、bmp.Rectangle 再描 clrLightGray 边,形成标准点数图单元格。底部两行 TypeText 分别打 array[k-2](品种描述+Box/Reverse 参数)与 array[k-1](BSD 授权信息),坐标 yshift+cs*height+50 和 +35 是硬编码偏移,字体不随 cs 缩放,cs 设低于 6 时文字会叠进图体。 图生成完并不结束,后面紧接趋势线抽取:while(thEnd<width-1) 里若 trend=='U' 就用 ArrayMinimum(tMin,thBeg,thEnd-thBeg) 找这段区间最低时间锚,tMin 存的是列首偏移减 1。外汇与贵金属点位图对参数极敏感,box 与 reverse 设错会让 thEnd 永远走不到 width-1,趋势线一段都画不出,开 MT5 跑前先把这两个值打印出来核对。

MQL5 / C++
SetMatrix(matrix,class="num">0,(class="type">int)(height-class="num">1),height,(class="type">int)(width-class="num">1),&class="macro">#x27;.&class="macro">#x27;);
SetMatrix(matrix,beg,end,height,(class="type">int)(width-class="num">1),pnf);
SetVector(VolByPrice,beg,end,v[i]);
VolByCol[width-class="num">1]=class="num">0;
VolByCol[width-class="num">1]=VolByCol[width-class="num">1]+v[i];
DateByCol[width-class="num">1]=d[i];
tMin[width-class="num">1]=beg-class="num">1;
tMax[width-class="num">1]=end+class="num">1;
class="kw">break;
};
class="kw">break;
};
};
};
class=class="str">"cmt">//--- credits
s="BSD License, class="num">2012, FXRays.info by Roman Rich";
k++;
ArrayResize(array,k,class="num">0);
array[k-class="num">1]=s;
s=SymbolInfoString(sName,SYMBOL_DESCRIPTION)+",
Box-"+DoubleToString(box,class="num">0)+",Reverse-"+DoubleToString(reverse,class="num">0);
k++;
ArrayResize(array,k,class="num">0);
array[k-class="num">1]=s;
class=class="str">"cmt">// --------------------------------- BMP -----------------------------------------
if(toPic==true)
  {
  class=class="str">"cmt">//-- BMP image size on the chart display
  class="type">int XSize=cs*width+class="num">2*startX+RowVolWidth;
  class="type">int YSize=cs*height+yshift+class="num">70;
  class=class="str">"cmt">//-- creating a bmp image sized XSize x YSize with the background class="type">class="kw">color clrWhite
  bmp.Create(XSize,YSize,clrWhite);
  class=class="str">"cmt">//-- displaying cells of the main field
  for(i=height-class="num">1;i>=class="num">0;i--)
    for(j=class="num">0;j<=width-class="num">1;j++)
      {
      bmp.Bar(RowVolWidth+startX+cs*j,yshift+cs*i,cs,cs,clrWhite);
      bmp.Rectangle(RowVolWidth+startX+cs*j,yshift+cs*i,cs,cs,clrLightGray);
      }
  bmp.TypeText(class="num">10,yshift+cs*(height)+class="num">50,array[k-class="num">2],clrDarkGray);
  bmp.TypeText(class="num">10,yshift+cs*(height)+class="num">35,array[k-class="num">1],clrGray);
  }
class=class="str">"cmt">// --------------------------------- BMP -----------------------------------------
class=class="str">"cmt">//--- calculating trend lines
i=class="num">0;
class="kw">while(thEnd<width-class="num">1)
  {
  class="kw">while(thBeg+i<thEnd)
    {
    if(trend==&class="macro">#x27;U&class="macro">#x27;)
      {
      i=ArrayMinimum(tMin,thBeg,thEnd-thBeg);
      j=tMin[i];
      }
    else
      {

常见问题

用品种名+周期做文件名后缀,每次写完关闭文件句柄,避免缓冲残留导致串档。
先用历史极值除以预期箱数反推箱宽,再按报价精度四舍五入,能明显减少空柱。
小布可读取你的坐标归一参数,批量回放近期行情并标出转向点,你只需核对误判列。
先在切换前把当前列向量末位补入矩阵对应行,再开新列,顺序反了会丢最后一根。
在吐 BMP 的循环里用锚点行号写像素标记,导出后图片自带高低点参考线。