您应当知道的 MQL5 向导技术(第 20 部分):符号回归(基础篇)
📘

您应当知道的 MQL5 向导技术(第 20 部分):符号回归(基础篇)

第 1/2 篇

用符号回归在 MT5 里找隐藏价格公式

符号回归和常见拟合不同,它不预设方程形态,而是让算法在运算符集合里搜索,拼出能解释价格序列的数学表达式。MQL5 向导里这套机制,适合把肉眼看不出的非线性关系挖出来,比如黄金 1 小时图上波动率与成交量的耦合项。 外汇与贵金属杠杆高、滑点突变频繁,任何从历史数据搜出的公式都只是概率性描述,实盘前必须在 MT5 策略测试器用近 12 个月数据重跑。 下面这段是向导生成的骨架,真正跑起来要把 SymbolInfoTick 拿到的 bid/ask 流喂进回归器,否则只会在样本内自嗨。

「符号回归凭什么比线性回归更适合盘口」

MQL5 向导不只是把标准交易函数和类库打包好让你写 EA,它还能让自定义类跟替代交易信号并行跑,这给快速编码和回测留了很大空间。在这个框架下,符号回归(SR)值得单独拎出来看。 传统线性回归假设 y = mx + c,也就是数据点理想情况下排成一条直线,求解斜率 m 和截距 c;神经网络也类似,先预设层数和激活函数再去拟合。SR 不这么干,它从随机分配的树型表达式节点起步,把两个数据集映射成一棵树,而不是先入为主假定关系形态。 理论上,这种「白纸起步」更可能挖出被线性假设掩盖的复杂市场联系。它每次分析都从无偏差开始,靠遗传优化迭代,所以对外汇、贵金属这种高波动、高风险的品种,换数据分布时不用推倒重来。 SR 还能塞进多个自变量进同一棵表达式树,不像线性回归死守单一 x 决定 y。哪怕 y 只依赖 x,关系也可能是二次甚至更弯的曲线,SR 通过遗传进化自己把非线性建出来,而且树结构本身就把「输入怎么映射到目标」写清楚了,可解释性比黑盒网络强。 简单说,SR 的核心卖点是:适应快、能多源、可解释。在 MT5 里用向导搭个 SR 信号类,拿 EURUSD 的 H1 跑一轮遗传优化,你大概就能直观比对它和线性信号的拟合残差。

◍ 符号回归里的树型表达式怎么长出来

符号回归(SR)把自变量和预测变量的映射写成树型表达式,而不是普通代数等式。一个最简例子是 (32/2) ^ (-0.5),算出来正好等于 0.25,这说明树可以由系数、幂次和运算符任意嵌套组合。 为了让遗传优化能跨规模比较,作者按“局次(round)”跑优化:每一局固定树的长度,只在同长度树木间做交叉,避免不同长度带来的节点错位。随机性靠“每一局对应一个特定长度”来维持,最大长度在此实现里锁死在 16 个单位,也就是 16 个 x 项配 15 个符号位。 表达式的基础格式是 coeff, x-exponent, sign 循环,最后不能是运算符。选初始树时,若长度大于 1,就严守“两个数字节点 + 一个符号节点”的节奏,且绝不结尾留符号。下面这段代码就是按长度 Size 拼出随机树的真实逻辑。 [CODE] //+------------------------------------------------------------------+ // Get Expression Tree //+------------------------------------------------------------------+ void CSignalSR::GetExpressionTree(int Size, string &ExpressionTree[]) { if(Size < 1) { return; } ArrayFree(ExpressionTree); ArrayResize(ExpressionTree, (2 * Size) + Size - 1); int _digit[]; GetDigitNode(2 * Size, _digit); string _sign[]; if(Size >= 2) { GetSignNode(Size - 1, _sign); } int _di = 0, _si = 0; for(int i = 0; i < (2 * Size) + Size - 1; i += 3) { ExpressionTree[i] = IntegerToString(_digit[_di]); ExpressionTree[i + 1] = IntegerToString(_digit[_di + 1]); _di += 2; if(Size >= 2 && _si < Size - 1) { ExpressionTree[i + 2] = _sign[_si]; _si ++; } } } //+------------------------------------------------------------------+ // Get Digit //+------------------------------------------------------------------+ void CSignalSR::GetDigitNode(int Count, int &Digit[]) { ArrayFree(Digit); ArrayResize(Digit, Count); for(int i = 0; i < Count; i++) [/CODE] 逐行看:先拦掉 Size<1 的非法调用;数组清空后按 2*Size + Size-1 扩容,即数字节点翻倍再加符号位。GetDigitNode 抓 2*Size 个随机数字,Size≥2 才抓 Size-1 个符号。循环里每跳 3 格写“系数、指数、符号”,_di 每次跳 2、_si 按符号数走,结尾自然不挂运算符。 在 MT5 里把这段直接塞进 CSignalSR 类,配一个返回随机数字和随机正负号的 GetDigitNode / GetSignNode,就能打印出第一条随机树。外汇与贵金属行情噪声大、高风险,这种随机初树若训练集太短,很容易拟合出白噪声而非真实形态,建议先用 500 根以上 H1 收盘价试跑。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Get Expression Tree
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalSR::GetExpressionTree(class="type">int Size, class="type">class="kw">string &ExpressionTree[])
{  if(Size < class="num">1)
   {  class="kw">return;
   }
   ArrayFree(ExpressionTree);
   ArrayResize(ExpressionTree, (class="num">2 * Size) + Size - class="num">1);
   class="type">int _digit[];
   GetDigitNode(class="num">2 * Size, _digit);
   class="type">class="kw">string _sign[];
   if(Size >= class="num">2)
   {  GetSignNode(Size - class="num">1, _sign);
   }
   class="type">int _di = class="num">0, _si = class="num">0;
   for(class="type">int i = class="num">0; i < (class="num">2 * Size) + Size - class="num">1; i += class="num">3)
   {  ExpressionTree[i] = IntegerToString(_digit[_di]);
      ExpressionTree[i + class="num">1] = IntegerToString(_digit[_di + class="num">1]);
      _di += class="num">2;
      if(Size >= class="num">2 && _si < Size - class="num">1)
      {  ExpressionTree[i + class="num">2] = _sign[_si];
         _si ++;
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Get Digit
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalSR::GetDigitNode(class="type">int Count, class="type">int &Digit[])
{  ArrayFree(Digit);
   ArrayResize(Digit, Count);
   for(class="type">int i = class="num">0; i < Count; i++)

符号节点与适应度计算的落地细节

这段遗传编程实现里,随机符号和数字的生成都靠 MathRand() 取模数组长度完成。GetSignNode 先把 Sign 数组清空并扩到 Count 大小,再逐个填入 __SIGN_NODE 里的加减乘除符号,调用一次就能拿到一组随机运算符序列。 结构体 Stree 和 Spopulation 只是把字符串树和种群做了轻量封装,构造函数里仅 ArrayFree(tree) 防脏数据,析构留空——实盘跑大量迭代时,这种裸结构比嵌套类更省开销。 GetFitness 是核心:它按每 3 个节点(系数、指数、符号)一组遍历表达式树,用 pow(XY[r][0], 指数) 乘系数后依符号累加进 Y[r],最后返回 Y 对 XY 第 1 列的回归指标。注意除法分支写了 _yy != 0.0 才除,但注释标了 un-handled,说明除零之外的符号错乱并没兜底,复制代码后建议先在小样本上打印 ExpressionTree 验证。 SetCrossover 做两点交叉前先卡 ParentA 与 ParentB 尺寸一致且非零,否则直接 return,ChildA/B 按父代长度 ArrayResize。外汇与贵金属价格序列噪声大,这类符号回归模型过拟合概率偏高,上 MT5 用历史数据回测时务必留 Out-of-Sample 段。

MQL5 / C++
  {
   Digit[i] = __DIGIT_NODE[MathRand() % __DIGITS];
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Get Sign
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalSR::GetSignNode(class="type">int Count, class="type">class="kw">string &Sign[])
{  ArrayFree(Sign);
   ArrayResize(Sign, Count);
   for(class="type">int i = class="num">0; i < Count; i++)
   {  Sign[i] = __SIGN_NODE[MathRand() % __SIGNS];
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">struct Stree
{  class="type">class="kw">string   tree[];
   Stree()
   {  ArrayFree(tree);
   };
   ~Stree() {};
};
class="kw">struct Spopulation
{  Stree   population[];
   Spopulation() {};
   ~Spopulation() {};
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Get Fitness
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalSR::GetFitness(matrix &XY, vector &Y, class="type">class="kw">string &ExpressionTree[])
{  Y.Init(XY.Rows());
   for(class="type">int r = class="num">0; r < class="type">int(XY.Rows()); r++)
   {  Y[r] = class="num">0.0;
      class="type">class="kw">string _sign = "";
      for(class="type">int i = class="num">0; i < class="type">int(ExpressionTree.Size()); i += class="num">3)
      {  class="type">class="kw">double _yy = pow(XY[r][class="num">0], StringToDouble(ExpressionTree[i + class="num">1]));
         _yy *= StringToDouble(ExpressionTree[i]);
         if(_sign == "+")
         {  Y[r] += _yy;
         }
         else if(_sign == "-")
         {  Y[r] -= _yy;
         }
         else if(_sign == "/" && _yy != class="num">0.0)class=class="str">"cmt">//un-handled
         {  Y[r] /= _yy;
         }
         else if(_sign == "*")
         {  Y[r] *= _yy;
         }
         else if(_sign == "")
         {  Y[r] = _yy;
         }
         if(i + class="num">2 < class="type">int(ExpressionTree.Size()))
         {  _sign = ExpressionTree[i + class="num">2];
         }
      }
   }
   class="kw">return(Y.RegressionMetric(XY.Col(class="num">1), m_regressor));
   class=class="str">"cmt">//class="kw">return(_y.Loss(XY.Col(class="num">1),LOSS_MAE));
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Set Crossover
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalSR::SetCrossover(class="type">class="kw">string &ParentA[], class="type">class="kw">string &ParentB[], class="type">class="kw">string &ChildA[], class="type">class="kw">string &ChildB[])
{  if(ParentA.Size() != ParentB.Size() || ParentB.Size() == class="num">0)
   {  class="kw">return;
   }
   class="type">int _length = class="type">int(ParentA.Size());
   ArrayResize(ChildA, _length);
   ArrayResize(ChildB, _length);

常见问题

在策略测试器里用向导建符号回归模型,让它自动演化树型数学表达式拟合收盘价,跑完看最优公式即可反推盘口规律。
线性回归只能给一条直线,符号回归能长出乘除、对数等组合表达式,更可能抓住盘口里的非线性依赖,但外汇贵金属高风险需自验。
可以,小布盯盘的 AIGC 能调用同类演化算法跑品种诊断,并把树型表达式转成白话结论,打开对应品种页直接看。
先随机生成一批公式树,再交叉变异淘汰,保留误差小的个体,迭代多代后倾向收敛出较简洁的拟合表达式。
要,通常把残差平方和加上树节点数惩罚项,避免过拟合,实战中节点少的公式概率上更稳。