深度神经网络 (第 II 部)。制定和选择预测因子(基础篇)
给深度网络挑预测因子
在 MT5 里做深度神经网络预测,第一步不是堆层数,而是先定清楚喂给网络的预测因子(feature)。因子选得脏,网络再深也只是在拟合噪声。 原文作者 Vladimir Perervenko 在 2017-09-22 发布的这一思路,截至统计时点获得 5,328 次浏览与 14 条讨论,说明社区对「因子制定」的关注度不低。 实操上,因子应来自价格行为本身:收益率、波动区间、成交量变化率等可比直接裸价更有效。外汇与贵金属杠杆高、跳空频繁,因子需先做平稳化处理,否则训练易发散。 打开 MT5 的 MetaEditor,新建一个特征计算脚本,先把候选因子打印成 CSV,肉眼看分布再决定留哪些,比直接丢进网络更省时间。
◍ 先把行情拆成可喂给模型的特征
做价格行为相关的 AIGC 分析,第一步不是跑模型,而是把 MT5 里裸 K 和指标转成机器能吃的数字。原文把这一过程拆成两条线:一是对已有数据做变换、常规化、离散;二是从原始序列里构造新特征。外汇和贵金属波动受杠杆与流动性影响极大,特征若不带风控视角,模型很容易过拟合到噪声上。 变换指的是对价格序列做数学映射,比如取对数收益率代替绝对价差,能让不同价位下的波动更可比。常规化通常是把特征缩到相近量纲,避免某个数值大的指标在神经网络里一家独大。离散则是把连续变量切箱,例如把 RSI 分成超买、中性、超卖三档,方便后续做类别型预测因子。 构造新特征这块,常见做法是用高低收算真实波幅、用不同周期均线差表达趋势强度。这些在 MT5 里都能用 iMA、iATR 直接拉,不必自己重写底层。直观评估先看特征和目标涨跌是否同屏有肉眼可辨的关系;分析评估才上互信息或相关系数;真要上规模,再用神经网络做非线性筛选。 开 MT5 接好数据后,建议先只做一两个变换特征回测,确认样本外胜率没有塌方,再谈扩展。贵金属隔夜跳空频繁,离散切箱时记得把跳空时段单独标出来,否则特征分布会骗人。
「先备好前序脚本与数据」
这一节只交代一个前置条件:想跑通本文配套的 MQL5 脚本,你得先把系列第一篇文章里的数据获取、清洗和目标变量构造全部实现。 如果懒得重做,也可以直接从 RStudio 导出第一部分的计算结果文件,让 MT5 端脚本直接读取。没有这份输入,后续任何信号逻辑都无从验证。 外汇与贵金属行情具有高杠杆和高波动风险,前置数据若采样区间过短,得出的特征在实盘里失效的概率会明显放大。
从已有报价里榨出隐藏维度
特征工程不是往数据集里塞外部行情,而是把 MT5 里已经下载的 OHLC、tick 量、持仓变化这些既有字段,重新组合出模型更容易吃进去的维度。比如把收盘价的滚动 z-score 当作新列,能让样本在训练集上的边界划分更干净,标注误差倾向更低。 实际落地分两步。第一步是变换:按场景选常规化、去偏度、剔异常或离散化中的一种,贵金属跳空造成的极端影线就该先处理,不然模型会被少数点带偏。第二步才是建特征——从老变量里拆出新变量,例如用 (High-Low)/(Close-Open) 构造波动不对称率,可能暴露出肉眼看不出的盘整破裂前兆。外汇与贵金属杠杆高,这类特征只提高概率,不等于信号必中。
◍ 把连续特征压进模型能吃的形状
做价格行为建模时,原始行情特征往往带着右偏、量纲不一、非线性纠缠。直接喂给神经网络或线性模型,容易让少数极端 K 线主导梯度。先对变量做函数替换——平方、立方根、取对数,或三角函数变换,本质就是改分布和变量间关系,让后续训练更稳。 右偏严重时,底为 2 的对数常用,但因原始值有负、有远小于 1 的数,实战里要取 log2(x+1) 保精度。下面对一段 MT5 外接 R 式特征变换代码逐行拆: x.ln <- apply(x, 2, function(x) log2(x + 1)):对数据框 x 每列(margin=2)套 log2(x+1),生成新矩阵 x.ln。 sk.ln <- skewness(x.ln):算 x.ln 各列偏度,存 sk.ln。 输出里 ftlm 偏度 -0.27、stlm -2.66、rbci -4.48,已明显左偏;v.fatl 1.25、v.satl 1.83、v.rftl 2.07 仍右偏。说明一次对数变换不能把所有列压对称,左偏列是矫枉过正。 神经网络要求输入在 {-1,+1},用 caret 的 spatialSign 常规化前,异常值插补参数和标准化均值/sd 都必须只从训练集定,再灌到验证/测试集,否则会泄露未来信息。切分顺序建议:训练集定异常值边界→删异常→定标准化参数→三集合插补→三集合常规化。 离散化是另一路:用 mdlp() 最小描述长度切分,stlm、v.rstl、v.pcci 三列因与目标无关没切出点,可删;v.fatl 切 4 段、ftlm 3 段、v.satl 2 段后,交会图显示它们和目标等级相关性极佳。即便强行把变量等分 10 段,好预测因子仍有清晰阈值,差变量(stlm、v.rstl)依旧糊。外汇/贵金属特征离散化能降维度噪声,但模型若不吃 dummy 变量就白做,且行情跳空高风险仍在。
evalq({x.ln <- apply(x, class="num">2, function(x) log2(x + class="num">1)) sk.ln <- skewness(x.ln)}, env) > env$sk.ln ftlm stlm rbci pcci v.fatl Skewness -class="num">0.2715663 -class="num">2.660613 -class="num">4.484301 class="num">0.4267873 class="num">1.253008 v.satl v.rftl v.rstl v.ftlm v.stlm Skewness class="num">1.83489 class="num">2.065224 -class="num">0.0343451 -class="num">15.62414 class="num">0.01529019 v.pcci Skewness class="num">0.1811206 evalq({
「剔除与封顶两种去极值后的偏度对比」
在 R 环境里对对数化特征矩阵 x.ln 做两种去极值处理:remove_outliers 直接删掉离群行,capping_outliers 把离群值压缩到边界。两者都按列循环并用 cbind 合并,列名沿用原矩阵,保证后续能对齐。 跑完 skewness 后,两组偏度差异肉眼可见。以 stlm 为例,删除离群后偏度从 -0.3549 变为 -0.3549119(基本不变是因为该列离群少),而封顶后偏度走到 -0.4582,比删除法更负。rbci 在删除法下偏度 -0.10999,封顶法下 -0.16589,封顶让分布左偏略增。 用 boxplot 把 x.ln、x.ln.out、x.ln.cap 三张箱线图并排(par(mfrow=c(2,2))),能直接看到封顶法保留了样本量但须须压缩了 whisker 范围;删除法样本变少但箱体更干净。外汇与贵金属特征工程里,删值会丢掉极端行情样本,封顶则保留但可能扭曲尾部,属高风险数据的常见权衡。 继续把封顶后的矩阵 cbind 回日期与分类标签,用 GGally 的 ggpairs 按 Class 着色画 2:7 与 8:13 两屏散点矩阵,可肉眼核查类别间线性可分性。最后对环境内 x 做 sin(2*pi*x) 变换并算偏度,给非线性特征另一条路。
foreach(i = class="num">1:ncol(x.ln), .combine = "cbind") %do% { remove_outliers(x.ln[ ,i]) } -> x.ln.out colnames(x.ln.out) <- colnames(x.ln) }, env) evalq({ foreach(i = class="num">1:ncol(x.ln), .combine = "cbind") %do% { capping_outliers(x.ln[ ,i]) } -> x.ln.cap colnames(x.ln.cap) <- colnames(x.ln) }, env) evalq({ sk.ln.out <- skewness(x.ln.out) sk.ln.cap <- skewness(x.ln.cap) }, env) > env$sk.ln.out ftlm stlm rbci pcci Skewness -class="num">0.119055 -class="num">0.3549119 -class="num">0.1099921 -class="num">0.01476384 v.fatl v.satl v.rftl v.rstl Skewness -class="num">0.02896319 -class="num">0.03634833 -class="num">0.06259749 -class="num">0.2120127 v.ftlm v.stlm v.pcci Skewness -class="num">0.05819699 -class="num">0.01661317 -class="num">0.05420077 > env$sk.ln.cap ftlm stlm rbci pcci Skewness -class="num">0.1814781 -class="num">0.4582045 -class="num">0.1658855 -class="num">0.02849945 v.fatl v.satl v.rftl v.rstl Skewness -class="num">0.04336238 -class="num">0.04400781 -class="num">0.0692754 -class="num">0.2269408 v.ftlm v.stlm v.pcci Skewness -class="num">0.06184128 -class="num">0.02856397 -class="num">0.06258243 par(mfrow = c(class="num">2,class="num">2)) boxplot(env$x.ln, main = "x.ln with outliers", xlab = "") boxplot(env$x.ln.out, main = "x.ln.out without outliers", xlab = "") boxplot(env$x.ln.cap, main = "x.ln.cap with imputed outliers", xlab = "") par(mfrow = c(class="num">1,class="num">1)) evalq(x.ln.cap %>% tbl_df() %>% cbind(Data = dataSetClean$Data, ., Class = dataSetClean$Class) -> dataSetLnCap, env) require(GGally) evalq(ggpairs(dataSetLnCap, columns = class="num">2:class="num">7, mapping = aes(class="type">class="kw">color = Class), title = "PredLnCap1"), env) evalq(ggpairs(dataSetLnCap, columns = class="num">8:class="num">13, mapping = aes(class="type">class="kw">color = Class), title = "PredLnCap2"), env) evalq({x.sin <- apply(x, class="num">2, function(x) sin(class="num">2*pi*x)) sk.sin <- skewness(x.sin) }, env)