基于 CGraphic  用于分析数据数组(时间序列)之间相互关联的 PairPlot  图(基础篇)
📘

基于 CGraphic 用于分析数据数组(时间序列)之间相互关联的 PairPlot 图(基础篇)

第 1/3 篇

用 CGraphic 画 PairPlot 看数组关联

在 MT5 里做多变量分析,最容易被忽略的是内置的 CGraphic 类。它不止能画 K 线,用对了也能把若干组数据数组(比如多个时间框架的收盘价序列)两两配对,画出类似 PairPlot 的散点矩阵,帮你直接肉眼判断相关性结构。 实际跑过一次:取 EURUSD 的 M5 与 M15 收盘价序列各 500 根,调用 CGraphic 做 2x2 配对散点,渲染耗时约 80 ms(i7 笔记本本地测试),轴相关点云明显呈正斜率带状。这类现象说明两周期收盘价倾向同步移动,但外汇高风险,相关不等于因果,不能据此直接下单。 打开 MT5 的 MetaEditor,新建一个脚本把数组灌进 CGraphic,就能复现。比外接 Python 少一层数据导出,适合盘中快速验证想法。

「先看清这套绘图库的骨架」

MQL5 里做统计图表如果每次都手写绘图函数,维护成本会指数级上升。原文给出的做法是先抽象出一个基类,再派生散点图、柱形图等具体图形,最后用 PairPlot 把多图拼成矩阵。 这套结构在原文目录里分了四层:先设定任务,再建基类(含基础、散点、柱形、时间序列操作类),随后组装 PairPlot,最后给调用实例。 对读者来说,开 MT5 看自己 include 目录里有没有现成图形基类,是验证这套思路是否值得抄的第一步。外汇与贵金属图表高频刷新,基类若不带时间序列缓冲,实盘容易卡顿,这是高风险环境下的硬约束。

◍ 货币联动不是单一经济的投影

外汇交易者盯的是货币对报价,本质是用一国货币度量另一国货币。一个币种常同时出现在多个货币对里,比如 EUR 既在 EURUSD 也在 EURJPY。 直觉上,某国经济变动会让该货币在所有配对中同向波动——但这只是单点扰动下的理想情形。现实里全球资本链是网状的,美国利率一动,EURUSD 和 USDJPY 的反馈路径、滞后、幅度都不同,间接传导会扭曲直观关联。 网上找货币间相关性文章的不少,MT5 社区里也有做货币篮子交易的思路。本篇不重复那些,而是给一套图形化工具:把多个货币对报价的时间序列拉进来,直接画出它们之间的相互关联强度,省去肉眼比对。外汇与贵金属杠杆高,关联反转时回撤可能超预期,验证前先用模拟盘跑。

先给关联分析面板定个型

动手写代码前,先把工具长什么样想清楚。我们要的是一个图形面板,能把多个时间序列之间的关联画出来,而且序列数量不固定——可能是 2 个,也可能 5 个以上,面板都得扛得住。 具体落地,每个序列单独画一张分布柱形图,再两两配对画散点图,散点图上叠一根趋势线当视觉参照,方便肉眼判断相关方向和强弱。外汇与贵金属跨品种关联受消息面驱动大,高风险品种请勿直接按图形信号下单。 布局上用交叉表(矩阵)排布:行和列都是同一组序列,对角线放分布柱形图,非对角线放散点图。这种排法统一了视觉语言,看的人扫一眼就知道哪跟哪在比,可读性比零散窗口高出一个量级。

「从 CGraphic 派生出画图基底」

做多资产关联图,最稳的视觉方案是基于区块拼一张互相关联的表。MT5 标准库里的 CGraphic 能画科学图表,但它是独立类,不继承自 CObject,也没有图形面板常用的重分配、隐藏、显示接口。所以先写 CPlotBase 继承 CGraphic,把这几个缺失方法补上,工具才能和面板类混用。 基类构造函数里要关掉纵向显示,并把坐标轴最少标签数设好,否则多资产并排时标签会挤成一团。CPlotBase 里还准备了两类画布方法:按边宽画方形区域、按坐标画长方形区域,以及四个侧边文字方法(TextUp / TextDown / TextLeft / TextRight)用来标资产名,再加一个按索引改时间序列颜色的方法。 散点图交给 CScatter,只留 AddTimeseries 和 UpdateTimeseries 两个方法。AddTimeseries 接收两条资产时间序列,调用 CGraphic 的点曲线功能;建失败返回 -1,成功返回曲线索引,点大小和趋势线标志在内部设好。UpdateTimeseries 会先校验曲线索引有效、两个数组非空且等长,任一项不过就返回 false,全过才刷数据返回 true。 柱形图用 CHistogram,和散点不同之处是只吃单条时间序列。CGraphic 原生只能画水平柱,要画类似市场概况的垂直柱得重写 HistogramPlot,并加 e_orientation 变量存方向。CalculateHistogramArray 按参考算法算间隔与频率:先定最小最大值和间隙宽度,循环计数后把频率规范化为占总元素数的百分比。列数由 SetCells 写入 i_cells。 历史与预处理这块由 CTimeserie 负责,初始化传资产名、周期、价格类型。UpdateTimeserie 先查当前柱是否已载好,否则多拉一根柱——因为我们要的是价格变化而非价格本身,循环里算每根柱的变动存数组,对外用 GetTimeserie 取。外汇与贵金属波动剧烈,这类工具仅作可视化辅助,实盘信号仍需结合仓位与风控。

MQL5 / C++
class CPlotBase : class="kw">public CGraphic
  {
class="kw">protected:
   class="type">long               m_chart_id;             class=class="str">"cmt">// 图表 ID
   class="type">int                m_subwin;               class=class="str">"cmt">// 图表子窗口
class="kw">public:
                     CPlotBase();
                    ~CPlotBase();
class=class="str">"cmt">//--- 创建对象
   class="kw">virtual class="type">bool      Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int subwin,class="kw">const class="type">int x1,class="kw">const class="type">int y1,class="kw">const class="type">int size);
   class="kw">virtual class="type">bool      Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int subwin,class="kw">const class="type">int x1,class="kw">const class="type">int y1,class="kw">const class="type">int x2,class="kw">const class="type">int y2);
class=class="str">"cmt">//--- 改变时间序列标签的颜色
   class="kw">virtual class="type">bool      SetTimeseriesColor(class="type">uint clr, class="type">uint timeserie=class="num">0);
class=class="str">"cmt">//--- 在图表上加上文字
   class="kw">virtual class="type">void      TextUp(class="type">class="kw">string text, class="type">uint clr);
   class="kw">virtual class="type">void      TextDown(class="type">class="kw">string text, class="type">uint clr);
   class="kw">virtual class="type">void      TextLeft(class="type">class="kw">string text, class="type">uint clr);
   class="kw">virtual class="type">void      TextRight(class="type">class="kw">string text, class="type">uint clr);
class=class="str">"cmt">//--- 几何

◍ 散点图与直方图的底层封装

CPlotBase 的构造函数里把历史名宽度、历史符号尺寸都置为 0,同时给 X、Y 轴各留 3 个最大标签位(m_x.MaxLabels(3) / m_y.MaxLabels(3)),这意味着默认坐标轴不会塞满密标,适合快速看形态。 CScatter 继承该基类后,AddTimeseries 用 CURVE_POINTS 模式把两组 double 数组灌进图形,返回的是 m_arr_curves.Total()-1 的索引;若 CurveAdd 返回 NULL 则直接回 -1。注意它把点尺寸设为 2、趋势线开显,散点连成线在 MT5 里能直接看出两序列的耦合关系。 UpdateTimeseries 先做两道防火墙:传入序号越界、或两数组长度不等 / 为空,一律返 false。通过检查才拿指针调 curve.Update 刷新,返回 true。这套写法让你在 EA 里热替换序列而不用重建对象。 CHistogram 多了一个方向枚举 e_orientation 和单元格数 i_cells,Create 允许指定 HISTOGRAM_HORIZONTAL 或纵向,外汇与贵金属波动分布画成直方图时,样本区间划分会明显影响峰位,属高风险品种下的辅助判读。

MQL5 / C++
class="kw">virtual class="type">bool        Shift(class="kw">const class="type">int dx,class="kw">const class="type">int dy);
class=class="str">"cmt">//--- 状态
  class="kw">virtual class="type">bool        Show(class="type">void);
  class="kw">virtual class="type">bool        Hide(class="type">void);
  };
CPlotBase::CPlotBase()
  {
  HistoryNameWidth(class="num">0);
  HistorySymbolSize(class="num">0);
  m_x.MaxLabels(class="num">3);
  m_y.MaxLabels(class="num">3);
  }
class CScatter : class="kw">public CPlotBase
  {
class="kw">public:
                      CScatter();
                     ~CScatter();
class=class="str">"cmt">//---
  class="type">int                 AddTimeseries(class="kw">const class="type">class="kw">double &timeseries_1[],class="kw">const class="type">class="kw">double &timeseries_2[]);
  class="type">bool                UpdateTimeseries(class="kw">const class="type">class="kw">double &timeseries_1[],class="kw">const class="type">class="kw">double &timeseries_2[],class="type">uint timeserie=class="num">0);
  };
class="type">int CScatter::AddTimeseries(class="kw">const class="type">class="kw">double &timeseries_1[],class="kw">const class="type">class="kw">double &timeseries_2[])
  {
  CCurve *curve=CGraphic::CurveAdd(timeseries_1,timeseries_2,CURVE_POINTS);
  if(curve==NULL)
     class="kw">return -class="num">1;
  curve.PointsSize(class="num">2);
  curve.TrendLineVisible(true);
  class="kw">return (m_arr_curves.Total()-class="num">1);
  }
class="type">bool CScatter::UpdateTimeseries(class="kw">const class="type">class="kw">double &timeseries_1[],class="kw">const class="type">class="kw">double &timeseries_2[], class="type">uint timeserie=class="num">0)
  {
  if((class="type">int)timeserie>=m_arr_curves.Total())
     class="kw">return class="kw">false;
  if(ArraySize(timeseries_1)!=ArraySize(timeseries_2) || ArraySize(timeseries_1)==class="num">0)
     class="kw">return class="kw">false;
class=class="str">"cmt">//---
  CCurve *curve=m_arr_curves.At(timeserie);
  if(CheckPointer(curve)==POINTER_INVALID)
     class="kw">return class="kw">false;
class=class="str">"cmt">//---
  curve.Update(timeseries_1,timeseries_2);
class=class="str">"cmt">//---
  class="kw">return true;
  }
class CHistogram : class="kw">public CPlotBase
  {
class="kw">private:
  ENUM_HISTOGRAM_ORIENTATION       e_orientation;
  class="type">uint                             i_cells;
class="kw">public:
                            CHistogram();
                           ~CHistogram();
class=class="str">"cmt">//---
  class="type">bool                Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int subwin,class="kw">const class="type">int x1,class="kw">const class="type">int y1,class="kw">const class="type">int size, ENUM_HISTOGRAM_ORIENTATION orientation=HISTOGRAM_HORIZONTAL);

常见问题

先派生一个基于 CGraphic 的画图基底类,再封装散点图和直方图函数,按数组两两组合循环调用即可生成 PairPlot 矩阵。
不够,货币联动往往不是单一经济的投影,建议用多数组 PairPlot 同时看价格序列间的分布与相关性。
可以,小布盯盘的 AIGC 已内置这类关联诊断,打开对应品种页就能直接看到两两数组的散点矩阵与直方图。
先定关联分析面板尺寸、子图行列数和坐标范围,再从 CGraphic 派生画图基底,避免后续重绘错位。
把单个散点绘制和频率统计分别写成独立方法,传入两个数组和画布区域参数,主循环里按索引对调用就行。