简介:一套基于机器学习算法的股票价格分析与预测源码包,面向计算机、人工智能、大数据、数学、电子信息等专业正在进行课程设计、期末大作业或毕业设计的学生,也适合对金融量化方向感兴趣、具备一定Python基础的学习者对照调试与二次开发。压缩包内共有2个文件,其中1个为Python主脚本,另1个为运行所需的依赖清单(txt),整体大小仅45KB,结构紧凑,便于快速浏览核心逻辑与安装环境。项目代码已经过严格调试,下载后即可直接运行;目前已有298人学习使用。通过研读这份脚本,可以了解股票数据读取、特征构造、模型训练与结果预测的基本流程,虽然体量小,但胜在完整清晰,适合作为课程入门或毕业设计的起点模板,在此基础上替换数据源或改进算法策略,即可拓展成更完整的量化分析实验。
1. 股价预测的机器学习源码,落点不在算法而在工程链路
“基于机器学习算法的股票市场股票价格的分析及预测源码.zip”这类压缩包在技术社区里一直有热度,下载的人一半想抄作业,另一半想看看别人怎么把机器学习算法落到金融时序上。先说结论:这个标题覆盖的是一条完整链路——数据采集、特征工程、算法训练、回测评估、源码工程化——模型本身只是其中一小块。真正的难点不在“用哪种机器学习算法”,而在标签怎么切、特征怎么构造、回测怎么防止前视偏差。打算复现同类项目的开发者,可以从下面几段内容里拿到可运行的最小代码骨架、每个环节的参数取舍,以及跑通之后才会意识到的坑。
2. 行情数据获取与特征工程:机器学习股票分析的第一步
2.1 行情数据源选择与本地化存储
做A股或美股的日线级预测,数据源常见有三类:免费开源接口(akshare、tushare、yfinance)、付费终端导出的CSV、自行维护的数据库快照。对这类“源码.zip”项目来说,最稳妥的做法是让代码同时支持CSV读取和在线拉取——演示和教学场景下,离线CSV能保证任何机器上结果可复现;在线接口适合验证时顺手拉取最新行情。
提示:在线拉取容易出现缺数、复权不一致等脏数据问题。下载之后先落一份本地快照,后续所有实验都基于快照运行,否则接口版本更新或网络异常会导致结果无法复现。
一个最小可用的行情数据集至少包含六个字段:date、open、high、low、close、volume。这里要特别区分复权方式:如果拿到的是未复权数据,除权除息日会出现价格跳空,技术指标会把这些跳空误判为真实行情;常见做法是先把全量历史数据统一处理为前复权,再去计算指标和标签。前复权的缺点是早期价格会随最新除权不断变化,所以原始未复权数据段要保留,供重算使用。
# data_loader.py import pandas as pd def load_price_frame(csv_path: str) -> pd.DataFrame: df = pd.read_csv(csv_path, parse_dates=['date']) df = df.sort_values('date').reset_index(drop=True) # pct_change 依赖时间升序,排序必须先于一切衍生计算 df['return_1d'] = df['close'].pct_change() return df.dropna(subset=['return_1d'])pct_change()计算相邻交易日简单收益率,天然跳过周末和非交易日,不需要手工补零。这里先排序再算收益,顺序不能换,否则收益率会被错误配对。
2.2 技术指标特征的构造与窗口参数
技术指标是这类项目中最常见的特征来源。我一般会从三个角度组织特征:价格衍生(收益率、动量、移动平均偏离)、成交量衍生(量比、换手率)、波动性衍生(滚动标准差、ATR 近似值)。
参数选择上,滚动窗口不要无脑用 20、60 这类“默认值”。先确定预测周期:预测未来 5 天,特征窗口至少 20 天以上;预测次日,5 到 10 天窗口更敏感。其次要警惕窗口重叠带来的样本依赖:相邻两行的滚动均值高度相似,模型看到的是重复模式,评估指标会偏乐观。
# feature_engineering.py import pandas as pd import numpy as np def build_features(df: pd.DataFrame) -> pd.DataFrame: out = df.copy() for w in [5, 10, 20]: out[f'ma_{w}'] = out['close'].rolling(w).mean() out[f'std_{w}'] = out['close'].rolling(w).std() out[f'momentum_{w}'] = out['close'] / out['close'].shift(w) - 1.0 out['volume_ratio'] = out['volume'] / out['volume'].rolling(20).mean() out = out.replace([np.inf, -np.inf], np.nan).dropna() return outshift(w)是关键操作:它保证第 t 天的动量只使用 t-w 到 t 的已知价格,不含未来信息。volume_ratio用 20 日平均量的比值表达“今天放量还是缩量”。全部计算完成后统一dropna(),会丢弃前 20 行,对几千行的历史数据影响可忽略。
同类型特征里最容易踩的坑是“指标计算顺序”——先做复权再做指标,和先做指标再做复权,结果完全不同。项目里要强制复权先行的流程,否则训练集和验证集之间会混入复权口径不一致的特征值。
2.3 标签构造与时序切分的硬约束
标签构造有两种路线。分类路线:预测未来 5 日涨跌方向,上涨超过 0.5% 记为 1,下跌低于 -0.5% 记为 0,中间小幅波动丢弃或单独归类;回归路线:直接预测未来 5 日收益率,再在后处理中按阈值转成买卖信号。分类任务的优点是评估直观、类别不平衡问题可以通过阈值调节;回归任务的优点是能直接对接仓位管理和止盈止损计算。
时间序列切分是这种项目“看起来能赚钱、一实盘就亏”的主要根源。train_test_split(random_state=42)在这里不可用——相邻交易日的特征高度相关,随机切分会让验证集中的样本在训练集里出现“近亲”,指标虚高 10 个百分点以上。
| 切分方式 | 是否可用 | 说明 |
|---|---|---|
| random_state 随机切分 | 不可用 | 相邻样本泄漏,评估结果虚高 |
| 按时间 8:2 一次切分 | 可用,但粗糙 | 单一测试段,无法覆盖牛熊切换 |
| 滚动前推(walk-forward) | 推荐 | 每段重新训练,最接近实盘节奏 |
在代码层面推荐走一个小的辅助函数:
# splitter.py def time_aware_split(df, train_ratio=0.7, valid_ratio=0.15): n = len(df) train_end = int(n * train_ratio) valid_end = int(n * (train_ratio + valid_ratio)) return (df.iloc[:train_end].copy(), df.iloc[train_end:valid_end].copy(), df.iloc[valid_end:].copy())注意这里返回三个副本,避免后续dropna()或特征变换时修改到原始 DataFrame。验证集用于模型选择和早停,测试集只在最终评估时使用一次。
3. 机器学习算法选型与训练:从线性基准到序列建模
3.1 线性回归:必须存在的下界基准
这类源码里最常见的算法是一上来就上 LSTM,这是一个方向性错误。正确步骤是先训练一个线性回归作为基线——线性模型在股价预测里通常表现平庸,但它的平庸是有参照价值的:如果线性模型测试集方向准确率接近随机水平,说明特征和标签之间线性关系很弱,此时换复杂模型必须警惕过拟合。
最小实现如下。特征用上一章的 feature_df,标签用未来 5 日收益率。
# train_linear.py from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_squared_error X_all = feat_df[feature_cols].values y_all = feat_df['future_5d_return'].values train_end = int(len(X_all) * 0.8) X_train, X_test = X_all[:train_end], X_all[train_end:] y_train, y_test = y_all[:train_end], y_all[train_end:] scaler = StandardScaler().fit(X_train) X_train = scaler.transform(X_train) X_test = scaler.transform(X_test) model = Ridge(alpha=10.0) model.fit(X_train, y_train) print('test_rmse:', mean_squared_error(y_test, model.predict(X_test), squared=False))Ridge的alpha承担两个职责:抑制技术指标之间的共线性;压低单个特征的极端权重。金融数据的信噪比低,alpha从 10 起步是常见做法,比较稳的取值区间在 10 到 100。注意StandardScaler只在训练集上fit,这是一个容易被忽视却至关重要的细节。
3.2 树模型与集成:随机森林和 XGBoost 的参数边界
线性模型跑完基线,下一步是随机森林或 XGBoost。随机森林的优势是几乎不用调参就能跑出一个可复现的分数,适合做“复杂模型的及格线”。XGBoost 的优势在于能捕捉交互效应,例如“量比超过 2 且 5 日动量为正”这类组合条件。
调参时我重点关注四个参数而不是全部参数:
n_estimators:500 到 1000 即可,更多树未必更好max_depth:3 到 5,股价预测不需要深树,深度超过 6 几乎必然过拟合learning_rate:0.01 到 0.05,金融数据用小学习率更稳subsample:0.8 左右,配合列采样共同控制随机性
# train_xgb.py import xgboost as xgb model = xgb.XGBRegressor( n_estimators=800, max_depth=4, learning_rate=0.02, subsample=0.8, colsample_bytree=0.8, early_stopping_rounds=50, verbosity=0 ) model.fit( X_train, y_train, eval_set=[(X_test, y_test)], verbose=False )early_stopping_rounds=50是最有效的一道过拟合闸门:当测试集损失连续 50 轮不再改善,训练就提前终止。这里容易犯一个错误——早停轮的评估集必须是按时间切分的验证集,不能是与训练集有重叠的随机样本。最终模型训练完成后,仍然要在从未参与早停判断的测试集上重新评估一次。
3.3 LSTM 与序列建模:窗口长度、步长和训练稳定性
LSTM 在日线级别预测中属于“锦上添花”的组件。大多数日线信号已经能被特征工程表达,LSTM 的边际价值在于捕捉最近 N 天的形态上下文,例如“连续缩量后放量”这种短时序模式。
实现 LSTM 之前要明确两个超参数。窗口长度seq_len:20 到 60 之间比较合理,对应一个月到季度级别的历史信息。采样步长stride:每 5 天取一个样本,能在保留序列特征的同时减少相邻样本的高度重叠,也大幅降低训练时间。
# train_lstm.py import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_sequences(X, y, seq_len=30, stride=5): xs, ys = [], [] for i in range(0, len(X) - seq_len, stride): xs.append(X[i:i + seq_len]) ys.append(y[i + seq_len]) return np.array(xs), np.array(ys) X_seq, y_seq = build_sequences(X_scaled, y, 30, 5) model = Sequential([ LSTM(64, return_sequences=True, input_shape=(30, X_scaled.shape[1])), Dropout(0.3), LSTM(32, return_sequences=False), Dropout(0.3), Dense(1) ]) model.compile(optimizer='adam', loss='mse')这里return_sequences=True让第一层 LSTM 输出完整的序列给第二层;第二层return_sequences=False只输出最后一个时间步的隐状态。Dropout(0.3)是防过拟合的主力,不是可选项。训练时保持numpy.random.seed和tensorflow.random.set_seed固定,否则每次运行结果差异很大,无法复现别人的实验。
LSTM 收敛困难时,先看前 10 个 epoch 的 loss 曲线:如果完全不下降,把学习率从1e-3降到3e-4;如果在某个水平震荡不再下降,说明容量够用,优先调 dropout 或减小单元数,而不是加层。
4. 回测与评估体系:让机器学习模型的预测经得起推敲
4.1 评估指标要分误差和方向两个视角
股价预测项目最常见的评估错误是只看 MSE 或 RMSE。这两个指标衡量预测值与真实值的绝对偏差,但对方向判断正确率不敏感——模型在 60% 的时间里方向正确,某一次大亏损就能把 MSE 拉得很高,让人误判模型无效。
正确做法是同时上报方向准确率(Directional Accuracy)。它的定义是:预测为正且实际为正、预测为负且实际为负的样本比例。对交易决策而言,方向准确率比 MSE 更贴近实际收益。
# evaluate.py import numpy as np from sklearn.metrics import mean_squared_error def evaluate_model(y_true, y_pred): mse = mean_squared_error(y_true, y_pred) direction = float((np.sign(y_true) == np.sign(y_pred)).mean()) # 简化信号:预测值为正则持多头,为负则空仓 strategy_returns = np.where(np.sign(y_pred) > 0, y_true, 0.0) cumulative = np.cumprod(1 + strategy_returns) return { 'mse': mse, 'direction_accuracy': direction, 'final_cumulative': cumulative[-1] }strategy_returns里使用了“非多即空仓”的简化规则。真实交易中还有手续费、滑点、涨跌停无法成交的限制,回测至少要把手续费按单边万二到千一加进成本,否则策略收益会被系统性高估。
4.2 向量化回测与交易成本建模
回测有循环逐日撮合和向量化两种写法。循环直观但是慢,向量化快且简洁,日线级别的策略用向量化完全足够。
# backtest.py import numpy as np import pandas as pd def simple_backtest(df: pd.DataFrame, signal_col: str, cost_rate=0.0005): df = df.copy() df['position'] = np.where(df[signal_col] > 0, 1, 0) df['position_change'] = df['position'].diff().fillna(0) df['gross_return'] = df['return_1d'] * df['position'] df['cost'] = df['position_change'].abs() * cost_rate df['net_return'] = df['gross_return'] - df['cost'] df['equity_curve'] = (1 + df['net_return']).cumprod() return dfposition_change记录仓位切换点,每次切换收一次手续费。return_1d表示“当天持有标的获得的收益率”,position使用前一交易日的预测信号,避免前视偏差。跑完回测除了看equity_curve的终值,还必须看最大回撤:一个回撤 20% 的策略与回撤 5% 的策略,即使最终收益相同,实际可交易性完全是两个等级。
4.3 过拟合的三道闸门
第一道是严格样本外检验:训练集、验证集、测试集按时间顺序切三份,测试集只使用一次。反复用测试集调参,会把它变成验证集,因此最后一段数据要在所有实验完成后才触碰。第二道是滚动验证:把数据按年分段,每次用前 3 年训练、下 1 年测试,逐年向外滚动。如果某一年结果特别差,通常不是模型的问题,而是市场风格切换,可以借此定位模型的能力边界。第三道是随机化标签测试:把训练集标签随机打乱后重新训练,理想的模型指标应当退化到随机水平。如果打乱标签后模型“依然优秀”,说明特征和标签之间并不存在你期望的因果关系,过拟合已经主导了结果。
5. 从源码.zip到可复用工程:目录组织、依赖管理与复现验证
5.1 一个可维护的股票预测项目目录长什么样
这类项目最普遍的硬伤不是模型不准,而是换一台机器就无法复现。拆开一个“源码.zip”,先看目录结构,再找配置文件。常见的可维护结构包含四个模块一层配置:data/ 存放原始行情和中间产物,src/ 放数据、特征、模型、评估四个脚本,根目录一个 YAML 配置文件加一个依赖清单。300 行代码全部堆在一个脚本里、没有任何函数划分的项目,通常只能在作者本机跑通。
requirements.txt建议锁小版本:pandas 1.5.x 与 2.x 在日期解析行为上存在差异,numpy 1.x 与 2.x 在部分数学函数上也有兼容性变化。先pip install -r requirements.txt,而不是缺什么装什么。
5.2 用 YAML 集中管理周期、特征和回测参数
data: csv_path: "data/raw/000001.csv" target_horizon: 5 features: windows: [5, 10, 20] model: name: "xgb" max_depth: 4 learning_rate: 0.02 n_estimators: 800 backtest: cost_rate: 0.0005 initial_cash: 100000target_horizon同时影响标签构造和回测持有周期,改这一个字段就能从“预测次日”切换成“预测周度”,特征窗口和回测逻辑不用动。cost_rate按单边收费,有人为了“看理想收益”把它设成 0,回测曲线立刻变好看,但这条路经不起实盘验证。一般建议底线设在 0.0003 到 0.0005。
5.3 复现过程中最常出现的两个报错
第一个报错是样本数量不一致。特征工程中shift()会引入 NaN,dropna()的时机和位置一旦和标签构造错开,特征矩阵和标签向量的长度就对不上,model.fit()会直接抛出 “Found input variables with inconsistent numbers of samples”。排查思路很简单:把特征矩阵和y放在同一个处理流程里,在统一的位置执行一次dropna()。
第二个报错是缩放器泄漏的静默问题。有些源码在全部数据上先fitStandardScaler 再做时间切分,这样测试集统计量已经泄漏进训练流程,报告出的指标必然虚高。正确写法是在训练集上完成fit,再分别transform训练集和测试集。如果下载到的源码测试指标高得异常,先查这一步。
复现时建议只先跑通线性回归与 XGBoost 这条链路,确认数据层和评估层无误后再考虑引入 LSTM。检验模型是否可信,看两个数字:验证集方向准确率是否稳定超过 52%,以及打乱标签后的模型是否回落至 50% 附近。这两项检查通过之前,再好看的回测曲线都不能作为实盘依据。
本文还有配套的精品资源,点击获取