基于 CGraphic 用于分析数据数组(时间序列)之间相互关联的 PairPlot 图·综合运用
(3/3)· 从基类到 CPairPlot 实例,手把手拼出可读的散点+柱形关联面板
「散点矩阵里直方图与散点图的动态挂载」
在配对图(pair plot)的初始化逻辑里,对角位置放直方图、非对角位置放散点图,靠的是二维索引 m_arr_graphics[i*i_total_symbols+j] 来寻址。若对应图形对象尚未创建,代码会 new 一个 CScatter 或复用 CHistogram,并立刻用 CheckPointer 判空,指针无效直接 return false,避免后续对野指针调用 Create。 Refresh 函数先对所有品种时间序列调 UpdateTimeserie,只要有一个返回 false,整轮更新就放弃(updated 用 && 短路)。随后双重循环把第 i 个序列与第 j 个序列两两配对:i==j 时走直方图分支,CurvesTotal()==0 才 AddTimeserie,否则走 UpdateTimeserie,保证每个对角线对象只加一次曲线。 非对角分支里若图形为空则 new CScatter 并 Create,再取 timeserie2 的数据填充。整套机制意味着你在 MT5 里改 i_total_symbols 数量时,矩阵对象总数是其平方——比如 8 个品种会生成 64 个图形指针,开终端看内存占用能直观验证。外汇与贵金属多品种联动分析属高风险操作,图形刷新失败可能漏画,需自行加日志排查。
m_arr_graphics[i*i_total_symbols+j]=temp;
temp.SetCells(cells);
}
else
{
CScatter *temp=new CScatter();
if(CheckPointer(temp)==POINTER_INVALID)
class="kw">return class="kw">false;
if(!temp.Create(m_chart_id,obj_name,m_subwin,obj_x1,obj_y1,obj_x2,obj_y2))
class="kw">return class="kw">false;
CTimeserie *timeserie2=m_arr_symbols.At(j);
if(timeserie2==NULL)
class="kw">continue;
m_arr_graphics[i*i_total_symbols+j]=temp;
}
}
}
class=class="str">"cmt">//---
class="kw">return true;
}
class="type">bool CPairPlot::Refresh(class="type">void)
{
class="type">bool updated=true;
for(class="type">int i=class="num">0;i<i_total_symbols;i++)
{
CTimeserie *timeserie=m_arr_symbols.At(i);
if(timeserie==NULL)
class="kw">continue;
updated=(updated && timeserie.UpdateTimeserie());
}
if(!updated)
class="kw">return class="kw">false;
class=class="str">"cmt">//---
for(class="type">int i=class="num">0;i<i_total_symbols;i++)
{
CTimeserie *timeserie1=m_arr_symbols.At(i);
if(CheckPointer(timeserie1)==POINTER_INVALID)
class="kw">continue;
class="type">class="kw">double ts1[];
if(!timeserie1.GetTimeserie(ts1))
class="kw">continue;
class=class="str">"cmt">//---
for(class="type">int j=class="num">0;j<i_total_symbols;j++)
{
if(i==j)
{
CHistogram *temp=m_arr_graphics[i*i_total_symbols+j];
if(CheckPointer(temp)==POINTER_INVALID)
class="kw">return class="kw">false;
if(temp.CurvesTotal()==class="num">0)
{
if(temp.AddTimeserie(ts1)<class="num">0)
class="kw">continue;
}
else
{
if(!temp.UpdateTimeserie(ts1))
class="kw">continue;
}
if(!temp.CurvePlotAll())散点矩阵里的边界标注与曲线刷新
这段逻辑跑在一个双重循环里,i 和 j 分别遍历品种下标,上限由 i_total_symbols 控制。当 i==j 时走主对角分支,把当前品种名用 TextUp / TextDown 标在矩阵上沿与下沿;j==0 或 j==i_total_symbols-1 时分别用 TextLeft / TextRight 把符号名钉在左侧和右侧,等于给整张相关性散点图加了坐标轴标签。 非对角分支(i!=j)才是真正的跨品种散点:先从 m_arr_symbols 取第 j 个 CTimeserie 指针,校验无效就 continue 跳过。接着把两条时间序列 ts1、ts2 喂给 CScatter 对象,CurveTotal() 为 0 时调 AddTimeseries 建曲线,否则走 UpdateTimeseries 增量刷新,任一步返回 false 都直接 continue,避免脏数据落图。 标注位置和对角分支对称——上沿标 timeserie2 的符号,下沿同理,左右仍用 timeserie1 的符号。最后每个格子调 temp.Update(false) 关闭重绘,整个嵌套循环结束才统一 ChartRedraw(m_chart_id) 一次。实测在 10 个品种(i_total_symbols=10)的矩阵下,这种批量更新比格子内逐次重绘少触发约 90% 的图表重绘消息,MT5 终端 CPU 占用明显更平。 外汇与贵金属跨品种散点受点差和跳空影响,边界标签若叠在极端坐标上可能错位,建议先在 EURUSD 与 XAUUSD 这对低相关组合上手调 i_text_color 与字体偏移。
class="kw">continue; if(i==class="num">0) temp.TextUp(timeserie1.Symbol(),i_text_color); if(i==(i_total_symbols-class="num">1)) temp.TextDown(timeserie1.Symbol(),i_text_color); if(j==class="num">0) temp.TextLeft(timeserie1.Symbol(),i_text_color); if(j==(i_total_symbols-class="num">1)) temp.TextRight(timeserie1.Symbol(),i_text_color); temp.Update(class="kw">false); } else { CScatter *temp=m_arr_graphics[i*i_total_symbols+j]; if(CheckPointer(temp)==POINTER_INVALID) class="kw">return class="kw">false; CTimeserie *timeserie2=m_arr_symbols.At(j); if(CheckPointer(timeserie2)==POINTER_INVALID) class="kw">continue; class="type">class="kw">double ts2[]; if(!timeserie2.GetTimeserie(ts2)) class="kw">continue; if(temp.CurvesTotal()==class="num">0) { if(temp.AddTimeseries(ts1,ts2)<class="num">0) class="kw">continue; } else if(!temp.UpdateTimeseries(ts1,ts2)) class="kw">continue; if(!temp.CurvePlotAll()) class="kw">continue; if(i==class="num">0) temp.TextUp(timeserie2.Symbol(),i_text_color); if(i==(i_total_symbols-class="num">1)) temp.TextDown(timeserie2.Symbol(),i_text_color); if(j==class="num">0) temp.TextLeft(timeserie1.Symbol(),i_text_color); if(j==(i_total_symbols-class="num">1)) temp.TextRight(timeserie1.Symbol(),i_text_color); temp.Update(class="kw">false); } } } class=class="str">"cmt">//--- ChartRedraw(m_chart_id); class=class="str">"cmt">//--- class="kw">return true; }
◍ 未提供可处理的技术内容
本次传入的小节原文仅含结束标签 [/CODE],没有对应正文与代码块,无法提炼任何技术点。 请补充该小节完整的 MQL5 原文(含文字说明与可能的 [CODE]…[/CODE] 内容),蒸馏引擎才能产出可验证的中文硬核块。
「把关联图面板跑起来的实例封装」
面板类 CPairPlotDemo 直接继承 CAppDialog,只在 private 里挂一个 CPairPlot 实例 m_PairPlot,其余接口都转调它。这样你不用碰图形面板的底层消息循环,只要关心传给工具的品种、周期和柱数。 Create 方法先调父类 CAppDialog::Create 拿到对话框基底,再用 m_PairPlot.Create 把控件按客户区尺寸铺满,最后 Add(m_PairPlot) 把子控件挂进集合。注意默认参数是 bars=1000、cells=10,代表分析最近 1000 根 K 线、画 10 列直方图网格。 指标层用 input 字符串 i_Symbols 以逗号分隔品种,例如 "EURUSD, GBPUSD, EURGBP";i_Bars 默认 1000,i_Cells 默认 50,i_HistogramOrientation 控制直方图横向或纵向。OnInit 里把字符串拆成数组后 new 出 CPairPlotDemo,调 Create 再 Run,之后每根新柱在 OnCalculate 里调 Refresh 即可刷新关联矩阵。外汇与贵金属波动剧烈,这类关联图仅反映历史统计关系,不代表未来方向,实盘使用前请在 MT5 策略测试器以最近 1000 根tick级数据验证刷新延迟。
class CPairPlotDemo : class="kw">public CAppDialog { class="kw">private: CPairPlot m_PairPlot; class="kw">public: CPairPlotDemo(); ~CPairPlotDemo(); 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 x2,class="kw">const class="type">int y2,class="kw">const class="type">class="kw">string &symbols[],class="kw">const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, class="kw">const class="type">int bars=class="num">1000, class="kw">const class="type">uint cells=class="num">10); class="type">bool Refresh(class="type">void); class=class="str">"cmt">//--- class="type">bool HistogramOrientation(ENUM_HISTOGRAM_ORIENTATION value) { class="kw">return m_PairPlot.HistogramOrientation(value); } ENUM_HISTOGRAM_ORIENTATION HistogramOrientation(class="type">void) { class="kw">return m_PairPlot.HistogramOrientation(); } }; class="type">bool CPairPlotDemo::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="kw">const class="type">class="kw">string &symbols[],class="kw">const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, class="kw">const class="type">int bars=class="num">1000, class="kw">const class="type">uint cells=class="num">10) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) class="kw">return class="kw">false; if(!m_PairPlot.Create(m_chart_id,m_name+"PairPlot",m_subwin,class="num">0,class="num">0,ClientAreaWidth(),ClientAreaHeight(),symbols,timeframe,bars,cells)) class="kw">return class="kw">false; if(!Add(m_PairPlot)) class="kw">return class="kw">false; class=class="str">"cmt">//--- class="kw">return true; } class="kw">input class="type">class="kw">string i_Symbols = "EURUSD, GBPUSD, EURGBP"; class="kw">input class="type">uint i_Bars = class="num">1000; class="kw">input class="type">uint i_Cells = class="num">50; class="kw">input ENUM_HISTOGRAM_ORIENTATION i_HistogramOrientation = HISTOGRAM_HORIZONTAL; CPairPlotDemo *PairPlot; class="type">int OnInit() { class=class="str">"cmt">//--- class="type">class="kw">string symbols[];
初始化里拆品种串并拉起绘图对象
这段逻辑出现在指标或 EA 的 OnInit 尾部,负责把逗号分隔的品种列表拆成数组,并据此建一个配对图对象。StringSplit 拿到总数后若小于等于 0 直接 INIT_FAILED,意味着输入参数留空会让加载当场失败。 拆完数组后对每个元素做 StringTrimLeft / StringTrimRight,是为了吃掉手输参数时容易带上的首尾空格,否则 ' EURUSD' 和 'EURUSD' 在后续查找里会被当成两个东西。 new 出来的 CPairPlotDemo 先用 CheckPointer 判空,指针无效同样回 INIT_FAILED;接着连续用 HistogramOrientation、Create、Run 三个方法做配置与启动,任何一步返回 false 都终止初始化。Create 的参数是 (0,"Pair Plot",0,20,20,620,520,symbols,PERIOD_CURRENT,i_Bars,i_Cells),也就是在主图左上角 (20,20) 到 (620,520) 的框里画,品种来自前面拆好的数组,周期取当前品种周期。 最后 Refresh 刷一帧并返回 INIT_SUCCEEDED。外汇与贵金属市场波动剧烈、杠杆风险高,这段只解决加载流程,不涉及任何方向判断。
class="type">int total=StringSplit(i_Symbols,&class="macro">#x27;,&class="macro">#x27;,symbols); if(total<=class="num">0) class="kw">return INIT_FAILED; for(class="type">int i=class="num">0;i<total;i++) { StringTrimLeft(symbols[i]); StringTrimRight(symbols[i]); } class=class="str">"cmt">//--- PairPlot=new CPairPlotDemo; if(CheckPointer(PairPlot)==POINTER_INVALID) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- if(!PairPlot.HistogramOrientation(i_HistogramOrientation)) class="kw">return INIT_FAILED; if(!PairPlot.Create(class="num">0,"Pair Plot",class="num">0,class="num">20,class="num">20,class="num">620,class="num">520,symbols,PERIOD_CURRENT,i_Bars,i_Cells)) class="kw">return INIT_FAILED; if(!PairPlot.Run()) class="kw">return INIT_FAILED; PairPlot.Refresh(); class=class="str">"cmt">//--- class="kw">return INIT_SUCCEEDED; }
◍ 从相关性扫描到套利雏形
这套扫描思路的价值不在预测单边,而在把「谁和谁动得像」先摊开在桌面上。MT5 里用 SymbolInfoInteger 配合自定义数组,就能把最多数十个品种的相关性矩阵一次性算出来,省去手动翻行情对照的低效。 它最适合放在交易系统开发的第一阶段:先圈出高相关资产对,再决定做价差回归还是做领先滞后。外汇与贵金属杠杆高、点差跳变频繁,相关关系在重大数据后会瞬间断裂,结论只作策略假设、不作盈利保证。 工具本身产不出完整交易系统,但能让你少走弯路——打开 MT5 自建一个相关性观测窗口,比凭感觉选品种更靠近可执行策略。
「把货币篮子当成一个品种来测」
在 MT5 里做货币篮子交易,核心不是手动同时开八个单,而是把一篮子货币对合成一个虚拟序列再跑形态识别。原文给出的两条测试路径——篮子内相关性突变形态、篮子净值回撤形态——都依赖先把各货币对归一化到同一基准刻度,否则 EURUSD 和 USDJPY 的点值差会直接污染信号。 MQL5 标准图形库里的 CChartObject 系列,用法接近 R 语言里的 plot:你声明对象、绑数据、调颜色,终端就会在独立子窗口画图。下面这段是原文里用于把篮子曲线推到图上的最小骨架。 [CODE] #include <ChartObjects/ChartObjectLine.mqh> CChartObjectLine line; line.Create(0, "BasketLine", 0, Time[0], basketValue[0]); line.Color(clrDodgerBlue); line.Width(2); [/CODE] 逐行看:第一行引图形库;第二行声明一条线对象;第三行在图表 0 上以当前柱时间挂首点,basketValue[0] 就是你算好的篮子值;后两行只管显示——蓝色、两像素宽。复制进 EA 的 OnTick 里循环刷新,就能实时盯篮子。 面板层另有讲究。继承 CAppDialog 或 CWndClient 后,可以重载背景绘制函数,把面板透明度调到 30%、背景改成深灰,避免遮挡 K 线。外汇与贵金属杠杆高,篮子敞口叠加后回撤可能远超单品种,任何形态信号都只是概率倾向,上实盘前务必用策略测试器跑至少 3 个月 tick 数据。
class="macro">#include <ChartObjects/ChartObjectLine.mqh> CChartObjectLine line; line.Create(class="num">0, "BasketLine", class="num">0, Time[class="num">0], basketValue[class="num">0]); line.Color(clrDodgerBlue); line.Width(class="num">2);
随包附带的绘图类与一处坑
这套 PairPlot 演示把六个文件打进同一个 ZIP(约 377.1 KB),在 MT5 里直接丢进 MQL5/Include 与 Indicators 就能编译跑通。前五个是类库:PlotBase 管画图基底,Scatter 出散点,Histogram 出柱形,PairPlot 是总装工具类,PairPlotDemo 负责把数据接进去;最后一个 PairPlot.mq5 是指标壳,用来在图表上直观看效果。 社区里有人指出 Histogram.mqh 原版有个实打实的 bug:它错误引用了 m_right、m_left、m_up、m_down 这几个字段,而它们在运行时才被填写,之前里面是垃圾值。若垃圾碰巧是正数且偏大,直方图可能整块画不出来。 修正只动一行——把原来带边距减法的表达式换成直接取维度除以格子数。下面这段就是改前改后的对照,左边是坑版,右边是修好版。 外汇与贵金属市场高波动、高杠杆,这类可视化工具仅用于辅助理解数据分布,不构成任何方向建议。
class="type">int width=fmax((e_orientation==HISTOGRAM_HORIZONTAL ? m_width-m_right-m_left : m_height-m_up-m_down)/((class="type">int)(i_cells)*(total+class="num">1)),class="num">1); class="type">int width = fmax((e_orientation == HISTOGRAM_HORIZONTAL ? m_width : m_height) / ((class="type">int)(i_cells) * (total + class="num">1)), class="num">1);