ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

比特币LSTM多因子策略:Python源码与防过拟合实战

比特币LSTM多因子策略:Python源码与防过拟合实战 简介这份资源是比特币基于LSTM的多因子交易策略Python实现面向计算机、人工智能及相关专业学生与量化交易初学者可用于课程设计、毕业设计或策略研究。项目通过LSTM对多因子行情数据进行建模预测旨在降低最大回撤与波动提升策略稳健性同时保留了对超参数调优与集成学习扩展的探索空间。压缩包共12个文件约797KB包含4个CSV行情数据文件、2个Jupyter Notebook源码、3个XML配置、1个PNG图表及README说明文档结构清晰便于直接运行与二次修改。目前已有263人学习下载。代码经过完整测试运行成功后才上传答辩评审平均分达96分读者可据此理解多因子特征构建、LSTM训练流程与回测评估思路并在此基础上调整因子组合或尝试集成模型适合作为量化策略入门与进阶的实践参考。1. 比特币 LSTM 多因子策略从一根 K 线到可复现的 Python 源码很多人第一次听到「比特币基于 LSTM 的多因子交易策略」脑子里浮现的是「喂价格、出涨跌、躺赚」。真跑一遍就知道单因子喂收盘价的 LSTM 在比特币上大概率跑不赢买入持有因为币圈的价格序列信噪比极低单靠时序记忆抓不住突发的资金面和情绪面变化。多因子的意义就在这把链上活跃度、资金费率、成交量结构、波动率状态这些不同维度的信号拼成一张特征表让 LSTM 去学「在什么市场状态下哪些因子组合更可能推动下一根 K 线」。这篇笔记拆的是这套策略的 Python 源码骨架和文档说明里真正要落地的部分——特征怎么造、标签怎么切、模型怎么防过拟合、回测怎么不被未来函数坑。适合已经会写 Python、想从单因子 demo 进阶到多因子实盘的量化新手也适合做过传统因子模型、想看看 LSTM 在币圈到底值不值得上的老手。2. 多因子特征工程把链上、合约、行情拼成一张表2.1 为什么单因子 LSTM 在比特币上会翻车先把反直觉的结论摆出来把比特币日线收盘价直接丢进 LSTM 做回归测试集上的方向准确率经常在 50% 上下晃跟抛硬币差不多。原因不复杂——价格序列本身是非平稳的均值方差随时间漂移LSTM 学到的「记忆」在训练集之外很快失效。更麻烦的是币圈 24 小时连续交易、无涨跌停一根大阳线能吞掉前面十天的形态模型刚记住的规律第二天就被打脸。多因子的价值不是「多加几个输入维度」这么简单而是给模型提供不同时间尺度、不同生成机制的信息。行情类因子收益率、波动率反映的是已经发生的价格行为合约类因子资金费率、持仓量反映的是杠杆资金的拥挤程度链上类因子活跃地址、转账笔数反映的是链上真实使用强度。这三类因子的自相关结构和领先滞后关系完全不同拼在一起才能让 LSTM 有机会学到「价格还没动、但资金费率已经极端」这类领先信号。常见做法是先把因子分成三组每组内部做标准化和去极值再横向拼接。不要一上来就把几十个因子全塞进去LSTM 对冗余特征很敏感维度一高就容易过拟合到噪声上。2.2 因子清单与计算口径下面这张表是我一般会先搭起来的最小因子集覆盖行情、合约、链上三个维度每个因子都标了口径和更新频率。注意链上数据在实盘里通常有延迟回测时必须按「当时可得」的时间戳对齐否则就是未来函数。因子名维度计算口径更新频率ret_1d行情当日收盘 / 前一日收盘 - 1日vol_7d行情过去 7 日收益率标准差日vol_ratio行情当日成交量 / 过去 20 日均量日funding_rate合约永续合约当期资金费率8 小时oi_change合约持仓量 24 小时变化率小时long_short_ratio合约大户多空账户数比小时active_addr链上当日活跃地址数日tx_count链上当日转账笔数日exchange_netflow链上交易所净流入流入-流出日这张表不是让你照抄而是给你一个「三类因子各占多少」的参考比例。我自己的习惯是行情类占一半、合约类三成、链上类两成链上因子虽然信息独特但噪声也大权重不宜过高。2.3 用 pandas 对齐多源数据并生成特征矩阵多因子最烦的一步不是算因子是对齐时间戳。链上数据按天、资金费率按 8 小时、行情按小时直接 merge 会出现大量 NaN 或者错位。下面这段代码演示的是「统一到日频、按可得时间对齐」的做法。import pandas as pd import numpy as np def build_feature_matrix(price_df, funding_df, onchain_df): # price_df: index日期, 含 close/volume # funding_df: index8小时时间戳, 含 funding_rate # onchain_df: index日期, 含 active_addr/tx_count/exchange_netflow df price_df.copy() df[ret_1d] df[close].pct_change() df[vol_7d] df[ret_1d].rolling(7).std() df[vol_ratio] df[volume] / df[volume].rolling(20).mean() # 资金费率按日聚合取当日均值代表当天杠杆情绪 funding_daily funding_df[funding_rate].resample(1D).mean() df df.join(funding_daily, howleft) # 链上数据本身是日频直接 join但要注意延迟一天 onchain_shifted onchain_df.shift(1) # 关键用前一天数据避免未来函数 df df.join(onchain_shifted, howleft) # 去极值按 1% 和 99% 分位缩尾 for col in [ret_1d, vol_7d, vol_ratio, funding_rate, active_addr, tx_count, exchange_netflow]: lo, hi df[col].quantile([0.01, 0.99]) df[col] df[col].clip(lo, hi) # 标准化用滚动窗口避免用全样本统计量 feat_cols [ret_1d, vol_7d, vol_ratio, funding_rate, active_addr, tx_count, exchange_netflow] df[feat_cols] (df[feat_cols] - df[feat_cols].rolling(60).mean()) \ / df[feat_cols].rolling(60).std() return df.dropna()这段代码有三个关键点。第一onchain_df.shift(1)是防未来函数的核心链上数据当天收盘后才统计出来回测里只能用前一天的值。第二标准化用的是rolling(60)而不是全样本mean/std全样本标准化会把未来信息泄漏进训练集。第三去极值放在标准化之前否则极端值会把滚动标准差拉爆。参数上滚动窗口 60 天是个经验值币圈周期短窗口太长会钝化太短又不稳定我一般会在 30 到 90 之间做敏感性测试。3. LSTM 模型搭建标签切法比网络结构更重要3.1 标签怎么切三分类比回归更稳新手最容易犯的错是把「预测下一日收益率」当回归任务用 MSE 损失。比特币收益率分布是尖峰厚尾的MSE 会被极端值主导模型学出来的预测值全挤在 0 附近方向准确率自然上不去。我一般改成三分类下一日收益率大于 0.5% 记为上小于 -0.5% 记为下中间记为平。阈值 0.5% 不是拍脑袋要覆盖交易成本——手续费加滑点大概就是这个量级低于这个幅度的波动不值得开仓。标签切完还要注意样本不平衡。币圈上涨和下跌的样本数往往不对称直接训练会让模型偏向多数类。常见做法是在损失函数里加类别权重或者对少数类做重采样。我倾向用CrossEntropyLoss(weight...)改动小、可控。3.2 PyTorch LSTM 网络结构与关键参数网络结构不用堆太深比特币日频数据量有限两层 LSTM 加一层全连接足够。下面是一个可以直接跑的 PyTorch 实现。import torch import torch.nn as nn class MultiFactorLSTM(nn.Module): def __init__(self, n_features, hidden_size64, num_layers2, n_classes3, dropout0.3): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout # 层间 dropoutnum_layers1 才生效 ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size, n_classes) def forward(self, x): # x: (batch, seq_len, n_features) out, (h_n, c_n) self.lstm(x) last out[:, -1, :] # 取最后一个时间步 last self.dropout(last) return self.fc(last)参数说明hidden_size64是起点因子数在 10 个以内时 64 够用因子多了可以加到 128但别超过 256否则小样本下必过拟合。num_layers2是币圈日频的常见选择单层欠拟合、三层以上梯度不稳。dropout0.3是防过拟合的主力如果验证集 loss 比训练集高很多先调这个。seq_len我一般设 20对应一个月的交易日太短抓不到周期太长会把早期噪声也喂进去。3.3 训练循环与早停别让模型记住噪声训练部分要盯的不是训练 loss是验证集的方向准确率。下面这段是带早停的训练骨架。from torch.utils.data import DataLoader, TensorDataset def train_model(model, X_train, y_train, X_val, y_val, epochs100, lr1e-3, patience10): device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 类别权重缓解样本不平衡 class_counts np.bincount(y_train) weights torch.tensor(1.0 / class_counts, dtypetorch.float32).to(device) criterion nn.CrossEntropyLoss(weightweights) optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decay1e-4) train_ds TensorDataset(torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.long)) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) best_val_acc, best_state, wait 0, None, 0 for epoch in range(epochs): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() # 验证 model.eval() with torch.no_grad(): val_logits model(torch.tensor(X_val, dtypetorch.float32).to(device)) val_pred val_logits.argmax(dim1).cpu().numpy() val_acc (val_pred y_val).mean() if val_acc best_val_acc: best_val_acc val_acc best_state {k: v.clone() for k, v in model.state_dict().items()} wait 0 else: wait 1 if wait patience: break model.load_state_dict(best_state) return model, best_val_acc逻辑上weight_decay1e-4是 L2 正则配合 dropout 一起压过拟合。clip_grad_norm_防止 LSTM 梯度爆炸币圈数据里偶尔有极端跳变不加这个训练容易发散。早停的patience10意思是验证准确率连续 10 轮不提升就停这个值别设太小LSTM 收敛有时会先平后升。batch_size64在几千条样本的量级下比较稳样本更少就降到 32。4. 回测与避坑未来函数和过拟合是两把刀4.1 回测框架的最小实现回测不是把预测结果和真实收益一乘就完事要模拟真实的开平仓、手续费和滑点。下面是一个最小可用的向量化回测。def backtest(df, preds, fee0.0004, slippage0.0005): # preds: 0下, 1平, 2上 df df.copy() df[signal] 0 df.loc[preds 2, signal] 1 # 预测上涨做多 df.loc[preds 0, signal] -1 # 预测下跌做空 # 信号滞后一天执行避免用当日收盘价成交 df[position] df[signal].shift(1).fillna(0) df[ret] df[close].pct_change().fillna(0) # 换仓成本仓位变化时扣手续费滑点 df[turnover] df[position].diff().abs().fillna(0) cost df[turnover] * (fee slippage) df[strategy_ret] df[position] * df[ret] - cost df[cum_strategy] (1 df[strategy_ret]).cumprod() df[cum_hold] (1 df[ret]).cumprod() return df关键在df[signal].shift(1)模型用的是当日收盘后的因子最早也只能在次日开盘成交不 shift 就是典型的未来函数。手续费fee0.0004和滑点slippage0.0005是主流合约平台的量级实际跑之前按自己用的平台改。turnover用diff().abs()算换仓幅度从多翻空算两次换手成本要扣两次。4.2 避坑清单五个血泪教训现象一回测曲线漂亮实盘一上就亏。原因因子标准化用了全样本统计量或者链上数据没做 shift未来信息泄漏进训练集。 解决所有滚动统计量只用历史窗口链上、资金费率这类「事后统计」的数据统一 shift 至少一期回测里加一段样本外验证训练集和测试集按时间切绝不随机打乱。现象二验证集准确率 60%测试集掉到 48%。原因过拟合。因子太多、网络太深、训练轮数太多模型把训练集的噪声也记住了。 解决先砍因子用相关性矩阵把两两相关高于 0.8 的因子去掉一个再降hidden_size和num_layers最后靠早停和 dropout。别指望调参能救过拟合减容量才是根本。现象三模型预测全是「平」方向准确率虚高。原因三分类样本不平衡中间类占了大头模型学会「全猜平」就能拿到不低的准确率。 解决看混淆矩阵别只看总体准确率。用带类别权重的损失函数或者把「平」类样本下采样。评估指标换成加权 F1 或者只看上/下两类的召回率。现象四回测夏普很高但交易次数少得可怜。原因信号阈值设得太严或者position变化被 shift 吃掉实际没几次开仓高收益来自个别几笔运气单。 解决统计换手次数和持仓分布交易次数低于 50 次的回测结果不可信。阈值 0.5% 可以调但要保证信号频率和成本匹配。现象五训练 loss 一直降验证 loss 先降后升。原因这是过拟合的标准形态不是 bug。 解决早停就是干这个的patience别设太大。同时检查验证集是不是太小币圈日频数据切验证集至少留 200 个交易日否则验证准确率本身波动就很大。5. 进阶技巧用滚动重训和因子衰减检验判断策略寿命模型训完不是终点币圈的市场结构变化快一个 2021 年训出来的 LSTM 拿到 2024 年大概率失效。我一般会做两件事来判断策略还有没有寿命。第一件是滚动重训。不要用一次训练的结果跑完整段回测而是每 90 个交易日重新训练一次只用之前的数据然后预测接下来 30 天。这样回测出来的曲线才接近实盘。代码上就是把训练和回测包成一个循环每次滑动窗口。def walk_forward(df, feature_cols, train_days500, retrain_step90): results [] for start in range(train_days, len(df) - retrain_step, retrain_step): train df.iloc[start - train_days:start] test df.iloc[start:start retrain_step] # 这里调用前面的 build_dataset / train_model / backtest # 每次只用 train 拟合标准化参数和模型权重 results.append(run_one_fold(train, test, feature_cols)) return pd.concat(results)train_days500大约两年日频数据retrain_step90是一个季度重训一次。这两个参数要按自己的数据量调数据少于三年的话train_days可以降到 365。第二件是因子衰减检验。把每个因子单独和未来收益算 IC信息系数看 IC 的滚动均值是不是在衰减。如果某个因子的 IC 从 0.05 掉到 0.01说明这个维度的信息已经被市场消化该换因子了。这一步不用改模型纯统计就能做是我判断「这套多因子还值不值得继续维护」的主要依据。最后说个我自己的习惯任何 LSTM 策略在上实盘之前先跑三个月模拟盘对比模拟盘和回测的换手率、滑点、信号延迟。回测和实盘的差距八成出在这三个地方而不是模型本身。这套东西值不值得做取决于你愿不愿意持续维护因子和重训而不是模型结构有多花哨。希望帮到你。本文还有配套的精品资源点击获取
返回列表