您应当知道的 MQL5 向导技术(第 23 部分):CNNs·综合运用
用价格差分矩阵给卷积打标签
卷积网络跑之前,得先有监督信号。这里把目标矩阵拆成三组:高点差分代理看涨强度,低点差分代理看跌强度,收盘差分幅度小则倾向震荡扫损行情。外汇与贵金属波动剧烈,这种标签只是概率映射,不是方向保证。 具体构造上,每一行对应不同价格加权:纯 High、 (H+H+C)/3、 (H+C)/2 三种;列方向则是 1 根、2 根、3 根 K 线的差分。比如 i==0 时,_targets[i][j][0] 直接算 _h[j] - _h[j+1],即单根新高落差,数值越大越可能暗示多头延续。 代码里用 ArrayResize 按核尺寸建矩阵,再双层循环填值。你可以把这段直接贴进 MT5 的 EA 初始化段,把 _h/_l/_c 换成你自己的价格序列缓冲,跑完 Print 一下 _targets 看前三根 bar 的差分符号分布。
matrix _targets[]; ArrayResize(_targets, __KERNEL_SIZES.Size()); for(class="type">int i = class="num">0; i < class="type">int(__KERNEL_SIZES.Size()); i++) { _targets[i].Init(__KERNEL_SIZES[i], __KERNEL_SIZES[i]); for(class="type">int j = class="num">0; j < __KERNEL_SIZES[i]; j++) { if(i == class="num">0)class=class="str">"cmt">// highs for &class="macro">#x27;bullishness&class="macro">#x27; { _targets[i][j][class="num">0] = _h[j] - _h[j + class="num">1]; _targets[i][j][class="num">1] = ((_h[j] + _h[j] + _c[j]) / class="num">3.0) - ((_h[j + class="num">1] + _h[j + class="num">1] + _c[j + class="num">1]) / class="num">3.0); _targets[i][j][class="num">2] = ((_h[j] + _c[j]) / class="num">2.0) - ((_h[j + class="num">1] + _c[j + class="num">1]) / class="num">2.0); } else if(i == class="num">1)class=class="str">"cmt">// lows for &class="macro">#x27;bearishness&class="macro">#x27; { _targets[i][j][class="num">0] = _l[j] - _l[j + class="num">1]; _targets[i][j][class="num">1] = ((_l[j] + _l[j] + _c[j]) / class="num">3.0) - ((_l[j + class="num">1] + _l[j + class="num">1] + _c[j + class="num">1]) / class="num">3.0); _targets[i][j][class="num">2] = ((_l[j] + _c[j]) / class="num">2.0) - ((_l[j + class="num">1] + _c[j + class="num">1]) / class="num">2.0); }
「CNN信号输出的多空权重归算」
上面这段是卷积网络推理收口的地方。GetOutput() 先通过 CopyRates 把最新 K线的高、低、收拷贝进 _h/_l/_c,长度取 __KERNEL+1 根,说明模型看的不是单根而是局部窗口。 输入张量由均线差填充:_inputs[i][j] = _c[__KERNEL] - _ma[__KERNEL],即用当前收盘价减对应均线值作为卷积核扫过的特征。随后走 Set→Pad→Convolve→Activate→Pool 标准前向流程。 输出层有三路:output[0] 取中位数大于0时记做多强度 _long,output[1] 中位数小于0时记做空强度 _short,output[2] 绝对值当中性权重 _neutral。 最终返回 (_long-_short)/(_long+_short+_neutral),区间理论在 [-1,1]。该比值越靠近1倾向多头信号越强,越靠近-1倾向空头信号越强;外汇与贵金属杠杆高,实盘使用前建议在 MT5 策略测试器用不同 __KERNEL 验证输出分布。
class="type">class="kw">double CSignalCNN::GetOutput() { ... ... _index = class="num">0; _h.CopyRates(m_symbol.Name(), m_period, class="num">2, class="num">0, __KERNEL + class="num">1); _l.CopyRates(m_symbol.Name(), m_period, class="num">4, class="num">0, __KERNEL + class="num">1); _c.CopyRates(m_symbol.Name(), m_period, class="num">8, class="num">0, __KERNEL + class="num">1); for(class="type">int i = class="num">0; i < m_input_size; i++) { for(class="type">int j = class="num">0; j < m_input_size; j++) { if(_ma.CopyIndicatorBuffer(m_ma[_index].Handle(), class="num">0, class="num">0, __KERNEL + class="num">1)) { _inputs[i][j] = _c[__KERNEL] - _ma[__KERNEL]; _index++; } } } CNN.Set(_inputs); CNN.Pad(); CNN.Convolve(); CNN.Activate(); CNN.Pool(); class="type">class="kw">double _long = class="num">0.0, _short = class="num">0.0; if(CNN.output[class="num">0].Median() > class="num">0.0) { _long = fabs(CNN.output[class="num">0].Median()); } if(CNN.output[class="num">1].Median() < class="num">0.0) { _short = fabs(CNN.output[class="num">1].Median()); } class="type">class="kw">double _neutral = fabs(CNN.output[class="num">2].Median()); if(_long+_short+_neutral == class="num">0.0) { class="kw">return(class="num">0.0); } class="kw">return((_long-_short)/(_long+_short+_neutral)); }
◍ EURJPY 日线回测暴露的阈值僵局
把信号类通过 MQL5 向导塞进 EA 后,按 EURJPY 2023 年日线数据跑策略测试器,多空信号输出只会出现 0 或 100 两种值。 根源在于网络输出没做归一化,导致开仓和平仓判定被卡在极端阈值上,调参空间实际被锁死。 若对网络结果做归一化,输出会连续分布,开仓/平仓阈值就能放开做优调,信号对价格变化的反应可能更灵敏。外汇与贵金属属高风险品种,此类回测结论仅代表历史样本表现,实盘存在显著失效概率。
把单层 CNN 类接着往上叠
前面几节把 CNN 的填充、前馈、激活、池化都塞进了一个独立的 MQL5 类文件,反向传播也顺着卷积在大小不等矩阵上的配对方式走通了。目前只实装了单层,cnn.mqh 这个头文件 14.55 KB,SignalWZ_23_.mqh 是 12.12 KB,都是可以直接在 MT5 里打开改的。 想验证的人,把 cnn.mq5(6.75 KB)拖进策略测试器跑一遍前馈,看卷积层输出维度是否和手算的填充后矩阵对得上;外汇和贵金属行情用这类模型自带过拟合风险,信号只作概率参考。 真要往下探,别只盯着图像输入,把不同品种 tick 序列喂进同一个单层类,再在外面套一层转换器做堆叠,未发掘的空间比换激活函数大得多。