您应该知道的 MQL5 向导技术(第 02 部分):Kohonen 映射·综合运用
把高维 feed 压进神经元的映射误差
神经元映射的本质,是在没有已知 functor 标签时,从输出层里捞出一个与 feed 数据欧氏距离最短的神经元,把它当作当前样本的最佳匹配。这个距离被直接称作 feed 误差:值越小,说明网络对该样本的表达越可靠,后续用于信号判定的置信度也越高。 Kohonen 映射里的量化误差,通常指高维输入被强行坍缩到低维输出时丢失的信息量。原文没有给出统一定义,这里直接用 feed 误差作为它的代理指标——也就是拿最佳匹配神经元的欧氏距离来近似整体量化质量。 下面这段代码做了一次完整的映射扫描:先随机挑一个输出神经元算初始误差,再遍历全部输出节点,遇更小距离就拷贝该神经元的 fd / fr 权重到 best_neuron,最后把最小 feed 误差除以 IN_RADIUS 写回 Map.QE。你在 MT5 里把 IN_RADIUS 调小,QE 读数会成比例放大,方便横向比较不同参数下的映射紧致度。 外汇与贵金属市场的高杠杆特性会放大模型误差,QE 偏高时网络给出的映射倾向并不可靠,需结合实盘波动验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSignalKM::NetworkMapping(Cmap &Map,Cneuron *MapNeuron) { Map.QE=class="num">0.0; Map.network.best_neuron = new Cneuron(); class="type">int _random_neuron=rand()%Map.network.GetOutputIndex(); Map.network.GetInNeuron(class="num">0); Map.network.GetOutNeuron(_random_neuron); class="type">class="kw">double _feed_error = EuclideanFeed(Map.network.i_neuron,Map.network.o_neuron); for(class="type">int i=class="num">0; i<Map.network.GetOutputIndex(); i++) { Map.network.GetOutNeuron(i); class="type">class="kw">double _error = EuclideanFeed(Map.network.i_neuron,Map.network.o_neuron); if(_error < _feed_error) { for(class="type">int w=class="num">0; w<IN_WIDTH; w++) { Map.network.best_neuron.fd.Set(w,Map.network.o_neuron.fd.Get(w)); } for(class="type">int l=class="num">0; l<OUT_LENGTH; l++) { Map.network.best_neuron.fr.Set(l,Map.network.o_neuron.fr.Get(l)); } _feed_error = _error; } } Map.QE=_feed_error/IN_RADIUS; }
「用向导先跑大周期省时间」
MQL5 向导装配 EA 是最直接的路径,不用手写框架就能把信号模块拼起来。但有个实操坑:训练迭代很吃算力。 理想情况下若训练覆盖足够长的样本,10000 次训练迭代会在每根柱线上都消耗时间。所以先在 H4 或 D1 这种大时间帧里做测试,单根柱数量少,跑通逻辑再切小周期,能省掉大把等待。 外汇与贵金属杠杆高、滑点跳空频繁,大周期验证通过的模型不等于小周期能直接用,切换后必须重测。
◍ MT5 测试器里跑两种参数档位
把量化误差代理(QE)和拓扑误差代理(TE)当成灵敏度旋钮,在策略测试器里只换这两值就能看出系统性格。保守档取 QE=0.5、TE=12.5;激进档取 QE=0.75、TE=25.0,其余输入保持默认。 输入项不多:训练读取控制初始化前是否加载已有训练文件,缺失则不验证;训练写入在逆初始化后决定是否落盘;训练本身随 EA 运行实时发生。Kohonen 映射另有两个关键项——训练率(上限 1.0)与训练迭代,数值越高性能倾向更好,但吃 CPU 和时间。 样本系统用 EURJPY 在 2018.10.01–2021.06.01 训练,再以前向验证拉到当前。保守档锋锐比率 0.43,激进档 0.85,前者几乎只有后者一半,说明保守设置回报可能不足。 外汇与贵金属属高风险品种,上述结果仅限历史区间。部署前建议用经纪商实际跳价数据做较长时段预测试,并依个人风格调 feed 与 functor。
Kohonen 映射把多维行情压成一维坐标
MQL5 向导在赶时间装配交易系统上确实灵活,本文用 Kohonen 映射把价格时间序列的多维 feed 数据,压到 -1.0 到 1.0 的单一维度。这种做法不常见,但贴合 Kohonen 降低复杂性、简化决策的本质。 文中还展示了 MQL 函数库里数组列表与哈希映射的更多代码,可直接抄进自己的信号类里改。 外汇与贵金属杠杆高、滑点跳空频繁,这类降维信号只作概率参考,实盘前务必在 MT5 策略测试器跑至少 3 个月 tick 数据。 下面这段 MACD 信号初始化代码,是向导生成类的典型骨架,参数接口已经暴露好,改 fast/slow/signal 周期就能换节奏。
class="type">bool CSignalMACD::InitMACD(CIndicators *indicators) { class=class="str">"cmt">//--- 将对象添加到集合中 if(!indicators.Add(GetPointer(m_MACD))) { printf(__FUNCTION__+": error adding object"); class="kw">return(false); } class=class="str">"cmt">//--- 初始化对象 if(!m_MACD.Create(m_symbol.Name(),m_period,m_period_fast,m_period_slow,m_period_signal,m_applied)) { printf(__FUNCTION__+": error initializing object"); class="kw">return(false); } class=class="str">"cmt">//--- ok class="kw">return(true); } class="kw">protected: CiMACD m_MACD; class=class="str">"cmt">// 对象振荡器 class=class="str">"cmt">//-- 调整参数 class="type">int m_period_fast; class=class="str">"cmt">// 振荡器的 "快速 EMA 周期 "参数 class="type">int m_period_slow; class=class="str">"cmt">// 振荡器的 "慢速 EMA 周期 "参数 class="type">int m_period_signal; class=class="str">"cmt">// 振荡器的 "差值平均周期 "参数 ENUM_APPLIED_PRICE m_applied; class=class="str">"cmt">// 摆动器的 "价格序列 "参数 class="type">void PeriodFast(class="type">int value) { m_period_fast=value; } class="type">void PeriodSlow(class="type">int value) { m_period_slow=value; } class="type">void PeriodSignal(class="type">int value) { m_period_signal=value; } class="type">void Applied(ENUM_APPLIED_PRICE value) { m_applied=value; class="kw">protected: class=class="str">"cmt">//--- 振荡器的初始化方法
「把这条线请下神坛」
上面这段 CSignalKM::OpenLongParams 的实现,把 MACD 主线和信号线直接封装成 Main(ind) 与 Signal(ind) 两个取值口,调用层只管拿索引取数,不用再碰底层指标句柄。 当 general 过滤器为空时,代码走 m_MACD.Refresh(-1) 强制刷新,再用 m_symbol.Ask() 或预设 base_price 兜底开仓基准价;_range 的计算把 MACD 主线值加上 (StopsLevel+FreezeLevel)*Point,等于给 broker 的最小止损距离留了硬冗余。 外汇和贵金属杠杆高、滑点跳空频繁,MACD 交叉在震荡市里假信号占比可能超过六成,把它当唯一入场依据大概率被扫。开 MT5 把这段塞进自己的信号类,把 StartIndex() 换成 0 或 1 跑一下回测,你会看到它在单边行情吃肉、在箱体里反复挨打——这条线,从来不是神。
class="type">bool InitMACD(CIndicators *indicators); class=class="str">"cmt">//--- 获取数据的方法 class="type">class="kw">double Main(class="type">int ind) { class="kw">return(m_MACD.Main(ind)); } class="type">class="kw">double Signal(class="type">int ind) { class="kw">return(m_MACD.Signal(ind)); } class="type">bool CSignalKM::OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration) { CExpertSignal *general=(m_general!=-class="num">1) ? m_filters.At(m_general) : NULL; class=class="str">"cmt">//--- if(general==NULL) { m_MACD.Refresh(-class="num">1); class=class="str">"cmt">//--- 如果没有明确指定基准价格,则采用当前市场价格 class="type">class="kw">double base_price=(m_base_price==class="num">0.0) ? m_symbol.Ask() : m_base_price; class=class="str">"cmt">//--- 价格重载,根据 MACD 设置入口价格 price =base_price; class="type">class="kw">double _range=m_MACD.Main(StartIndex())+((m_symbol.StopsLevel()+m_symbol.FreezeLevel())*m_symbol.Point()); class=class="str">"cmt">//