点数图指标·进阶篇
(2/3)· 从 Dow 时代的“书写法”到 MT5 实战标绘,多数交易者只知 K 线却读不懂无时间轴的趋势角度
「用 Tick 粒度把开盘序列切成趋势柱」
这段逻辑干的事很直接:把最近 HistoryInt 根 M1 开盘价拉进数组,再以 Tick 为最小变动单位,按价格穿越阈值把历史切成一段段同向运动。CopyOpen 取的是当前品种 PERIOD_M1 的 0 号偏移(即最新柱)往后 HistoryInt 根开盘价,返回给 OpenPrice 数组供后续循环使用。 FuncCalculate 先清零一批辅助变量:Trend 记方向(0 未定、-1 空、1 多),BeginPrice 锚定首根开盘,ColumnsInt 累计切出来的段数。循环里只要累计偏移超过 Cell*CellForChange 个 Tick,就判定趋势发动,并按 (BeginPrice-OpenPrice[x])/Tick 的正负分多空,用 MathRound 算穿越了多少个 Cell,再 NormalizeDouble 到品种小数位。 注意 BeginPrice 每次被改写为上一段的 InterimClosePrice,这意味着切片是接力式推进的;外汇与贵金属点差和 Tick 尺寸差异大,跑之前务必确认 Cell 与 Tick 的量纲匹配,否则 ColumnsInt 会虚高。
class="type">int Open=CopyOpen(Symbol(),PERIOD_M1,class="num">0,(HistoryInt),OpenPrice); class=class="str">"cmt">//--- class="kw">return(Open); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function for calculating the number of columns | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int FuncCalculate(class="type">int HistoryInt) { class="type">int ColumnsInt=class="num">0; class=class="str">"cmt">//--- Zero out auxiliary variables Trend=class="num">0; class=class="str">"cmt">// Direction of the price trend BeginPrice=OpenPrice[class="num">0]; class=class="str">"cmt">// Starting price for the calculation FirstTrend=class="num">0; class=class="str">"cmt">// Direction of the initial market trend Columns=class="num">0; class=class="str">"cmt">// Variable for the calculation of columns InterimOpenPrice=class="num">0; InterimClosePrice=class="num">0; NumberCell=class="num">0; class=class="str">"cmt">// Variable for the calculation of cells class=class="str">"cmt">//--- Loop for the calculation of the number of main buffers(column opening and closing prices) for(class="type">int x=class="num">0; x<HistoryInt; x++) { if(Trend==class="num">0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick)) { class=class="str">"cmt">//--- Downtrend if(((BeginPrice-OpenPrice[x])/Tick)>class="num">0) { NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimOpenPrice=BeginPrice; InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick); InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits()); Trend=-class="num">1; } class=class="str">"cmt">//--- Uptrend if(((BeginPrice-OpenPrice[x])/Tick)<class="num">0) { NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimOpenPrice=BeginPrice; InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick); InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits()); Trend=class="num">1; } BeginPrice=InterimClosePrice; ColumnsInt++; FirstTrend=Trend; } class=class="str">"cmt">//--- Determine further actions in case of the downtrend if(Trend==-class="num">1) { if(((BeginPrice-OpenPrice[x])/Tick)>class="num">0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick)) { NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
趋势翻转时的格位递推逻辑
这段脚本负责在已知 Trend 方向后,依据价格相对 BeginPrice 的跳变距离,决定是否新增一列并翻转方向。判断核心是把 (BeginPrice-OpenPrice[x])/Tick 的绝对值,与 Cell*CellForChange 或 Cell 做比较:超过阈值才允许计数加一列。 以 Trend==-1 为例,若价格高于 BeginPrice 且偏离超过 Cell*CellForChange 个 Tick,则 ColumnsInt++,用 MathRound 算跨越的格数 NumberCell,再向下回铺 InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick),并把 Trend 置为 1。反向对称逻辑在 Trend==1 分支里同样存在。 所有临时收盘价都过一道 NormalizeDouble(...,Digits()),避免报价精度尾巴导致画柱错位。外汇与贵金属点差和 Tick 尺寸波动大,这类基于 Tick 的整数格递推在极端滑点下可能漏计或重复计列,实盘前务必在 MT5 策略测试器用历史 Tick 跑一遍 ColumnsInt 输出。 末尾 FuncColor 仅接收一个已算好的 ColumnsInt,用 for(x=0;x<ColumnsInt;x++) 循环去填颜色缓冲——说明列数先定、着色后补,两件事解耦。
NumberCell=MathRound(NumberCell); InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick); InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits()); Trend=-class="num">1; BeginPrice=InterimClosePrice; } if(((BeginPrice-OpenPrice[x])/Tick)<class="num">0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick)) { ColumnsInt++; NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimOpenPrice=BeginPrice+(Cell*Tick); InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick); InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits()); Trend=class="num">1; BeginPrice=InterimClosePrice; } } class=class="str">"cmt">//--- Determine further actions in case of the uptrend if(Trend==class="num">1) { if(((BeginPrice-OpenPrice[x])/Tick)>class="num">0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick)) { ColumnsInt++; NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimOpenPrice=BeginPrice-(Cell*Tick); InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick); InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits()); Trend=-class="num">1; BeginPrice=InterimClosePrice; } if(((BeginPrice-OpenPrice[x])/Tick)<class="num">0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick)) { NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick); InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits()); Trend=class="num">1; BeginPrice=InterimClosePrice; } } } class=class="str">"cmt">//--- class="kw">return(ColumnsInt); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function for coloring columns | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int FuncColor(class="type">int ColumnsInt) { class="type">int x; class=class="str">"cmt">//--- Fill the buffer of colors for drawing for(x=class="num">0;x<ColumnsInt; x++)
◍ 奇偶缓冲与列绘制的染色分支
在自定义蜡烛渲染里,FirstTrend 决定首根趋势方向,进而用 x 的奇偶性给 CandlesBufferColor 上色:若首趋势为 -1(下行),偶数索引着颜色 1、奇数索引着颜色 0;若首趋势为 1(上行),配色正好反转。这样交替染色能让相邻列在视觉上区分多空段,读者可直接把这段逻辑塞进 OnCalculate 后的着色循环验证。 FuncDraw 负责把价格流重排成「列」结构。它先用 ArrayResize 按 Columns 给 InterimOpen / InterimClose 开临时空间,再把 Trend、BeginPrice、NumberCell、z 全部归零,其中 BeginPrice 锁定 OpenPrice[0] 作为测算起点。 主循环里,当 (Cell*CellForChange) 小于价格偏移的 Tick 绝对值时触发转向判定:BeginPrice 减 OpenPrice[x] 为正判为下行,为负判为上行;NumberCell 由偏移 Tick 数除 Cell 后 MathRound 取整,InterimClose 按 BeginPrice ± NumberCell*Cell*Tick 算出并经 NormalizeDouble 对齐报价小数位。外汇与贵金属点差跳变频繁,这类重绘在高波动时段可能产出非预期列宽,建议先在 MT5 策略测试器用历史数据跑一遍看缓冲分布。
{
if(FirstTrend==-class="num">1)
{
if(x%class="num">2==class="num">0) CandlesBufferColor[x]=class="num">1; class=class="str">"cmt">// All even buffers of class="type">color class="num">1
if(x%class="num">2>class="num">0) CandlesBufferColor[x]=class="num">0; class=class="str">"cmt">// All odd buffers of class="type">color class="num">0
}
if(FirstTrend==class="num">1)
{
if(x%class="num">2==class="num">0) CandlesBufferColor[x]=class="num">0; class=class="str">"cmt">// All odd buffers of class="type">color class="num">0
if(x%class="num">2>class="num">0) CandlesBufferColor[x]=class="num">1; class=class="str">"cmt">// All even buffers of class="type">color class="num">1
}
}
class=class="str">"cmt">//---
class="kw">return(x);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function for determining the column size |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int FuncDraw(class="type">int HistoryInt)
{
class=class="str">"cmt">//--- Determine the sizes of temporary arrays
ArrayResize(InterimOpen,Columns);
ArrayResize(InterimClose,Columns);
class=class="str">"cmt">//--- Zero out auxiliary variables
Trend=class="num">0; class=class="str">"cmt">// Direction of the price trend
BeginPrice=OpenPrice[class="num">0]; class=class="str">"cmt">// Starting price for the calculation
NumberCell=class="num">0; class=class="str">"cmt">// Variable for the calculation of cells
class="type">int z=class="num">0; class=class="str">"cmt">// Variable for indices of temporary arrays
class=class="str">"cmt">//--- Loop for filling the main buffers(column opening and closing prices)
for(class="type">int x=class="num">0; x<HistoryInt; x++)
{
if(Trend==class="num">0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
{
class=class="str">"cmt">//--- Downtrend
if(((BeginPrice-OpenPrice[x])/Tick)>class="num">0)
{
NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
NumberCell=MathRound(NumberCell);
InterimOpen[z]=BeginPrice;
InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
Trend=-class="num">1;
}
class=class="str">"cmt">//--- Uptrend
if(((BeginPrice-OpenPrice[x])/Tick)<class="num">0)
{
NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
NumberCell=MathRound(NumberCell);
InterimOpen[z]=BeginPrice;
InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); class=class="str">"cmt">// Normalize the number of decimal places「下跌与上涨分支里的网格递推逻辑」
这段代码处理的是在已判定 Trend 方向后,如何根据价格偏离步长来更新临时开平仓价与趋势状态。下跌分支里,若当前价相对 BeginPrice 的 Tick 计数为正且未超 Cell,就按格数回撤刷新 InterimClose;若偏离超过 Cell*CellForChange,则反向加仓并翻转 Trend 为 1。 上涨分支对称处理:偏离超 Cell*CellForChange 时新建反向段、Trend 置 -1;未超 Cell 的同向偏离则继续累加 InterimClose。注意 NumberCell 用 MathRound 取整,InterimClose 必须 NormalizeDouble 到当前品种精度,否则跨品种回测会出现点位漂移。 直接把这段粘进 MT5 的 EA 循环里,把 Cell、CellForChange、Tick 打码输出到日志,能用 EURUSD 的 0.00001 点位验证递推是否对齐。外汇与贵金属杠杆高,网格类逻辑在单边行情中可能快速放大浮亏,参数须先在历史数据上压力测试。
Trend=class="num">1; } BeginPrice=InterimClose[z]; } class=class="str">"cmt">//--- Determine further actions in case of the downtrend if(Trend==-class="num">1) { if(((BeginPrice-OpenPrice[x])/Tick)>class="num">0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick)) { NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick); InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); Trend=-class="num">1; BeginPrice=InterimClose[z]; } if(((BeginPrice-OpenPrice[x])/Tick)<class="num">0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick)) { z++; NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimOpen[z]=BeginPrice+(Cell*Tick); InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick); InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); Trend=class="num">1; BeginPrice=InterimClose[z]; } } class=class="str">"cmt">//--- Determine further actions in case of the uptrend if(Trend==class="num">1) { if(((BeginPrice-OpenPrice[x])/Tick)>class="num">0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick)) { z++; NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimOpen[z]=BeginPrice-(Cell*Tick); InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick); InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); Trend=-class="num">1; BeginPrice=InterimClose[z]; } if(((BeginPrice-OpenPrice[x])/Tick)<class="num">0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick)) { NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell; NumberCell=MathRound(NumberCell); InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick); InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); Trend=class="num">1; BeginPrice=InterimClose[z]; }
反转缓冲与网格横线的绘制逻辑
这段 MQL5 实现里,FuncTurnArray 负责把临时数组按倒序填回主缓冲:用 d=ColumnsInt 做递减计数器,从尾端取 InterimOpen/InterimClose 写到下标 x,从而让最新柱线落在索引 0。 阳线判定走 Close>Open 分支,High 取 Close、Low 取 Open;阴线反之。该处理保证后续绘制时高低点不会错位,MT5 里若发现反转后影线异常,先查这里的条件赋值。 FuncDrawHorizontal 在 Draw=true 时先 ObjectsDeleteAll 清旧横线,再用 ArrayMaximum/ArrayMinimum 锁定 OpenPrice 的极值区间。以 Cell*Tick 为步长双向循环 ObjectCreate 画 OBJ_HLINE,样式设为 STYLE_DOT、宽度 1,返回实际创建条数 Horizontal。外汇与贵金属波动大,Cell 与 Tick 取值不当可能让横线过密,建议先在 XAUUSD 的 M5 上以 Cell=10 试渲染。
class="type">int FuncTurnArray(class="type">int ColumnsInt) { class=class="str">"cmt">//--- Variable for array reversal class="type">int d=ColumnsInt; for(class="type">int x=class="num">0; x<ColumnsInt; x++) { d--; CandlesBufferOpen[x]=InterimOpen[d]; CandlesBufferClose[x]=InterimClose[d]; if(CandlesBufferClose[x]>CandlesBufferOpen[x]) { CandlesBufferHigh[x]=CandlesBufferClose[x]; CandlesBufferLow[x]=CandlesBufferOpen[x]; } if(CandlesBufferOpen[x]>CandlesBufferClose[x]) { CandlesBufferHigh[x]=CandlesBufferOpen[x]; CandlesBufferLow[x]=CandlesBufferClose[x]; } } class=class="str">"cmt">//--- class="kw">return(d); } class="type">int FuncDrawHorizontal(class="type">bool Draw) { class="type">int Horizontal=class="num">0; if(Draw==true) { class=class="str">"cmt">//--- Create horizontal lines(lines for separation of columns) ObjectsDeleteAll(class="num">0,ChartWindowFind(),OBJ_HLINE); class=class="str">"cmt">// Delete all old horizontal lines class="type">int MaxPriceElement=ArrayMaximum(OpenPrice); class=class="str">"cmt">// Determine the maximum price level class="type">int MinPriceElement=ArrayMinimum(OpenPrice); class=class="str">"cmt">// Determine the minimum price level for(class="type">class="kw">double x=OpenPrice[class="num">0]; x<=OpenPrice[MaxPriceElement]+(Cell*Tick); x=x+(Cell*Tick)) { ObjectCreate(class="num">0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),class="num">0,NormalizeDouble(x,Digits())); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_WIDTH,class="num">1); Horizontal++; } for(class="type">class="kw">double x=OpenPrice[class="num">0]-(Cell*Tick); x>=OpenPrice[MinPriceElement]; x=x-(Cell*Tick)) {
◍ 把水平线画进图表窗口
这段收尾逻辑负责把算好的价位以水平虚线形式钉在对应图表子窗口上。ObjectCreate 用 DoubleToString(x,Digits()) 当对象名,类型 OBJ_HLINE,挂在 ChartWindowFind() 找到的窗口;价格先用 NormalizeDouble(x,Digits()) 按品种精度收敛,避免 EURUSD 出现 1.23456789 这种越界小数。 随后四行 ObjectSetInteger 分别定颜色、点线样式、不可选中、线宽 1,循环里每画一条 Horizontal++ 计数,画完 ChartRedraw() 强制重绘。外汇与贵金属点差跳变频繁,Digits() 返回 4 或 5 取决于经纪商,硬编码小数位会令对象创建失败。 OnCalculate 首行 ArraySetAsSeries(close,true) 把收盘价反转成时间序列,prev_calculated==0 时走初始化:FuncCopy 拉历史数据,返回 -1 就 Alert 提示仍在加载并 return 0;通过后依次跑 FuncCalculate 算列数、FuncColor 上色、FuncDraw 落线。开 MT5 把这段接进自定义指标,改 LineColor 变量就能直观比对不同价位层的密度。
ObjectCreate(class="num">0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),class="num">0,NormalizeDouble(x,Digits())); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false); ObjectSetInteger(class="num">0,DoubleToString(x,Digits()),OBJPROP_WIDTH,class="num">1); Horizontal++; } ChartRedraw(); } class=class="str">"cmt">//--- class="kw">return(Horizontal); } class=class="str">"cmt">// +++ Main calculations and plotting +++ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- Reverse the array to conveniently get the last price value ArraySetAsSeries(close,true); class=class="str">"cmt">//--- if(prev_calculated==class="num">0) { class=class="str">"cmt">//--- Start the function for copying data for the calculation class="type">int ErrorCopy=FuncCopy(History); class=class="str">"cmt">//--- In case of error, print the message if(ErrorCopy==-class="num">1) { Alert("Failed to copy. Data is still loading."); class="kw">return(class="num">0); } class=class="str">"cmt">//--- Call the function for calculating the number of columns Columns=FuncCalculate(History); class=class="str">"cmt">//--- Call the function for coloring columns class="type">int ColorCalculate=FuncColor(Columns); class=class="str">"cmt">//--- Call the function for determining column sizes class="type">int z=FuncDraw(History);