在交易中应用 OLAP(第 2 部分):可视化交互式多维数据分析的结果·综合运用
📊

在交易中应用 OLAP(第 2 部分):可视化交互式多维数据分析的结果·综合运用

(3/3)· 当 OLAP 超立方体遇上可拉伸窗口,硬编码坐标的静态弹窗终于可以退休了

进阶 第 3/3 篇
多数 MQL5 GUI 库把按钮和标签写死在 x/y 坐标上,窗口一拉伸标签就叠成一团。想看完整 OLAP 轴名,交易者只能眯着眼在日志里翻文本。用布局容器接管定位,界面会自己顺着窗口大小重排。

「用下拉控件搭一个可交互的 OLAP 分析面板」

新建一个 EA(OLAPGUI),把窗口、控件响应和 OLAP 调用全部收进 OLAPGUI.mqh 头文件,EA 自身只留与 HTML/CSV 导入有关的输入:ReportFile、Prefix、Suffix。若 ReportFile 留空,EA 直接分析当前账户的历史成交,这一点在 MT5 里打开任意实盘或模拟账户都能验证。 面板第一排放 3 个下拉列表对应 X/Y/Z 三个条件轴的选择器,右边缘放“处理”按钮触发分析;第二排两个下拉分别选聚合函数和聚合字段,再加排序与图表样式下拉,筛选器刻意拿掉以降低 UI 复杂度。下拉选项把选择器类型和输出字段合并显示,例如 symbol 对应 FIELD_SYMBOL,lot 对应 FIELD_LOT,带 * 的 TradeSelector/QuantizationSelector 由聚合器决定——IdentityAggregator 用前者,其余用后者。 支持的聚合函数共 9 种:sum、average、max、min、count、profit factor、progressive total、identity,除 identity 外都需在右侧下拉指定聚合字段。progressive total 必须配 X 轴选 ordinal 选择器,表示按记录顺序累加。若只选了 X 选择器,排序下拉才可用的逻辑能在代码里直接核对。 Z 轴不用三维投影,而是用“处理”按钮在多个 Z 平面间翻页:存在 z 坐标时按钮标题变成“i/n title”,i 是当前层编号,n 是样本总数;改了构建立即复位成“处理”。identity 聚合器例外,永远两维且 X/Y/Z 三条曲线同图不滚动。 除图形外每个超立方体都会写日志。用 lot 作 X、profit amount 作 sum 时,图表 X 轴只显示整数单元格索引(0、1、2…),真实手数在日志里以 title 给出。下面这段日志样本来自 0.01~1.0 手区间的成交:0 号格利润 365.96 对应手数 0.01,2 号格 4.65 对应 0.03,3 号格 15.98 对应 0.06——说明小仓位反而出了最大单笔利润,外汇贵金属高杠杆下这种分布很常见,复盘时别只看聚合总额。 GUI 与 OLAP 核心通过 OLAPWrapper 层衔接,原 OLAPDEMO 的 process 函数变成类方法,设置改由控件状态填参传入。因 MQL5 不支持多重继承,OLAPDialog 继承自 MaximableAppDialog 后,由辅助类 OLAPDisplay 实现 display 接口,核心创建立方体后调用它把维度、标题和向量推给窗口。标准柱状图样式无法偏移同索引多数组,可能重叠,自定义绘制可借 CGraphic 解决但不在本节流中。

MQL5 / C++
Selectors: class="num">1
SumAggregator<TRADE_RECORD_FIELDS> FIELD_PROFIT_AMOUNT [class="num">6]
X: QuantizationSelector(FIELD_LOT) [class="num">6]
===== QuantizationSelector(FIELD_LOT) =====
[value] [title]
[class="num">0] class="num">365.96000 "class="num">0.01"
[class="num">1]   class="num">0.00000 "class="num">0.0"
[class="num">2]   class="num">4.65000 "class="num">0.03"
[class="num">3]  class="num">15.98000 "class="num">0.06"
[class="num">4]  class="num">34.23000 "class="num">0.02"
[class="num">5]   class="num">0.00000 "class="num">1.0"
class OLAPWrapper
{
class="kw">protected:
Selector<TRADE_RECORD_FIELDS> *createSelector(class="kw">const SELECTORS selector, class="kw">const TRADE_RECORD_FIELDS field);
class="kw">public:
class="type">void process(
class="kw">const SELECTORS &selectorArray[], class="kw">const TRADE_RECORD_FIELDS &selectorField[],
class="kw">const AGGREGATORS AggregatorType, class="kw">const TRADE_RECORD_FIELDS AggregatorField, Display &display,
class="kw">const SORT_BY SortBy = SORT_BY_NONE,
class="kw">const class="type">class="kw">double Filter1value1 = class="num">0, class="kw">const class="type">class="kw">double Filter1value2 = class="num">0)
{
class="type">int selectorCount = class="num">0;
for(class="type">int i = class="num">0; i < MathMin(ArraySize(selectorArray), class="num">3); i++)
{
selectorCount += selectorArray[i] != SELECTOR_NONE;
}
...
HistoryDataAdapter<CustomTradeRecord> history;
HTMLReportAdapter<CustomTradeRecord> report;
CSVReportAdapter<CustomTradeRecord> external;
DataAdapter *adapter = &history;
if(ReportFile != "")
{
if(StringFind(ReportFile, ".htm") > class="num">0 && report.load(ReportFile))
{
adapter = &report;
}
else
if(StringFind(ReportFile, ".csv") > class="num">0 && external.load(ReportFile))
{
adapter = &external;
}
else
{
Alert("未知的文件格式: ", ReportFile);
class="kw">return;
}
}
else
{

把成交记录塞进 OLAP 面板跑多维聚合

MQL5 不支持多重继承,所以显示层得用委托对象 OLAPDisplay 包一层,把 Dialog 指针传进去,才能在不破坏类结构的前提下接管立方体数据的渲染。 核心触发在 OnClickButton:从下拉框读出聚合算法 at、字段 af、排序 sb,再把四个轴的 Selectors/Fields 初始化成 NONE,交给 olapcore.process 跑实际计算。 [CODE] void OLAPDialog::OnClickButton(void) { SELECTORS Selectors[4]; TRADE_RECORD_FIELDS Fields[4]; AGGREGATORS at = (AGGREGATORS)m_algo[0].Value(); TRADE_RECORD_FIELDS af = (TRADE_RECORD_FIELDS)(AGGREGATORS)m_algo[1].Value(); SORT_BY sb = (SORT_BY)m_algo[2].Value(); ArrayInitialize(Selectors, SELECTOR_NONE); ArrayInitialize(Fields, FIELD_NONE); ... olapcore.process(Selectors, Fields, at, af, olapdisplay, sb); } [/CODE] 上面这段里,m_algo[0] 取聚合器类型,m_algo[1] 取目标字段,m_algo[2] 取排序方式;ArrayInitialize 把轴选择清空,避免上一次筛选污染本次结果。 当维度只有 1 个时,display 会走 getVector 拿到 PairArray,先 Print 出标题与维度范围,再调 parent.accept1D 把曲线塞给 CPlot,最后 finalize 刷新图表、按钮文字复原为 Process。 [CODE] if(selectorCount == 1) { PairArray *result; if(metaData.getVector(0, consts, result, sortby)) { Print("===== " + metaData.getDimensionTitle(0) + " ====="); ArrayPrint(result.array); parent.accept1D(result, metaData.getDimensionTitle(0)); } parent.finalize(); return; } [/CODE] 你在 MT5 里加载后,点一次 Process 按钮,日志会打出类似「EURUSD [120]」这样的维度规模——表示当前轴下有 120 个离散值,据此可判断历史样本是否够薄。外汇与贵金属杠杆高,历史聚合仅反映过去分布,对后续走势只具概率参考。

MQL5 / C++
class="type">void OLAPDialog::OnClickButton(class="type">void)
{
  SELECTORS Selectors[class="num">4];
  TRADE_RECORD_FIELDS Fields[class="num">4];
  AGGREGATORS at = (AGGREGATORS)m_algo[class="num">0].Value();
  TRADE_RECORD_FIELDS af = (TRADE_RECORD_FIELDS)(AGGREGATORS)m_algo[class="num">1].Value();
  SORT_BY sb = (SORT_BY)m_algo[class="num">2].Value();
  ArrayInitialize(Selectors, SELECTOR_NONE);
  ArrayInitialize(Fields, FIELD_NONE);
  ...
  olapcore.process(Selectors, Fields, at, af, olapdisplay, sb);
}

if(selectorCount == class="num">1)
{
  PairArray *result;
  if(metaData.getVector(class="num">0, consts, result, sortby))
  {
    Print("===== " + metaData.getDimensionTitle(class="num">0) + " =====");
    ArrayPrint(result.array);
    parent.accept1D(result, metaData.getDimensionTitle(class="num">0));
  }
  parent.finalize();
  class="kw">return;
}

◍ 文件清单与落地路径

这套可缩放 GUI 方案最终落在四个 MT5 项目里:OLAPGUI 负责把交易报告做成可拖拽的 OLAP 面板,SlidingPuzzle3、Controls3、PlotDemo 则分别演示网格布局、可改尺寸控件和图表控件的复用方式。想直接看效果,把 MQLOLAP2.zip(73.86 KB)解进 MT5 的 MQL5 目录,编译 Experts/OLAP/OLAPGUI.mq5 即可。 头文件层级值得记一下:Include/Layouts 下的 Box.mqh、MaximizableAppDialog.mqh 是尺寸自适应的底层,Include/OLAP 里的 OLAPcube.mqh 与 PairArray.mqh 承接了第一篇的立方体逻辑。若你用较新的 MQL5 编译器,fxsaber 在 2022 年提到过语言变动导致需小幅重构,作者也确认会按反馈修兼容性。 外汇与贵金属复盘本身高风险,这类多维交叉分析只帮你识别历史样本里的隐藏偏好,不预示未来盈亏;拿去优化系统前,先在策略测试器跑几组不同品种验证稳定性。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多维持仓分布的自适应视图,把重复劳动交给小布,你专注决策。

常见问题

标准库窗口尺寸固定,轴标签长时会重叠且无法最大化,CBox/CGrid 布局容器可随窗口缩放自动重排,更适配宽表数据。
小布盯盘的品种页已内置自适应多维视图,无需自行编译 MQL 类即可查看成交分布与时段剖面,外汇贵金属属高风险,视图仅作概率参考。
从原文章存档中取 Box.mqh、Grid.mqh、GridTk.mqh,置于 Include/Layouts 目录下再 #include,嵌套布局才能正常解析。
可先以最大化窗口启动脚本,或在 CGrid 外再包一层 CBox 垂直布局,手动触发重绘事件补偿缩放,后续再补 resize 逻辑。