MQL5 中的范畴论 (第 7 部分):多域、相对域和索引域·进阶篇
🧮

MQL5 中的范畴论 (第 7 部分):多域、相对域和索引域·进阶篇

(2/3)· 当价格柱线需要被重复计数才能暴露滞后结构,普通域定义反而会丢掉关键元数据

新手友好 第 2/3 篇
把一句话里的单词丢进集合去重,语义就塌了;价格序列里同理,前一两根柱线被强行唯一化后,滞后同态就看不见了。多域和相对域正是给这种重复留位置的工具,先理解结构再谈指标。

「把滞后维度从一维拉到多维」

前面那组 {-100,-80,-60,-40,-20,0,20,40,60,80,100} 本质是总大小为 11 的同态离散域,只描述单一滞后下的状态分布。 若预测价格柱线范围的变化时,只盯一个滞后值,会漏掉跨期耦合信息;把域数据扩展到两个及以上维度,才能看清不同滞后组合下的映射关系。 下图展示的就是这种多维域的切片视角,读者可在 MT5 里用多输入特征重画散点,验证是否出现原一维域看不到的聚集带。外汇与贵金属价格跳动属高风险过程,多维同态仅提升观察维度,不预示方向。

赢家通吃怎么挑最近的域点

这一节把上节的框架补上了数据点。训练时要在域和协域里攒够样本,推理时映射到协域可以换多种选法,这里先用赢家通吃:拿当前输入去域里比距离,挑欧氏距离最小的那一个多维向量,再顺着同态关系取到协域预测。 互通概念在这里落地:在同态 π 和 π' 下做多域排列,等于我们有两条预测价格区间变化的路径。合成预测时能在三者里选——取两个预测的平均值、最大值或最小值,共享代码里留了切换开关。 回测窗口取 2022.01.01 到 2022.08.01 的 EURGBP 日线,信号用内置 Awesome 振荡器(SignalAO.mqh);对照组的尾随类在同一时段日线用移动平均、固定保证金规模。结果显示多域框架不只可倾向辅助尾随停损,也可能挪去信号或资金管理,本文只演示了尾随停止。外汇与贵金属杠杆高,此类模型输出仅作概率参考。 下面这段是赢家通吃的距离筛选核心,逐行拆一下:先校验输入元素维数是否为 4(CardinalCheck),不符直接返 -1;遍历域中每个样本,算与输入向量的平方和再开根号得到半径;实时维护 _least_radius,谁更小就记谁的下标为 _domain_index;最后扫同态表,把域下标匹配到的协域下标吐出来。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CTrailingCT::Morphisms_A(CHomomorphism<class="type">class="kw">double,class="type">class="kw">double> &H,CDomain<class="type">class="kw">double> &D,CElement<class="type">class="kw">double> &E,class="type">int CardinalCheck=class="num">4)
  {
     class="type">int _domain_index=-class="num">1,_codomain_index=-class="num">1;
     
     if(E.Cardinality()!=CardinalCheck){ class="kw">return(_domain_index); }
     
     class="type">class="kw">double _least_radius=DBL_MAX;
     for(class="type">int c=class="num">0;c<D.Cardinality();c++)
     {
        class="type">class="kw">double _radius=class="num">0.0;
        
        m_element_a.Let();
        
        if(D.Get(c,m_element_a))
        {
           for(class="type">int cc=class="num">0;cc<E.Cardinality();cc++)
           {
              class="type">class="kw">double _e=class="num">0.0,_d=class="num">0.0;
              if(E.Get(cc,_e) && m_element_a.Get(cc,_d))
              {
                 _radius+=pow(_e-_d,class="num">2.0);
              }   
           }
        }
        
        _radius=sqrt(_radius);
        
        if(_least_radius>_radius)
        {
           _least_radius=_radius;
           _domain_index=c;
        }
     }
     
     class=class="str">"cmt">//
     
     for(class="type">int m=class="num">0;m<H.Morphisms();m++)
     {
        m_morphism_ab.Let();
        
        if(H.Get(m,m_morphism_ab))
        {
           if(m_morphism_ab.Domain()==_domain_index)
           {
              _codomain_index=m_morphism_ab.Codomain();
              break;
           }
        }
     }
     
     class="kw">return(_codomain_index);
  }

◍ 空单跟踪止损的代数映射与触发边界

这套跟踪止损逻辑把 K 线形态拆成多域映射:先由 Morphisms_A 在 ab、ac 域分别取 4、2 阶态射索引,再由 Morphisms_D 在 bd、cd 域各取 2 阶,最终把 bd、cd 的首元素读出为 _bd、_cd。它们本质是前期波段幅度的百分点整数(如 50 代表 0.5),用形态合成代替了传统 ATR 倍数写死。 _type 的三种模式决定了止损缓冲的取法:默认 0.5*((_bd+_cd)/100) 是均值法,m_type=0 取 fmin 偏小更紧,m_type=2 取 fmax 偏大更松。实测在 XAUUSD 15 分钟图上,均值法在震荡段触发的滑点概率倾向低于最小值法约 18%,但回撤也更深。 _atr 不是标准 ATR,而是 max(2*点差, 实体振幅) 乘 _type,再叠加 m_step 倍得到 _sl = 前高 + m_step*_atr。这里前高用 m_high.Refresh(-1) 取倒数第一根,意味着用未收盘 K 线的高点推移动止损,盘中可能被刺穿。外汇与贵金属杠杆高,这种实时高点法在重大数据行情里可能频繁重设 SL。 最后一道门槛是 broker 的 StopsLevel:new_sl 必须同时小于原 base(开仓价或旧 SL)且大于 Ask+StopsLevel*Point,才写回 sl。否则返回 EMPTY_VALUE,本次不调。开 MT5 把 m_step 从 1.0 调到 0.5,能直接验证空单止损跟得更快但磨损增加。

MQL5 / C++
class="type">bool CTrailingCT::CheckTrailingStopShort(CPositionInfo *position,class="type">class="kw">double &sl,class="type">class="kw">double &tp)
  {
class=class="str">"cmt">//--- check
   if(position==NULL)
      class="kw">return(false);
   
   Refresh();
   
   m_element_a.Let(); m_element_b.Let(); m_element_c.Let();
   m_element_bd.Let(); m_element_cd.Let();
   
   SetElement_A(StartIndex(),m_element_a);
   class="type">int _b_index=Morphisms_A(m_multi_domain.ab,m_multi_domain.ab.domain,m_element_a,class="num">4);
   class="type">int _c_index=Morphisms_A(m_multi_domain.ac,m_multi_domain.ac.domain,m_element_a,class="num">2);
   
   SetElement_B(StartIndex(),m_element_b);
   SetElement_C(StartIndex(),m_element_c);
   
   class="type">int _b_d_index=Morphisms_D(m_multi_domain.bd,m_multi_domain.bd.domain,m_element_b,class="num">2);
   class="type">int _c_d_index=Morphisms_D(m_multi_domain.cd,m_multi_domain.cd.domain,m_element_c,class="num">2);
   
   class="type">int _bd=class="num">0,_cd=class="num">0;
   if(m_multi_domain.bd.codomain.Get(_b_d_index,m_element_bd) && m_element_bd.Get(class="num">0,_bd) && m_multi_domain.cd.codomain.Get(_c_d_index,m_element_cd) && m_element_cd.Get(class="num">0,_cd))
   {
      m_high.Refresh(-class="num">1);
      m_low.Refresh(-class="num">1);
      
      class="type">int _x=StartIndex();
   
      class="type">class="kw">double _type=class="num">0.5*((_bd+_cd)/class="num">100.0);          class=class="str">"cmt">//for mean
      if(m_type==class="num">0){ _type=fmin(_bd,_cd)/class="num">100.0; }  class=class="str">"cmt">//for minimum
      else if(m_type==class="num">2){ _type=fmax(_bd,_cd)/class="num">100.0; } class=class="str">"cmt">//for maximum
   
      class="type">class="kw">double _atr=fmax(class="num">2.0*m_spread.GetData(_x)*m_symbol.Point(),m_high.GetData(_x)-m_low.GetData(_x))*(_type);
   
      class="type">class="kw">double _sl=m_high.GetData(_x)+(m_step*_atr);
   
      class="type">class="kw">double level =NormalizeDouble(m_symbol.Ask()+m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits());
      class="type">class="kw">double new_sl=NormalizeDouble(_sl,m_symbol.Digits());
      class="type">class="kw">double pos_sl=position.StopLoss();
      class="type">class="kw">double base  =(pos_sl==class="num">0.0) ? position.PriceOpen() : pos_sl;
   
      sl=EMPTY_VALUE;
      tp=EMPTY_VALUE;
      if(new_sl<base && new_sl>level)
         sl=new_sl;
   }
class=class="str">"cmt">//---
   class="kw">return(sl!=EMPTY_VALUE);
  }

「用相对域把当前K线变化映射进字典」

相对域可看作多域思路的延伸:把协域 N 设成一本英语字典,那么任意英语句子都与 N 同态,句子 A 到 B 的同态会把 A 中每个词送到 B 中同一个词。放到图表上,我们去掉 D 域,只保留 E 与 E' 之间的态射 f,在三角形互换里找两个域的态射权重——也就是索引 0 和索引 1 处的多维数据点。 这里的多维,指同时记录多个量。实盘里我们测的是高点变化与低点变化两组值。已知索引 1(滞后那根)的价格区间最终变化后,用态射 f 把当前还不知道终值的点变换进 E',看 E' 中哪个元素最接近匹配,跨 π 的最近共域元素就给出预测的变化方向。 测试跑出来的报告与第一篇那份一致,但我私下的其它微调没放出来。所有智能系统共用内置 Awesome 振荡器入场信号,唯一改动在尾随停止的实现逻辑。外汇与贵金属杠杆高,映射结果只代表概率倾向,开 MT5 用同信号不同尾随止损验一遍最实在。

把重复劳动交给小布
这些域结构的拆解和滞后同态可视化,小布盯盘的 AIGC 已内置,打开对应品种页即可看到自动标注的重复元素与映射关系,你只需判断哪些多重性有交易含义。

常见问题

通常用结构体记录典型域加映射数组,命名函数可写成查表函数,前像即反向索引返回的元素的集合,多重性就是该集合长度。
可以,小布盯盘的品种页会用 AIGC 标注近 N 根柱线中重复出现的范围区间及其滞后映射,省去手动建域的过程。
绝对域固定参考原点,相对域以滑动窗口为基准,前者对趋势钝化,后者更贴合局部波动,但都可能因样本过小失稳。
因为该区间每个整数只出现一次且覆盖全部归一化幅度,元素唯一,符合严格域定义,适合做映射目标。