MT5点数图指标开发实战教学
📊

MT5点数图指标开发实战教学

从原理到MQL5代码实现完整解析

技术分析基础 难度 · 进阶 2026-07-08 7 分钟阅读
#点数图#MQL5指标#MT5开发#图表形态

点数图是什么:被时间遗忘的图表

点数图(Point and Figure,简称P&F)是一种早在十九世纪末就已出现的图表类型。1901年7月20日,Charles Dow在《华尔街日报》的评论中首次正式提及这种方法,称之为“书写”法。与今天常见的蜡烛图、折线图不同,点数图完全基于价格行为标绘,而不考虑时间因素。它用垂直列中的X表示上升、O表示下滑,由于去掉了时间轴,趋势线往往以45度角呈现,让交易者能更清晰地识别纯粹的价格运动。

对于交易新手,由Thomas J. Dorsey编著的《点数图:预测和追踪市场价格的基本应用》是了解该方法的优秀入门书籍。尽管Dow当年只在评论中简要介绍,但如今已有大量文献专述此法,足见其在技术分析中持久的生命力。

✦ 核心特征
点数图独一无二之处在于:以价格变动为驱动,而非时间。每一列只记录方向性突破,震荡噪音被格值与转向值过滤。

格值与转向值:点数图的两个基石

点数图的绘制依赖两个预定义参数。格值(Cell)是指添加一个X或一个O所需的最小价格变动量。最初它以每股美元数表示,在MT5指标中我们将其转化为点(point)或tick的倍数。转向值(CellForChange)是指将列从X变为O(或相反)所需的、以“格值”计价的反转量。例如格值10点、转向值3,则价格需反向移动30点才会新开一列。

具体绘制规则如下:选定起始点后,若价格上涨一个格值就在X列顶部加X;下跌一个格值在O列底部加O。若价格反方向变动达到格值×转向值,则另起一列,新列的第一个符号比前一列末端少一个格值(如从X转O时,新O列起点为前高减去一个格值)。这种机制保证了列的切换清晰且不重叠。

手工绘图示例:从分钟数据理解逻辑

假设我们有EURUSD的分钟段数据:第一段高价1.3117、低价1.2989;第二段高1.3118、低1.3093;第三段高1.3101、低1.3080;第四段高1.3134、低1.2955。设格值=10点,转向值=3。首先价格从1.2989到1.3117涨128点,绘12个X。随后跌25点不足转向(30点),不变。接着跌到1.3080,较1.3118变动38点,超30点新开O列,但只放2个O(起点降一格)。之后涨54点新开4个X列,再跌179点新开16个O列。这个粗略示例展示了忽略时间、只记突破的思想。

⚠ 常见误区
手工示例中新列O的数量不是简单用总跌幅除以格值,而是从上一列末端价减一格值作为起点再算,否则会与X列重叠,导致形态误读。

为什么用分钟数据与蜡烛风格绘制

为获得平滑准确的点数图,文章决定采用MQL5的分钟柱开盘价。因为一分钟内波动通常不超过6点,用其可避免日线忽略日内波动的粗糙,也避免秒级数据的过度噪声。MQL5支持7种绘图风格,其中箭头(交易品种)风格最贴合X/O视觉,但每符号需独立缓冲区,内存开销不可控。因此指标作者选用彩色日本烛形(DRAW_COLOR_CANDLES):X列用一种颜色、O列用另一种,仅消耗5个缓冲区,高效且可读。

水平线用于将各列按格值切分,形成网格。最终在日周期主图副窗口中,EURUSD的点数图以红/金烛形展现,直观区分涨跌列。

指标输入参数与OnInit初始化

指标APFD.mq5定义了以下输入:History(计算的分钟柱数量,避免全历史占用资源)、Cell(格值)、CellForChange(转向值)、ColorUp(X列色)、ColorDown(O列色)、LineColor(网格线色)。在OnInit中,绑定5个缓冲区至数组,设置空值为0,计算Tick=SYMBOL_TRADE_TICK_SIZE,配置颜色与数组时间序列索引。若CellForChange<2会弹窗警告,因绘制特性要求至少2。

#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1
#property indicator_type1   DRAW_COLOR_CANDLES
input int   History=10000;
input int   Cell=5;
input int   CellForChange=3;
input color ColorUp=clrRed;
input color ColorDown=clrGold;
input color LineColor=clrAqua;
double CandlesBufferOpen[];
double CandlesBufferHigh[];
double CandlesBufferLow[];
double CandlesBufferClose[];
double CandlesBufferColor[];
double OpenPrice[];
double Tick=0;
int OnInit()
  {
   SetIndexBuffer(0,CandlesBufferOpen,INDICATOR_DATA);
   SetIndexBuffer(1,CandlesBufferHigh,INDICATOR_DATA);
   SetIndexBuffer(2,CandlesBufferLow,INDICATOR_DATA);
   SetIndexBuffer(3,CandlesBufferClose,INDICATOR_DATA);
   SetIndexBuffer(4,CandlesBufferColor,INDICATOR_COLOR_INDEX);
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
   Tick=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,ColorUp);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,1,ColorDown);
   ArraySetAsSeries(CandlesBufferClose,true);
   if(CellForChange<2) Alert("The CellForChange parameter must be more than 1 due to plotting peculiarities");
   return(0);
  }

六大计算函数拆解

OnCalculate仅在前次计算为0时启动全流程,调用六个函数:1) FuncCopy用CopyOpen从M1复制开盘价到OpenPrice;2) FuncCalculate循环确定列数ColumnsInt,用Trend=0/1/-1状态机,以格值×转向值判断新列;3) FuncColor按首趋势奇偶列分配颜色;4) FuncDraw用临时数组存每列开收盘价并确定大小;5) FuncTurnArray将数组反转让图表右到左显示;6) FuncDrawHorizontal用OBJ_HLINE画网格。下面给出核心片段。

int FuncCopy(int HistoryInt)
  {
   ArrayResize(OpenPrice,(HistoryInt));
   int Open=CopyOpen(Symbol(),PERIOD_M1,0,(HistoryInt),OpenPrice);
   return(Open);
  }

int FuncCalculate(int HistoryInt)
  {
   int ColumnsInt=0;
   Trend=0; BeginPrice=OpenPrice[0]; FirstTrend=0; Columns=0;
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           { NumberCell=MathRound(fabs((BeginPrice-OpenPrice[x])/Tick)/Cell);
             InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
             Trend=-1; }
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           { NumberCell=MathRound(fabs((BeginPrice-OpenPrice[x])/Tick)/Cell);
             InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
             Trend=1; }
         BeginPrice=InterimClosePrice; ColumnsInt++; FirstTrend=Trend;
        }
      // ... similar blocks for Trend==-1 and Trend==1
     }
   return(ColumnsInt);
  }

FuncTurnArray中将InterimOpen/Close倒序赋给缓冲区,并据开收大小填High/Low,保证蜡烛实体正确。FuncDrawHorizontal先删旧线,再依最大最小价循环创建点线。由于复杂数组偶尔需重载,作者用OnChartEvent监听'C'(清屏)与'R'(重算)键。

void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_KEYDOWN)
     {
      if(lparam==67) // C key
        { for(int x=0; x<Bars(Symbol(),PERIOD_CURRENT); x++) CandlesBufferOpen[x]=0; ChartRedraw(); }
      if(lparam==82) // R key
        { FuncCopy(History); Columns=FuncCalculate(History); FuncColor(Columns);
          FuncDraw(History); FuncTurnArray(Columns); FuncDrawHorizontal(true); }
     }
  }

标准形态与交易信号

点数图分析分形态法与支撑阻力法。因去时间,支撑阻力线常呈45度。常见形态:双顶(涨-跌O列-再涨超前一X一列,买入)、双底(相反,卖出);三顶/三底(重复两次后突破,强信号);对称三角形突破(升破买、跌破卖);牛市/熊市弹射器(类上升/下降三角形突破);45度趋势线突破(破支撑卖、破阻力买)。这些形态在副窗口指标中可肉眼辨识,也可代码化扫描。

⚠ 实战注意
指标用蜡烛代替X/O,且角度随主图缩放可能变形,45度线未必精确。信号需结合成交量与其他周期验证,不宜单独使用。

局限、扩展与总结

该指标虽生成准确图表,但不足明显:用方块烛形而非标准X/O;不能在策略测试器回测(因依赖分钟复制与事件键);偶发需手动R键重绘。作者指出算法稍改即可用于Renko或Kagi图,也可整合为带菜单的单一指标,甚至主窗口直接绘制。点数图至今活跃,证明去时间分析的价值。本文分享的MQL5实现思路,可作为中文交易者开发自制图表工具的基础模板。

✦ 延伸阅读
同作者另有Renko、Kagi指标文章;社区也有免费P&F指标代码(https://www.mql5.com/zh/code/954)。对比使用可深化理解。

常见问题

默认Cell=5点、CellForChange=3,即反转需15点。日内交易可减小Cell至2-3、转向值2-3;波段用Cell=10、转向值3-5。在指标输入参数中修改,重开图表或按R键重算。
因为它用CopyOpen从实盘M1柱复制且依赖OnChartEvent按键重绘,测试器无实时事件与完整历史分钟流,导致缓冲计算异常。需改写为基于历史序列直接循环才能回测。
看颜色列:红色X列涨、金色O列跌。双顶即一段红列后金列回撤,再红列创新高(高于前红列顶一格),此时为买入信号,可结合水平网格线确认。
需将#property indicator_separate_window去掉,用OnCalculate中往主图缓冲写值,并将绘图风格改DRAW_COLOR_CANDLES于主窗,调整坐标映射。原代码数组反转逻辑保留,但需处理时间轴对齐。
确认图表获得焦点且未屏蔽按键事件;检查Expert Advisors中允许指标事件。若仍无重绘,用MetaEditor重新编译APFD.mq5,并确认History参数足够大以覆盖当前数据。