市场轮廓指标·进阶篇
📊

市场轮廓指标·进阶篇

(2/3)· 跳过正态分布的玄学,直接拆解初始平衡区与POC在MT5实战中的读法

实战向进阶 第 2/3 篇
把市场轮廓当成信号指示器的人,往往在POC被击穿后还死扛仓位。它不报买卖点,只告诉你谁在控盘、公允价值在哪。读不懂初始平衡区,后面所有轮廓线都是噪声。

「日线数据增量计算的循环控制」

在自定义指标里,OnCalculate 的首跑与后续 tick 更新要走两套逻辑。首跑时 prev_calculated==0,循环天数取 InpStartDate+InpShowDays;之后每次报价进来只补算当天(即 lastday=InpStartDate+1),避免把历史日线重算一遍拖慢 MT5。 静态变量 open_time 用来记当前日线开盘时间。若 InpStartDate!=0 且本次取到的 iTime(Symbol(),PERIOD_D1,0) 与上次相同,直接 return(rates_total) 跳过,说明当日 K 线未换柱。 循环内用 CopyRates 按日线索引取 MqlRates 结构,失败时(周末/假期无 tick 常触发)返回 prev_calculated 等下一轮。这套写法在 EURUSD 日线挂指标时,首跑算 3 天、之后每 tick 仅算 1 天,CPU 占用倾向明显下降。 别把周末空跑当 bug 周末或节假日 MT5 可能取不到日线 bar,此时 CopyRates 返回 -1 属正常。先手动开一次该品种日线图让数据落地,再挂指标更稳。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
class=class="str">"cmt">//--- opening time of the current daily bar
   class="type">class="kw">datetime class="kw">static open_time=class="num">0;
class=class="str">"cmt">//--- number of the last day for calculations
class=class="str">"cmt">//--- (if InpStartDate = class="num">0 and InpShowDays = class="num">3, lastday = class="num">3)
class=class="str">"cmt">//--- (if InpStartDate = class="num">1 and InpShowDays = class="num">3, lastday = class="num">4) etc ...
   class="type">uint lastday=InpStartDate+InpShowDays;
class=class="str">"cmt">//--- if the first calculation has already been made
   if(prev_calculated!=class="num">0)
     {
      class=class="str">"cmt">//--- get the opening time of the current daily bar
      class="type">class="kw">datetime current_open=iTime(Symbol(), PERIOD_D1, class="num">0);
      
      class=class="str">"cmt">//--- if we do not calculate the current day
      if(InpStartDate!=class="num">0)
        {
         class=class="str">"cmt">//--- if the opening time was not received, leave
         if(current_open==open_time)
            class="kw">return(rates_total);
        }
      class=class="str">"cmt">//--- update opening time
      open_time=current_open;
      class=class="str">"cmt">//--- we will only calculate one day from now on, since all other days have already been calculated during the first run
      lastday=InpStartDate+class="num">1;
     }
class=class="str">"cmt">//--- in a loop for the specified number of days(either InpStartDate+InpShowDays on first run, or InpStartDate+class="num">1 on each tick)
   for(class="type">uint day=InpStartDate; day<lastday; day++)
     {
      class=class="str">"cmt">//--- get the data of the day with index day into the structure
      class="type">MqlRates day_rate[];
      class=class="str">"cmt">//--- if the indicator is launched on weekends or holidays when there are no ticks, you should first open the daily chart of the symbol
      class=class="str">"cmt">//--- if we have not received bar data for the day index of the daily period, we leave until the next call to OnCalculate()
      if(CopyRates(Symbol(), PERIOD_D1, day, class="num">1, day_rate)==-class="num">1)
         class="kw">return(prev_calculated);
      class=class="str">"cmt">//--- get daily range(Range) in points

◍ 按点值切分全天价格栅格

要把一天内的亚欧美时段分别统计成交密集区,第一步是把当日高低点之间的空间按 symbol 的最小变动单位(point)切成连续整数索引。代码里用 (high_day-low_day)/point 算出 day_size,若 EURUSD 日波幅 80 点、point=0.0001,day_size 就是 800,意味着全天价格轴被拆成 800 个 1 point 宽的薄层。 double high_day=day_rate[0].high; double low_day=day_rate[0].low; double point=SymbolInfoDouble(Symbol(), SYMBOL_POINT); int day_size=(int)((high_day-low_day)/point); //--- prepare arrays for storing price level rectangles int boxes_asia[], boxes_europe[], boxes_america[]; //--- array sizes equal to the number of points in the day range ArrayResize(boxes_asia, day_size); ArrayResize(boxes_europe, day_size); ArrayResize(boxes_america, day_size); //--- zero out the arrays ZeroMemory(boxes_asia); ZeroMemory(boxes_europe); ZeroMemory(boxes_america); //--- get all intraday bars of the current day MqlRates bars_in_day[]; datetime start_time=day_rate[0].time+PeriodSeconds(PERIOD_D1)-1; datetime stop_time=day_rate[0].time; //--- if the indicator is launched on weekends or holidays when there are no ticks, you should first open the daily chart of the symbol //--- if it was not possible to get the bars of the current timeframe for the specified day, leave until the next call of OnCalculate() if(CopyRates(Symbol(), PERIOD_CURRENT, start_time, stop_time, bars_in_day)==-1) return(prev_calculated); //--- we go through all the bars of the current day in a loop and mark the price cells that the bars fall into int size=ArraySize(bars_in_day); for(int i=0; i<size; i++) { //--- calculate the range of price level indices in the daily range int start_box=(int)((bars_in_day[i].low-low_day)/point); // index of the first cell of the price array corresponding to the Low price of the current i bar int stop_box =(int)((bars_in_day[i].high-low_day)/point); // index of the last cell of the price array corresponding to the High price of the current i bar //--- get the bar hour by the loop index MqlDateTime bar_time; TimeToStruct(bars_in_day[i].time, bar_time); uint hour=bar_time.hour; //--- determine which session the bar belongs to by the bar hour //--- American session if(hour>=InpAmericaStartHour) { //--- in the American session array, in cells from start_box to stop_box, increase the bar counters for(int ind=start_box; ind<stop_box; ind++) boxes_america[ind]++; } //--- Europe or Asia else 逐行看:前 4 行取当日高低与 point,把日波幅转成整数格数;随后三个 int 数组 boxes_asia/europe/america 用 ArrayResize 撑到 day_size 大小,ZeroMemory 清成 0。这样每个价格薄层对应一个计数器,后面按 bar 落点累加。 取数部分用 day_rate[0].time 加减 PeriodSeconds(PERIOD_D1) 框出「昨根日线到当前」的时间窗,CopyRates 拉出当日所有 Intraday 棒。若周末或假期无 tick 导致 CopyRates 返回 -1,直接 return(prev_calculated) 等下一轮,避免空数组崩指标。 主循环里对每根 bar 算 start_box / stop_box:用 (low-low_day)/point 和 (high-low_day)/point 映射到栅格下标,再 TimeToStruct 取 hour。hour>=InpAmericaStartHour 就判定美盘,把 boxes_america 里 start_box 到 stop_box 的格子全 +1。外汇与贵金属波动受时段切换影响明显,此类栅格统计仅描述历史分布,不预示后续方向,实盘请自担高风险。

MQL5 / C++
class="type">class="kw">double high_day=day_rate[class="num">0].high;
class="type">class="kw">double low_day=day_rate[class="num">0].low;
class="type">class="kw">double point=SymbolInfoDouble(Symbol(), SYMBOL_POINT);
class="type">int   day_size=(class="type">int)((high_day-low_day)/point);
class=class="str">"cmt">//--- prepare arrays for storing price level rectangles
class="type">int boxes_asia[], boxes_europe[], boxes_america[];
class=class="str">"cmt">//--- array sizes equal to the number of points in the day range
ArrayResize(boxes_asia, day_size);
ArrayResize(boxes_europe, day_size);
ArrayResize(boxes_america, day_size);
class=class="str">"cmt">//--- zero out the arrays
ZeroMemory(boxes_asia);
ZeroMemory(boxes_europe);
ZeroMemory(boxes_america);
class=class="str">"cmt">//--- get all intraday bars of the current day
class="type">MqlRates bars_in_day[];
class="type">class="kw">datetime start_time=day_rate[class="num">0].time+PeriodSeconds(PERIOD_D1)-class="num">1;
class="type">class="kw">datetime stop_time=day_rate[class="num">0].time;
class=class="str">"cmt">//--- if the indicator is launched on weekends or holidays when there are no ticks, you should first open the daily chart of the symbol
class=class="str">"cmt">//--- if it was not possible to get the bars of the current timeframe for the specified day, leave until the next call of OnCalculate()
if(CopyRates(Symbol(), PERIOD_CURRENT, start_time, stop_time, bars_in_day)==-class="num">1)
   class="kw">return(prev_calculated);
class=class="str">"cmt">//--- we go through all the bars of the current day in a loop and mark the price cells that the bars fall into
class="type">int size=ArraySize(bars_in_day);
for(class="type">int i=class="num">0; i<size; i++)
  {
  class=class="str">"cmt">//--- calculate the range of price level indices in the daily range
  class="type">int         start_box=(class="type">int)((bars_in_day[i].low-low_day)/point);   class=class="str">"cmt">// index of the first cell of the price array corresponding to the Low price of the current i bar
  class="type">int         stop_box =(class="type">int)((bars_in_day[i].high-low_day)/point); class=class="str">"cmt">// index of the last cell of the price array corresponding to the High price of the current i bar
  class=class="str">"cmt">//--- get the bar hour by the loop index
  class="type">MqlDateTime bar_time;
  TimeToStruct(bars_in_day[i].time, bar_time);
  class="type">uint        hour=bar_time.hour;
  class=class="str">"cmt">//--- determine which session the bar belongs to by the bar hour
  class=class="str">"cmt">//--- American session
  if(hour>=InpAmericaStartHour)
    {
    class=class="str">"cmt">//--- in the American session array, in cells from start_box to stop_box, increase the bar counters
    for(class="type">int ind=start_box; ind<stop_box; ind++)
       boxes_america[ind]++;
    }
  class=class="str">"cmt">//--- Europe or Asia
  else

分时段把成交量塞进价格格子

欧盘与亚盘的分箱逻辑靠 hour 判断切分:当 hour 落在 InpEuropeStartHour 到 InpAmericaStartHour 之间,就给 boxes_europe 数组里从 start_box 到 stop_box 的格子各加一;否则归入 boxes_asia 同样区间加一。这样每个价格格子里存的是该时段触及过的 K 线根数,而非真实成交量,但形态上已经够画轮廓。 绘图阶段用矩形对象模拟水平线段:矩形高为一个 point,宽等于该格存储的 bar 数乘当前周期秒数再乘 InpMultiplier。以亚盘为例,price = low_day + ind*point 算出格子对应价,time1 取日线开盘,time2 = time1 + boxes_asia[ind]*box_length*InpMultiplier,前缀拼上日期和 '_Asia_' 与价格,调 DrawBox 落对象。 欧盘循环结构完全一致,只是数组换成 boxes_europe、前缀标 Europe、颜色走 InpEuropeSession。实盘里把 InpMultiplier 从 1 调到 3,线段会明显向右拉伸,MT5 上能直接看出哪个时段在某一价位堆积更厚。外汇与贵金属杠杆高,这种轮廓只反映历史触及频率,后续价格突破倾向不等于必然。

MQL5 / C++
  {
      class=class="str">"cmt">//--- European session
      if(hour>=InpEuropeStartHour && hour<InpAmericaStartHour)
        class=class="str">"cmt">//--- in the European session array, in cells from start_box to stop_box, increase the bar counters
        for(class="type">int ind=start_box; ind<stop_box; ind++)
          boxes_europe[ind]++;
      class=class="str">"cmt">//--- Asian session
      else
        class=class="str">"cmt">//--- in the Asian session array, in cells from start_box to stop_box, increase the bar counters
        for(class="type">int ind=start_box; ind<stop_box; ind++)
          boxes_asia[ind]++;
      }
    }
  class=class="str">"cmt">//--- draw a market profile based on the created arrays of price levels
  class=class="str">"cmt">//---  market profile on the chart is drawn with segments of colored lines class="kw">using the class="type">class="kw">color specified in the settings for each trading session
  class=class="str">"cmt">//--- segments are drawn as rectangle objects with the height of the rectangle equal to one price point and the width equal to the distance to the next bar to the right

  class=class="str">"cmt">//--- define the day for the name of the graphical object and the width of the rectangle
  class="type">class="kw">string day_prefix=TimeToString(day_rate[class="num">0].time, TIME_DATE);
  class="type">int     box_length=PeriodSeconds(PERIOD_CURRENT);
  class=class="str">"cmt">//--- Asian session
  class=class="str">"cmt">//--- in a loop by the number of points of the daily bar
  for(class="type">int ind=class="num">0; ind<day_size; ind++)
    {
    class=class="str">"cmt">//--- if the Asian session array is full
    if(boxes_asia[ind]>class="num">0)
      {
      class=class="str">"cmt">//--- get the next price by adding the number of &class="macro">#x27;ind&class="macro">#x27; points to the Low price of the daily bar
      class=class="str">"cmt">//--- get the start time of the segment(opening time of the daily bar)
      class=class="str">"cmt">//--- and the end time of the segment(opening time of the daily bar + the number of bars stored in the &class="macro">#x27;ind&class="macro">#x27; cell of the boxes_asia[] array)
      class="type">class="kw">double  price=low_day+ind*point;
      class="type">class="kw">datetime time1=day_rate[class="num">0].time;
      class="type">class="kw">datetime time2=time1+boxes_asia[ind]*box_length*InpMultiplier;
      class=class="str">"cmt">//--- create a prefix for the name of the Asian session graphical object
      class="type">class="kw">string  prefix=ExtPrefixUniq+"_"+day_prefix+"_Asia_"+StringFormat("%.5f", price);
      class=class="str">"cmt">//--- draw a rectangle(line segment) at the calculated coordinates with the class="type">class="kw">color for the Asian session
      DrawBox(prefix, price, time1, time2, InpAsiaSession);
      }
    }
  class=class="str">"cmt">//--- European session
  for(class="type">int ind=class="num">0; ind<day_size; ind++)
    {

「欧盘美盘区块的绘制逻辑」

欧盘段的绘制从判断 boxes_europe[ind] 是否大于 0 开始,只有该价格层在欧盘时段确有成交堆积才会进入绘制。价格坐标用当日低点 low_day 加上 ind 个 point 步进,时间起点取自日线开盘时间叠加亚盘右边界跨度,终点再叠上 boxes_europe[ind] 控制的柱数乘 box_length 与 InpMultiplier。 美盘段思路一致,但时间起点要同时吃掉亚盘与欧盘的已绘制跨度,即 (boxes_asia[ind]+boxes_europe[ind]) 共同决定偏移。prefix 里写死 _America_ 与价格五位格式化字符串,便于在对象列表中按日与价位检索。 两段都调用 DrawBox 把矩形线段落到图上,循环结束跑一次 ChartRedraw(0) 强制刷新,最后 return(rates_total) 把下次 OnCalculate 的基准柱数交还系统。外汇与贵金属品种波动受时段流动性切换影响明显,这类分时段画像仅反映历史成交分布,后续走势仍属概率事件,实操请自担高风险。

MQL5 / C++
  class=class="str">"cmt">//--- if the European session array is full
  if(boxes_europe[ind]>class="num">0)
    {
      class=class="str">"cmt">//--- get the next price by adding the number of &class="macro">#x27;ind&class="macro">#x27; points to the Low price of the daily bar
      class=class="str">"cmt">//--- get the start time of the segment(opening time of the daily bar + time of the right edge of the Asian session profile)
      class=class="str">"cmt">//--- and the end time of the segment(start time of the European session segment + the number of bars stored in the &class="macro">#x27;ind&class="macro">#x27; cell of the boxes_europe[] array)
      class="type">class="kw">double  price=low_day+ind*point;
      class="type">class="kw">datetime time1=day_rate[class="num">0].time+boxes_asia[ind]*box_length*InpMultiplier;
      class="type">class="kw">datetime time2=time1+boxes_europe[ind]*box_length*InpMultiplier;
      class=class="str">"cmt">//--- create a prefix for the name of the graphical object of the European session
      class="type">class="kw">string  prefix=ExtPrefixUniq+"_"+day_prefix+"_Europe_"+StringFormat("%.5f", price);
      class=class="str">"cmt">//--- draw a rectangle(line segment) at the calculated coordinates with the class="type">class="kw">color for the European session
      DrawBox(prefix, price, time1, time2, InpEuropeSession);
    }
   }
  class=class="str">"cmt">//--- American session
  for(class="type">int ind=class="num">0; ind<day_size; ind++)
    {
    class=class="str">"cmt">//--- if the American session array is full
    if(boxes_america[ind]>class="num">0)
      {
      class=class="str">"cmt">//--- get the next price by adding the number of &class="macro">#x27;ind&class="macro">#x27; points to the Low price of the daily bar
      class=class="str">"cmt">//--- get the start time of the segment(opening time of the daily bar + time of the right edge of the Asian session profile + time of the right edge of the European session profile)
      class=class="str">"cmt">//--- and the end time of the segment(start time of the American session segment + the number of bars stored in the &class="macro">#x27;ind&class="macro">#x27; cell of the boxes_america[] array) 
      class="type">class="kw">double  price=low_day+ind*point;
      class="type">class="kw">datetime time1=day_rate[class="num">0].time+(boxes_asia[ind]+boxes_europe[ind])*box_length*InpMultiplier;
      class="type">class="kw">datetime time2=time1+boxes_america[ind]*box_length*InpMultiplier;
      class=class="str">"cmt">//--- create a prefix for the name of the American session graphical object
      class="type">class="kw">string  prefix=ExtPrefixUniq+"_"+day_prefix+"_America_"+StringFormat("%.5f", price);
      class=class="str">"cmt">//--- draw a rectangle(line segment) at the calculated coordinates with the class="type">class="kw">color for the American session
      DrawBox(prefix, price, time1, time2, InpAmericaSession);
      }
    }
   }
class=class="str">"cmt">//--- when the loop is complete, redraw the chart
   ChartRedraw(class="num">0);
class=class="str">"cmt">//--- class="kw">return the number of bars for the next OnCalculate call
   class="kw">return(rates_total);
  }
让小布替你标好每日POC
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到当日的初始平衡区与价值区域自动绘制,你只管判断失衡方向。

常见问题

第一个交易小时形成全日主要模式,两个30分钟柱覆盖买卖双方在最早时段的成交共识,是后续扩展范围的基准。
可以,在品种页开启轮廓监控后,价格脱离70%价值区域或POC失守时会有提示,省去手动量柱。
尾部短说明其他时间框架交易者攻击性强,方向突破意愿足;长尾则可能是试探未果,回归价值区概率偏大。
原文建议M30,能平衡日内结构清晰度与噪声,过小周期会把初始平衡区切得太碎。