用线性序函子重排指标缓冲
用线性序函子重排指标缓冲
在 MQL5 里把指标缓冲当成范畴对象处理时,线性序函子(linear order functor)提供了一条把无序时间序列映射成严格单调索引的通道。它不改变样本值,只重排访问顺序,便于后续做基于顺序的统计。 实际写代码时,我们常借 ArraySort 配合自定义比较器模拟这一函子行为。下面这段演示了如何把某缓冲按值升序重排并保留原索引映射,读者可直接贴进 MT5 指标脚本验证。 外汇与贵金属行情受杠杆与流动性影响,重排后的顺序特征仅反映历史样本,对下一根 K 线的指向只是概率倾向,请视作高风险环境下的辅助视图。
class="type">class="kw">double buf[class="num">10]; class="type">int idx[class="num">10]; for(class="type">int i=class="num">0;i<class="num">10;i++){ buf[i]=iCustom(NULL,class="num">0,"someInd",class="num">0,i); idx[i]=i; } ArraySort(buf,class="num">0,class="num">10,idx); class=class="str">"cmt">// 按 buf 值升序,同步换 idx 序
◍ 从子范畴跳到函子桥接
范畴论由 Eilenberg 与 Mac Lane 在 1950 年代提出,本质是研究系统各阶段之间转变结构的工具,而非盯着阶段本身。它早已溢出纯数学,在 Haskell 函数式编程、自然语言组合性分析、代数拓扑的不变量统一描述里都有落点。 前几篇我们一直停在子范畴层面,只在一个范畴内抠集合(对象)及其关系与属性。这一步相当于把镜头锁死在局部。 本文往前迈半步:缩小单个范畴,转而研究不同范畴之间的映射关系,形式化叫函子(Functors)。在交易者数据里能挑的范畴很多,但为显出范畴论的脱钩能力,我们故意不碰常规标的,拿加州海岸海潮观测数据去桥接纳斯达克指数波动性。海潮里是否藏着预示该指数波动的成分,文末会给出一定程度的回答。 本篇及后续几篇不新造概念,只复用已讲过的结构并换尺度应用。外汇与贵金属属高风险市场,这类跨域桥接仅提供分析视角,不构成方向判定。
「把蒙特雷潮位和纳指成分拉到同一张图上」
海潮高度由 NOAA 每天四次记录基准面高程,全年无间断,覆盖四大洋区;仅北美与南美西海岸从智利到阿拉斯加就布了 33 个观测站。做相关性验证时,可直接取 2020 年加利福尼亚州蒙特雷单站数据,样本干净、时区一致,省去跨站加权平均的麻烦。 纳斯达克指数这边,成分里 MSFT、AAPL、GOOG、AMZN 总部都在加州,和蒙特雷同属一个海岸带。很多券商的报价源能直接拉指数分钟线,把它和潮位时序叠在一起,才可能看出加州科技市值波动与当地海洋引力周期是否存在统计上的耦合。 这类跨域相关不代表因果,外汇与贵金属之外的权益指数也自带高波动风险;真要验证,开 MT5 接 CSV 潮位、挂纳指ymbol 做相位比对最实在。
用函子把证券范畴映射到策略范畴
函子在数学上定义为范畴之间的映射,它必须保留对象结构与态射关系。若 C、D 为范畴,函子 F 对 C 中每个对象 c 指定 D 中对象 F(c),对态射 f: b→c 指定 F(f): F(b)→F(c),且满足 F(g∘f)=F(g)∘F(f) 与 F(id_c)=id_{F(c)}。对交易者而言,这套形式化不是炫技:它让“可交易证券”和“交易策略”这两个不同范畴能被显式关联。 一个可直接验证的落点是海潮数据。NOAA 单日潮汐记录含 4 个时间戳与 4 个浮点高度值,外加当日日期戳,共 9 项多维数据;但并非每日都有 4 个观测点,有的仅 3 个。若用线性序(满足反对称性、可比性)按时间戳比较,可压成 2 项的时间序列(日期戳 + 高度)。要将其建成范畴,须先常规化——补齐缺失观测使每域元素数一致,否则同构关系断裂。 把每日补齐后的 4 点看作一个对象、相邻日间的比较运算看作态射,就得到潮汐高度范畴。纳斯达克波动率范畴可照做:以时间序列链接价格点为态射。若函子映射覆盖时间滞后,你便能依组合持仓反推策略,或据当前策略筛选证券。外汇与贵金属波动更剧,此类映射仅提供概率性结构参考,实盘须自担高风险。
◍ 外源数据怎么接进 MT5 做范畴映射
把海洋潮汐这类外源数据塞进 MT5,核心是先构造域范畴与协域范畴两个实例,再用恒定函子把前者映射到纳斯达克波动率集合。原文测试里海潮数据只滞后一天,因此直接读公用文件夹的 csv 即可,不需要上数据库。
数据接入最麻烦的一步是让 MQL5 能按时间戳对齐 csv 里的记录。文件里带日期戳字段,代码用 _date > _data_time 的判断来定位当前柱对应的海潮幅度,跳过周末(day_of_week!=6 && !=0)避免脏数据进域。
函子分两层:对象映射把海潮每日三采样点映射到纳指日线对象;态射映射则比较相邻日之间的差值。两段 GetOutput() 重载分别对应「只验对象」和「只验态射」两种设置,回测窗口取 2020-01-01 至 2020-03-15,纳指恰从历史高位摔进新冠急跌,短窗口但波动极端。
实测两种函子设置结果不同,但都不构成因果,只显示滞后关联可能存在。想复现就把 csv 丢进公共文件夹,用内置 Awesome Oscillator 默认信号跑日线,您自己的滞后天数可以拉长到两三天再比。
除海潮外,商品价、技术新闻发文量、社媒情绪词典打分都能当替代外源集。这类深奥数据偏向帮您建边缘优势,更直接的其实是别的证券价格或它们的指标值,接法完全一样。
class="kw">protected: ... CCategory _category_ocean,_category_nasdaq; CDomain<class="type">class="kw">string> _domain_ocean,_domain_nasdaq; CHomomorphism<class="type">class="kw">string,class="type">class="kw">string> _hmorph_ocean,_hmorph_nasdaq; class="type">void CTrailingCT::SetOcean(class="type">int Index) { ... if(_handle!=INVALID_HANDLE) { ... class="kw">while(!FileIsLineEnding(_handle)) { ... if(_date>_data_time) { _category_ocean.SetDomain(_category_ocean.Domains(),_domain_ocean); break; } else if(__DATETIME.day_of_week!=class="num">6 && __DATETIME.day_of_week!=class="num">0 && class="type">class="kw">datetime(class="type">int(_data_time)-class="type">int(_date))<=PeriodSeconds(PERIOD_D1)) { _element_value.Let();_element_value.Cardinality(class="num">1);_element_value.Set(class="num">0,DoubleToString(_value)); _domain_ocean.Cardinality(_elements);_domain_ocean.Set(_elements-class="num">1,_element_value); _elements++; } } FileClose(_handle); } else { printf(__FUNCSIG__+" failed to load file. Err: "+IntegerToString(GetLastError())); } } class="type">void CTrailingCT::SetNasdaq(class="type">int Index) { m_high.Refresh(-class="num">1); m_low.Refresh(-class="num">1); _value=class="num">0.0; _value=(m_high.GetData(Index+StartIndex()+m_high.MaxIndex(Index,_category_ocean.Homomorphisms()))-m_low.GetData(Index+StartIndex()+m_low.MinIndex(Index,_category_ocean.Homomorphisms())))/m_symbol.Point(); _element_value.Let();_element_value.Cardinality(class="num">1);_element_value.Set(class="num">0,DoubleToString(_value)); _domain_nasdaq.Cardinality(class="num">1);_domain_nasdaq.Set(class="num">0,_element_value); _category_nasdaq.SetDomain(_category_nasdaq.Domains(),_domain_nasdaq); } class="type">class="kw">double CTrailingCT::GetOutput() { ... ... _domain.Init(class="num">3+class="num">1,class="num">3); for(class="type">int r=class="num">0;r<class="num">4;r++) { CDomain<class="type">class="kw">string> _d;_d.Let(); _category_ocean.GetDomain(_category_ocean.Domains()-r-class="num">1,_d); for(class="type">int c=class="num">0;c<_d.Cardinality();c++) { CElement<class="type">class="kw">string> _e; _d.Get(c,_e); class="type">class="kw">string _s; _e.Get(class="num">0,_s); _domain[r][c]=StringToDouble(_s); } } _codomain.Init(class="num">3); for(class="type">int r=class="num">0;r<class="num">3;r++) { CDomain<class="type">class="kw">string> _d; _category_nasdaq.GetDomain(_category_nasdaq.Domains()-r-class="num">1,_d); CElement<class="type">class="kw">string> _e; _d.Get(class="num">0,_e); class="type">class="kw">string _s; _e.Get(class="num">0,_s); _codomain[r]=StringToDouble(_s); } _inputs.Init(class="num">3);_inputs.Fill(m_consant_morph); M(_domain,_codomain,_inputs,_output,class="num">1); class="kw">return(_output); } class="type">class="kw">double CTrailingCT::GetOutput() { ... ... _domain.Init(class="num">3+class="num">1,class="num">3); for(class="type">int r=class="num">0;r<class="num">4;r++) { ... if(_category_ocean.Domains()-r-class="num">1-class="num">1>=class="num">0){ _category_ocean.GetDomain(_category_ocean.Domains()-r-class="num">1-class="num">1,_d_old); } for(class="type">int c=class="num">0;c<_d_new.Cardinality();c++) { ... CElement<class="type">class="kw">string> _e_old; _d_old.Get(c,_e_old); class="type">class="kw">string _s_old; _e_old.Get(class="num">0,_s_old); _domain[r][c]=StringToDouble(_s_new)-StringToDouble(_s_old); } } _codomain.Init(class="num">3); for(class="type">int r=class="num">0;r<class="num">3;r++) { ... if(_category_nasdaq.Domains()-r-class="num">1-class="num">1>=class="num">0){ _category_nasdaq.GetDomain(_category_nasdaq.Domains()-r-class="num">1-class="num">1,_d_old); } ... CElement<class="type">class="kw">string> _e_old; _d_old.Get(class="num">0,_e_old); class="type">class="kw">string _s_old; _e_old.Get(class="num">0,_s_old); _codomain[r]=StringToDouble(_s_new)-StringToDouble(_s_old); } _inputs.Init(class="num">3);_inputs.Fill(m_consant_morph); M(_domain,_codomain,_inputs,_output,class="num">1); class="kw">return(_output); }
「把函子思路接回自己的MT5工作台」
把海潮高程这类外部序列经函子滞后一天映射到纳斯达克波动,测试窗口虽短,但对象到对象与态射到态射两种映射给出的入场与仓位调整逻辑一致,回测结果却明显分化,说明映射格式本身就会改写风险暴露。 想自己跑一遍,先把 'TrailingCT_14_1a.mqh' 和 'TrailingCT_14_1b.mqh' 丢进 MQL5\include\Expert\Trailing\,'ct_14_1s.mqh' 放 include 根目录;EA 主体 ct_14_1a.mq5 / ct_14_1b.mq5 用向导拼装,入场取标准库的 Awesome Oscillator,资金管采用固定保证金。外汇与贵金属杠杆高、滑点跳空频繁,这类跨学科函子策略仅提供构造思路,不预示任何胜率。 跨域线性序还能往预测建模、知识图谱、迁移学习推:同一套函子权重若能在不同市场复用,机器学习效率有可能提升,但这一切都取决于你自己的交易视野与标的选择。 剩下的路得自己走——把 CSV 源换成你熟悉的宏观或另类数据,编译改参数,看信号是不是还立得住。