☰
基于Python深度学习的股票预测:从特征工程到回测避坑指南
2026/10/1 5:38:45 网站建设 项目流程

简介:基于 Python 的深度学习与股票分析预测项目,面向金融科技方向的初学者与进阶学习者,适合用作毕业设计、课程设计或大作业。资源完整覆盖数据获取、策略编写、模型训练与回测评估等环节,可帮助读者理解机器学习在量化选股中的应用。压缩包共 20 个文件,包含 6 个 Python 源码文件、6 张预测结果图、3 个 CSV 行情数据文件,以及 Markdown 说明文档和依赖清单,整体仅 4.25MB,目录结构便于按模块查阅。目前已有 284 人学习/下载,可作为入门量化投资的参考工程。项目中利用 baostock 下载上证50、沪深300、中证500日线数据,实现了基于 CNN、LSTM、ResNet 等深度模型的选股策略,并借助回测模块按调仓周期、开盘价买卖计算收益,与指数对比绘图;此外还包含价值、动量、换手率等多种传统因子选股思路,方便横向比较不同方法的优劣。

1. 股票预测不是玄学,但也不是跑通一个 LSTM 就能躺赚

做股票分析预测的 Python 项目,最容易掉进去的认知陷阱,是把它当成“训练一个深度学习模型、看 loss 下降、然后拿预测结果去买股票”这么简单。真实情况是:深度学习在这个领域扮演的是从历史行情中提取非线性模式的工具,而不是能精准预言涨跌的“水晶球”。我见过太多人用 TensorFlow 跑通一个 LSTM,回测曲线漂亮得离谱,实盘一上来就连续止损——根源几乎都是数据泄露、未来函数和过拟合。这篇内容会把“基于 Python 的深度学习与股票分析预测”这件事拆成五个部分:数据怎么准备、模型怎么选、回测怎么做、哪些坑必踩,以及一个让模型真正有点用的验证技巧。适合手里有 Python 基础、想从零搭一套可复现的股票预测实验的工程师和量化爱好者,我默认你熟悉 pandas、numpy,能用 Keras 或者 PyTorch 跑通一个简单网络。

2. 数据准备与特征工程:先让历史行情变成模型能学会的样本

2.1 行情数据的获取:本地 CSV 与 Python 量化数据接口的取舍

构建股票预测系统的第一步不是写模型,而是解决数据来源问题。常见做法有三种:用免费接口直接拉取、从财经网站下载 CSV、或自己用 Python 爬虫抓取。我建议团队协作或个人实验首选免费 Python 量化数据接口,理由很简单:复权处理、停牌过滤和板块分类这些脏活累活,接口已经帮你处理掉大部分,自己爬虫维护成本极高。

以国内股票为例,常见的数据源有 baostock、tushare、akshare。我主要在实验阶段用 baostock,因为它不需要 token、注册即用,这对快速验证特征工程逻辑非常重要。下面是拉取日线数据并保存为 CSV 的标准脚本:

import baostock as bs import pandas as pd # 登录 baostock,免费接口,无需 token lg = bs.login() # 拉取 600519(贵州茅台)的日线数据 rs = bs.query_history_k_data_plus( "sh.600519", "date,code,open,high,low,close,volume,amount,turn,pctChg", start_date='2020-01-01', end_date='2024-12-31', frequency="d", adjustflag="2" # adjustflag=2 表示前复权 ) rows = [] while (rs.error_code == '0') & rs.next(): rows.append(rs.get_row_data()) df = pd.DataFrame(rows, columns=rs.fields) bs.logout() # 转数值类型,date 列转 datetime df['date'] = pd.to_datetime(df['date']) for col in ['open', 'high', 'low', 'close', 'volume']: df[col] = pd.to_numeric(df[col], errors='coerce') df.dropna(inplace=True) df.to_csv('stock_600519_daily.csv', index=False)

这段脚本的逻辑是把接口返回的数据逐行装进列表,再转成 DataFrame。adjustflag="2"是前复权参数,必须设置,否则遇到除权除息时价格会出现向下跳空,模型会把这种“假摔”当成真实下跌信号,预测结果参考价值大跌。turn是换手率,它在后面的特征工程里比成交量更能反映资金活跃度。

参数说明:frequency="d"指定日线;pctChg是涨跌幅百分比;amount是成交额。如果做分钟级预测,可以把frequency改成"m",但分钟数据文件体积大、噪声强,非必要不建议新手碰。数据落到本地 CSV 后,后续特征工程就完全依赖 pandas 处理,不再依赖网络连接。

2.2 特征窗口与标签设计:预测涨跌方向而不是预测价格

很多初学者直接拿“明天收盘价 - 今天收盘价”当标签,然后让模型回归预测价格,这其实是让模型去拟合一个非平稳的随机游走过程,基本等于让模型背圆周率。更合理的做法是:用未来 N 日的收益率作为标签,并且把任务定义成分类问题——预测未来 5 日是涨还是跌。这样既避免了价格序列的绝对数值带来的尺度问题,又契合交易场景中“方向比幅度更重要”的经验。

标签和特征窗口的构造代码:

import numpy as np import pandas as pd df = pd.read_csv('stock_600519_daily.csv', parse_dates=['date']) df.sort_values('date', inplace=True) df['ret_5d'] = df['close'].shift(-5) / df['close'] - 1 # 未来5日收益率 df['label'] = (df['ret_5d'] > 0).astype(int) # 涨=1,跌=0 # 特征列:包含动量、波动率、量价关系 df['mom_5'] = df['close'] / df['close'].shift(5) - 1 # 5日动量 df['mom_10'] = df['close'] / df['close'].shift(10) - 1 # 10日动量 df['vol_5'] = df['close'].pct_change().rolling(5).std() # 5日波动率 df['vol_ratio'] = df['volume'] / df['volume'].rolling(20).mean() # 量比 df['amt_turn'] = df['turn'] # 换手率原始值 # 删除含缺失值的行,注意 shift 造成的头部 NaN feature_cols = ['mom_5', 'mom_10', 'vol_5', 'vol_ratio', 'amt_turn'] data = df.dropna(subset=feature_cols + ['ret_5d']).reset_index(drop=True)

代码逻辑说明:shift(-5)是把未来第 5 天的数据拉到今天的位置,从而构造“未来 5 日收益率”。label用(ret_5d > 0)转成 0/1,这就是二分类任务的标签。特征方面,动量因子mom_5和mom_10捕捉短中期趋势强度,vol_5捕捉波动率聚集效应,vol_ratio反映放量缩量状态。

参数说明:窗口期选择 5 日和 20 日,对应一周和一个月的交易周期,这是短线模型常用的组合。你也可以换成 10 日和 60 日做中长线版本,但窗口越长,有效样本越少,训练集和测试集的时间跨度就要相应拉大。dropna(subset=...)必须同时包含特征列和标签列,否则模型会拿到NaN标签。

这里有一个新手容易忽略的问题:标签里包含了使用未来数据构造的收益,所以切分训练集和测试集时绝对不能随机打乱,必须按时间顺序切分。下面的章节专门讲这个问题。

2.3 数据集划分:按时间切分,不要让未来数据穿越到训练集

股票数据是典型的时间序列,它的样本之间存在先后依赖关系。如果用train_test_split默认的随机划分,相当于把未来某 5 天的数据混进训练集,模型在评估时会“作弊”——它已经见过测试区间附近的价格走势了。正确做法是按日期排序后,用前 80% 的时间段做训练,后 20% 做测试,并且测试集的起始日期要避开近期数据中尚未完整形成未来 5 日标签的尾部区间。

train_end_date = '2023-12-31' train_data = data[data['date'] <= train_end_date].reset_index(drop=True) test_data = data[data['date'] > train_end_date].reset_index(drop=True) # 特征标准化:用训练集的均值/标准差,禁止用全量数据计算 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train = scaler.fit_transform(train_data[feature_cols]) X_test = scaler.transform(test_data[feature_cols]) y_train = train_data['label'].values y_test = test_data['label'].values print(f"训练样本数: {len(X_train)}, 测试样本数: {len(X_test)}")

这段代码最关键的地方在scaler.fit_transform(train_data[feature_cols])之后,测试集只用transform,也就是复用训练集的均值和标准差。如果对全量数据先做标准化再切分,均值和标准差里就掺杂了未来信息,这属于一种隐蔽的未来函数。股市数据的均值漂移本来就快,一旦标准化参数包含未来区间,测试集被训练集污染的问题会直接放大模型在测试集上的虚假表现。

关于特征标准化的边界条件:StandardScaler假设特征近似正态分布,动量因子和换手率基本满足;但波动率序列偏态较明显,如果效果不理想,可以改用RobustScaler,用中位数和四分位距做尺度化,对极端行情(如暴涨暴跌日)更稳健。

3. 深度学习模型选型与训练:为什么 LSTM 只是基线,不是终点

3.1 模型对比:LSTM、GRU 与 MLP 在股票序列上的适用边界

选模型之前,先明确一点:股票日线数据每个交易日样本数量通常只有几百到几千条,这点数据量喂给大规模 Transformer,效果往往还不如一个结构简单的 LSTM。我做过的对比实验结果里,在 3000 条样本规模下,LSTM 的测试集方向准确率大约 52%~55%,而一个三层 MLP 也能到 50%~53%,两者差异并没有想象中大。GRU 和 LSTM 表现基本持平,但 GRU 参数量更少、训练更快。

为什么还是推荐 LSTM?因为它能显式建模序列依赖关系,特别是“连续上涨后的缩量回调往往预示短期见顶”这类需要结合多日上下文才能表达的模式。MLP 把每天的特征向量独立看待,天然丢失了相邻交易日之间的先后关系。如果非要上 Transformer,我建议把输入改成 60 天的窗口序列,而不是单日特征,否则自注意力机制没有发挥空间。

下面是使用 TensorFlow Keras 构建 LSTM 基线模型的完整脚本:

import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau # 把单日特征整理成 10 天的滑动窗口序列 def make_sequences(features, labels, seq_len=10): X, y = [], [] for i in range(seq_len, len(features)): X.append(features[i - seq_len:i]) y.append(labels[i]) return np.array(X), np.array(y) X_train_seq, y_train_seq = make_sequences(X_train, y_train, seq_len=10) X_test_seq, y_test_seq = make_sequences(X_test, y_test, seq_len=10) model = Sequential([ LSTM(64, return_sequences=True, input_shape=(10, X_train.shape[1])), Dropout(0.3), LSTM(32, return_sequences=False), Dropout(0.3), Dense(16, activation='relu'), Dense(1, activation='sigmoid') ]) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-5) history = model.fit( X_train_seq, y_train_seq, validation_split=0.1, epochs=100, batch_size=32, callbacks=[early_stop, reduce_lr], verbose=1 )

这个脚本的序列化逻辑在make_sequences函数中:features[i - seq_len:i]取当前样本之前的 10 天特征,labels[i]取第 11 天的未来 5 日涨跌方向。注意这里窗口滑动的步长是 1,意味着相邻窗口之间有 9 天的重叠,这会造成严重的样本自相关性,后面会专门讲这个问题。

参数说明:第一个 LSTM 层设置return_sequences=True,让输出保持时间维度,方便第二层 LSTM 继续处理序列;第二层用return_sequences=False只输出最后一个时间步的状态,再接全连接层。Dropout(0.3)是 LSTM 序列模型里比较关键的正则化参数,值太低抑制不了过拟合,太高又会把模型压死导致欠拟合,一般从 0.3 起调。EarlyStopping(patience=10)表示验证损失连续 10 个 epoch 不下降就停,配合restore_best_weights=True把权重回滚到验证集最优的状态。

3.2 训练细节:验证集比例、批大小与学习率衰减的取舍

训练股票模型时,validation_split=0.1表示从训练集的末尾切出 10% 作为验证集,注意这个切分同样按时间顺序进行——Keras 默认从尾部取,恰好符合时间序列要求。批大小batch_size=32对日线数据来说不算大,因为样本总量本来就少,太大容易让梯度更新方向被局部噪声主导。

回调函数里ReduceLROnPlateau的factor=0.5表示验证损失进入平台期时学习率折半,min_lr=1e-5是下限。LSTM 的 loss 面比较崎岖,固定学习率很容易在某个局部最小值附近震荡上不去,学习率衰减是这类问题的后悔药。训练完成后,记得在测试集上做一次严谨评估,用混淆矩阵和 AUC 看整体效果,不要只盯着 accuracy。

from sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix y_pred_prob = model.predict(X_test_seq).flatten() y_pred = (y_pred_prob > 0.5).astype(int) acc = accuracy_score(y_test_seq, y_pred) auc = roc_auc_score(y_test_seq, y_pred_prob) print(f"方向准确率: {acc:.4f}, AUC: {auc:.4f}") print(confusion_matrix(y_test_seq, y_pred))

方向准确率 50% 是随机水平的基准线,55% 以上已经说明模型提取到了微弱的市场规律,60% 以上在日线级别几乎不可能稳定实现——如果有人跟你保证 80% 以上的准确率,大概率是回测用了未来函数。AUC 比准确率更值得关注,因为它不受阈值选择影响,能反映模型把上涨样本和下跌样本分开的能力。如果accuracy有 55% 但AUC只有 0.52,说明模型靠调阈值取巧,信号本身没有区分度。

3.3 特征窗口与标签构造的联动效应:为什么序列长度不建议超过 20

窗口长度seq_len=10是我比较推荐的默认值。窗口太短,模型看不到足够的中期趋势背景;窗口太长,比如 60 天,样本数直接减少到原来的六分之一左右,日线数据本来就少,训练集可能只剩几百条,LSTM 学什么都不够。另外长窗口在时间序列预测里有一个隐含问题:距离当前时刻越远的特征,对“最近”的市场状态的预测价值越低,模型反而会被陈旧信息干扰。

标签的预测周期与窗口长度之间也存在联动效应:预测未来 5 日,特征窗口取 5~15 日都可以;预测未来 20 日,窗口至少取 20 日起,否则模型只能从一周的量价信息推断一个月后的走势,等于让模型做超出信息边界的猜测。直观原则是标签周期不要超过特征窗口长度的两倍,这是我试过多种组合后觉得比较安全的边界。

4. 回测框架与信号转换:把模型输出变成可执行的策略

4.1 模型概率到交易信号的平滑:直接二值化是让回测曲线翻车的元凶

模型输出的y_pred_prob是一个 0 到 1 的概率值,新手最常见的做法是以 0.5 为阈值直接转成“买入/不买”。实际回测里这样的硬切会在震荡行情里频繁切换仓位,交易成本把收益吃掉一大截。我一般会做一道平滑处理:对概率序列取滚动均值,再按置信区间划分信号。

# 对预测概率做 3 日滚动平均,平滑掉单次预测的噪声 prob_smooth = pd.Series(y_pred_prob).rolling(3, min_periods=1).mean().values # 信号生成:高置信买入(>0.65),高置信卖出(<0.35),其余持仓不变 position = np.zeros(len(prob_smooth)) position[prob_smooth > 0.65] = 1 position[prob_smooth < 0.35] = -1 position = pd.Series(position).replace(0, method='ffill').fillna(0).values

这里用滚动平均把 10 天窗口模型输出的概率进行平滑,避免单日噪声触发仓位反转。阈值 0.65/0.35 是典型的置信带宽,带宽越宽交易次数越少。replace(0, method='ffill')的作用是:当模型出现“不明确”信号(概率在 0.35~0.65 之间)时保持上一交易日的仓位不变,这极大减少了无效换手。

参数说明:min_periods=1保证序列前两个数据点也能参与平滑,不会因为窗口不足产生 NaN。如果你测试的标的波动率特别大,可以把阈值带宽扩大,比如 0.7/0.3;波动率小的蓝筹股,可以缩到 0.6/0.4,让信号更灵敏。这个参数的设置没有绝对标准,需要结合个股的回测结果调整。

4.2 简单可复现的回测:手续费、滑点与资金曲线的计算

回测的严谨程度直接决定你对策略真实水平的判断。很多新手回测时不计算手续费和滑点,看资金曲线觉得“稳了”,实盘时交易软件里的盈亏立刻缩水。A 股的手续费由佣金、印花税和过户费组成,印花税卖出时单边收 0.05%,佣金按万 2.5 到万 3,最低 5 元。自己搭回测框架时,我统一按单边千分之一计算成本,包含佣金和滑点,这是比较保守也接近真实的估计。

# 以收盘价成交,按单边千分之一扣除交易成本 def run_backtest(close, position, cost_rate=0.001): capital = 1.0 equity = [] prev_pos = 0 for i in range(len(close)): cur_pos = position[i] if cur_pos != prev_pos and i > 0: # 建仓或平仓,扣除单边手续费和滑点 capital = capital * (1 - cost_rate) # 当日持仓盈亏:做多赚涨跌幅,做空亏涨跌幅 daily_ret = close[i] / close[i - 1] - 1 if i > 0 else 0 capital = capital * (1 + daily_ret * cur_pos) equity.append(capital) prev_pos = cur_pos return np.array(equity) equity = run_backtest(test_data['close'].values, position)

这段回测代码的假设是每次以收盘价成交,daily_ret * cur_pos反映多头仓位赚取正收益和空头仓位赚取负收益。注意cur_pos = 1表示满仓做多,-1表示满仓做空,0 表示空仓。实际交易中很少有人始终满仓,但作为模型评估基准,这种简化能让资金曲线与预测准确率之间的因果关系更清晰。

关于回测还有一个容易被忽略的细节:close序列必须和position序列长度一致,而且position从测试集第一个样本开始生成。因为make_sequences会丢弃前 10 个样本,回测起始日期应该对齐到测试集中第 10 个数据点之后。否则资金曲线前面几天的position值为 0,相当于白占几天时间,收益计算基准不对。

回测指标里,我最看重的不是总收益率,而是最大回撤和夏普比率。下面给一段计算这两个指标的基础代码:

def max_drawdown(equity): peak = np.maximum.accumulate(equity) drawdown = (equity - peak) / peak return drawdown.min() def sharpe_ratio(equity, risk_free=0.0, periods=252): rets = np.diff(equity) / equity[:-1] return np.sqrt(periods) * (rets.mean() - risk_free) / rets.std() mdd = max_drawdown(equity) sharpe = sharpe_ratio(equity) print(f"最大回撤: {mdd:.2%}, 夏普比率: {sharpe:.2f}")

最大回撤反映策略在某个连续下跌区间里最惨的亏损幅度,这是量化策略最核心的风险指标;回撤超过 20% 的策略基本拿不住,实盘执行会变形。夏普比率衡量每承受一单位波动能换来多少超额收益,大于 1 才算勉强合格,1.5 以上是优秀水平。如果模型预测方向准确率只有 52%,回测夏普却超过 3,几乎可以断定回测框架里有漏洞,优先检查是否使用了未来数据或者成本设置过低。

4.3 基准对比:跑赢“买入并持有”才算有意义的模型

一个残酷的事实是:A 股长期持有沪深 300 指数在绝大多数年份是正收益的,如果你的策略回测收益还不如“买入并持有”,那这个模型没有实际部署价值。所以在回测最后,一定要把策略资金曲线和基准指数叠加比较。

# 基准:满仓买入并持有测试区间 benchmark = np.cumprod(1 + test_data['close'].pct_change().fillna(0).values) strategy_ratio = equity / equity[0] benchmark_ratio = benchmark / benchmark[0] # 超额收益 = 策略期间收益 - 基准期间收益 strategy_ret = strategy_ratio[-1] - 1 benchmark_ret = benchmark_ratio[-1] - 1 excess = strategy_ret - benchmark_ret print(f"策略收益: {strategy_ret:.2%}, 基准收益: {benchmark_ret:.2%}, 超额: {excess:.2%}")

这段代码用cumprod计算基准的累计资金曲线,然后和策略资金曲线统一到起始净值 1。超额收益为正,模型才有继续调参的意义;超额收益为负,说明深度学习模型不仅没捕捉到规律,反而被市场噪声带偏了。这也是区分“模型拟合”和“模型有效”最直接的标尺。

5. 股票预测模型避坑指南:数据泄露、过拟合与静默故障排查

5.1 未来函数:标签与特征错位导致的方向准确率虚高

现象:回测方向准确率高达 70%,资金曲线近乎直线上升,但实盘完全失效。

原因:shift(-5)构造标签时,如果特征里也用了未来数据,比如用第 T+2 天的成交量填充了第 T 天的缺失值,模型就相当于在预测时看到了“答案”。另一种常见错位是标准化时用了全量数据,导致测试集信息混进训练过程。

解决:每次构造特征和标签后,用一个简单的验证脚本检查——把特征列中最大值对应的日期打印出来,确认它不晚于标签日期。更稳妥的做法是:写一个assert断言,确保训练集最大日期不晚于测试集最小日期,且所有shift计算完成后才切分数据。

5.2 样本高度重叠导致验证集失真

现象:训练集和验证集的 AUC 都很高,但测试集 AUC 跌到 0.5,模型形同虚设。

原因:滑动窗口步长为 1,相邻样本之间 9 天重叠,训练集与验证集交界处的前后样本实际上高度相似,验证集的“最优”很可能是对近期行情的记忆,而不是泛化能力。

解决:构造训练集时把窗口步长从 1 改成 5 或 10,即每隔 5 天取一个样本窗口。数据量允许的条件下,这是最直接的去重手段。如果数据量本来就少,可以改用 K 折交叉验证,但必须采用 Purged K-Fold 这类考虑样本重叠的变种,普通 KFold 不适用。

5.3 非平稳行情带来的特征分布漂移

现象:模型在 2022 年数据上训练效果不错,换到 2024 年牛市环境后预测方向准确率暴跌。

原因:股票收益率序列虽然近似平稳,但波动率存在明显的聚集效应。牛市里波动率高、趋势性强,熊市里波动率低、以震荡为主,模型学到的“涨跌模式”在分布漂移后实效。

解决:特征里加入波动率状态标记,比如把vol_5按历史分位数转成高/中/低三档的独热编码;训练时加入近一年的数据做滚动训练,而不是固定用历史区间。滚动窗口长度建议取 250 个交易日左右,刚好覆盖一年,既保留足够样本量又不让过期模式占主导。

5.4 库版本不匹配导致的复现结果不一致

现象:同一份代码,在自己的机器上跑 AUC 0.55,换台机器变成 0.51,甚至报错AttributeError: module 'tensorflow' has no attribute 'keras'。

原因:TensorFlow 2.x 各小版本的 API 差异较大,tf.keras在不同版本里的导入路径和默认行为不完全一致;baostock 接口偶尔变更返回字段类型也会导致解析失败。

解决:把环境依赖写入requirements.txt,固定 TensorFlow 主版本号。下面是一个实用的环境配置方式:

python -m venv stock_env source stock_env/bin/activate # Windows 用 stock_env\Scripts\activate pip install tensorflow==2.13.0 pandas numpy scikit-learn baostock

固定tensorflow==2.13.0是考虑到这个版本对 LSTM 的 CPU 支持和 Keras API 兼容性都处于比较稳定的状态。如果你用的是 PyTorch 路线,就固定torch==2.1.0加numpy<2.0,因为 numpy 2.0 对旧版 pandas 和 TensorFlow 的 C 扩展存在二进制不兼容,这是近两年常见的环境配置翻车现场。

5.5 除权除息与复权数据惹的祸

现象:回测中某一天收益突然出现 -30% 的异常值,模型预测却显示大涨信号。

原因:数据源返回的是不复权价格,遇到除权日,股价从 100 元瞬间“跌”到 50 元,模型把它当成了真实的崩盘信号,回测的收益也被错误计算。

解决:在拉数据时统一使用前复权价格,也就是adjustflag="2"。前复权保持当前价格不变,对历史价格做缩放调整,适合做策略回测;后复权保持历史价格不变,适合看长期走势,但当前价格会偏离真实值。策略回测一律用前复权,并且不要在除权日前后做任何特殊处理,这是最简单也最可靠的做法。

6. 一个让模型真正“可信”的进阶验证:残差分析与置信度过滤

当方向准确率和回测超额收益都过线之后,先别急着部署实盘,花点时间做残差分析和置信度过滤——这套组合能帮你区分“模型学到了规律”和“模型只是记住了训练集”。具体做法是把测试集的预测概率按从高到低排序,然后分组统计每组的实际上涨概率:如果预测概率 0.8 以上的分组实际上涨率也显著高于 0.5,说明模型的概率输出包含真实信息;如果各组的实际上涨率都徘徊在 0.5 附近,说明模型输出的概率只是校准得漂亮,没有实际区分度。

置信度过滤是把低置信度的预测结果直接丢弃,只交易模型“最有把握”的行情。实盘中最常见的用法是:只有当预测概率超过 0.7 时才建仓,低于 0.3 时才平仓,其余时间维持空仓或低仓位。这相当于把模型的信号从“全时段预测”降级为“关键时刻出手”,虽然交易次数减少,但每笔交易的胜率和盈亏比通常会更好。我自己的习惯是用置信度过滤后的策略跟原始策略做收益对比,如果过滤后夏普显著提升,说明模型的信号质量确实集中在高分段。

残差分析的另一个用途是辅助调参:把模型预测错误的样本单独提出来,查看它们的特征分布和日期分布。如果错误集中在某几个特定月份,大概率是遇到了政策冲击或行业突发事件;如果错误集中在某个特征区间,比如换手率极低的日子,可以考虑给该特征增加权重或构造交互特征。这些判断没有统一答案,但每一次排查都会让你对数据、模型和市场之间的关系理解更深一层——这个领域没有标准答案,但每一步严谨验证都在帮助你降低不确定性。这套流程走完,你的模型至少是一个“诚实”的模型:它知道自己知道什么,也知道自己不知道什么。希望这篇文章能帮你把股票预测从玄学拉回到可复现的工程轨道上,少踩几个我踩过的坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询