
简介这份资源是一套面向金融量化初学者与投资者的A股走势预测实战项目围绕机器学习算法与历史行情数据展开帮助读者用数据驱动方式理解股票趋势判断。压缩包共12个文件约2.41MB包含6个ipynb交互式笔记、3个csv行情数据集、2个py脚本和1份md运行说明覆盖数据获取、特征工程、模型训练与回测等环节。内容涉及K线数据入库、小市值结合盈利指标过滤策略、FFT滤波、单层与改进LSTM建模以及基于sklearn的单票回测并针对A股T1与涨跌停等特性做了处理。已有521人学习下载适合希望把机器学习落地到金融场景、需要完整代码与数据对照练习的读者可据此复现预测流程、理解特征提取与模型评估思路并在此基础上调整策略与优化模型。1. 从一份带数据集和运行说明的 A 股走势预测源码说起很多人第一次拿到「基于机器学习算法实现对A股股票走势预测系统源码数据集运行说明.zip」这类工程包第一反应是解压、装依赖、跑main.py然后盯着准确率数字发愣。我当年也这样直到发现模型在训练集上 92%、一到实盘就接近抛硬币才意识到问题不在算法而在数据对齐和标签定义。这个标题真正要解决的是用可复现的机器学习流程把 A 股日线行情转成监督学习样本训练一个能给出涨跌方向的分类器并配一套能跑通的源码、数据集和运行说明。它适合想入门量化、做课程设计、或者想验证某个特征工程思路的开发者不适合指望它直接下单赚钱的人。下面我按自己踩过的路径把这份工程从数据到推理拆开讲。2. 数据准备把 A 股日线变成模型能吃的样本2.1 数据集里通常有什么以及你必须先检查的三列这类工程包附带的数据集常见格式是 CSV字段一般包括date、code、open、high、low、close、volume、amount有的还会带pct_chg、turnover_rate。拿到手先别急着喂模型用 pandas 做三件事看日期范围是否连续、看股票代码是否被混在一起、看复权状态是否一致。A 股有除权除息如果一份数据里前复权和不复权混着模型学到的就是跳空假信号。import pandas as pd df pd.read_csv(data/stock_daily.csv, parse_dates[date]) # 检查每只股票的交易日数量少于 250 天的直接剔除 counts df.groupby(code)[date].count() valid_codes counts[counts 250].index df df[df[code].isin(valid_codes)].copy() # 按股票和日期排序避免后续 shift 错位 df.sort_values([code, date], inplaceTrue) df[ret] df.groupby(code)[close].pct_change() print(df.groupby(code)[ret].describe().head())逻辑说明groupby(code)保证每只股票独立计算收益率pct_change()得到日收益率。参数上250 天是我一般会设的最低门槛对应一年交易日太短的股票样本噪声大。如果数据集里没有复权字段至少要用close的收益率而不是价格绝对值这样能部分抵消除权影响。2.2 标签怎么定义未来 N 日涨跌比当日涨跌更稳很多源码默认用「明天收盘比今天收盘涨」作为标签这在 A 股 T1 和涨跌停制度下会引入大量不可交易样本。更稳的做法是定义未来 5 日收益率是否超过某个阈值比如 2%。这样标签更接近实际持仓周期也能过滤掉微小波动。HORIZON 5 THRESH 0.02 df[future_ret] df.groupby(code)[close].shift(-HORIZON) / df[close] - 1 df[label] (df[future_ret] THRESH).astype(int) # 最后 HORIZON 行没有未来数据必须删掉 df df.dropna(subset[future_ret]) print(df[label].value_counts(normalizeTrue))参数说明HORIZON5对应一周持仓THRESH0.02是 2% 的收益门槛。如果标签比例严重失衡比如正样本低于 20%可以调低阈值或改用三分类。注意shift(-HORIZON)是按股票分组后的未来值不能直接对整表 shift否则会跨股票串行。2.3 特征工程别把未来函数混进来常见特征包括过去 5/10/20 日收益率、成交量变化率、均线偏离度、RSI、MACD。关键是所有特征只能用到当日及之前的数据。我见过有人用df[close].rolling(20).mean()算均线但没加shift(1)结果当日收盘价参与了当日均线计算模型在训练集上虚高。df[ma5] df.groupby(code)[close].transform(lambda x: x.rolling(5).mean()) df[ma20] df.groupby(code)[close].transform(lambda x: x.rolling(20).mean()) df[ma_bias] df[close] / df[ma20] - 1 df[vol_chg] df.groupby(code)[volume].pct_change() # 所有特征再整体 shift(1)确保预测时只用到昨天及以前 feature_cols [ma_bias, vol_chg, ret] df[feature_cols] df.groupby(code)[feature_cols].shift(1) df df.dropna(subsetfeature_cols)逻辑说明transform保证按股票分组计算shift(1)把特征整体后移一天避免用当日信息预测当日标签。参数上均线窗口 5 和 20 是常见短中期组合成交量变化用pct_change比绝对量更稳定。这一步做完数据才真正能进模型。3. 模型训练从逻辑回归到 LightGBM 的选型与调参3.1 为什么我优先用树模型而不是 LSTMA 股日线数据信噪比低样本量通常几万到几十万条LSTM 这类序列模型容易过拟合训练慢且调参玄学。LightGBM 或 XGBoost 在表格特征上更稳训练快特征重要性可解释。如果工程包里用的是 LSTM你可以先跑通但建议同时用 LightGBM 做基线对比。常见做法是逻辑回归看线性可分性随机森林看非线性LightGBM 做最终模型。import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit # 按时间切分不能随机打乱 train df[df[date] 2022-01-01] test df[df[date] 2022-01-01] X_train, y_train train[feature_cols], train[label] X_test, y_test test[feature_cols], test[label] model lgb.LGBMClassifier( n_estimators300, learning_rate0.05, max_depth5, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train) print(model.score(X_test, y_test))参数说明n_estimators300配合learning_rate0.05是保守组合防止过拟合max_depth5限制树深A 股特征少深树容易记住噪声subsample和colsample_bytree做行和列采样。时间切分必须用TimeSeriesSplit或固定日期切随机切分会导致未来数据泄露准确率虚高。3.2 类别不平衡与阈值调整如果正样本比例只有 30% 左右模型会偏向预测跌。可以用scale_pos_weight或class_weightbalanced但更直接的是调整预测阈值。默认 0.5 不一定最优用验证集画 PR 曲线找最佳阈值。from sklearn.metrics import precision_recall_curve proba model.predict_proba(X_test)[:, 1] precision, recall, thresholds precision_recall_curve(y_test, proba) f1 2 * precision * recall / (precision recall 1e-8) best_thresh thresholds[f1.argmax()] print(best threshold:, best_thresh) pred (proba best_thresh).astype(int)逻辑说明predict_proba输出正类概率precision_recall_curve给出不同阈值下的精确率和召回率取 F1 最大对应的阈值。参数上如果更看重召回可以手动调低阈值但要注意交易成本。这一步能明显改善实盘表现比单纯调模型参数更有效。3.3 回测验证别只看准确率准确率 55% 不代表能赚钱还要看盈亏比和最大回撤。简单做法是当模型预测涨且概率超过阈值时买入持有 HORIZON 天后卖出统计累计收益。用backtrader可以做多股回测但工程包通常只给信号你可以先用 pandas 算净值曲线。test test.copy() test[signal] (proba best_thresh).astype(int) test[strategy_ret] test[signal] * test[future_ret] nav (1 test.groupby(date)[strategy_ret].mean()).cumprod() print(nav.iloc[-1])参数说明future_ret是之前定义的未来 5 日收益signal为 1 时持有。这里用等权平均模拟组合实际还要考虑手续费和滑点。如果净值曲线在 2022 年单边下跌时回撤超过 30%说明模型没有择时能力需要加止损或市场状态过滤。4. 避坑与排查源码跑不通时先看这五条4.1 现象训练集准确率 95%测试集 50%原因特征里混入了未来数据最常见的是均线没 shift或者标签用了当日收盘后信息。解决逐列检查特征计算是否只用到date及之前的数据把所有滚动计算加shift(1)并用时间切分验证。4.2 现象KeyError: code或分组后行数对不上原因数据集里股票代码列名可能是stock_code、symbol或ts_code不同来源不统一。解决先print(df.columns)统一重命名为code再做groupby。如果代码列有空格或大小写不一致用.str.strip().str.upper()清洗。4.3 现象LightGBM 报错Do not support special JSON characters in feature name原因特征名里有中文或特殊符号。解决训练前把feature_cols重命名为英文比如ma_bias、vol_chg或者用model.fit(X.values, y)传数组而不是 DataFrame。4.4 现象回测净值曲线一路向上实盘却亏原因回测用了未来函数或者没有考虑涨跌停无法成交。解决检查signal是否在收盘后生成、次日开盘才能买入涨停日买入信号要剔除。另外手续费按万分之三、印花税千分之一估算滑点按 0.1% 加进去。4.5 现象运行说明里的命令执行后缺包原因requirements.txt没锁版本或者用了pip install -r但网络源不对。解决先pip install pandas numpy scikit-learn lightgbm再按报错逐个补。如果说明里写python main.py但实际入口是train.py直接看目录结构找if __name__ __main__。5. 进阶技巧用滚动训练和特征筛选把模型稳住5.1 滚动窗口训练比一次性训练更贴近实盘A 股风格切换快用 2015 到 2020 年训练、2021 年测试模型可能学到的是旧规律。我一般用滚动窗口每次用过去 3 年数据训练预测未来 1 个月然后窗口前移。这样模型能跟上市场变化但计算量会大几倍。dates sorted(df[date].unique()) start_idx 750 # 约 3 年交易日 step 20 # 每月滚动一次 results [] for i in range(start_idx, len(dates) - step, step): train_end dates[i] test_end dates[min(i step, len(dates) - 1)] train df[df[date] train_end] test df[(df[date] train_end) (df[date] test_end)] if len(test) 0: continue model lgb.LGBMClassifier(n_estimators200, learning_rate0.05, max_depth5) model.fit(train[feature_cols], train[label]) proba model.predict_proba(test[feature_cols])[:, 1] test test.copy() test[proba] proba results.append(test) rolling_result pd.concat(results) print(rolling_result.groupby(date)[proba].mean().tail())逻辑说明start_idx750保证首次训练有足够样本step20控制滚动频率。每次训练都重新拟合模型避免用未来数据。参数上如果股票数量多可以按股票分别滚动但计算时间会线性增加。这个技巧能显著降低过拟合代价是代码复杂度上升。5.2 特征重要性筛选砍掉噪声特征LightGBM 训练后可以输出feature_importances_把重要性低于阈值的特征删掉再训练。我一般保留前 10 到 15 个特征太多特征反而让模型学到噪声。下面是一个简单的筛选循环。import numpy as np importance model.feature_importances_ idx np.argsort(importance)[::-1] selected [feature_cols[i] for i in idx if importance[i] 10] print(selected features:, selected) # 用筛选后的特征重新训练 model2 lgb.LGBMClassifier(n_estimators300, learning_rate0.05, max_depth5) model2.fit(X_train[selected], y_train) print(model2.score(X_test[selected], y_test))参数说明importance[i] 10是经验阈值具体看特征数量如果只有 5 个特征可以设 0。筛选后如果准确率下降超过 2 个百分点说明砍多了要放宽阈值。这一步配合滚动训练能把模型从「训练集好看」拉到「测试集可用」。5.3 一个我常犯的错误用准确率选模型早期我总挑测试集准确率最高的模型后来发现准确率高的模型往往偏向预测多数类实盘收益反而差。现在我会同时看三个指标测试集 F1、滚动回测夏普、最大回撤。如果 F1 高但回撤大说明模型在极端行情下失效。希望帮到你少走我当年那段只看准确率的弯路。本文还有配套的精品资源点击获取