LSTM价格预测与多因子决策:构建稳健的量化交易回测系统
简介一套基于LSTM的比特币与黄金价格走势综合决策模型项目源码含数据、代码和项目说明面向计算机专业期末大作业、课程设计或毕业设计学生也适合学习金融时间序列预测的实战型学习者。项目以zip压缩包发布共97个文件含42个CSV数据文件、38个Python脚本及PDF报告、README文档整体仅1.39MB数据、脚本与报告分类清晰。代码覆盖数据预处理、技术指标、模型训练、交易信号判断与回测评估具体实现LSTM预测、MACD、RSI、乖离率、灰色预测、综合评分、牛熊市判断与敏感度分析并配有烛台图、MACD曲线、资产变化、买卖点等可视化脚本数据涉及比特币、黄金、买卖点、控制变量、资产变化等多维记录。目前已有303人学习下载。项目由个人独立完成经导师指导认可评审分为99分代码完整可直接运行便于快速复现与研究也可作为课程设计或毕业论文参考模板。1. LSTM对价格拟合得越好决策收益反而越差LSTM 对价格序列的拟合误差可以压得很低但如果你直接拿预测值去回测买卖收益往往是负的——这是时间序列预测项目里最常见的反直觉现象。原因在于 LSTM 解决的是“方向”问题而交易决策还要处理“什么时候买卖、投入多少、信号冲突时信谁”。这个项目把 LSTM 作为预测核心同时引入 MACD、RSI、乖离率、牛熊市判定等传统技术指标构造综合评分再用灰色预测 GM(1,1) 和灰色-马尔科夫复合模型做对照验证最终输出买卖时机、每次投入量与资产值变化的完整决策链路。数据全部落在 CSV 中代码按模块拆分适合课程设计、期末大作业也适合想一次看清从数据清洗、特征工程到决策回测全流程的从业者。2. 特征工程层CSV 资产表到 MACD、RSI、乖离率因子2.1 原始数据结构与清洗起点项目 data 目录下的 CSV 分为几类一是直接记录价格序列的“比特币价值.csv”“黄金价值.csv”二是由原始价格加工出来的技术指标“ema12.csv”“ema26.csv”“macd.csv”“rsi.csv”“比特币乖离率.csv”三是决策结果文件“买卖时机.csv”“买点.csv”“每次投入量.csv”“资产值变化.csv”四是敏感度分析产物例如“资产值变化-随机提前或推迟一天.csv”。这些文件之间的依赖关系其实就是一套“原始价格 → 因子 → 信号 → 决策 → 回测”的流水线。拿到这类数据第一步不是建模而是统一时间索引与缺失值。判断牛熊市.py 和 main.py 都依赖日频序列如果 CSV 里有交易所休市导致的空洞直接用会污染 LSTM 的滚动窗口。常见做法是先用 date 列做索引排序再对缺失日期做前向填充并显式剔除重复行。下面这段代码是我在处理该项目数据时惯用的清洗骨架import pandas as pd df pd.read_csv(比特币价值.csv) df[date] pd.to_datetime(df[date]) df df.sort_values(date).drop_duplicates(subsetdate) df df.set_index(date).asfreq(D).ffill() df df.dropna(subset[close]) df.to_csv(比特币价值-clean.csv, encodingutf-8-sig) print(df.head())asfreq(D)会把索引重采样到日频缺的日期补 NaN再用ffill()填充这样后续计算 EMA、RSI 时窗口不会断。清洗后的 close 列作为所有后续因子的基准。这里需要注意只对价格做前向填充适用于连续交易中断但像 BTC 这种 7×24 小时市场如果源数据本身就是日线重采样反而可能引入并不存在的空窗所以建议先检查原始 CSV 里日期是否连续再决定要不要重采样。2.2 EMA12、EMA26 与 MACD 的构造项目里“ema12.csv”“ema26.csv”“dif.csv”“macd.csv”四个文件对应 MACD 指标的分步计算。MACD 由快线 EMA12 减慢线 EMA26 得到 DIF再用 DIF 的 9 日 EMA 得到 DEA 柱。这类递推公式在 pandas 里用ewm一行就能完成但要注意adjustFalse与默认值的区别。adjustTrue时权重会做归一化修正导致早期数值与交易软件不一致所以我一般统一用adjustFalse保证与通达信、TradingView 的 MACD 口径对齐。import pandas as pd df pd.read_csv(比特币价值-clean.csv, index_coldate) df[ema12] df[close].ewm(span12, adjustFalse).mean() df[ema26] df[close].ewm(span26, adjustFalse).mean() df[dif] df[ema12] - df[ema26] df[macd] df[dif].ewm(span9, adjustFalse).mean() df[[ema12, ema26, dif, macd]].to_csv(综合指标-比特币.csv)span参数直接对应 EMA 周期12、26、9 是 MACD 标准参数不建议在决策基准中随意改动。如果把span9改成 5柱状图会更敏感但假信号会显著增加。2.3 RSI 与乖离率从两个角度度量“过热”RSI 和乖离率在项目里分别由“RSI.py”和“比特币乖离率.csv”承载。RSI 衡量一段时间内涨跌幅的相对强度默认 14 日乖离率衡量当前价对均线的偏离程度项目里用 EMA26 作基准。两者都是超买超卖类指标但视角不同RSI 看动量乖离率看价格与均线的绝对距离二者同时高于阈值才是更强的风险信号。import pandas as pd df pd.read_csv(综合指标-比特币.csv, index_coldate) delta df[close].diff() gain delta.clip(lower0).rolling(14).mean() loss (-delta.clip(upper0)).rolling(14).mean() rs gain / loss df[rsi] 100 - 100 / (1 rs) df[bias] (df[close] - df[ema26]) / df[ema26] * 100 df[[close, rsi, bias]].to_csv(综合因子.csv)RSI 的计算核心是平均涨幅与平均跌幅的比值rolling(14).mean()用的是简单平均Wilder 平滑需要ewm(alpha1/14)两者在数据量小的时候差异明显。选择哪种取决于你后续做信号阈值时习惯用哪套口径如果参考 70/30 阈值Wilder 平滑更常见如果只是作为机器学习特征简单平均足够稳定。3. LSTM 价格预测序列切片、归一化与训练参数边界3.1 为什么选 LSTM 而不是 ARIMA 或 Transformer比特币和黄金的价格序列都带有明显波动聚集和趋势惯性ARIMA 对这类非线性关系的表达能力有限。Transformer 在同长度序列上效果可能更好但需要的数据量和调参成本更高对本项目几千行级别的日频数据来说容易过拟合。LSTM 的定位在这类任务里恰好合适门控机制能保留趋势记忆又不像 Transformer 那样需要位置编码和大量正则。项目源码中“LSTM-比特币.csv”“LSTM-黄金.csv”就是两个资产分别训练、分别预测后的输出文件。3.2 滑动窗口构造与归一化的常见坑构造 LSTM 输入的第一步是把价格序列切成固定长度的子序列。窗口长度选 7 还是 30会直接决定模型记住的是“一周波动”还是“月线结构”。项目的做法按常规日频数据来看取 7 到 14 之间比较稳。数据归一化方面min-max 缩放比标准化更适合价格序列因为价格有明确边界但要注意必须用训练集的 min/max 去变换验证集和测试集否则会把未来信息泄漏进训练过程。import numpy as np def build_sequences(data, lookback7): X, y [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i]) y.append(data[i]) return np.array(X), np.array(y) close df[close].values.reshape(-1, 1) X, y build_sequences(close, lookback7) split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] print(X_train.shape, y_train.shape)build_sequences里i从lookback开始X[i]取前 7 个点y[i]预测第 8 个点。注意输入形状是(样本数, 时间步, 特征数)即使特征只有一条也要保留最后一维否则 Keras 会报维度不匹配。训练集和测试集不 shuffle保持时间顺序这样才能模拟真实预测场景。3.3 模型结构与训练参数项目配套的 LSTM 源码主体是一个两层的 LSTM 叠加 Dropout 的结构。第一层返回完整序列第二层只返回最后一个时间步的输出再接一层 Dense 输出预测价格。hidden units 一般取 32 到 64太大容易在一千多个样本上过拟合太小则学不到趋势。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(7, 1))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizeradam, lossmse) history model.fit( X_train, y_train, validation_split0.1, epochs100, batch_size32, verbose0 )return_sequencesTrue让第一层 LSTM 把每个时间步的隐藏状态都传给下一层第二层return_sequencesFalse只输出最后一个时间步的结果。epochs100在数据量小时偏多建议配合callbacks.EarlyStopping(patience10)使用防止验证损失在 40 轮后开始回升。batch_size 取 32 是常规值如果序列长度不同需要 padding 或 mask。3.4 预测误差评价与 csv 输出格式训练完模型后需要把测试集的预测结果反归一化回真实价格区间再和真实值计算误差。项目里“LSTM-比特币.csv”就是这种带日期、真实价、预测价的表格它是后续综合评分的输入之一。from sklearn.metrics import mean_squared_error, mean_absolute_error y_pred model.predict(X_test) y_pred_rescaled y_pred * (max_price - min_price) min_price y_test_rescaled y_test * (max_price - min_price) min_price print(RMSE:, mean_squared_error(y_test_rescaled, y_pred_rescaled, squaredFalse)) print(MAE:, mean_absolute_error(y_test_rescaled, y_pred_rescaled))指标含义本项目数据量下的参考区间RMSE大误差惩罚重用于判断预测极端值的偏离程度RMSE 约为 MAE 的 1.2 到 1.5 倍时模型稳定MAE预测值与真实值的平均绝对偏差比特币通常高于黄金因为绝对价位不同MAPE相对误差百分比跨资产可比5% 以下说明趋势捕捉成功超过 10% 需回查窗口长度误差计算后不要只盯着 loss 曲线要画一张“真实价 vs 预测价”的折线图项目里“绘图-lstm预测准度.py”就是这个用途。如果预测值整体滞后真实值一个时间步说明模型学到的是随机游走此时应减少 LSTM 层数、增大 Dropout而不是增加训练轮次。4. 综合决策模型牛熊市判定、信号权重与买卖点生成4.1 为什么单靠 LSTM 信号不能直接下单LSTM 输出的是数值预测但下单决策需要的是分类信号。价格预测误差哪怕只有 3%转换为买卖方向后仍可能产生大量假信号。因此项目用“判断牛熊市.py”先判断当前处于什么市场状态再用综合评分把多个信号合成一个分数只有分数超过阈值才触发买入或卖出。这种设计把预测和决策解耦后续改任一模块都不会影响其他部分。牛熊市判定的常见逻辑是以 EMA26 为多空分界价格在 EMA26 之上定义为牛市之下为熊市。在熊市里买入信号阈值提高 10 个百分点在牛市里卖出信号阈值提高。这样可以过滤掉震荡市里的大部分假突破。项目里“判断牛熊市.py”配合“买卖时机.csv”输出就是这个思路。4.2 综合评分的权重构成与阈值综合评分把 LSTM 预测变化率、MACD 柱方向、RSI 区间、乖离率偏离程度映射到同一量纲再按权重加权。项目里有两个文件“综合评分-比特币.py”和“综合评分-黄金.py”黄金的权重和比特币略有差异原因在于黄金价格受宏观因素驱动动量持续性弱于比特币。这是一张我在复现时整理出的默认权重表因子比特币权重黄金权重信号取值逻辑LSTM 预测变化率0.350.30预测值相对前一日变化率的归一化得分MACD 柱方向0.250.30DIF 与 MACD 柱同向为强信号RSI 区间0.200.2050 以上偏多70 以上超买减分乖离率0.200.20偏离 EMA26 超过 8% 时反向扣分import pandas as pd lstm pd.read_csv(LSTM-比特币.csv) macd pd.read_csv(综合指标-比特币.csv) df lstm.merge(macd, ondate) def compute_score(row): score 0.35 * row[lstm_change_norm] score 0.25 * (1 if row[dif] row[macd] else -1) score 0.20 * ((row[rsi] - 50) / 50) score 0.20 * (-abs(row[bias]) / 8) return max(-1, min(1, score)) df[score] df.apply(compute_score, axis1) df[signal] df[score].apply( lambda x: 买入 if x 0.3 else (卖出 if x -0.3 else 持有) )lstm_change_norm在合并前就要归一化到 -1 到 1 区间否则它会以绝对数值优势压过其他因子。每个因子的映射函数都被限制在 -1 到 1这样权重加减才有意义。信号阈值设 0.3 是经验值回测时可扫描 0.2 到 0.5 之间找最优切分点。4.3 从评分到资产值变化和每次投入量评分只告诉方向投入量由“Trading.py”和“每次投入量.csv”决定。常见做法是阶梯式仓位分数越高仓位越大但单次不超过总资产的 50%避免一次错误判断造成无法挽回的回撤。项目里“资产值变化.csv”就是按这个规则逐日模拟出来的资金曲线。INVEST_TABLE [ (0.80, 0.50), (0.60, 0.30), (0.30, 0.15), (-0.30, 0.00), (-0.60, -0.20), (-1.00, -0.50), ] def to_invest_ratio(score): for threshold, ratio in INVEST_TABLE: if score threshold: return ratio return 0.0INVEST_TABLE的语义是分数 0.8 以上投入一半资金0.6 以上投入三成0.3 以上投入一成五负数区间是反向减仓。这种阶梯映射比线性映射更稳因为线性映射会在阈值临界区频繁产生小仓位交易累积手续费会吃掉利润。回测时如果当日有持仓先按收盘价计算浮动盈亏再叠加当日的投入或赎回才是真正的“资产值变化”。5. 稳健性检验与误差溯源随机偏移、灰色预测和马尔科夫修正5.1 随机提前或推迟交易日的敏感度分析项目 data 目录里“资产值变化-随机提前或推迟一天.csv”一直到推迟五天这组文件是敏感度分析的结果。实际操作中预测模型给出的买卖信号可能晚一天或早一天执行真实场景里滑点和网络延迟也会造成类似影响。分析方法是把信号对应的交易日期随机偏移 1 到 5 天再重算资产值变化观察收益曲线对时间偏移的容忍度。import numpy as np for offset in range(1, 6): results [] for seed in range(50): rng np.random.default_rng(seed) shifted df[signal_date] pd.to_timedelta( rng.choice([-offset, offset], sizelen(df)), unitD ) asset backtest_with_dates(shifted) results.append(asset) print(f偏移 {offset} 天: 均值 {np.mean(results):.2f}, f最差 {np.min(results):.2f})如果资产值均值随偏移天数增加快速下滑说明决策过度依赖精确日期如果变化不大说明策略本身对执行时点鲁棒。项目里输出多个 CSV 就是为了对比这一曲线。5.2 权重和乖离值的二维/三维敏感度分析“绘图-敏感度分析-二维.py”和“绘图-敏感度分析-三维.py”是控制变量法的可视化呈现。二维分析固定其他权重扫描某一个权重从 0 到 0.6 步长 0.05三维分析同时扫描 LSTM 权重和 MACD 柱权重。这样能定位出哪个因子在哪个权重区间内收益最稳定而不是只看单一最优值。import itertools results [] for w_lstm in np.arange(0.1, 0.6, 0.05): for w_macd in np.arange(0.1, 0.6, 0.05): w_rsi 1 - w_lstm - w_macd if w_rsi 0: continue score (w_lstm * lstm_signal w_macd * macd_signal w_rsi * rsi_signal) asset backtest(score) results.append((w_lstm, w_macd, asset))空间搜索能发现的最重要结论通常是最优权重往往落在一个平缓的高原区域而不是唯一尖峰。这意味着在最优权重附近各加 0.05 的扰动收益变化很小策略才是可靠的。5.3 灰色预测 GM(1,1) 与灰色-马尔科夫复合模型“灰色预测.py”里实现的是 GM(1,1) 灰色模型适合少样本时间序列的短期趋势预测。它的做法是先对原始序列做一次累加生成新序列用一阶微分方程拟合再累减还原。对比 20 个样本时灰色预测往往比 LSTM 更稳但样本超过 100 个后误差会明显偏大。import numpy as np def gm11(x0): x1 x0.cumsum() z1 (x1[:-1] x1[1:]) / 2 B np.vstack([-z1, np.ones(len(z1))]).T Y x0[1:] a, b np.linalg.lstsq(B, Y, rcondNone)[0] def predict(k): return (x0[0] - b / a) * np.exp(-a * k) b / a return predict, a, ba是发展系数b是灰作用量。预测函数predict(k)的k从 0 开始0 对应原始序列第一个点。当a绝对值过大时模型会发散这是灰色预测在强波动数据上的典型失效模式。“灰色-马尔科夫复合模型.py”是在灰色预测结果之上用马尔科夫链预测残差的正负符号再做二次修正。残差正负号通常三五天内有连续性所以简单的一阶马尔科夫链就有效。5.4 误差修正策略“修改误差.txt”和“修改误差.csv”描述的是如何把预测误差反馈到下一次预测中。常见做法是记录最近 N 次预测误差均值在输出预测值时做反向补偿因为 LSTM 的误差往往有系统性偏移。errors [] def predict_with_correction(model, window): raw model.predict(window)[0, 0] correction np.mean(errors[-10:]) if errors else 0 result raw - correction errors.append(raw - true_value) return result这个策略只在误差序列平稳时有效。6. 复现排错与进阶文件运行顺序、环境坑和参数扫描6.1 从源码仓库跑通全流程的运行顺序项目源码没有硬性规定运行入口但按文件之间的数据依赖关系存在一个推荐的执行顺序。先跑数据清洗和特征工程再跑预测模型然后算综合评分最后回测。乱序执行会导致读不到依赖文件而报错。运行“main.py”读取“比特币价值.csv”“黄金价值.csv”生成统一的清洗后价格序列。运行“MACD.py”“RSI.py”生成“ema12.csv”“ema26.csv”“macd.csv”“rsi.csv”。运行 LSTM 训练脚本生成“LSTM-比特币.csv”“LSTM-黄金.csv”。运行“综合评分-比特币.py”“综合评分-黄金.py”生成综合评分。最后运行“Trading.py”和“计算结果.py”得到买卖时机、投入量和资产值变化。6.2 环境部署时的常见报错该项目依赖 pandas、numpy、matplotlib、scikit-learn 和 TensorFlow 2.x。最容易出问题的地方就是 TensorFlow 版本与 Keras API 的兼容性。报错场景现象处理方法TensorFlow 2.16 与 Keras 3 混用keras.layers.LSTM报参数不识别显式引入from tensorflow.keras import layers不要用裸keras包读 CSV 中文乱码列名出现\ufeff前缀pd.read_csv中指定encodingutf-8-sig文件路径带空格FileNotFoundError但文件存在路径整体加引号或用pathlib.Path处理训练时内存不足ResourceExhaustedError把lookback从 30 调回 7或减小 batch_size6.3 批量参数扫描与结果留档调参阶段不要手动改权重反复跑脚本直接在主脚本外层加一层参数扫描。把权重、阈值、偏移天数全部参数化用循环跑完并把结果追加写入 CSV 留档。这样后续分析“哪个参数组合收益最稳”时可以直接读表不用重跑整个流程。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →