ARTICLE DETAIL

资讯详情

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

用LSTM同时预测比特币和黄金:综合决策模型实战指南

用LSTM同时预测比特币和黄金:综合决策模型实战指南 简介面向金融量化研究者与深度学习学习者这套源代码和数据资源围绕LSTM构建比特币与黄金价格走势的综合决策模型覆盖数据清洗、LSTM网络搭建、MACD与RSI等指标融合、回测验证、买卖点计算及敏感性分析等完整研究流程。压缩包共97个文件含42个CSV历史行情与评价数据、38个Python脚本、1个PDF论文成果另附txt说明、xml工程配置、pyc编译文件等整体约1.35MB便于在学术研究或量化策略开发中参考。已有215人学习资源按核心工作、模拟测试、数据目录和成果阐述分模块组织可快速定位到模型代码、灰色预测对照方案、绘图脚本和测试数据。使用者能直接获取可运行的LSTM预测脚本、多次模拟测试的结果变化以及资产价值对比等实验数据有助于理解时间序列预测在二元资产配置中的应用细节。1. 用LSTM同时预测比特币和黄金为什么“综合决策”比单资产模型更值得做做金融时间序列预测的人大概率都经历过这种尴尬单吊一个BTC的LSTM模型验证集上Loss挺漂亮一上实盘就连续踏空换成黄金走势倒是平滑了可波动小到连手续费都覆盖不了。我最早做这个方向时也以为问题出在模型不够深后来才发现真正的问题是把两个高度相关的宏观资产硬生生拆开建模——比特币和黄金同为避险/风险资产的两端它们对通胀预期、美元指数、风险事件的反应存在明显的错位和互补。把LSTM预测比特币和黄金价格走势放进同一个决策框架里用两个模型的输出做交叉验证和信号合成这个综合决策模型在稳健性上远胜任何单资产模型。这篇笔记就把这套方案的完整落地路径讲清楚从特征工程、数据预处理、LSTM训练到双资产信号融合与回测验证每一步都给可复现的代码和参数也会把我在这个方向上踩过的坑一并交代。2. 数据先行比特币和黄金的价格数据怎么对齐、怎么处理成LSTM能吃的样本2.1 为什么不能直接拿原始收盘价训练对数收益率与归一化的选型LSTM对输入尺度极其敏感这是我第一次跑模型翻车换来的教训。当时我直接把BTC的美元价格几千到几万美元的区间和黄金价格每盎司一千多美元拼在一起做特征结果训练Loss震荡得像心电图模型预测完全偏向数值更大的比特币。问题出在两个资产价格量纲差异过大梯度更新被大数值特征主导。正确的做法是先做两步变换。第一步把价格序列转成对数收益率import numpy as np import pandas as pd def prepare_price_series(df, price_colclose): # 对数收益率log(P_t / P_{t-1})比简单差分更稳定 df[log_return] np.log(df[price_col] / df[price_col].shift(1)) # 首行shift产生NaN直接丢弃 df df.dropna().reset_index(dropTrue) return df # 用法示例假设你从数据源拿到了BTC和黄金的日线DataFrame btc_df pd.read_csv(btc_daily.csv, parse_dates[date]) gold_df pd.read_csv(gold_daily.csv, parse_dates[date]) btc_df prepare_price_series(btc_df) gold_df prepare_price_series(gold_df)对数收益率的价值在于把价格序列变成近似平稳的序列LSTM不需要去拟合“价格从3万涨到4万”这种巨大的绝对数值漂移只需要学习收益率的条件分布。第二步是归一化我一般用Z-score而非MinMax因为收益率序列本身近似正态分布Z-score能保留离群点的相对幅度——这对捕捉暴涨暴跌的尾部事件很重要MinMax会把离群点压扁。from sklearn.preprocessing import StandardScaler def normalize_feature(feature_series): scaler StandardScaler() # shape: (n_samples, 1) normalized scaler.fit_transform(feature_series.values.reshape(-1, 1)) return normalized.flatten(), scaler btc_returns_norm, btc_scaler normalize_feature(btc_df[log_return]) gold_returns_norm, gold_scaler normalize_feature(gold_df[log_return])注意fit_transform只能用在训练集上验证集和测试集必须复用训练集的scaler做transform否则会把未来数据的信息泄漏进训练过程。这个坑后面避坑章里还会细说。提示如果你拿到的源数据里已经有成交量、开盘价、最高价、最低价这些字段建议把“当日振幅”(high-low)/close和“成交量对数变化率”也加进特征矩阵。这两个特征对BTC这种情绪驱动型资产尤其有用能让LSTM少走很多弯路。2.2 时间对齐比特币7x24小时 vs 黄金场内交易日期错位怎么处理比特币市场一周七天、一天24小时都在交易而黄金有明确的收盘时间周末和节假日休市。如果你直接把两个DataFrame按日期合并会发现BTC有365天的数据黄金只有大约250天的数据中间差出来的全是BTC在周末产生的“孤儿记录”。这些周末数据在黄金侧没有对应特征直接merge会产生大量NaN。我见过的常见做法有两种各有取舍。第一种是直接取交集日期周末和节假日的BTC数据全部丢弃——这样两个资产的时间索引完全对齐LSTM在每个时间步拿到的都是同一天的两个资产状态。第二种是保留全部BTC数据黄金缺失的位置用前向填充forward fill补齐。前向填充的问题在于它会引入“未来信息”周五收盘后到周一开盘前黄金价格被假设为不变但实际周一开盘往往跳空。我的建议是除非你做的是日内高频策略否则就用交集日期干净省事def align_two_assets(btc_df, gold_df, date_coldate): # 仅保留两边都有的交易日 merged pd.merge( btc_df[[date_col, log_return, volume]], gold_df[[date_col, log_return]], ondate_col, suffixes(_btc, _gold) ) # 列名整理btc_return, gold_return, btc_volume merged.columns [date, btc_return, btc_volume, gold_return] return merged.dropna().reset_index(dropTrue) aligned_df align_two_assets(btc_df, gold_df) print(aligned_df.head())对齐之后的“时间对齐”还有一个隐藏问题——时区。如果你用的是小时级数据而不是日线比特币的UTC时间戳和黄金的纽约收盘时间之间有时差直接按字符串对齐会错位一天。我一般统一先转成UTC再对齐日线级别这个问题不突出但小时级必须处理。2.3 滑窗样本构造lookback窗口、预测步长与数据集划分的黄金比例LSTM不能像传统回归模型那样直接吃一整条序列你需要把对齐后的数据切成“过去N天 → 未来M天”的样本对。这里的N叫lookback窗口M叫预测步长它们是整个模型里最重要也最需要反复试的两个超参数。def create_sequences(features, target, lookback60, horizon5): X, y [], [] for i in range(len(features) - lookback - horizon 1): X.append(features[i:ilookback]) # 预测未来horizon天后的方向0/1分类或收益率回归 # 这里以二分类为例未来5天累计收益率为正则标签为1 future_return target[ilookback:ilookbackhorizon].sum() y.append(1 if future_return 0 else 0) return np.array(X), np.array(y) # 特征矩阵把比特币收益率、黄金收益率、比特币成交量变化拼在一起 feature_matrix np.column_stack([ aligned_df[btc_return], aligned_df[gold_return], np.log(aligned_df[btc_volume] 1e-8) ]) X, y create_sequences(feature_matrix, aligned_df[btc_return].values, lookback60, horizon5) print(样本形状:, X.shape, 标签形状:, y.shape)这个create_sequences函数有个细节值得展开。预测目标我用的是“未来5天累计收益的正负”而非“未来第5天的价格”原因很简单累计收益的方向才是交易决策真正关心的信号而且它对单日噪声更鲁棒。如果你做回归任务把y改成future_return本身即可但回归模型的Loss会被极端行情主导我实测下来不如分类稳。数据集划分上金融时间序列绝对不能随机打乱——随机打乱等于让模型“偷看”未来。正确做法是按时间顺序切分前70%做训练、中间15%做验证、最后15%做测试并且验证集和测试集必须严格在时间上晚于训练集。这个顺序切分的代价是测试集只有最近两三年样本量会偏少但这是金融建模的铁律。再补一个特征工程的进阶选项。如果你对技术指标熟悉可以把MACD的DIF/DEA差、RSI、布林带位置也拼进特征矩阵。这些指标本质上是对价格序列的滤波变换LSTM自己也能学到类似模式但显式给出来能显著加快收敛。前提是计算指标时只能用历史数据不能用未来数据。热词里提到的“MACD双底”这种形态特征我建议提取成一个独立的二值特征是否出现双底形态而非让LSTM从原始序列里自己找——形态识别规则明确直接喂给模型效率最高。3. 模型构建用PyTorch实现LSTM预测比特币和黄金综合决策模型3.1 为什么选LSTM而不是Transformer或简单全连接金融序列的时序依赖先说一个反直觉的事实很多论文里Transformer在金融时间序列上的表现并不比LSTM好甚至更差。原因在于金融序列的有效信息密度极低——一天一根K线一年才250个样本而Transformer的注意力机制需要大量数据才能学到有意义的依赖模式。LSTM的门控结构天然适合小样本、强时序依赖的场景它对“过去几天发生了什么”的建模方式更接近交易员的直觉。具体到“预测比特币和黄金”这个任务LSTM还有一个额外优势两个资产之间存在复杂的交叉影响——比如美元走强时黄金跌、BTC可能因为风险偏好变化而同向或反向联动——LSTM的隐状态可以隐式地维护这种跨资产的交互记忆。这也是为什么我强烈建议把两个资产的数据拼进同一个特征矩阵喂给同一个LSTM而不是分别训练两个独立模型。独立模型学不到资产间的联动关系综合决策模型的价值就大打折扣了。3.2 LSTM网络结构设计输入维度、隐藏层大小、层数与Dropout的取舍网络结构我给出一个经过多轮实验的基线配置适合日线级别的双资产预测任务。输入维度特征数这里是3如果你加了技术指标就是6或更多隐藏层64、2层LSTM、最后接一个全连接层输出分类概率。这个配置的参数量大约在10万级别对2500个交易日左右的样本量来说足够表达又不容易过拟合。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_dim3, hidden_dim64, num_layers2, dropout0.3): super().__init__() self.lstm nn.LSTM( input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) self.classifier nn.Sequential( nn.Linear(hidden_dim, 32), nn.ReLU(), nn.Dropout(dropout), nn.Linear(32, 1) # 二分类输出 ) def forward(self, x): # x shape: (batch, lookback, input_dim) lstm_out, (h_n, c_n) self.lstm(x) # 取最后一层最后一步的隐状态 last_hidden h_n[-1] # shape: (batch, hidden_dim) logits self.classifier(last_hidden) return logits几个参数值得逐一说清楚。hidden_dim64是经验值64以下表达力不够256以上在小样本上很容易过拟合——我在测试集上观察到hidden_dim超过128后验证Loss不再下降但训练Loss持续走低这就是过拟合的典型信号。num_layers2是性价比最高的选择3层LSTM的训练时间几乎翻倍且收益微乎其微。dropout0.3用于减轻过拟合注意PyTorch的LSTM内部dropout只在num_layers1时生效所以我额外在全连接层前又加了一个Dropout把正则化做扎实。训练阶段的损失函数和优化器选择也有讲究。分类任务用BCEWithLogitsLoss内部已经包含sigmoid数值稳定优化器用Adam学习率1e-3配合ReduceLROnPlateau做动态衰减。我不用SGD是因为金融序列的非平稳特性让SGD的收敛路径很不稳定。def train_model(model, train_loader, val_loader, epochs100, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.BCEWithLogitsLoss() optimizer torch.optim.Adam(model.parameters(), lrlr) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience10 ) for epoch in range(epochs): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: X_batch X_batch.to(device) y_batch y_batch.to(device).float().view(-1, 1) optimizer.zero_grad() logits model(X_batch) loss criterion(logits, y_batch) loss.backward() # 梯度裁剪防止LSTM的梯度爆炸这是LSTM训练的必备操作 nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() # 验证阶段 model.eval() val_loss 0.0 with torch.no_grad(): for X_batch, y_batch in val_loader: X_batch X_batch.to(device) y_batch y_batch.to(device).float().view(-1, 1) logits model(X_batch) val_loss criterion(logits, y_batch).item() scheduler.step(val_loss) if (epoch 1) % 20 0: print(fEpoch {epoch1}: train_loss{train_loss:.4f}, val_loss{val_loss:.4f})这段代码里的clip_grad_norm_是LSTM训练的救命稻草。金融序列里经常出现极端单日波动一个异常大的梯度可能导致整个模型的权重被“震碎”之后无论怎么训练Loss都回不来。梯度裁剪把梯度的L2范数限制在1.0以内虽然不能根治这个问题的所有变体但能挽救绝大多数训练崩溃的场景。3.3 数据加载器与训练流程的工程细节DataLoader的shuffle陷阱PyTorch的DataLoader有个参数shuffle在做时间序列时必须设为False。我见过不止一个同行把图像分类的习惯带过来shuffleTrue一开模型的验证集Loss在训练初期异常低——因为它已经“见过”了未来的样本。对于时间序列shuffleFalse保证每个batch内的样本在时间上是有序的LSTM的隐状态初始化也更自然。from torch.utils.data import TensorDataset, DataLoader # 把numpy数组转成Tensor X_tensor torch.FloatTensor(X) # (n_samples, lookback, input_dim) y_tensor torch.FloatTensor(y).view(-1, 1) # 按时间顺序切分 train_n int(len(X_tensor) * 0.7) val_n int(len(X_tensor) * 0.15) train_dataset TensorDataset(X_tensor[:train_n], y_tensor[:train_n]) val_dataset TensorDataset(X_tensor[train_n:train_nval_n], y_tensor[train_n:train_nval_n]) train_loader DataLoader(train_dataset, batch_size64, shuffleFalse) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse)batch_size64是基于我的序列长度经验值。日线60天的lookback窗口64个样本组成的batch在反向传播时的梯度估计足够稳定显存占用也友好。如果你用的是分钟级数据batch_size可以上探到128或256因为短序列的梯度方差更小。4. 综合决策层的设计比特币和黄金两个LSTM输出如何合成一个交易信号4.1 加权投票 vs 状态机为什么要一个决策层而不是直接平均现在你有两条LSTM的输出比特币的未来5天上涨概率p_btc黄金的未来5天上涨概率p_gold。最懒的做法是直接取平均大于0.5就做多——这个方案的问题我已经用真金白银验证过了当两个资产概率都接近0.5时平均值的微小变化会被噪声主导产生大量虚假信号。我推荐的方案是设计一个“置信度过滤器 状态切换”的综合决策层。核心逻辑分三步只有当某个资产的预测概率超过阈值比如0.65时才认为该资产有可交易信号如果两个资产同时给出高置信度信号则比较它们的置信度差值差值大的资产优先如果两个信号方向相反且置信度都不足输出“观望”。这个策略的逻辑很直观——LSTM的输出是连续概率不是二值判断置信度过滤能大幅减少无意义的进出场。def comprehensive_decision(p_btc, p_gold, threshold0.65, lookback_state5): 综合决策函数 返回: buy_btc, buy_gold, hold btc_signal p_btc threshold gold_signal p_gold threshold # 两者同向强信号选置信度更高者 if btc_signal and gold_signal: return buy_btc if p_btc p_gold else buy_gold # 只有单一资产触发 if btc_signal: # 黄金侧必须确认没有反向强信号避险对冲场景 if p_gold 0.4: return buy_btc return buy_btc # 弱反向不算阻碍 if gold_signal: if p_btc 0.4: return buy_gold return buy_gold # 双资产都无信号结合近期状态避免频繁空仓 return hold代码里我特意保留了lookback_state参数它在更复杂的实现中可以配合持仓状态做信号平滑——比如连续3天都是hold而第4天出现信号就直接入场但如果前一天刚做过一次交易则强制等待2天。这个“交易冷却”机制在实际回测里能显著降低手续费损耗。4.2 状态切换策略震荡市跟黄金趋势市跟BTC的切换逻辑“综合决策”比单资产模型多出来的价值就体现在市场状态识别上。比特币和黄金在市场中的角色差异很大BTC是典型的高波动风险资产趋势一旦确立惯性很强适合趋势跟踪黄金是典型的避险资产波动率低但在风险事件期间会有脉冲式行情。它们之间存在一种近似“跷跷板”的关系。def regime_switch(p_btc, p_gold, btc_volatility, gold_volatility): 基于波动率比率的状态切换 返回: btc_mode / gold_mode / neutral # 近20日波动率比率大于1.5认为BTC主导市场 vol_ratio btc_volatility / gold_volatility if vol_ratio 1.5: # BTC高波动期黄金避险属性弱化只看BTC信号 return btc_mode elif vol_ratio 0.8: # 黄金波动相对更大可能处于避险事件窗口 return gold_mode else: return neutral这里的逻辑是当BTC的波动率是黄金的1.5倍以上时市场大概率处于风险偏好驱动阶段黄金的“避险溢价”被压制模型的黄金预测可信度降低此时应把决策权交给BTC信号相反如果黄金波动率占比上升说明市场可能处于避险阶段BTC信号会充满噪声。这个波动率比率过滤器很粗糙但在实际使用中效果出奇地好——它本质上是在做“市场环境识别”让模型只在擅长的市场状态下出手。需要提前说明的是这个综合决策层的有效性建立在两个单资产LSTM都足够好的前提下。如果其中一个模型本身就过拟合了决策层的所有逻辑都是空中楼阁。所以下一步回测验证时一定要分开看两个模型分别的预测准确率再综合看决策层的最终收益曲线哪一层出了问题一目了然。5. 避坑指南LSTM预测比特币和黄金价格走势时最容易翻车的4个环节5.1 归一化泄漏验证集和测试集被“偷看”的最隐蔽方式现象训练Loss正常下降验证Loss也比预期低得多但实盘效果一塌糊涂几乎没有任何预测能力。原因在构造训练集之前就对全量数据做了StandardScaler.fit_transform。这一步看着无伤大雅但实际上scaler的均值和方差是从包含“未来”的数据里计算出来的验证集和测试集的分布信息已经泄漏给了模型。模型在训练时看到的是被“全局统计量”标准化过的数据而实盘场景下你只能用截至当前时刻的滚动统计量。解决严格把scaler的fit限制在训练集上验证集和测试集用同一组mean和std做transform。更稳妥的做法是做“滚动归一化”——每预测一天用过去250天的数据重新计算均值和方差。后者计算开销大但最大程度模拟了实盘环境。# 错误示范对全量数据统一fit # scaler StandardScaler().fit(all_data) # all_data_scaled scaler.transform(all_data) # 正确做法只fit训练集 train_data feature_matrix[:train_n] scaler StandardScaler().fit(train_data) train_scaled scaler.transform(train_data) val_scaled scaler.transform(feature_matrix[train_n:train_nval_n]) test_scaled scaler.transform(feature_matrix[train_nval_n:])5.2 预测步长与标签偏移你训练的模型可能在“预测昨天”现象模型准确率在训练集上超过70%但输出信号和价格走势之间明显有错位——信号总是晚一天甚至几天才出现。原因构造样本时标签y[i]对应的是特征X[i]的“未来收益”。但如果你的数据索引有误——比如对齐两个资产时发生了日期错位或者滑动窗口代码里把shift方向写反了——模型学到的实际是“使用未来数据预测过去”即标签偏移。解决写一个简单的自检函数随机抽几个样本人工核对。具体做法是打印X[i]的最后一天日期和y[i]的收益区间肉眼确认时间顺序是否正确。这个检查只需要一次但能省掉后面数小时的无效调参。import random def sanity_check_sequences(aligned_df, X, y, lookback60, horizon5): idx random.randint(0, len(X) - 1) end_date aligned_df[date].iloc[idx lookback - 1] future_start aligned_df[date].iloc[idx lookback] future_end aligned_df[date].iloc[idx lookback horizon - 1] print(f窗口结束日期: {end_date}, 预测区间: {future_start} ~ {future_end}) print(f标签值: {y[idx]}) # 核对预测区间与标签的累计收益是否一致 future_returns aligned_df[btc_return].iloc[idxlookback:idxlookbackhorizon] print(f实际累计收益: {future_returns.sum():.4f}, 标签方向: {上涨 if y[idx]1 else 下跌})5.3 数据集划分不当随机切分让模型“穿越”到未来现象验证集准确率极高但同样的模型在实盘中完全失灵。原因直接调sklearn.train_test_split且忘记设置shuffleFalse或者虽然按时间切分但把相邻样本分到了训练集和验证集两侧——由于滑窗样本高度重叠相邻样本只差一天训练集和验证集之间实际上存在大量“信息重叠”模型在验证集上看到的是几乎见过无数遍的样本。解决切分时严格按时间顺序并且让训练集和验证集之间留出一段“间隙”我一般空出10个交易日。这段间隙消除样本重叠带来的信息泄漏让验证集评估更接近真实的前向预测表现。5.4 交易成本被忽略模型预测准了但你的账户还是亏的现象回测曲线年化收益率很高但一旦用小资金实盘收益被手续费和滑点吃掉大半甚至变成亏损。原因LSTM模型只负责预测方向完全不考虑交易成本。BTC的合约手续费、黄金的价差和隔夜库存费这些都是实打实的损耗。回测中如果假设每次买卖都以收盘价成交且零成本结果必然过度乐观。解决在回测逻辑中显式加入交易成本模型。最简单的做法是每次从hold切换到buy或切换资产时扣减固定比例的手续费BTC永续合约按taker费率0.05%计黄金价差按0.1%计。更精细的做法是同时加入滑点模型——假设成交价始终比信号价差0.1%能更真实地反映冲击成本。def backtest_with_cost(decisions, prices, cost_rate0.001): position hold equity 1.0 for i, dec in enumerate(decisions): if dec ! position and dec ! hold: # 切换仓位扣除交易成本 equity * (1 - cost_rate) # 持仓期间跟随资产价格涨跌 if position buy_btc: equity * (1 prices[btc_return].iloc[i]) elif position buy_gold: equity * (1 prices[gold_return].iloc[i]) position dec return equity6. 模型验证与进阶调优从准确率到收益曲线的最后一个环节6.1 时间序列的滚动前向验证walk-forward validation贴合实盘最紧的评估方式传统的训练/验证/测试三段切分有一个结构性问题测试集只代表某一段固定的历史时期如果那段时期恰好是单边牛市或震荡市评估结果就不具有代表性。实盘中模型是持续滚动更新的——每周用最新数据重新训练或微调然后用最新模型预测下一个交易周。要模拟这个过程常见做法是做“滚动前向验证”。def walk_forward_validation(data, lookback60, retrain_interval20, total_windows10): 滚动前向验证每次用截至当前的所有数据训练预测未来retrain_interval天 results [] n len(data) start int(n * 0.4) # 从40%位置开始滚动 for w in range(total_windows): train_end start w * retrain_interval test_end train_end retrain_interval if test_end n: break # 用train_end之前的数据训练 train_data data[:train_end] test_data data[train_end:test_end] # 重新训练模型保留超参数只用新数据 model LSTMPredictor(input_dim3, hidden_dim64, num_layers2) # 这里省略训练代码与前面train_model()一致 # ... # 在test_data上评估 acc evaluate_predictions(model, test_data) results.append(acc) print(fWindow {w1}: test_acc{acc:.4f}) return np.mean(results)滚动前向验证的价值在于每一轮的测试窗口都严格在训练窗口之后且模型每隔一段就会用最新数据重训这完全模拟了实盘运营的节奏。retrain_interval20意味着每20个交易日重新训练一次对日线策略来说是合理的频率。如果你有足够的算力可以缩短到5天或10天预测精度通常会有小幅提升但训练时间线性增长。6.2 预测概率校准让模型的0.65阈值真正代表65%的置信度LSTM输出的概率是在训练集上通过交叉熵损失学出来的它天然是“分类边界附近的相对置信度”而不是严格的概率估计。举个例子模型输出0.7和0.8不代表前者有70%概率涨、后者有80%概率涨——可能两者对应的真实上涨概率都只有55%。阈值0.65在训练集上的表现并不能直接映射到实盘上的等价置信水平。解决这个问题要用Platt缩放温度缩放。做法是在训练完成后用验证集的数据拟合一个逻辑回归把LSTM的原始logits映射到校准后的概率。这样综合决策层的阈值才有真实的概率意义。from sklearn.linear_model import LogisticRegression def calibrate_probabilities(val_logits, val_labels): val_logits: 模型在验证集上的原始输出未过sigmoid val_labels: 验证集标签 calibrator LogisticRegression() # 把logits变成二维特征 X_cal val_logits.reshape(-1, 1) calibrator.fit(X_cal, val_labels) return calibrator # 使用方式 val_logits model(X_val_tensor).cpu().numpy() calibrator calibrate_probabilities(val_logits, y_val.numpy()) calibrated_probs calibrator.predict_proba(val_logits.reshape(-1, 1))[:, 1]校准后你会发现原本阈值0.65的过滤作用被放大了——校准前的0.65对应实际大约52%的置信度校准后的0.65才算真正的高置信信号。这一步做与不做回测曲线的差异非常明显我不止一次见过校准后收益反而下降的情况原因就是过滤掉了太多看似高置信实则噪声的信号——这说明模型在那些区域本来就没有真正的预测力早过滤早省手续费。6.3 特征组合调优波动率特征与宏观代理变量的收益上限最后一个进阶技巧是对特征矩阵做系统性的扩展实验。我到目前的经验是在“比特币收益率 黄金收益率 BTC成交量”三个基础特征之上依次加入以下特征观察验证集准确率的边际变化一是“黄金波动率近20日滚动标准差”它能在黄金模式中辅助状态识别二是“BTC波动率 / 黄金波动率”这个比值特征直接编码了4.2节中的状态切换逻辑三是“美元指数DXY的收益率”如果你能拿到数据这个宏观代理变量对两个资产都有显著的解释力。# 扩展特征矩阵示例 aligned_df[btc_vol_20] aligned_df[btc_return].rolling(20).std() aligned_df[gold_vol_20] aligned_df[gold_return].rolling(20).std() aligned_df[vol_ratio] aligned_df[btc_vol_20] / aligned_df[gold_vol_20] extended_features np.column_stack([ aligned_df[btc_return], aligned_df[gold_return], np.log(aligned_df[btc_volume] 1e-8), aligned_df[vol_ratio] ]) # 丢弃前19行rolling窗口产生NaN aligned_df aligned_df.dropna().reset_index(dropTrue)每次加特征后只跑一次walk-forward验证不要只盯测试集准确率。滚动验证的窗口间方差能告诉你这个特征是在稳定提升还是只对某一段市场有效。这里还有个细节加特征后必须同步调整模型结构——input_dim从3变为4hidden_dim可以不变但训练轮数建议增加20%左右因为特征维度增加后模型需要更多迭代才能充分拟合。做这套方案的完整流程走下来最大的感受是LSTM预测比特币和黄金价格走势这件事真正的壁垒不在模型结构有多新而在数据处理、状态识别和交易执行的每一道工序上是否经得起推敲。综合决策模型的本质是“让两个资产互相兜底”——BTC信号不明朗时黄金的避险判断还能顶上双资产共振时才重仓出手。直到今天我做新策略的第一个动作仍然是先跑通这套双资产框架再做单资产细化。希望这篇笔记能帮你少走一些我已经走过的弯路。本文还有配套的精品资源点击获取
返回列表