在交易中应用 OLAP(第 3 部分):为开发交易策略而分析报价·进阶篇
(2/3)· 还在用肉眼翻 K 线找规律?多维聚合能把报价压成可筛选的立方体
把 MqlRates 塞进可分析的记录结构
做价格行为统计的第一步,是把 MT5 每根 K 线的原始结构体 MqlRates 转成带字段标签的记录对象。下面这段构造逻辑直接把 high/low/close 落库,同时算了两根区间:OC 用收盘价减开盘价除以 _Point,HL 则乘上收盘相对开盘的方向符号,正数代表阳线区间、负数代表阴线区间,单位都是点。 [CODE] set(FIELD_PRICE_HIGH, rate.high); set(FIELD_PRICE_LOW, rate.low); set(FIELD_PRICE_CLOSE, rate.close); set(FIELD_PRICE_RANGE_OC, (rate.close - rate.open) / _Point); set(FIELD_PRICE_RANGE_HL, (rate.high - rate.low) * MathSign(rate.close - rate.open) / _Point); set(FIELD_SPREAD, (double)rate.spread); set(FIELD_TICK_VOLUME, (double)rate.tick_volume); set(FIELD_REAL_VOLUME, (double)rate.real_volume); [/CODE] 逐行拆解:前 3 行存高低收价格;第 4 行算实体点数(close-open 转点);第 5 行算影线加实体的带符号区间,阴线时整体取负;后 3 行把 spread 与两类成交量从整型强转 double,方便后续统一聚合。 数据类型表用 13+4 个字段描述一条记录:i 表示整型(序号、方向、点差、tick、real),d 表示双精度(价格与各类区间、4 个自定义字段)。你在 MT5 里改自定义字段数量时,要同步动 QUOTE_RECORD_FIELDS_LAST 和 datatypes 数组,否则 legend() 会返回 unknown。 适配器 QuotesDataAdapter 在 reset() 里用 MathMin(Bars(_Symbol,_Period), TerminalInfoInteger(TERMINAL_MAXBARS)) 限制读取上限,避免历史太多拖死回测。getNext() 从最新一根倒序取 CopyRates,每调一次 cursor 减 1,直到 0 返回 NULL,这套机制让你可以直接拿它喂给 OLAP 引擎做形状分组。外汇与贵金属波动受杠杆与事件影响,倒序回测结果仅反映历史概率,实盘仍属高风险。
set(FIELD_PRICE_HIGH, rate.high); set(FIELD_PRICE_LOW, rate.low); set(FIELD_PRICE_CLOSE, rate.close); set(FIELD_PRICE_RANGE_OC, (rate.close - rate.open) / _Point); set(FIELD_PRICE_RANGE_HL, (rate.high - rate.low) * MathSign(rate.close - rate.open) / _Point); set(FIELD_SPREAD, (class="type">class="kw">double)rate.spread); set(FIELD_TICK_VOLUME, (class="type">class="kw">double)rate.tick_volume); set(FIELD_REAL_VOLUME, (class="type">class="kw">double)rate.real_volume);
◍ 选择器分支与引擎初始化的落地写法
上面这段是 OLAP 行情引擎里按枚举派发选择器实例的核心分支。SELECTOR_WEEKDAY、SELECTOR_DAYHOUR、SELECTOR_HOURMINUTE 都直接挂到 FIELD_DATETIME 上,说明时间维度的切片在框架里被当成一类统一处理;而 SCALAR、QUANTS、FILTER 则要求 field 不等于 FIELD_NONE 才 new 对象,否则返回 NULL,避免了对空字段建选择器导致的运行期异常。 注意 SELECTOR_DAYHOUR 和 SELECTOR_HOURMINUTE 在复用时都走了 DayHourSelector 模板,只是语义层分开,如果你要在 MT5 里做分钟级聚合,得自己补一个独立的 MinuteSelector,否则会沿用小时切片逻辑。外汇与贵金属行情具有高杠杆高风险,这类聚合偏差可能放大止损误差,先在策略测试器里跑小周期验证。 initialize() 里只做了一件事:Print 出 QuotesRecord::getRecordCount() 的读数。这个数字就是实际载入的 K 线条数,开 MT5 跑完 EA 初始化后去专家日志看,若和你指定的历史范围不符,八成是 DataAdapter 没正确绑定 symbol 或周期。 _defaultQuotesAdapter 和 _defaultEngine 是栈上全局实例,构造时走默认或带 DataAdapter* 的重载。想换数据源,直接传自定义 adapter 指针给 OLAPEngineQuotes(ptr) 即可,不用改选择器逻辑。
case SELECTOR_WEEKDAY: class="kw">return new WorkWeekDaySelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME); case SELECTOR_DAYHOUR: class="kw">return new DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME); case SELECTOR_HOURMINUTE: class="kw">return new DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME); case SELECTOR_SCALAR: class="kw">return field != FIELD_NONE ? new BaseSelector<QUOTE_RECORD_FIELDS>(field) : NULL; case SELECTOR_QUANTS: class="kw">return field != FIELD_NONE ? new QuantizationSelector<QUOTE_RECORD_FIELDS>(field, QuantGranularity) : NULL; case SELECTOR_FILTER: class="kw">return field != FIELD_NONE ? new FilterSelector<QUOTE_RECORD_FIELDS>(field) : NULL; } class="kw">return NULL; } class="kw">virtual class="type">void initialize() class="kw">override { Print("Bars read: ", QuotesRecord::getRecordCount()); } class="kw">public: OLAPEngineQuotes(): OLAPEngine() {} OLAPEngineQuotes(DataAdapter *ptr): OLAPEngine(ptr) {} }; QuotesDataAdapter<RECORD_CLASS> _defaultQuotesAdapter; OLAPEngineQuotes _defaultEngine;
「用 OLAP 思路把报价切成可分析的维度」
非交易型 EA OLAPQTS.mq5 的结构和分析交易报告的脚本类似,核心是把每根报价封装成带自定义字段的记录类。CustomQuotesRecord 继承自 QuotesRecord,在 fillCustomFields 里算形态字段,这些字段后续能当策略地基用。 要让默认适配器认得自己的记录类,必须在 include OLAPQuotes.mqh 之前定义宏 RECORD_CLASS。代码里这行 #define RECORD_CLASS CustomQuotesRecord 就是插进默认适配器的接口,漏掉会导致引擎只建基类实例、自定义字段全空。 EA 用 X/Y/Z 三个轴选维度,再叠加一个过滤器(支持单值或范围)和聚合器。所有选择器与字段都塞进 _selectorArray[4] 和 _selectorField[4] 两个数组,直接丢给引擎处理,不用在 UI 之外写死逻辑。 过滤器值以字符串输入,按字段类型自动分流:若 datatype 返回 't'(时间型),用 StringToTime 转成 double;否则走 StringToDouble。这比第一篇文章里强制手敲数字友好,用户填 YYYY.MM.DD 就能按日期切报价。 OnInit 里 EventSetTimer(1) 拉起 1 秒计时器,OnTimer 首行先 EventKillTimer 自杀,保证只跑一次数据处理——周末没即时报价也能回看历史。处理完经 LogDisplay 把结果打印到 EA 日志,外汇与贵金属报价波动剧烈、杠杆风险高,这类离线切片仅作概率倾向参考,别直接当入场信号。
class CustomQuotesRecord: class="kw">public QuotesRecord { class="kw">public: CustomQuotesRecord(): QuotesRecord() {} CustomQuotesRecord(class="kw">const class="type">MqlRates &rate): QuotesRecord(rate) { } class="kw">virtual class="type">void fillCustomFields() class="kw">override { class=class="str">"cmt">// ... } class="kw">virtual class="type">class="kw">string legend(class="kw">const class="type">int index) class="kw">const class="kw">override { class=class="str">"cmt">// ... class="kw">return QuotesRecord::legend(index); } }; class=class="str">"cmt">// this line plugs our class into class="kw">default adapter in OLAPQuotes.mqh class="macro">#define RECORD_CLASS CustomQuotesRecord class="macro">#include <OLAP/OLAPQuotes.mqh> sinput class="type">class="kw">string X = "————— X axis —————"; class=class="str">"cmt">// · X · class="kw">input SELECTORS SelectorX = DEFAULT_SELECTOR_TYPE; class=class="str">"cmt">// · SelectorX class="kw">input ENUM_FIELDS FieldX = DEFAULT_SELECTOR_FIELD class=class="str">"cmt">/* field does matter only for some selectors */; class=class="str">"cmt">// · FieldX sinput class="type">class="kw">string Y = "————— Y axis —————"; class=class="str">"cmt">// · Y · class="kw">input SELECTORS SelectorY = SELECTOR_NONE; class=class="str">"cmt">// · SelectorY class="kw">input ENUM_FIELDS FieldY = FIELD_NONE; class=class="str">"cmt">// · FieldY sinput class="type">class="kw">string Z = "————— Z axis —————"; class=class="str">"cmt">// · Z · class="kw">input SELECTORS SelectorZ = SELECTOR_NONE; class=class="str">"cmt">// · SelectorZ class="kw">input ENUM_FIELDS FieldZ = FIELD_NONE; class=class="str">"cmt">// · FieldZ sinput class="type">class="kw">string F = "————— Filter —————"; class=class="str">"cmt">// · F · class="kw">input SELECTORS _Filter1 = SELECTOR_NONE; class=class="str">"cmt">// · Filter1 class="kw">input ENUM_FIELDS _Filter1Field = FIELD_NONE; class=class="str">"cmt">// · Filter1Field class="kw">input class="type">class="kw">string _Filter1value1 = ""; class=class="str">"cmt">// · Filter1value1 class="kw">input class="type">class="kw">string _Filter1value2 = ""; class=class="str">"cmt">// · Filter1value2 sinput class="type">class="kw">string A = "————— Aggregator —————"; class=class="str">"cmt">// · A · class="kw">input AGGREGATORS _AggregatorType = DEFAULT_AGGREGATOR_TYPE; class=class="str">"cmt">// · AggregatorType class="kw">input ENUM_FIELDS _AggregatorField = DEFAULT_AGGREGATOR_FIELD; class=class="str">"cmt">// · AggregatorField class="kw">input SORT_BY _SortBy = SORT_BY_NONE; class=class="str">"cmt">// · SortBy SELECTORS _selectorArray[class="num">4]; ENUM_FIELDS _selectorField[class="num">4]; class="type">int OnInit() { _selectorArray[class="num">0] = SelectorX; _selectorArray[class="num">1] = SelectorY; _selectorArray[class="num">2] = SelectorZ; _selectorArray[class="num">3] = _Filter1; _selectorField[class="num">0] = FieldX; _selectorField[class="num">1] = FieldY; _selectorField[class="num">2] = FieldZ; _selectorField[class="num">3] = _Filter1Field; _defaultEngine.setAdapter(&_defaultQuotesAdapter); EventSetTimer(class="num">1); class="kw">return INIT_SUCCEEDED; } LogDisplay _display(class="num">11, _Digits); class="type">void OnTimer() { EventKillTimer(); class="type">class="kw">double Filter1value1 = class="num">0, Filter1value2 = class="num">0; if(CustomQuotesRecord::datatype(_Filter1Field) == &class="macro">#x27;t&class="macro">#x27;) { Filter1value1 = (class="type">class="kw">double)StringToTime(_Filter1value1); Filter1value2 = (class="type">class="kw">double)StringToTime(_Filter1value2); } else { Filter1value1 = StringToDouble(_Filter1value1); Filter1value2 = StringToDouble(_Filter1value2); } _defaultQuotesAdapter.reset(); _defaultEngine.process(_selectorArray, _selectorField, _AggregatorType, _AggregatorField, _display, _SortBy, Filter1value1, Filter1value2); }
用 OLAP 切片 EURUSD 报价看真实点差
在 MT5 里挂上 OLAPQTS 这类分析 EA,把 EURUSD 切到 D1 并限定 2019 全年(Filter1Value1=2019.01.01,Value2=2020.01.01),默认 X 轴取形态、COUNT 聚合。日志显示总共读了 12626 根 D1 柱,但落进时间窗的只有 259 根:134 根偏空、125 根偏多,多空分布基本对称。 切到 H1 同样过滤 2019 年,命中 6196 根小时柱,空头 3051、多头 3090,量级和 D1 结论一致但样本更密。 点差才是容易踩坑的地方。MT5 的 MqlRates 只存最小点差,回测若直接拿它算成本,利润倾向被高估。用 hour-of-day 做 X 轴、AVERAGE 聚合 spread 字段,H1 下各小时平均点差在 3.19~4.72 点之间,午夜 01 点最低、09–21 点偏高。 但这只是最小点差均值。换 M1 重跑同样过滤,00 点平均点差跳到 14.06,23 点 6.95,比 H1 结论放大 2–3 倍;用 MAX 聚合更狠:00 点峰值 157 点,而白天多数小时在 11–16 点。做剥头皮或短线,入场离场条件要按这种分钟级极值重新校准。 再用 DEVIATION 聚合能拿到单小时点差标准差,用来给波动跟踪型机器人设过滤阈值。最后切回 D1,QuantGranularity=100(5 位报价点)、SelectorX=quants、FieldX=price range(OC)、Aggregator=COUNT、SortBy=value 降序,结果 72 根柱落在 ±100 点内,更远区间数量递减——这是柱内波动分布的直观切片,不是策略,但能告诉你样本长什么样。
OLAPQTS(EURUSD,D1) Bars read: class="num">12626 OLAPQTS(EURUSD,D1) CountAggregator<QUOTE_RECORD_FIELDS> FIELD_NONE [class="num">3] OLAPQTS(EURUSD,D1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[class="num">1546300800.0 ... class="num">1577836800.0]; OLAPQTS(EURUSD,D1) Selectors: class="num">1 OLAPQTS(EURUSD,D1) X: ShapeSelector(FIELD_SHAPE) [class="num">3] OLAPQTS(EURUSD,D1) Processed records: class="num">259 OLAPQTS(EURUSD,D1) class="num">134.00000: bearish OLAPQTS(EURUSD,D1) class="num">0.00000: flat OLAPQTS(EURUSD,D1) class="num">125.00000: bullish OLAPQTS(EURUSD,H1) Bars read: class="num">137574 OLAPQTS(EURUSD,H1) CountAggregator<QUOTE_RECORD_FIELDS> FIELD_NONE [class="num">3] OLAPQTS(EURUSD,H1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[class="num">1546300800.0 ... class="num">1577836800.0]; OLAPQTS(EURUSD,H1) Selectors: class="num">1 OLAPQTS(EURUSD,H1) X: ShapeSelector(FIELD_SHAPE) [class="num">3] OLAPQTS(EURUSD,H1) Processed records: class="num">6196 OLAPQTS(EURUSD,H1) class="num">3051.00000: bearish OLAPQTS(EURUSD,H1) class="num">55.00000: flat OLAPQTS(EURUSD,H1) class="num">3090.00000: bullish OLAPQTS(EURUSD,H1) Bars read: class="num">137574 OLAPQTS(EURUSD,H1) AverageAggregator<QUOTE_RECORD_FIELDS> FIELD_SPREAD [class="num">24] OLAPQTS(EURUSD,H1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[class="num">1546300800.0 ... class="num">1577836800.0]; OLAPQTS(EURUSD,H1) Selectors: class="num">1 OLAPQTS(EURUSD,H1) X: DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME) [class="num">24] OLAPQTS(EURUSD,H1) Processed records: class="num">6196 OLAPQTS(EURUSD,H1) class="num">4.71984: class="num">00 OLAPQTS(EURUSD,H1) class="num">3.19066: class="num">01 OLAPQTS(EURUSD,H1) class="num">3.72763: class="num">02 OLAPQTS(EURUSD,H1) class="num">4.19455: class="num">03 OLAPQTS(EURUSD,H1) class="num">4.38132: class="num">04 OLAPQTS(EURUSD,H1) class="num">4.28794: class="num">05 OLAPQTS(EURUSD,H1) class="num">3.93050: class="num">06 OLAPQTS(EURUSD,H1) class="num">4.01158: class="num">07 OLAPQTS(EURUSD,H1) class="num">4.39768: class="num">08 OLAPQTS(EURUSD,H1) class="num">4.68340: class="num">09 OLAPQTS(EURUSD,H1) class="num">4.68340: class="num">10 OLAPQTS(EURUSD,H1) class="num">4.64479: class="num">11 OLAPQTS(EURUSD,H1) class="num">4.57915: class="num">12 OLAPQTS(EURUSD,H1) class="num">4.62934: class="num">13 OLAPQTS(EURUSD,H1) class="num">4.64865: class="num">14 OLAPQTS(EURUSD,H1) class="num">4.61390: class="num">15 OLAPQTS(EURUSD,H1) class="num">4.62162: class="num">16 OLAPQTS(EURUSD,H1) class="num">4.50579: class="num">17 OLAPQTS(EURUSD,H1) class="num">4.56757: class="num">18 OLAPQTS(EURUSD,H1) class="num">4.61004: class="num">19 OLAPQTS(EURUSD,H1) class="num">4.59459: class="num">20 OLAPQTS(EURUSD,H1) class="num">4.67054: class="num">21 OLAPQTS(EURUSD,H1) class="num">4.50775: class="num">22 OLAPQTS(EURUSD,H1) class="num">3.57312: class="num">23 OLAPQTS(EURUSD,M1) Bars read: class="num">1000000 OLAPQTS(EURUSD,M1) AverageAggregator<QUOTE_RECORD_FIELDS> FIELD_SPREAD [class="num">24] OLAPQTS(EURUSD,M1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[class="num">1546300800.0 ... class="num">1577836800.0]; OLAPQTS(EURUSD,M1) Selectors: class="num">1 OLAPQTS(EURUSD,M1) X: DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME) [class="num">24] OLAPQTS(EURUSD,M1) Processed records: class="num">371475 OLAPQTS(EURUSD,M1) class="num">14.05653: class="num">00 OLAPQTS(EURUSD,M1) class="num">6.63397: class="num">01 OLAPQTS(EURUSD,M1) class="num">6.00707: class="num">02 OLAPQTS(EURUSD,M1) class="num">5.72516: class="num">03 OLAPQTS(EURUSD,M1) class="num">5.72575: class="num">04 OLAPQTS(EURUSD,M1) class="num">5.77588: class="num">05 OLAPQTS(EURUSD,M1) class="num">5.82541: class="num">06 OLAPQTS(EURUSD,M1) class="num">5.82560: class="num">07 OLAPQTS(EURUSD,M1) class="num">5.77979: class="num">08 OLAPQTS(EURUSD,M1) class="num">5.44876: class="num">09 OLAPQTS(EURUSD,M1) class="num">5.32619: class="num">10 OLAPQTS(EURUSD,M1) class="num">5.32966: class="num">11 OLAPQTS(EURUSD,M1) class="num">5.32096: class="num">12 OLAPQTS(EURUSD,M1) class="num">5.32117: class="num">13 OLAPQTS(EURUSD,M1) class="num">5.29633: class="num">14 OLAPQTS(EURUSD,M1) class="num">5.21140: class="num">15 OLAPQTS(EURUSD,M1) class="num">5.17084: class="num">16 OLAPQTS(EURUSD,M1) class="num">5.12794: class="num">17 OLAPQTS(EURUSD,M1) class="num">5.27576: class="num">18 OLAPQTS(EURUSD,M1) class="num">5.48078: class="num">19 OLAPQTS(EURUSD,M1) class="num">5.60175: class="num">20 OLAPQTS(EURUSD,M1) class="num">5.70999: class="num">21 OLAPQTS(EURUSD,M1) class="num">5.87404: class="num">22 OLAPQTS(EURUSD,M1) class="num">6.94555: class="num">23 OLAPQTS(EURUSD,M1) Bars read: class="num">1000000 OLAPQTS(EURUSD,M1) MaxAggregator<QUOTE_RECORD_FIELDS> FIELD_SPREAD [class="num">24] OLAPQTS(EURUSD,M1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[class="num">1546300800.0 ... class="num">1577836800.0]; OLAPQTS(EURUSD,M1) Selectors: class="num">1 OLAPQTS(EURUSD,M1) X: DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME) [class="num">24] OLAPQTS(EURUSD,M1) Processed records: class="num">371475 OLAPQTS(EURUSD,M1) class="num">157.00000: class="num">00 OLAPQTS(EURUSD,M1) class="num">31.00000: class="num">01 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">02 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">03 OLAPQTS(EURUSD,M1) class="num">13.00000: class="num">04 OLAPQTS(EURUSD,M1) class="num">11.00000: class="num">05 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">06 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">07 OLAPQTS(EURUSD,M1) class="num">11.00000: class="num">08 OLAPQTS(EURUSD,M1) class="num">11.00000: class="num">09 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">10 OLAPQTS(EURUSD,M1) class="num">13.00000: class="num">11 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">12 OLAPQTS(EURUSD,M1) class="num">13.00000: class="num">13 OLAPQTS(EURUSD,M1) class="num">12.00000: class="num">14 OLAPQTS(EURUSD,M1) class="num">14.00000: class="num">15 OLAPQTS(EURUSD,M1) class="num">16.00000: class="num">16 OLAPQTS(EURUSD,M1) class="num">14.00000: class="num">17 OLAPQTS(EURUSD,M1) class="num">15.00000: class="num">18 OLAPQTS(EURUSD,M1) class="num">21.00000: class="num">19
◍ 正文
OLAPQTS (EURUSD,M1) 17.00000: 20 OLAPQTS (EURUSD,M1) 25.00000: 21 OLAPQTS (EURUSD,M1) 31.00000: 22 OLAPQTS (EURUSD,M1) 70.00000: 23 OLAPQTS (EURUSD,M1) Bars read: 1000000 OLAPQTS (EURUSD,M1) VarianceAggregator<QUOTE_RECORD_FIELDS> FIELD_SPREAD [24] OLAPQTS (EURUSD,M1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[1546300800.0 ... 1577836800.0]; OLAPQTS (EURUSD,M1) Selectors: 1 OLAPQTS (EURUSD,M1) X: DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME) [24] OLAPQTS (EURUSD,M1) Processed records: 371475 OLAPQTS (EURUSD,M1) 9.13767: 00 OLAPQTS (EURUSD,M1) 3.12974: 01 OLAPQTS (EURUSD,M1) 2.72293: 02 OLAPQTS (EURUSD,M1) 2.70965: 03 OLAPQTS (EURUSD,M1) 2.68758: 04 OLAPQTS (EURUSD,M1) 2.64350: 05 OLAPQTS (EURUSD,M1) 2.64158: 06 OLAPQTS (EURUSD,M1) 2.64934: 07 OLAPQTS (EURUSD,M1) 2.62854: 08 OLAPQTS (EURUSD,M1) 2.72117: 09 OLAPQTS (EURUSD,M1) 2.80259: 10 OLAPQTS (EURUSD,M1) 2.79681: 11 OLAPQTS (EURUSD,M1) 2.80850: 12 OLAPQTS (EURUSD,M1) 2.81435: 13 OLAPQTS (EURUSD,M1) 2.83489: 14 OLAPQTS (EURUSD,M1) 2.90745: 15 OLAPQTS (EURUSD,M1) 2.95804: 16 OLAPQTS (EURUSD,M1) 2.96799: 17 OLAPQTS (EURUSD,M1) 2.88021: 18 OLAPQTS (EURUSD,M1) 2.76605: 19 OLAPQTS (EURUSD,M1) 2.72036: 20 OLAPQTS (EURUSD,M1) 2.85615: 21 OLAPQTS (EURUSD,M1) 2.94224: 22 OLAPQTS (EURUSD,M1) 4.60560: 23 OLAPQTS (EURUSD,D1) Bars read: 12627 OLAPQTS (EURUSD,D1) CountAggregato
「用利润因子挖出 EURUSD 的时段偏向」
把报价按「日内时辰 × 周内日辰」切成 24×5=120 个格子,再用 ProfitFactorAggregator 算每个格子的 OC 价差利润因子(正增量和 ÷ 负增量和取模),就能看出哪些时段多头占优、哪些空头占优。因子远大于 1 倾向做多,远小于 1(空头实际是倒数)倾向做空;作者设的可用门槛是 ≥2 或 ≤0.5。 以 EURUSD H1、2019 全年为样本跑 OLAP,排序后头部格子显示:周四 0/1/4 时做多因子 5.85、5.79、4.10,周五 0 时做多因子 5.79;尾部如周三 9 时做空因子 0.49、周四 13 时 0.48。这些数字没扣点差,用自定义字段 1(柱线范围减点差)重算后,结论收敛到:周四凌晨 0/1/4 时多、晚 19/23 时空,周五 0/3/4/9 时多、11/14/23 时空,但周五 23 时临近周末缺口风险偏高。 把上面时间表写进 SingleBar EA(BuyHours/SellHours 逗号分隔,ActiveDayOfWeek=5 锁周五),用 2019 年数据回测结果漂亮,但把测试起点拉回 2018 年初,形态从 2018 年中才稳定生效——说明 OLAP 找出的周期有有效期,不是永久结构。 拉长样本到 2015–2019 再前向验证 2019,单时辰盈利普遍衰减,周三相对最稳却仍起伏;这指向一个事实:外汇和贵金属属高风险品种,时段形态会退化,OLAP 只是研究筛子,真上线前得配合正向验证和多因子扩展,不能单靠一张利润因子表就固定参数。
OLAPQTS(EURUSD,H1) Bars read: class="num">137597 OLAPQTS(EURUSD,H1) ProfitFactorAggregator<QUOTE_RECORD_FIELDS> FIELD_PRICE_RANGE_OC [class="num">120] OLAPQTS(EURUSD,H1) Filters: FilterRange::FilterSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME)[class="num">1546300800.0 ... class="num">1577836800.0]; OLAPQTS(EURUSD,H1) Selectors: class="num">2 OLAPQTS(EURUSD,H1) X: DayHourSelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME) [class="num">24] OLAPQTS(EURUSD,H1) Y: WorkWeekDaySelector<QUOTE_RECORD_FIELDS>(FIELD_DATETIME) [class="num">5] OLAPQTS(EURUSD,H1) Processed records: class="num">6196 OLAPQTS(EURUSD,H1) [value] [title] OLAPQTS(EURUSD,H1) [ class="num">0] class="num">5.85417 "class="num">00; class="num">1`Monday" OLAPQTS(EURUSD,H1) [ class="num">1] class="num">5.79204 "class="num">00; class="num">5`Friday" OLAPQTS(EURUSD,H1) [ class="num">2] class="num">5.25194 "class="num">00; class="num">4`Thursday" OLAPQTS(EURUSD,H1) [ class="num">3] class="num">4.10104 "class="num">01; class="num">4`Thursday" OLAPQTS(EURUSD,H1) [ class="num">4] class="num">4.00463 "class="num">01; class="num">2`Tuesday" OLAPQTS(EURUSD,H1) [ class="num">5] class="num">2.93725 "class="num">01; class="num">3`Wednesday" OLAPQTS(EURUSD,H1) [ class="num">6] class="num">2.50000 "class="num">00; class="num">3`Wednesday" OLAPQTS(EURUSD,H1) [ class="num">7] class="num">2.44557 "class="num">15; class="num">1`Monday" OLAPQTS(EURUSD,H1) [ class="num">8] class="num">2.43496 "class="num">04; class="num">5`Friday" OLAPQTS(EURUSD,H1) [ class="num">9] class="num">2.36278 "class="num">20; class="num">3`Wednesday" OLAPQTS(EURUSD,H1) [ class="num">10] class="num">2.33917 "class="num">04; class="num">4`Thursday" ... OLAPQTS(EURUSD,H1) [class="num">110] class="num">0.49096 "class="num">09; class="num">3`Wednesday" OLAPQTS(EURUSD,H1) [class="num">111] class="num">0.48241 "class="num">13; class="num">4`Thursday"