📘

用数字墙给EA交易划禁区

◍ 用数字墙给EA交易划禁区

在 MQL5 向导里做 EA 时,经常会遇到一类参数:它们不是用来优化,而是用来硬性卡住交易逻辑边界的。这类参数堆在一起,就像一面「数字墙」,挡住不符合条件的订单。 典型场景是限制单笔风险或总持仓手数。比如下面这段,用 Input 宏把最大风险百分比和单日最大交易次数钉死,向导生成代码后这些值不可被策略优化器随意越过。 外汇与贵金属杠杆高,墙设得太宽可能一夜回撤超标,设得太窄又容易错过信号;建议先在 MT5 策略测试器用 2023 年 XAUUSD 的 H1 数据跑一遍,看墙内外的净值曲线差异。

MQL5 / C++
class="macro">#include <Expert\Expert.mqh>
class="macro">#include <Expert\ExpertMoney.mqh>

input class="type">class="kw">double MaximumRiskPercent = class="num">2.0;   class=class="str">"cmt">// 单笔最大风险百分比
input class="type">int    MaxTradesPerDay   = class="num">5;       class=class="str">"cmt">// 每日最多交易次数

class="type">void OnTick()
  {
   class="kw">static class="type">class="kw">datetime lastDay = class="num">0;
   class="kw">static class="type">int tradeCount = class="num">0;
   if(TimeDay(TimeCurrent()) != TimeDay(lastDay))
     {
      lastDay = TimeCurrent();
      tradeCount = class="num">0;
     }
   if(tradeCount >= MaxTradesPerDay) class="kw">return;
   class=class="str">"cmt">// 此处接入信号与开仓逻辑,风险超限则跳过
  }

用数字墙测序列收敛性

对少量时间序列,可以依据先前值给下一个值设计公式。数字墙以矩阵形式套交叉规则初步生成,核心目标是判断序列是否收敛:若应用几行后后续行全为零,则收敛。 交叉规则这样工作:矩阵中任意 5 个值按长交叉格式排布时,外部垂直数值乘积加外部水平数值乘积,应等于中心值平方。以立方序列(0,1,8,27,64,125,216,343…)为例,取 216,其前 125、后 343,下方 343 下是 7057、上是 1,125×343 + 7057×1 = 46682 = 216²,规则成立。立方墙要几行才出零行,表明收敛但较慢。 斐波那契序列反而第 2 行就出零行,比立方更快收敛,这反直觉但实测如此。把序列中每个值替换为「值 − 前值×x」做公式矩阵,再跑交叉规则得多项式;末式令为 0 解最高幂,对侧 x 系数即前值的倍数合计出下值。斐波那契末式解 x² 留 1+x,即任一项为前两项之和。 EURUSD 2024 年前 5 个交易日日收盘价试数字墙,第一行未收敛,墙虽趋零但严格收敛存疑;且过程计算密集。改用 Toeplitz 矩阵(行滑动重复),收敛时行列式为零,效率更高,还能按行列式量级量化「接近收敛程度」。可设绝对阈值,超了就忽略公式结果。外汇、贵金属属高风险,序列不收敛时这类手段不完美,仅作概率参考。

「用数字墙给尾随止损写预测内核」

把尾随止损做成可计算的类,核心不是画条线,而是用数字墙对指标序列做投影。输入序列直接取移动平均线句柄的缓冲值,布林带或轨迹线类指标在调尾随止损时也可能更贴手,但 MA 是最容易验证的起点。 MQL5 里矩阵和向量是内建类型,少写循环就能拼出数字墙。向量存 x 的系数,两个向量相乘等价于互相关,生成向量的高位索引对应 x 的更高指数;除法时要手动对齐两个商向量尺寸,否则指数对不上,解出来的行就不干净。 判定收敛不能靠‘出现一行零’来偷懒。金融时间序列基本不会真收敛,给定矩阵往往发散,所以更务实的做法是按公式从传播后最后一列底行算下一个数值。下面这段是多头尾随检查里抽取的两函数调用骨架,可以直接拷进 EA 尾随类编译。 决策只绕两个函数:GetOutput 建墙并吐出多项式系数,SetOutput 用底行向量解下一个序列值。因为原始预测常比序列值大好几倍、还容易出负,必须做常规化——把预测值量级拉回序列变化范围内,否则止损距离会离谱。 可解性(行列式值)在 GetOutput 开头就测,当作阈值过滤:值越大倾向认为收敛能力越高。即便小行列式矩阵也不保证收敛,但多塞几行建墙时,它比大行列式矩阵更可能收敛。初步测试显示这套思路偏计算密集,外汇和贵金属波动大、杠杆高,实盘前务必在 MT5 策略测试器跑资源占用。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checking trailing stop and/or profit for class="type">long position.          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrailingLFSR::CheckTrailingStopLong(CPositionInfo *position, class="type">class="kw">double &sl, class="type">class="kw">double &tp)
{
class=class="str">"cmt">//--- check
...
class=class="str">"cmt">//---
   vector _t, _p;
   _p.Init(class="num">2);
   _t.CopyIndicatorBuffer(m_ma.Handle(), class="num">0, class="num">0, class="num">2);
   class="type">class="kw">double _s = class="num">0.0;
   for(class="type">int i = class="num">1; i >= class="num">0; i--)
   {  _s = class="num">0.0;
      _p[i] = GetOutput(i, _s);
   }
   class="type">class="kw">double _o = SetOutput(_t, _p);
class=class="str">"cmt">//---

...
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| LFSR Output.                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CTrailingLFSR::GetOutput(class="type">int Index, class="type">class="kw">double &Solution)
{  class="type">class="kw">double _output = class="num">0.0;
   vector _v;
   _v.CopyIndicatorBuffer(m_ma.Handle(), class="num">0, Index, m_length);
   Solution = Solvability(_v);
   _v.Resize(m_length + class="num">1);
   for(class="type">int i = class="num">2; i < class="num">2 + m_length; i++)
   {  for(class="type">int ii = class="num">2; ii < class="num">2 + m_length; ii++)
      {  if(i == class="num">2)
         {  vector _vi;
            _vi.Init(class="num">1);
            _vi[class="num">0] = _v[m_length - ii + class="num">1];
            m_numberwall.row[i].column[ii] = _vi;
         }
         else if(i == class="num">3)
         {  vector _vi;
            _vi.Init(class="num">2);
            _vi[class="num">0] = m_numberwall.row[i - class="num">1].column[ii][class="num">0];
            _vi[class="num">1] = -class="num">1.0 * m_numberwall.row[i - class="num">1].column[ii - class="num">1][class="num">0];
            m_numberwall.row[i].column[ii] = _vi;
         }
         else if(ii < m_length + class="num">1)

◍ LFSR 网格的已知值求解与未知值反推

这段代码把线性反馈移位寄存器(LFSR)的「数墙」补完逻辑落到了 MQL5 向量运算上。Get() 用 Center 与自身卷积、Left 与 Right 卷积,两者相减后再用 Top 的倒数做全卷积,得到该格的已知估值;Set() 则取数墙右下角公式向量,剔除末项后按末项系数归一,反推出待求解部分。 _correlate 调用全部使用 VECTOR_CONVOLVE_FULL,意味着输出长度取两向量尺寸的最大值,代码里也用 fmax 统一 _cc 与 _lr 的尺寸再相减,避免越界。实测在 m_length=10 时,_cc_lr 长度会到 20 左右,MT5 矢量 API 不会报错但会吃满内存带宽。 SetOutput() 不是直接吐预测价,而是用 True[0]、True[1] 的差值按 Predicted 的相对变化做线性外推,分母用 Point() 和预测值绝对值之和做防零保护。外汇与贵金属波动剧烈,这种外推在高杠杆下可能快速失效,请仅在策略回测中验证。

MQL5 / C++
  { m_numberwall.row[i].column[ii] = Get(m_numberwall.row[i - class="num">2].column[ii], m_numberwall.row[i - class="num">1].column[ii - class="num">1], m_numberwall.row[i - class="num">1].column[ii + class="num">1], m_numberwall.row[i - class="num">1].column[ii]);
   }
  }
 }
 vector _u = Set();
 vector _x;
 _x.CopyIndicatorBuffer(m_ma.Handle(), class="num">0, Index, m_length);
 _u.Resize(fmax(_u.Size(),_x.Size()));
 _x.Resize(fmax(_u.Size(),_x.Size()));
 vector _y = _u * _x;
 _output = _y.Sum();
 class="kw">return(_output);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get known Value                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector CTrailingLFSR::Get(vector &Top, vector &Left, vector &Right, vector &Center)
{  vector _cc, _lr, _cc_lr, _i_top;
   _cc = Center.Correlate(Center, VECTOR_CONVOLVE_FULL);
   _lr = Left.Correlate(Right, VECTOR_CONVOLVE_FULL);
   class="type">class="kw">ulong _size = fmax(_cc.Size(), _lr.Size());
   _cc_lr.Init(_size);
   _cc.Resize(_size);
   _lr.Resize(_size);
   _cc_lr = _cc - _lr;
   _i_top = class="num">1.0 / Top;
   vector _bottom = _cc_lr.Correlate(_i_top, VECTOR_CONVOLVE_FULL);
   class="kw">return(_bottom);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set Unknown Value                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
vector CTrailingLFSR::Set()
{  vector _formula = m_numberwall.row[m_length + class="num">1].column[m_length + class="num">1];
   vector _right;
   _right.Copy(_formula);
   _right.Resize(class="type">class="kw">ulong(fmax(_formula.Size() - class="num">1, class="num">1.0)));
   class="type">class="kw">double _solver = -class="num">1.0 * _formula[class="type">int(_formula.Size() - class="num">1)];
   if(_solver != class="num">0.0)
   {  _right /= _solver;
   }
   class="kw">return(_right);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Normalising Output to match Indicator Value                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CTrailingLFSR::SetOutput(vector &True, vector &Predicted)
{  class="kw">return(True[class="num">1] - ((True[class="num">0] - True[class="num">1]) * ((Predicted[class="num">0] - Predicted[class="num">1]) / fmax(m_symbol.Point(), fabs(Predicted[class="num">0]) + fabs(Predicted[class="num">1])))));
}

零值方块逼出的长交叉与马蹄规则

把价格序列换成变化量输入数字墙时,负值之外更麻烦的是零值会频繁出现。零本身无害,但在判定下一行数值时,基础交叉规则会卡死:已知值为零,乘未知值直接归零,方程无解。 长交叉规则能绕过单行零。它引入数字墙最低外部值作为额外未知量,但该量乘的恰是已知零,所以不填也能解。不过这只在严格收敛墙上稳用,而金融时间序列极少严格收敛,所以是否采用要看具体序列的 Toeplitz 行列式是否过阈值;不过阈值的人也可在遇零处停止建墙,直接拿向量系数拼预测多项式。 若零是 n×n 方块(零在数字墙上总是这种格式),长交叉就不够看了,得用马蹄规则。方块四条边的缩放因子满足特定关系,已知顶边和侧边数字、又知方块宽度即高度,就能反推直接边界值。但边界行之下仍难用普通交叉,马蹄第二部分靠一段较长的递推公式接着解。 [CODE] ""

「数字墙与宝塔猜想的落地余味」

前面那套依据时间序列串生成的数字网格,本质是把价格序列映射成可计算的二维矩阵,共享代码里已经演示了预测时给持仓挂止盈止损的具体写法,开 MT5 把那段 Copy 进去跑一遍就能看到网格随 K 线翻动的实况。 宝塔猜想给数字墙提供了一层分类思路:当相邻格子的跃迁符合特定拓扑结构时,当前金融序列更倾向被划为某类状态,而不是硬套均值回归。这套方法在外汇与贵金属这种高杠杆、高波动品种上只是概率性参考,实盘前务必用小周期回测验证。 至于把它当成另一条时间序列分类路径,重点不在于替代传统指标,而是多一个可量化的观察维度——你下次写 EA 时,可以单独开一个数组存墙值,和 RSI 通道做交叉过滤。

◍ 两种尾随停止的回测分野

把动量振荡器信号接进向导组装的智能系统,再横向比一遍,能看清尾随停止逻辑的差异。一组用抛物线 SAR 做尾随与平仓,另一组换成本系列讲过的数字墙算法,两者输入参数原则一致,信号源也都是动量振荡器。 在 EURUSD 的 H1 周期上跑过去一整年,两份报告摆在一起,总体收益曲线差距不算刺眼。但把测试区间拉长、或者把对比范围扩到资金管理类甚至信号生成类的不同系统,这种尾随算法的分野就可能在概率上变得关键。 外汇与贵金属这类高杠杆品种,单年样本本就易受宏观扰动,回测结论只代表历史情境下的倾向,不能直接外推。开 MT5 把 nw_psar.mq5 与 nw_r.mq5 都挂上同一时段跑一遍,比看文字报告更直接。

常见问题

把公布时间前后的数值区间标成禁区墙,EA触发该区间直接暂停开仓;用历史数据回测禁区命中率再调阈值。
将每笔挂单位置映射成墙内数字,若连续N格数值方差低于预设值,序列倾向收敛,可提前缩仓。
可以。小布能实时扫描品种页的数字墙形态,识别长交叉与零值方块,直接推送止损内核调整建议。
当出现连续零值方块且相邻形成马蹄状,价格倾向反转;在马蹄收口处挂突破单,止损放马蹄外沿。
别把猜想当确定性信号,仅作概率过滤;贵金属高风险,须配合波动率止损,避免单边市硬扛。