用数字墙给EA交易划禁区
◍ 用数字墙给EA交易划禁区
在 MQL5 向导里做 EA 时,经常会遇到一类参数:它们不是用来优化,而是用来硬性卡住交易逻辑边界的。这类参数堆在一起,就像一面「数字墙」,挡住不符合条件的订单。 典型场景是限制单笔风险或总持仓手数。比如下面这段,用 Input 宏把最大风险百分比和单日最大交易次数钉死,向导生成代码后这些值不可被策略优化器随意越过。 外汇与贵金属杠杆高,墙设得太宽可能一夜回撤超标,设得太窄又容易错过信号;建议先在 MT5 策略测试器用 2023 年 XAUUSD 的 H1 数据跑一遍,看墙内外的净值曲线差异。
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 策略测试器跑资源占用。
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() 和预测值绝对值之和做防零保护。外汇与贵金属波动剧烈,这种外推在高杠杆下可能快速失效,请仅在策略回测中验证。
{ 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 都挂上同一时段跑一遍,比看文字报告更直接。