
简介这份资源面向计算机、人工智能及相关专业的在校学生与量化交易初学者提供一套基于LSTM的多因子比特币交易策略Python实现可用于课程设计、毕业设计或策略研究入门。压缩包共12个文件约797KB包含4个CSV行情数据文件、2个Jupyter Notebook源码、3个XML与1个IML工程配置、1张运行结果图及README说明文档数据、代码与配置分层清晰便于直接复现与二次修改。目前已有263人学习下载。策略通过多因子输入与LSTM预测降低最大回撤与波动整体较为稳健收益率与预测准确率直接相关读者可在此基础上增加因子、调整超参或尝试集成学习并观察同数据下的随机性表现。代码均经测试运行成功答辩评审平均分达96分适合作为毕设、课设或作业参考也可用于项目初期立项演示下载后建议先阅读README了解结构。1. 拆开这份 BTC_LSTM 源码它到底能不能跑、值不值得下如果你正在找一份能直接跑起来的比特币量化策略源码尤其是带 LSTM 预测和多因子打分的那种这份BTC_LSTM-master大概率已经在你的候选清单里了。它不是一个空壳 demo压缩包里塞了BTC_LSTM.ipynb主程序、daily_price_btc_cny.csv日线行情、btc_nvt.csv链上 NVT 指标、raw.csv和input.csv中间数据还有README.md说明文档。核心逻辑是用 LSTM 对比特币价格做时间序列预测再把预测信号和多个因子NVT、动量等合成交易信号最终输出一个回测收益曲线。作者在摘要里说得很实在这个策略能减少最大回撤、降低波动但收益率直接取决于 LSTM 预测准不准而且同样的数据跑两次结果可能不一样——这句话本身就是最大的使用提示。适合谁计算机、人工智能、通信、自动化方向的在校学生拿来做毕设或课程设计尤其是想同时展示「深度学习 金融回测」两条线的也适合刚转量化、想找一个完整 Python 项目练手的人。不适合谁指望拿它直接实盘赚钱的——作者自己都说了「同样的数据收益率存在随机性」这句话翻译过来就是别把回测曲线当未来收益。下面我从数据、模型、回测三个层面拆开讲每一步都落到你能复现的命令和参数上。2. 环境与数据管线从 CSV 到 LSTM 输入张量2.1 先看清目录里每个文件是干什么的拿到压缩包解压后根目录下大概是这样一堆东西。别急着打开 notebook先把文件角色分清楚后面调参和排错才不会迷路。文件/目录作用是否可替换BTC_LSTM.ipynb主程序含数据读取、特征工程、LSTM 建模、回测核心不建议直接改daily_price_btc_cny.csv比特币人民币日线行情含 OHLCV可换成你自己的行情源btc_nvt.csvNVT 链上指标作为多因子之一可替换或增删因子raw.csv/input.csv中间产物特征拼接后的数据集删掉会重新生成README.md运行说明和依赖列表先读它.idea/、*.imlPyCharm 工程配置与代码无关可忽略常见做法是先把daily_price_btc_cny.csv和btc_nvt.csv按日期对齐再构造监督学习需要的「过去 N 天 → 预测第 N1 天」样本。这一步是整个项目里最容易翻车的地方因为两个 CSV 的时间粒度、缺失值、时区如果不一致后面 LSTM 学到的就是噪声。2.2 依赖安装与 Python 环境README 里一般会列依赖但为了少踩坑我建议直接按下面这套来。注意 PyTorch 和 TensorFlow 二选一即可这份源码常见实现是 Keras/TensorFlow 风格如果你机器上已经有 PyTorch 环境改起来也不难。# 建议 Python 3.8 ~ 3.10太新的版本某些库轮子不全 python -m venv btc_lstm_env source btc_lstm_env/bin/activate # Windows 用 btc_lstm_env\Scripts\activate pip install numpy pandas matplotlib scikit-learn pip install tensorflow2.10.0 # 或 torch看 notebook 里 import 的是哪个 pip install jupyter notebook逻辑说明先建独立虚拟环境避免和你系统里的其他项目打架。tensorflow2.10.0是一个比较稳的版本对 LSTM 层和MinMaxScaler兼容性好。如果你 import 的是torch那就换成pip install torch但要注意 notebook 里model.fit和model.train写法不同不能混用。参数说明python -m venv后面的目录名随意但路径里不要有中文和空格否则 Jupyter 启动时可能找不到内核。激活后pip list确认一下pandas版本建议 1.3 以上因为后面pd.merge_asof或resample对时间索引处理更稳。2.3 数据对齐与特征拼接打开BTC_LSTM.ipynb前几个 cell 通常是读 CSV 和画图。这里我一般会先做一件事把两个数据源按日期合并并检查缺失。import pandas as pd import numpy as np # 读行情和 NVT注意 parse_dates 把日期列转成 datetime price pd.read_csv(daily_price_btc_cny.csv, parse_dates[date]) nvt pd.read_csv(btc_nvt.csv, parse_dates[date]) # 统一按日期升序去重 price price.sort_values(date).drop_duplicates(date) nvt nvt.sort_values(date).drop_duplicates(date) # 外连接后前向填充再删掉开头还没数据的行 df pd.merge(price, nvt, ondate, howouter).sort_values(date) df df.ffill().dropna().reset_index(dropTrue) # 构造预测目标下一日收盘价 df[target] df[close].shift(-1) df df.dropna().reset_index(dropTrue) print(df.shape) print(df[[date, close, nvt, target]].head())逻辑说明merge用 outer 是为了不丢任何一边的日期ffill处理 NVT 偶尔缺一天的情况。shift(-1)把「明天收盘价」对齐到今天这是时间序列监督学习最常用的标签构造方式。最后dropna去掉最后一行没有 target 的记录。参数说明如果你的行情是小时级shift(-1)就变成预测下一小时窗口长度也要相应调整。ffill只适合缺失少的场景如果 NVT 缺一大段应该考虑换数据源而不是硬填。常见做法是打印df.isnull().sum()确认每列缺失数超过 5% 就要警惕。2.4 归一化与滑动窗口LSTM 对输入尺度敏感所以收盘价、NVT 这些量纲差很大的列必须归一化。注意归一化要在划分训练集和测试集之后分别做或者至少用训练集的 scaler 去 transform 测试集否则就是未来函数。from sklearn.preprocessing import MinMaxScaler feature_cols [close, nvt] # 你还可以加 volume、动量等 scaler MinMaxScaler() scaled scaler.fit_transform(df[feature_cols]) def make_windows(data, window30): X, y [], [] for i in range(len(data) - window): X.append(data[i:iwindow]) y.append(data[iwindow, 0]) # 第0列是 close return np.array(X), np.array(y) WINDOW 30 X, y make_windows(scaled, WINDOW) print(X.shape, y.shape) # 例如 (样本数, 30, 2)逻辑说明make_windows把连续 30 天的多因子序列切成一个样本标签是第 31 天的收盘价归一化后的。X.shape的第二个维度是时间步第三个维度是因子数。参数说明WINDOW30是这份源码里常见的默认值代表用过去 30 天预测下一天。你可以改成 14 或 60但要注意样本数会相应减少。feature_cols里加因子时记得同步改scaled的列并且y仍然取 close 那一列不要取错索引。3. LSTM 模型搭建与训练层数、超参和随机性从哪来3.1 模型结构怎么定这份源码的 LSTM 部分通常不会太深常见是 1 到 2 层 LSTM 接一个 Dense 输出。原因很简单数据量只有几千条日线层数一多就过拟合验证 loss 会先降后升。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model Sequential([ LSTM(64, return_sequencesTrue, input_shape(WINDOW, len(feature_cols))), Dropout(0.2), LSTM(32), Dropout(0.2), Dense(1) ]) model.compile(optimizeradam, lossmse) model.summary()逻辑说明第一层LSTM(64, return_sequencesTrue)把序列传给第二层第二层LSTM(32)只输出最后时间步的隐状态再接Dense(1)回归出价格。Dropout放在两层之间用来压过拟合。参数说明64和32是隐藏单元数不是越大越好。日线数据我一般从 32/16 起步如果训练 loss 远低于验证 loss就继续降单元数或加 Dropout。input_shape必须和X.shape[1:]一致写错会直接报维度错误。3.2 训练集测试集划分与 EarlyStopping时间序列不能随机 shuffle必须按时间顺序切。常见做法是前 80% 训练后 20% 测试。split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] es EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_split0.1, epochs100, batch_size32, callbacks[es], verbose1 )逻辑说明validation_split0.1是从训练集尾部再切 10% 做验证不碰测试集。EarlyStopping在验证 loss 连续 10 轮不降时停止并恢复最优权重这是防止过拟合最省事的办法。参数说明batch_size32对几千条样本比较合适太小训练慢太大容易陷局部最优。epochs100只是上限实际会被 EarlyStopping 提前终止。注意validation_split也是按顺序切的不会打乱时间。3.3 随机性到底从哪来作者在摘要里说「同样的数据收益率存在随机性」这不是玄学来源很具体一是权重随机初始化二是 Dropout 的随机丢弃三是如果用了shuffleTrue默认在 fit 里对训练集是开的每个 epoch 的样本顺序不同。要复现结果就得固定随机种子。import tensorflow as tf import random SEED 42 random.seed(SEED) np.random.seed(SEED) tf.random.set_seed(SEED)逻辑说明这三行要放在建模型之前。tf.random.set_seed管 TensorFlow 的随机操作np.random.seed管 numpyrandom.seed管 Python 原生随机。三个都设才能最大程度复现。参数说明SEED值随意但一旦选定就不要改。即便如此GPU 上的某些并行计算仍可能带来微小差异所以「完全一致」在 GPU 上不保证CPU 上更稳。如果你要写论文或答辩建议记录种子值并在同一台机器上跑。4. 多因子信号合成与回测预测值怎么变成买卖动作4.1 从预测价格到交易信号LSTM 输出的是归一化后的价格先反归一化再和实际价格比较生成信号。常见规则是预测下一日价格上涨就持有下跌就空仓或做空。# 反归一化只对 close 那一列做逆变换 pred model.predict(X_test) # 构造一个和 scaler 同维度的占位把预测值放回 close 列 tmp np.zeros((len(pred), len(feature_cols))) tmp[:, 0] pred.flatten() pred_price scaler.inverse_transform(tmp)[:, 0] actual_price scaler.inverse_transform( np.concatenate([y_test.reshape(-1,1), np.zeros((len(y_test), len(feature_cols)-1))], axis1) )[:, 0] signal (pred_price actual_price).astype(int) # 1 持有0 空仓逻辑说明inverse_transform要求输入维度和 fit 时一致所以用零占位其他因子列只取 close 列的结果。signal是比较预测价和当日实际价预测涨就持有。参数说明这里比较的是「预测下一日价格」和「当日实际价格」不是和「下一日实际价格」比否则又是未来函数。如果你改成预测收益率信号规则也要相应改成pred_return 0。4.2 回测收益与最大回撤计算有了 signal就可以算策略每日收益。注意 signal 要滞后一天因为今天收盘后才能知道今天的预测。df_test df.iloc[split WINDOW:].reset_index(dropTrue) df_test df_test.iloc[:len(signal)].copy() df_test[ret] df_test[close].pct_change().fillna(0) df_test[strategy_ret] df_test[ret] * df_test[signal].shift(1).fillna(0) df_test[cum] (1 df_test[strategy_ret]).cumprod() df_test[cum_max] df_test[cum].cummax() df_test[drawdown] df_test[cum] / df_test[cum_max] - 1 print(累计收益:, df_test[cum].iloc[-1] - 1) print(最大回撤:, df_test[drawdown].min())逻辑说明signal.shift(1)是关键保证用昨天收盘后产生的信号来吃今天的收益。cumprod算净值曲线cummax算历史高点两者相除减一就是回撤序列。参数说明pct_change默认按行算如果数据有跳空要注意。最大回撤越接近 0 越好但这份策略的回撤改善主要来自空仓躲跌不是预测多准。你可以把signal改成 -1 做空看看收益和回撤怎么变但现货回测一般不做空。4.3 多因子到底加了什么源码里除了 close还用了 NVT。NVT 是链上交易价值与网络价值的比值常被当作估值过热或过冷的参考。把它作为 LSTM 的输入因子相当于让模型在价格之外多看到一个链上维度。但要注意NVT 的更新频率和行情不一定一致合并后如果大量前向填充这个因子的信息量会打折。我一般会先单独画一下 NVT 和未来收益的相关性弱相关就考虑换因子比如加成交量、RSI 或链上活跃地址数。作者在摘要里也提到「应该加入更多因子」这是实话——单靠 close 和 NVTLSTM 很容易退化成只会拟合价格趋势。5. 避坑与排查这份源码跑不起来时先看这几条5.1 现象notebook 一运行就报 KeyError 或列名不存在原因daily_price_btc_cny.csv的列名可能是中文、大小写不一致或者你换了自己的数据但没改代码里的close、date。解决先单独跑一个 cell 打印df.columns把代码里的列名改成实际列名。别硬套列名对不上后面全错。5.2 现象LSTM 输入维度报错提示 expected shape 和 got shape 不一致原因WINDOW改了但input_shape没同步改或者feature_cols增删后X.shape[2]变了。解决在model.fit之前打印X_train.shape确保第三维等于len(feature_cols)第二维等于WINDOW。改一个参数就检查一次别攒着一起改。5.3 现象回测收益高得离谱最大回撤几乎为零原因九成是未来函数。要么归一化时用了全量数据 fit scaler要么 signal 没有 shift要么在划分训练测试之前就做了填充。解决把 scaler 的 fit 限制在训练集上signal 强制shift(1)所有涉及「未来」的操作都在划分之后做。回测曲线太漂亮时先怀疑自己别怀疑市场。5.4 现象每次运行结果都不一样答辩时没法解释原因随机种子没固定或者用了 GPU 且开了非确定性算子。解决按 3.3 节固定三个种子并在同一次运行里记录最终收益和回撤。如果必须用 GPU可以在代码开头加tf.config.experimental.enable_op_determinism()但会牺牲一点速度。5.5 现象NVT 合并后大量缺失前向填充后因子几乎不变原因两个数据源时间范围不重叠或者 NVT 本身更新频率低。解决先画两条时间序列的重叠区间重叠太短就换数据源。前向填充只适合零星缺失连续缺失超过一周就应该考虑删掉这个因子而不是硬填。6. 进阶技巧用 walk-forward 验证代替单次回测单次 80/20 划分的回测说服力有限因为测试集只有一段。更接近实盘的做法是 walk-forward每次用一段历史训练预测紧接着的一小段然后窗口向前滚动。这样你能看到策略在不同市场阶段的真实表现而不是一段运气好的曲线。def walk_forward(X, y, train_size500, test_size60, step60): results [] start 0 while start train_size test_size len(X): X_tr X[start:starttrain_size] y_tr y[start:starttrain_size] X_te X[starttrain_size:starttrain_sizetest_size] y_te y[starttrain_size:starttrain_sizetest_size] model build_model() # 每次重新建模型避免信息泄漏 model.fit(X_tr, y_tr, epochs50, batch_size32, verbose0) pred model.predict(X_te, verbose0) results.append((start, pred.flatten(), y_te)) start step return results逻辑说明train_size500约等于两年日线test_size60约一个季度step60表示每次向前滚一个季度。每次循环都重新建模型不能用上一次的权重继续训否则训练集就「看到」了之前的测试段。参数说明train_size不能太小否则 LSTM 学不到东西test_size不能太大否则失去滚动意义。常见组合是 500/60 或 750/90。跑完把每段的收益拼起来算整体净值和回撤比单次回测可信得多。我自己的习惯是任何时序策略只要作者说「结果有随机性」我就先固定种子跑三遍再用 walk-forward 跑一遍。三遍结果方差大说明模型不稳walk-forward 各段收益方向不一致说明策略依赖特定行情。这两关过了才值得花时间加因子、调超参。从那以后我每次拿到这类源码都强制走一遍「固定种子 walk-forward」再决定要不要继续投入。希望帮到你。本文还有配套的精品资源点击获取