☰
Python深度学习股票量化系统实战:从数据到模型与可视化
2026/9/28 18:17:48 网站建设 项目流程

简介:这是一套基于Python与深度学习实现的股票量化系统源码,面向计算机、人工智能、通信工程等专业的在校学生与教师,也适合作为毕业设计、课程设计或项目立项演示的参考方案。系统覆盖数据采集与保存、数据分析、可视化展示及深度学习预测等环节,并集成日常买卖、做套、MACD、KDJ、网格交易等策略,支持机器盯盘与定时更新,当股价出现异动时可通过企业微信或邮箱推送提醒;均价线借助时序预测判断后续涨跌方向,另含基金实时、历史与排行数据展示。压缩包共244个文件,约3.53MB,以71个py源码、80个pyc编译文件、38张png与11张jpg图表、20个json配置及7个ui界面文件为主,辅以css、html等前端资源,结构完整便于二次开发。目前已有266人学习,代码均经测试运行成功,答辩评审平均分达96分,下载后建议先阅读README.md,仅供学习参考。

1. 从一份股票量化系统源码包说起:Python 深度学习怎么把行情变成可复现的信号

很多人第一次接触股票量化,是从拿到一个压缩包开始的:里面有 Python 脚本、深度学习模型、可视化页面,还有架构图和文档说明。标题写着「基于 Python 和深度学习实现的股票量化系统及可视化源码+文档说明+架构图」,看起来什么都有,但真正打开后往往卡在第一步——环境跑不起来,数据接不上,模型输出一堆看不懂的数字。这篇笔记就围绕这个方向,把一套可落地的股票量化系统从数据到模型再到可视化拆开讲清楚。适合两类人:一是会点 Python、想用深度学习做量化但不知道从哪下手的新手;二是已经写过简单策略、想补上模型和可视化链路的熟手。核心不是复刻某个压缩包,而是让你理解这套系统每一层在干什么、参数怎么设、哪里最容易翻车。

2. 数据层与特征工程:股票量化系统的地基怎么打

2.1 行情数据从哪里来、怎么存

股票量化系统的第一道坎不是模型,是数据。常见做法是用开源数据接口拉日线或分钟线,存成 CSV 或直接进 SQLite。我一般会先用一个轻量脚本把数据落盘,避免每次训练都重新请求。下面这段代码演示用 akshare 拉一只股票的日线并存入 SQLite,字段包括日期、开盘、收盘、最高、最低、成交量。

import akshare as ak import sqlite3 import pandas as pd # 拉取贵州茅台日线数据,start_date 和 end_date 按需修改 df = ak.stock_zh_a_hist(symbol="600519", period="daily", start_date="20200101", end_date="20241231") df = df.rename(columns={ "日期": "date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume" }) df["date"] = pd.to_datetime(df["date"]) # 存入 SQLite,表名 stock_daily conn = sqlite3.connect("quant.db") df.to_sql("stock_daily", conn, if_exists="replace", index=False) conn.close() print(df.tail())

逻辑说明:akshare 返回的是中文列名,先统一成英文方便后续处理;日期转成 datetime 类型,避免字符串排序出错;if_exists="replace"在首次建表时最省事,但正式环境建议改成append并加唯一索引去重。参数上,period可选 daily、weekly、monthly,做深度学习一般用 daily 或更细的分钟线;start_date和end_date决定样本区间,太短模型学不到东西,太长又容易混入不同市场阶段。

存好数据后,下一步是特征工程。股票量化里常见的特征包括:收益率、移动平均线、波动率、成交量变化率、RSI、MACD。这些不需要深度学习也能算,但它们是模型的输入。我一般会写一个build_features函数,把原始 OHLCV 转成特征矩阵。

import numpy as np def build_features(df): df = df.copy() df["ret"] = df["close"].pct_change() # 日收益率 df["ma5"] = df["close"].rolling(5).mean() # 5日均线 df["ma20"] = df["close"].rolling(20).mean() # 20日均线 df["vol_std"] = df["ret"].rolling(20).std() # 20日波动率 df["vol_chg"] = df["volume"].pct_change() # 成交量变化率 df["rsi"] = compute_rsi(df["close"], 14) # RSI 指标 df = df.dropna() return df def compute_rsi(series, period=14): delta = series.diff() gain = delta.where(delta > 0, 0).rolling(period).mean() loss = -delta.where(delta < 0, 0).rolling(period).mean() rs = gain / loss return 100 - (100 / (1 + rs))

逻辑说明:pct_change()算收益率,rolling().mean()算均线,rolling().std()算波动率。RSI 用 14 天是常见默认值,可以改成 6 或 24 做对比。dropna()会丢掉前 20 行左右,因为均线和波动率需要窗口。参数上,窗口越大特征越平滑但滞后越明显,做短线可以缩小到 5 和 10,做中长线可以放大到 20 和 60。

2.2 标签怎么定义:回归还是分类

深度学习做股票量化,标签定义决定了模型是回归还是分类。常见做法有两种:一是预测未来 N 天收益率,做回归;二是预测未来 N 天涨跌方向,做分类。我一般先用分类,因为方向比幅度更容易学,也更容易评估。

def make_label(df, horizon=5, threshold=0.0): df = df.copy() df["future_ret"] = df["close"].shift(-horizon) / df["close"] - 1 df["label"] = (df["future_ret"] > threshold).astype(int) df = df.dropna() return df

逻辑说明:shift(-horizon)把未来第 N 天的收盘价挪到当前行,算未来收益率;threshold设 0 表示只要涨就算正样本,设 0.01 表示涨超 1% 才算。参数上,horizon取 1 到 10 都有人用,短线取 1 到 3,中长线取 5 到 10。注意标签必须用未来数据,但特征只能用当前及历史数据,否则就是未来函数,回测会虚高。

提示:特征工程阶段最容易犯的错是把未来信息混进特征,比如用全样本均线而不是滚动均线。检查方法是看特征计算是否只依赖当前行及之前的数据。

3. 深度学习模型选型与训练:LSTM、CNN 还是 Transformer

3.1 为什么股票量化常用 LSTM 和 CNN

股票时序数据有两个特点:一是时间依赖,今天价格和昨天、上周有关;二是局部模式,比如连续几天放量上涨可能预示短期见顶。LSTM 擅长捕捉长短期依赖,CNN 擅长提取局部形态,两者在量化里都有成熟应用。Transformer 近年也有人在用,但数据量要求高,小样本容易过拟合。

我一般先用 LSTM 做基线,因为结构简单、调参少。下面是一个用 PyTorch 写的 LSTM 分类模型,输入是 30 天窗口的特征序列,输出涨跌概率。

import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_dim, hidden_dim=64, num_layers=2, dropout=0.3): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True, dropout=dropout) self.fc = nn.Linear(hidden_dim, 1) def forward(self, x): # x: (batch, seq_len, input_dim) out, _ = self.lstm(x) out = out[:, -1, :] # 取最后一个时间步 return torch.sigmoid(self.fc(out))

逻辑说明:input_dim是特征数量,比如 6 个特征就填 6;hidden_dim是 LSTM 隐藏层维度,64 是常见起点;num_layers=2表示两层 LSTM,层数越多越容易过拟合;dropout=0.3在层间随机丢弃,抑制过拟合。out[:, -1, :]取序列最后一个时间步的输出,代表模型对当前时刻的判断。最后用 sigmoid 压到 0 到 1,输出涨的概率。

参数上,seq_len一般取 20 到 60,太短看不到趋势,太长训练慢且容易梯度消失。batch_size取 32 或 64,学习率从 1e-3 开始,用 Adam 优化器。损失函数用 BCE Loss,因为标签是 0 或 1。

3.2 训练流程与验证集划分

股票数据不能随机划分训练集和验证集,因为时间序列有先后顺序。常见做法是按时间切分:前 70% 训练,中间 15% 验证,最后 15% 测试。下面是一个训练循环的骨架。

from torch.utils.data import DataLoader, TensorDataset def train_model(model, X_train, y_train, X_val, y_val, epochs=50, lr=1e-3): train_ds = TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) val_ds = TensorDataset(torch.FloatTensor(X_val), torch.FloatTensor(y_val)) train_loader = DataLoader(train_ds, batch_size=64, shuffle=False) val_loader = DataLoader(val_ds, batch_size=64, shuffle=False) optimizer = torch.optim.Adam(model.parameters(), lr=lr) criterion = nn.BCELoss() for epoch in range(epochs): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb).squeeze() loss = criterion(pred, yb) loss.backward() optimizer.step() model.eval() val_loss = 0 with torch.no_grad(): for xb, yb in val_loader: pred = model(xb).squeeze() val_loss += criterion(pred, yb).item() print(f"Epoch {epoch}, val_loss {val_loss:.4f}") return model

逻辑说明:shuffle=False在时间序列里很重要,打乱会破坏时序依赖。训练集和验证集分别用 DataLoader 包装,每轮先训练再验证。BCELoss适合二分类,如果改成回归就用 MSELoss。参数上,epochs取 50 到 100,早停可以看验证集损失连续几轮不降就停。学习率太大容易震荡,太小收敛慢,1e-3 是安全起点。

注意:验证集损失下降但测试集效果差,通常是过拟合。可以加 dropout、减小 hidden_dim、增加数据量,或者改用更简单的模型。

4. 回测与可视化:把模型输出变成能看的图

4.1 回测框架怎么搭

模型输出概率后,需要转成交易信号再回测。常见规则是概率大于 0.6 买入,小于 0.4 卖出,中间持有。回测要算累计收益、最大回撤、夏普比率。下面是一个简化回测函数。

def backtest(df, prob_col="prob", threshold=0.6): df = df.copy() df["signal"] = 0 df.loc[df[prob_col] > threshold, "signal"] = 1 df.loc[df[prob_col] < 1 - threshold, "signal"] = -1 df["strategy_ret"] = df["signal"].shift(1) * df["ret"] df["cum_ret"] = (1 + df["strategy_ret"]).cumprod() df["benchmark"] = (1 + df["ret"]).cumprod() return df

逻辑说明:signal.shift(1)表示今天收盘后出信号,明天开盘执行,避免未来函数。cumprod()算累计收益。参数上,threshold越高信号越少但越保守,0.6 是常见起点。回测结果要和基准对比,如果策略跑不赢买入持有,说明模型没有超额收益。

4.2 可视化:用 ECharts 或 Matplotlib 画收益曲线

可视化是这套系统的门面。常见做法是用 Matplotlib 画收益曲线和回撤,或者用 ECharts 做交互式大屏。下面是一个 Matplotlib 画累计收益和回撤的例子。

import matplotlib.pyplot as plt def plot_backtest(df): fig, axes = plt.subplots(2, 1, figsize=(12, 8)) axes[0].plot(df["date"], df["cum_ret"], label="Strategy") axes[0].plot(df["date"], df["benchmark"], label="Benchmark") axes[0].legend() axes[0].set_title("Cumulative Return") drawdown = df["cum_ret"] / df["cum_ret"].cummax() - 1 axes[1].fill_between(df["date"], drawdown, 0, color="red", alpha=0.3) axes[1].set_title("Drawdown") plt.tight_layout() plt.savefig("backtest.png")

逻辑说明:上面画累计收益,下面画回撤。cummax()算历史最高点,除以最高点减一就是当前回撤。参数上,figsize控制图片大小,alpha控制填充透明度。如果要放到网页上,可以用 pyecharts 生成 HTML,支持缩放和悬停查看数值。

提示:可视化不只是画图,还要能回答「策略在哪些时间段亏钱」「回撤最深多少」「和基准比谁强」。图里至少要有策略曲线、基准曲线和回撤区域。

5. 避坑与排查:股票量化系统最常见的 5 个翻车点

5.1 未来函数:回测收益虚高的头号原因

现象:回测累计收益高得离谱,实盘完全跑不出来。原因:特征或标签里用了未来数据,比如用全样本均值归一化、用当天收盘价算当天信号。解决:所有特征只用当前及历史数据,信号用shift(1)延迟一天执行,归一化参数只用训练集计算。

5.2 数据对齐错误:日期错位导致信号对不上

现象:模型预测和实际涨跌完全相反。原因:特征和标签的日期没有对齐,比如特征用了 T 日数据,标签却用了 T+1 日收盘价但没 shift。解决:统一用date列做 merge,检查每一行的特征日期和标签日期是否一致,打印几行样本人工核对。

5.3 过拟合:训练集准确率 90%,测试集 50%

现象:训练损失一直降,验证损失先降后升。原因:模型太复杂、数据太少、特征太多。解决:减小 hidden_dim 和 num_layers,加 dropout,减少特征数量,增加训练数据。早停也是有效手段,验证损失连续 5 轮不降就停。

5.4 类别不平衡:涨跌样本比例悬殊

现象:模型全预测涨或全预测跌,准确率看着不低但没意义。原因:A 股长期看涨,正样本远多于负样本。解决:用pos_weight给少数类加权,或者用 F1、AUC 代替准确率评估。重采样也可以,但时间序列里要小心破坏时序。

5.5 环境与依赖:PyTorch 和 akshare 版本冲突

现象:import torch报错,或者 akshare 拉数据返回空。原因:Python 版本、PyTorch 版本、akshare 版本不匹配。解决:用 conda 建独立环境,固定版本,比如 Python 3.9 + PyTorch 2.0 + akshare 最新。拉数据失败先检查网络和接口是否变更,akshare 接口经常更新。

注意:这五个坑里,未来函数和数据对齐是最隐蔽的,建议每次改完特征都打印几行样本,人工看一眼日期和数值。

6. 进阶技巧:用 walk-forward 验证和模型集成提升稳健性

6.1 walk-forward 验证:比单次切分更接近实盘

单次按时间切分训练测试,只能看一个时间段的表现。walk-forward 验证是滚动向前:用前 3 年训练,预测第 4 年;再用前 4 年训练,预测第 5 年,依次滚动。这样能看出模型在不同市场阶段是否稳定。

def walk_forward(df, train_years=3, test_years=1): results = [] start = df["date"].min().year end = df["date"].max().year for year in range(start + train_years, end - test_years + 1): train = df[df["date"].dt.year < year] test = df[df["date"].dt.year == year] # 这里调用训练和回测函数 results.append((year, train.shape[0], test.shape[0])) return results

逻辑说明:按年份滚动,训练集逐年扩大,测试集始终是下一年。参数上,train_years至少 3 年,否则模型学不到完整周期;test_years取 1 年,方便按年评估。walk-forward 的结果如果每年都跑不赢基准,说明模型没有稳定 alpha。

6.2 模型集成:LSTM + CNN + 简单规则

单个模型容易受随机种子影响,集成能降低方差。常见做法是训练 LSTM 和 CNN 两个模型,把输出概率平均,再叠加一个简单规则比如均线过滤。下面是一个集成预测的示例。

def ensemble_predict(lstm_prob, cnn_prob, ma_signal, w1=0.4, w2=0.4, w3=0.2): # ma_signal: 1 表示均线向上,0 表示向下 combined = w1 * lstm_prob + w2 * cnn_prob + w3 * ma_signal return (combined > 0.5).astype(int)

逻辑说明:w1、w2、w3是权重,加起来等于 1。LSTM 和 CNN 各占 0.4,规则占 0.2。参数可以按验证集表现调整,如果某个模型明显更强就加大权重。集成后信号更平滑,回撤通常更小,但收益也可能被拉低,需要权衡。

6.3 一个具体技巧:用概率阈值动态调整仓位

固定阈值 0.6 买入太死板,可以根据概率大小动态调仓位。比如概率 0.6 到 0.7 半仓,0.7 到 0.8 七成仓,0.8 以上满仓。这样在模型信心高时加大暴露,信心低时减少暴露。

def dynamic_position(prob): if prob < 0.5: return 0 elif prob < 0.6: return 0.3 elif prob < 0.7: return 0.5 elif prob < 0.8: return 0.7 else: return 1.0

逻辑说明:概率越低仓位越轻,概率越高仓位越重。参数上,分档和仓位比例可以按回测调优,但不要过度拟合,档位太多容易在实盘失效。我一般用 4 到 5 档,简单可解释。

这套系统从数据到模型到可视化,每一层都有坑,但每一层也都有成熟做法。我自己踩过最深的坑是未来函数,回测收益翻倍,实盘一塌糊涂,后来每次改特征都强制打印样本核对日期。希望帮到你。

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

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

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

立即咨询