利用Python进行季节性过滤并为EA的ONNX深度学习模型选择时间周期·进阶篇
(2/3)· 下载全量tick、做季节性切片、训ONNX,再看哪个周期真的把噪声滤干净了
连续报价间隔里的自相关有多强
自相关衡量的是同一序列在不同时间间隔上的相似度,值越靠近 1,说明前后时段的数值越像、惯性越明显。对外汇和贵金属这种高波动品种,先看自相关再谈策略,能少踩很多坑——这类品种杠杆高,惯性误判可能直接放大亏损。 下面这组从分钟级报价里算出的自相关系数,前 36 个滞后阶全部高于 0.9:首阶 0.99736,第 10 阶还有 0.97356,到第 36 阶降到 0.90826。这种衰减极慢的形态,说明短期价格记忆很强,均值回复类手法在裸跑时大概率会被趋势拖垮。 真要验证你手上的货币对,别只看 EURUSD,把 XAUUSD 也拉进来自算一遍,黄金的自相关结构和币种常常不是一回事。
[<span class="number">class="num">1</span>. <span class="number">class="num">0.99736147</span> <span class="number">class="num">0.99472432</span> <span class="number">class="num">0.99206626</span> <span class="number">class="num">0.98937664</span> <span class="number">class="num">0.98671649</span> <span class="number">class="num">0.98405706</span> <span class="number">class="num">0.98144222</span> <span class="number">class="num">0.9787753</span> <span class="number">class="num">0.97615525</span> <span class="number">class="num">0.97356318</span> <span class="number">class="num">0.97099777</span> <span class="number">class="num">0.96848029</span> <span class="number">class="num">0.96602671</span> <span class="number">class="num">0.96360361</span> <span class="number">class="num">0.96113539</span> <span class="number">class="num">0.95865344</span> <span class="number">class="num">0.95615626</span> <span class="number">class="num">0.95362417</span> <span class="number">class="num">0.95108177</span> <span class="number">class="num">0.94854957</span> <span class="number">class="num">0.94599045</span> <span class="number">class="num">0.94346076</span> <span class="number">class="num">0.94091564</span> <span class="number">class="num">0.93837742</span> <span class="number">class="num">0.93583734</span> <span class="number">class="num">0.9332909</span> <span class="number">class="num">0.93074655</span> <span class="number">class="num">0.92826504</span> <span class="number">class="num">0.92579028</span> <span class="number">class="num">0.92330505</span> <span class="number">class="num">0.92084645</span> <span class="number">class="num">0.91834403</span> <span class="number">class="num">0.91581296</span> <span class="number">class="num">0.91328091</span> <span class="number">class="num">0.91076099</span> <span class="number">class="num">0.90826447</span>]
「把二月行情压成一个可训练的CSV」
做二月的 ONNX 模型,第一步不是直接训网络,而是先把分散的季节性数据并成一张表。用 concat_seasonal.py 把各来源数据拼进 seasonal_feb_concat.zip 里的单一 CSV,这一步没做对,后面 LSTM 读到的是错位样本。 训练侧调用 onnx_LSTM_simple_EURUSD_concat_seasonals.py,它直接吃上面那份合并 CSV,产出可用于 MT5 推理的 ONNX 权重。欧元兑美元属高杠杆品种,模型在样本外仅代表概率倾向,实盘前务必用历史数据回测验证过拟合程度。 附带的 create_concat_seasonal_data.py 是合并逻辑的可复现脚本,改路径就能套到别的月份。开 MT5 前先确认 CSV 行数与脚本日志一致,否则 ONNX 初始化会报维度错。
◍ 季节性模型跑分:负夏普占比只有五分之一
把季节性模型单独拉出来看,整体表现谈不上惊艳,但胜在稳。在回测表格里,夏普比率为负的品种并不多,和带 120 天(1 小时)过滤的模型放一起对比更有意思:过滤模型优化结果里约一半是负夏普,季节性模型里负夏普只占大概五分之一。 值得老手留意的是,季节性模型即便 r2 只有 0.72 上下,看上去解释力一般,却更倾向产出正回报,属于低 r2 高鲁棒的那一类。我没去测去掉止损止盈的版本,实盘里 EA 始终挂 SL/TP 更不容易被极端跳空洗掉。 附在下面的 ONNX 模型和 mq5 源文件可直接拖进 MT5 跑 EURUSD H1 验证。关键拟合指标如下,R2 卡在 0.7167,RMSE 约 0.0131,样本内误差可控。 外汇与贵金属杠杆高、滑点跳空频繁,上述回测不代表未来概率,复制前先开 MT5 用历史中心数据重跑一遍。
RMSE : class="num">0.013137568368684325 MSE : class="num">0.00017259570264185493 R2 score : class="num">0.7166764010650979
周期拉长,结果更稳
EURUSD 上把过滤器微调后,策略按周期做了分段回测:1小时跑的是 2024 年 2 月整月,30分钟只跑 2 月 1 日至 15 日,15分钟则分带过滤器与不带过滤器两组对照。带过滤的 15分钟用了 LSTM.15m.EURUSD.120.0.98.onnx 配合 ONNX.eurusd.H1.120.15m_eurusd.mq5,30分钟与 1小时均切到 LSTM.30m.EURUSD.120.0.94.onnx 并固定启用过滤器。 对比下来,15分钟加过滤器在夏普比率上整体优于裸跑;而 2天周期不可直接用,便退到 1天周期给 EA 喂后续天数的值,2小时文件单独打包(2h.zip),超过 32 个文件的部分只能塞进文件夹上传。 一个明显倾向是:周期越大,各档 TP 与 SL 组合下的净值曲线越不容易塌。外汇与贵金属属高风险品种,这种稳健只是概率层面的回测现象,实盘仍可能背离。 下面这段是过滤器本身的低通处理,cutoff 取 0.1(相对奈奎斯特频率的比例),order=1,先 MinMax 缩放到 0~1 再走 butter 低通,最后反缩放回价格量纲。
cutoff_frequency = class="num">0.1 # Frecuencia de corte en proporción a la frecuencia de Nyquist order = class="num">1 from sklearn.preprocessing class="kw">import MinMaxScaler from scipy.signal class="kw">import butter, lfilter # Parámetros del filtro pasa bajo cutoff_frequency = class="num">0.1 # Frecuencia de corte en proporción a la frecuencia de Nyquist order = class="num">1 # Aplicar filtro pasa bajo def butter_lowpass_filter(data, cutoff, fs, order): nyquist = class="num">0.5 * fs normal_cutoff = cutoff / nyquist b, a = butter(order, normal_cutoff, btype=&class="macro">#x27;low&class="macro">#x27;, analog=False) print("Coeficientes del filtro - b:", b) print("Coeficientes del filtro - a:", a) y = lfilter(b, a, data) class="kw">return y scaled = MinMaxScaler(feature_range=(class="num">0,class="num">1)) valores_df1 = df.filter([&class="macro">#x27;close&class="macro">#x27;]).values valores_df1 = pd.DataFrame(valores_df1) x_features1 = valores_df1[[class="num">0]] valores_df2 = x_features1.values data_escalada = scaled.fit_transform(valores_df2) print(data_escalada) filtered_data_low = butter_lowpass_filter(data_escalada, cutoff_frequency, fs=class="num">1, order=order) print("filtered_data_low",filtered_data_low) filtered_data_low_unscaled = scaled.inverse_transform(filtered_data_low)