简介:这份资源是一套面向金融量化初学者与进阶开发者的A股走势预测实战项目,围绕机器学习算法与真实市场数据展开,帮助读者理解如何用数据驱动方式辅助投资决策。压缩包共12个文件,以6个ipynb交互式笔记、3个csv行情数据集、2个py脚本和1份md运行说明为主,整体约2.41MB,涵盖数据获取、特征工程、模型训练与回测等环节。内容预览显示,项目涉及K线数据入库、小市值结合盈利指标过滤策略、FFT滤波、单层与改进LSTM模型以及sklearn单票回测等实践,并针对A股T+1、涨跌停等特性做了专门处理。已有521人学习下载,读者可据此掌握从数据预处理、特征提取到模型评估的完整流程,并借助运行说明快速复现实验,积累可迁移的量化建模与排错经验。
1. 拆开这个 A 股机器学习预测包:它到底能跑出什么结果
很多人第一次看到「基于机器学习算法实现对A股股票走势预测系统」这种标题,第一反应是又一个套壳的荐股工具。我把压缩包解开之后发现不是——里面是一整套从数据落库到模型回测的 Jupyter Notebook 工作流,配合几个单票 CSV 样本,能让你在本地把「K线数据入库 → 特征工程 → LSTM 训练 → sklearn 回测」这条链路完整跑一遍。它解决的不是「明天买哪只」,而是「怎么用机器学习的方式去验证一个交易想法」。适合两类人:一是想入门量化、但被各种框架劝退的 Python 开发者;二是手里有策略想法、缺一套可改可跑的代码骨架的从业者。数据集和源码都在包里,运行说明也在,门槛比想象中低。
2. 数据管道与特征工程:从 K 线落库到 newfeature.py 的加工链
这个包最值钱的地方不是模型,是前面那条数据管道。很多人做股票预测翻车,不是模型不行,是数据从第一步就脏了。包里getstockcsv.py、保存K线数据到数据库.ipynb、newfeature.py三个文件串起来,正好覆盖了「拉数据 → 存起来 → 加工成特征」这条完整链路。
2.1 getstockcsv.py 与 K 线落库:先把数据源固定住
getstockcsv.py的作用是从行情接口拉取单只股票的日线数据并落地成 CSV。包里自带的600256.csv、002475.csv、xmm.csv就是它的产物样本。常见做法是用 akshare 或 tushare 这类库拉数据,核心逻辑是「按股票代码 + 日期区间请求 → 整理成标准列 → 写文件」。
# getstockcsv.py 核心逻辑示意 import akshare as ak import pandas as pd def fetch_daily(code: str, start: str, end: str, out_path: str): # 拉取 A 股日线,adjust="qfq" 表示前复权 df = ak.stock_zh_a_hist(symbol=code, period="daily", start_date=start, end_date=end, adjust="qfq") # 统一列名,方便后续入库和特征计算 df = df.rename(columns={ "日期": "date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume", "成交额": "amount", "换手率": "turnover" }) df["date"] = pd.to_datetime(df["date"]) df = df.sort_values("date").reset_index(drop=True) df.to_csv(out_path, index=False, encoding="utf-8-sig") return df if __name__ == "__main__": fetch_daily("600256", "20180101", "20240101", "600256.csv")这段代码里几个参数值得说清楚。adjust="qfq"是前复权,做历史回测必须用复权价,否则除权除息那天会出现一个假跳空,模型会把它当成真实波动学进去。period="daily"是日线,如果你想做日内可以改成分钟级,但 A 股 T+1,日线才是主流。输出用utf-8-sig是为了 Excel 打开不乱码,这个细节很多人不注意,后面用 pandas 读的时候如果编码不对会直接报错。
保存K线数据到数据库.ipynb则是把 CSV 进一步写进数据库。常见做法是 SQLite 或 MySQL,SQLite 更省事,一个文件搞定,适合本地复现。核心是建一张以(code, date)为联合主键的表,重复写入时用INSERT OR REPLACE避免脏数据堆积。
# 保存K线数据到数据库.ipynb 关键片段 import sqlite3 import pandas as pd conn = sqlite3.connect("stock.db") df = pd.read_csv("600256.csv", parse_dates=["date"]) # 建表:code + date 联合主键,防止重复插入 conn.execute(""" CREATE TABLE IF NOT EXISTS kline ( code TEXT, date TEXT, open REAL, close REAL, high REAL, low REAL, volume REAL, turnover REAL, PRIMARY KEY (code, date) ) """) # 批量写入,冲突则替换 df["code"] = "600256" df.to_sql("kline_tmp", conn, if_exists="replace", index=False) conn.execute(""" INSERT OR REPLACE INTO kline SELECT code, date(date), open, close, high, low, volume, turnover FROM kline_tmp """) conn.commit()这里有个容易忽略的点:date存成 TEXT 而不是 DATETIME,是为了跨库兼容,SQLite 对日期类型支持本来就弱,存字符串反而省心。INSERT OR REPLACE依赖主键冲突判断,所以建表时联合主键不能少,否则每次跑都追加一遍,数据量翻倍,回测结果直接失真。
2.2 newfeature.py 的特征构造:把 OHLCV 变成模型能吃的信号
原始 OHLCV 对模型来说信息密度太低,newfeature.py干的就是把价格序列转成有预测意义的特征。股票预测里常见的特征分几类:动量类(收益率、均线偏离)、波动类(振幅、ATR)、量能类(量比、换手率变化)、以及技术指标类(MACD、RSI)。
# newfeature.py 特征构造示意 import pandas as pd import numpy as np def build_features(df: pd.DataFrame) -> pd.DataFrame: df = df.copy() # 1. 对数收益率,比简单收益率更接近正态 df["log_ret"] = np.log(df["close"] / df["close"].shift(1)) # 2. 多周期均线偏离,衡量价格相对位置 for w in [5, 10, 20, 60]: df[f"ma_dev_{w}"] = df["close"] / df["close"].rolling(w).mean() - 1 # 3. 波动率:20 日收益率标准差 df["vol_20"] = df["log_ret"].rolling(20).std() # 4. 量比:当日成交量 / 5 日均量 df["vol_ratio"] = df["volume"] / df["volume"].rolling(5).mean() # 5. 振幅 df["amplitude"] = (df["high"] - df["low"]) / df["close"].shift(1) # 6. 预测目标:未来 1 日涨跌方向(分类)或收益率(回归) df["target_cls"] = (df["close"].shift(-1) > df["close"]).astype(int) df["target_reg"] = df["close"].shift(-1) / df["close"] - 1 return df.dropna().reset_index(drop=True)参数上,均线窗口选 5/10/20/60 是 A 股常用的短中长组合,分别对应一周、两周、一月、一季。log_ret用对数是为了让收益率可加,多日累计收益直接求和就行。target_cls用shift(-1)构造未来一日方向,注意这会产生最后一行 NaN,dropna会把它去掉,但这也意味着你损失了最新一天的数据——预测当天的时候要单独处理,不能直接喂进训练集。
fft-filter.ipynb是个有意思的补充,它用傅里叶变换对价格序列做滤波,试图剥离高频噪声。这个思路在信号处理里很常见,但用在股票上要小心:滤波会引入未来信息(因为 FFT 是全局变换),如果直接拿滤波后的序列做特征,回测会虚高。正确做法是只用历史窗口做滚动 FFT,或者干脆把它当探索性分析,别进生产特征。
提示:特征构造阶段最容易犯的错是「未来函数」。任何用到
shift(-n)的列都只能作为标签,不能作为特征。检查方法是把特征列和目标列分开存,训练时只取特征列。
3. LSTM 单票建模:singlelstm.ipynb 的输入形状与训练参数
包里有两个 LSTM 相关的 notebook:singlelstm.ipynb和singlelstm2.ipynb。从命名看是单票建模的两个版本,大概率一个是基础版、一个是调优版。LSTM 处理时间序列的优势在于它能记住长距离依赖,但股票数据信噪比极低,LSTM 很容易过拟合,所以参数设置和验证方式比模型结构本身更关键。
3.1 滑动窗口构造与输入张量形状
LSTM 的输入是三维张量:(样本数, 时间步长, 特征数)。假设你用 20 天窗口预测第 21 天方向,特征有 10 个,那每个样本就是(20, 10)。构造逻辑如下:
# singlelstm.ipynb 滑动窗口构造 import numpy as np def make_sequences(features: np.ndarray, labels: np.ndarray, window: int = 20): X, y = [], [] for i in range(window, len(features)): # 取过去 window 天作为输入 X.append(features[i - window:i]) # 第 i 天作为预测目标 y.append(labels[i]) return np.array(X), np.array(y) # features 形状 (N, F),labels 形状 (N,) X, y = make_sequences(features, labels, window=20) print(X.shape) # (N-20, 20, F)window=20对应一个月交易日,是股票预测里比较常用的窗口。窗口太短模型看不到趋势,太长则样本数骤减且引入过多噪声。这里有个血泪经验:构造完序列后一定要检查X和y的时间对齐,X[i]的最后一天和y[i]必须是相邻的,否则模型学的是错位的关系,回测看着好、实盘全废。
3.2 训练参数与过拟合控制
LSTM 在股票数据上的标准配置一般是:1-2 层、隐藏单元 32-128、dropout 0.2-0.5、早停 patience 10-20。下面是一个可跑的 Keras 版本:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model = Sequential([ # 第一层 LSTM,return_sequences=True 传给下一层 LSTM(64, return_sequences=True, input_shape=(20, X.shape[2])), Dropout(0.3), # 第二层 LSTM,只取最后时间步输出 LSTM(32), Dropout(0.3), # 二分类输出 Dense(1, activation="sigmoid") ]) model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"]) # 早停:验证损失 15 轮不降就停,并恢复最优权重 es = EarlyStopping(monitor="val_loss", patience=15, restore_best_weights=True) history = model.fit(X_train, y_train, validation_split=0.2, epochs=100, batch_size=32, callbacks=[es], verbose=1)参数逐个说:LSTM(64)的 64 是隐藏单元数,太小欠拟合、太大过拟合,单票数据量通常几千条,64 够用。Dropout(0.3)是随机丢弃 30% 神经元,股票数据必须加,不加基本必过拟合。patience=15是容忍 15 轮不改善,太小会早停错过最优、太大浪费时间。validation_split=0.2从训练集尾部切 20% 做验证,注意这里不能随机切,时间序列必须按时间顺序切,否则验证集里混入了未来数据。
singlelstm2.ipynb大概率是在这个基础上做了调整,可能是加了更多特征、改了窗口长度、或者换成了回归任务预测收益率。你可以两个都跑一遍对比验证集准确率,但别只看准确率——股票二分类里如果涨跌样本比例是 55:45,模型全猜涨也有 55% 准确率,所以要看 AUC 或者混淆矩阵。
注意:LSTM 训练结果对随机种子敏感,同一份数据跑两次可能差好几个百分点。建议固定
np.random.seed和tf.random.set_seed,并且用多次运行的均值来评估,别拿单次结果下结论。
4. sklearn 回测与策略验证:从模型输出到交易信号
模型训练完只是第一步,能不能赚钱要看回测。包里sklearn机器学习单票回测.ipynb和验证小市值结合盈利指标过滤策略.ipynb是两个不同层面的验证:前者是单票的模型信号回测,后者是带基本面过滤的策略验证。
4.1 sklearn 单票回测框架
sklearn机器学习单票回测.ipynb大概率是用 sklearn 的模型(随机森林、梯度提升之类)做基准,对比 LSTM 的效果。sklearn 模型在表格特征上往往不输深度学习,而且训练快、可解释性强。回测的核心逻辑是:模型输出信号 → 按信号买卖 → 计算收益曲线 → 对比基准。
# sklearn 单票回测核心逻辑 import pandas as pd import numpy as np from sklearn.ensemble import RandomForestClassifier # 特征与标签 X = df[feature_cols].values y = df["target_cls"].values # 按时间切分,前 80% 训练,后 20% 回测 split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] clf = RandomForestClassifier(n_estimators=200, max_depth=5, min_samples_leaf=50, random_state=42) clf.fit(X_train, y_train) # 预测信号:1 做多,0 空仓 signal = clf.predict(X_test) # 策略收益 = 信号 * 次日收益率 strategy_ret = signal * df["target_reg"].values[split:] # 基准收益 = 一直持有 benchmark_ret = df["target_reg"].values[split:] # 累计净值 cum_strategy = (1 + strategy_ret).cumprod() cum_benchmark = (1 + benchmark_ret).cumprod()参数上,n_estimators=200是树的数量,200 棵在几千条样本上足够稳定。max_depth=5限制树深,防止过拟合,股票特征噪声大,深树基本是在记噪声。min_samples_leaf=50要求每个叶子至少 50 个样本,进一步压制过拟合。这三个参数是 sklearn 在金融数据上的常用保守配置。
回测里最关键的是strategy_ret = signal * target_reg,这里假设信号当天收盘生成、次日开盘执行,所以用次日收益率。如果你用当天收益率,就是未来函数,回测会好得离谱。另外交易成本没算进去,A 股双边大概千分之一到千分之三,高频交易的话成本会吃掉大部分利润,回测时至少扣个千分之一。
4.2 小市值 + 盈利指标过滤策略的验证思路
验证小市值结合盈利指标过滤策略.ipynb走的是另一条路:不单纯靠技术面模型,而是先用基本面因子筛股票池,再在池子里做技术面择时。小市值因子在 A 股历史上长期有效,但波动极大,配合盈利指标(比如 ROE、净利润增速)过滤掉垃圾股,能降低尾部风险。
这个 notebook 的验证逻辑通常是:构造股票池 → 按市值排序取最小的 N 只 → 再用盈利指标筛掉亏损的 → 等权买入 → 定期调仓 → 计算组合收益。关键参数是调仓频率(月度或季度)、持仓数量(20-50 只)、以及盈利指标的阈值(比如 ROE > 10%)。
# 小市值 + 盈利过滤策略骨架 def filter_universe(df_fundamental: pd.DataFrame, top_n: int = 30): # 先按市值升序,取最小的 top_n 只 df = df_fundamental.sort_values("market_cap").head(top_n * 2) # 再用 ROE 过滤,保留盈利为正且 ROE 达标的 df = df[df["roe"] > 0.08] # 最终取 top_n 只 return df.sort_values("market_cap").head(top_n) # 月度调仓回测 portfolio = filter_universe(fundamental_data) monthly_ret = portfolio["next_month_ret"].mean()这里top_n * 2是先扩大候选池再过滤,避免过滤后数量不够。roe > 0.08是 8% 的 ROE 门槛,这个值可以调,太高会筛掉太多股票导致分散度不够,太低则过滤效果不明显。月度调仓是平衡交易成本和因子衰减的常见选择,季度调仓成本更低但反应慢。
提示:小市值策略在 A 股有风格切换风险,2017 年和 2024 年初都出现过小市值集体杀跌。回测时一定要看最大回撤和回撤持续时间,不能只看年化收益。如果最大回撤超过 40%,实盘很难拿住。
5. 避坑与排查:这套代码跑不起来时先查这五处
这个包里的 notebook 不少,依赖库版本、数据接口、路径设置都可能出问题。下面是我实际跑这类项目时最常遇到的五个坑,按「现象 → 原因 → 解决」整理。
现象一:getstockcsv.py报接口连接失败或返回空 DataFrame。原因通常是行情接口变更或需要 token,akshare 这类库的接口经常调整。解决方法是先单独跑一行ak.stock_zh_a_hist(symbol="600256", period="daily", start_date="20240101", end_date="20240201")看返回,如果报错就升级库版本pip install --upgrade akshare,如果返回空就检查股票代码格式和日期区间是否在交易日内。
现象二:LSTM 训练时 loss 不下降或直接变 NaN。原因多半是特征没做标准化,OHLCV 的数值范围差异太大,成交量可能是百万级、收益率是小数级,直接喂进 LSTM 梯度会炸。解决方法是在构造序列之前对所有特征做 z-score 标准化,注意用训练集的均值和标准差去标准化测试集,不能全量标准化。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() # 只用训练集 fit X_train_scaled = scaler.fit_transform(X_train.reshape(-1, X_train.shape[-1])) # 测试集用同样的 scaler transform X_test_scaled = scaler.transform(X_test.reshape(-1, X_test.shape[-1]))现象三:回测收益曲线好得不真实,年化几百个百分点。这是典型的未来函数,检查三处:特征里有没有用到shift(-n)、标准化有没有用全量数据、回测收益有没有用当天而不是次日。最常见的隐蔽未来函数是滚动窗口计算指标时用了center=True,这会让窗口包含未来数据。
现象四:notebook 之间路径不一致,读不到 CSV 或数据库。包里文件是平铺的,但 notebook 默认工作目录可能是子目录。解决方法是在每个 notebook 开头统一设置工作目录,或者用绝对路径。我一般会在开头加一段import os; os.chdir(os.path.dirname(os.path.abspath("__file__"))),但 notebook 里__file__不一定可用,更稳的是手动os.chdir("你的项目根目录")。
现象五:数据库写入重复,回测样本量翻倍。原因是建表时没设联合主键,或者用了to_sql(if_exists="append")反复追加。解决方法是建表时明确PRIMARY KEY (code, date),写入用INSERT OR REPLACE,并且每次跑之前先SELECT COUNT(*)确认行数是否合理。一只股票 6 年日线大概 1500 行左右,如果查出 3000 行就是重复了。
6. 把单票模型扩展成多票轮动:一个可复用的改造技巧
单票模型跑通之后,下一步自然是多票。但直接把singlelstm.ipynb复制 N 份分别训练,管理起来是灾难。我一般会把它改造成「统一特征管道 + 按票分组训练 + 信号汇总排序」的结构,这样加新股票只需要往列表里加代码。
具体做法是:先用getstockcsv.py批量拉取一篮子股票数据,统一走newfeature.py构造特征,然后按code分组,每组单独训练一个模型(或者训练一个带股票 ID 嵌入的共享模型)。预测时输出每只股票次日上涨概率,按概率排序取 top K 等权买入,定期调仓。
# 多票轮动骨架 codes = ["600256", "002475", "000001", "600519"] all_signals = [] for code in codes: df = load_and_build_features(code) # 复用单票管道 X, y = make_sequences(df[feature_cols].values, df["target_cls"].values) model = train_lstm(X, y) # 复用单票训练函数 prob = model.predict(X[-1:])[0, 0] # 最新一天的上涨概率 all_signals.append({"code": code, "prob": prob}) # 按概率排序,取 top 2 等权 signals = pd.DataFrame(all_signals).sort_values("prob", ascending=False) top_k = signals.head(2) weights = {row["code"]: 0.5 for _, row in top_k.iterrows()}这个改造的关键在于特征管道必须统一,所有股票用同一套特征列和同样的标准化参数,否则模型之间不可比。另外多票训练时样本量变大,LSTM 的隐藏单元可以适当增加,但 dropout 也要同步加大。回测时要注意股票之间的相关性,同行业股票信号可能高度一致,分散效果会打折扣,最好在选股时加一个行业分散约束。
验证多票轮动是否有效,不能只看组合收益,还要看信号 IC(信息系数)——即预测概率和实际收益的秩相关系数。IC 均值在 0.03 以上就算有微弱预测力,0.05 以上算不错,但 A 股日频 IC 能稳定在 0.03 已经很难了。如果 IC 接近 0 甚至为负,说明模型没学到东西,回去检查特征和标签对齐。
从那以后我每次跑这类股票预测项目,都会先把数据管道单独跑一遍、确认行数和日期范围无误,再动模型。模型可以换、参数可以调,但数据错了后面全白搭。希望帮到你。
本文还有配套的精品资源,点击获取