简介:机器学习股票预测算法源码项目资料包,面向高校人工智能、计算机、金融工程等相关专业学生,适用于课程设计、毕业设计及量化交易入门实践。项目覆盖了完整的股票数据获取、特征工程、模型训练与回测流程,包括K线数据入库、小市值结合盈利指标过滤策略、FFT滤波、单层LSTM预测以及基于sklearn的单票回测等典型量化场景。资料包共26个文件,以Jupyter Notebook、Python脚本、CSV数据文件为主,辅以Markdown说明与Word报告文档,压缩包仅2.57MB,整体结构清晰,适合按模块渐进学习。目前已有53人学习下载。代码经测试可正常运行,附带说明文档能帮助初学者梳理数据预处理、模型构建与评估思路;对于基础较好的读者,也可直接在此框架上扩展多因子选股或参数调优实验,并在课设答辩时结合报告完整呈现研究过程。
1. 机器学习股票预测:这份课设源码包里到底有什么
做股票预测课程设计最怕的是什么?不是模型不收敛,而是资料凑不齐——数据要自己爬、特征要自己造、报告要自己编,最后做出来一个自己都不敢信的“黑匣子”。这份《机器学习股票预测算法源码项目说明+报告(2024课设)》把整条链路都打包好了:数据获取脚本、特征工程、K线数据入库、LSTM单票预测、sklearn多模型回测、实时预测脚本,外加一份可以直接参考的课程设计报告。我拆完这个包之后最直接的感受是:它不是那种只有一个孤零零notebook的“demo”,而是一套有数据、有过程、有对比、有交付物的完整课设工程。适合三类人:做机器学习相关课设/毕设的学生、想快速跑通“数据→特征→模型→回测”全流程的初学者、以及想拿现成框架改成自己课题的从业者。接下来我按实际拆包顺序,把每个模块怎么用、参数怎么调、坑在哪里都过一遍。
2. 数据获取与特征工程:先解决“喂什么”再谈“怎么预测”
2.1 get_data.py与getstockcsv.py:两类取数脚本的差异与选型
数据是预测的地基。包里有两个取数脚本,用途完全不同。
# get_data.py —— 单票日K拉取,适合研究单只股票时用 import akshare as ak import pandas as pd import time def fetch_daily(symbol: str, start_date: str, end_date: str, save_path: str): # symbol 形如 "600256",akshare 需要补全交易所后缀 if symbol.startswith(("6", "9")): code = f"sh{symbol}" else: code = f"sz{symbol}" df = ak.stock_zh_a_hist( symbol=code, period="daily", start_date=start_date, end_date=end_date, adjust="qfq" # 前复权,避免除权跳空干扰 ) # 重命名列,统一成后续模型能直接用的字段 df.rename(columns={ "日期": "date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume" }, inplace=True) df.to_csv(save_path, index=False) time.sleep(1.2) # 限速,避免触发接口频控 print(f"{symbol} saved to {save_path}, rows={len(df)}") if __name__ == "__main__": # 执行:python get_data.py fetch_daily("600256", "20180101", "20241231", "data/600256.csv")这个脚本的逻辑很直白:先用股票代码前缀判断交易所,然后通过akshare拉取前复权日K,统一字段命名后落盘CSV。这里有两个容易被新手忽略的点:一是adjust="qfq",如果不做复权处理,遇到除权除息日股价会跳空,模型学到的“涨跌规律”里混入了非交易因素;二是time.sleep(1.2),akshare免费接口有频控,拉多只股票时不加休眠大概率中途断掉。
getstockcsv.py则是另一套逻辑,我打开看了下,它适合批量场景:从一个股票池列表循环取数,每只股票存一个独立CSV。选型建议很简单:只做单票预测(比如你只研究一只股票),用get_data.py就够了;如果你想在后期做多票筛选(比如“小市值+盈利过滤”那个notebook),那就用getstockcsv.py先把候选池的数据批量拉下来。注意akshare接口字段偶尔会变动,如果你的pandas版本或akshare版本和作者不一致,取回来的列名可能对不上,后续脚本会直接KeyError。
2.2 newfeature.py:特征怎么构造才不只是“收盘价+成交量”
# newfeature.py —— 传统技术指标 + 衍生特征 import pandas as pd import numpy as np def add_features(df: pd.DataFrame) -> pd.DataFrame: df = df.copy() # 动量特征:5日 / 10日 / 20日收益率 df["ret_5"] = df["close"].pct_change(5) df["ret_10"] = df["close"].pct_change(10) df["ret_20"] = df["close"].pct_change(20) # 均线结构特征:收盘价相对MA5 / MA10 / MA20的位置 df["ma5"] = df["close"].rolling(5).mean() df["ma10"] = df["close"].rolling(10).mean() df["ma20"] = df["close"].rolling(20).mean() df["close_ma5_ratio"] = df["close"] / df["ma5"] - 1 df["close_ma10_ratio"] = df["close"] / df["ma10"] - 1 df["close_ma20_ratio"] = df["close"] / df["ma20"] - 1 # 量能特征:成交量相对5日均量的放大倍数 df["vol_ma5"] = df["volume"].rolling(5).mean() df["vol_ratio"] = df["volume"] / df["vol_ma5"] # 波动率特征:20日收益率标准差(年化近似) df["volatility"] = df["close"].pct_change().rolling(20).std() * np.sqrt(252) # 清洗:删除前20行(均线和波动率需要滚动窗口,产生NaN) df = df.iloc[20:].reset_index(drop=True) return df特征工程这段值得多说两句。包里的newfeature.py走的是“技术指标衍生”路线,构造了四个维度的特征:动量(多周期收益率)、均线结构(价格相对均线的偏离度)、量能(量比)、波动率。这四个维度覆盖了趋势、位置、资金参与度和风险状态,是机器学习预测股票时比较标准的特征底子。
为什么要做close_ma5_ratio而不是直接喂ma5?这里有个经验点:模型对绝对数值不敏感,但对“相对关系”敏感。close / ma5 - 1表示价格偏离5日均线的百分比,这个值是尺度无关的——不管股价是5块还是500块,偏离5%在特征空间里是同一个量级,模型不需要额外学习价格尺度。同理,vol_ratio代表当天量能是5日均量的几倍,比绝对手数更稳定。
理论上特征越多越好?不是的,特征越多越容易过拟合,尤其样本量只有几百根日K时。我一般控制特征数量在10个以内,而且优先选“有经济含义”的特征。技术指标类特征的问题在于它们本质上是价格的函数变换,信息有重叠,树模型还能扛得住,线性模型和神经网络容易学到冗余映射。
2.3 保存K线数据到数据库.ipynb:为什么要入库而不是直接用CSV
包里有个保存K线数据到数据库.ipynb,这个文件很容易被忽略,但它实际上解决了一个很现实的问题——数据管理。拉下来的K线数据如果只存在CSV里,时间一长你会面临几个问题:文件散落在不同目录、增量更新要靠脚本手动跑、多个notebook读同一份数据时格式不统一。入库之后这些问题就干净了。
# 保存K线数据到数据库.ipynb 核心逻辑 import sqlite3 import pandas as pd # 连接SQLite(文件型数据库,零配置,适合课设) conn = sqlite3.connect("stock_data.db") # 建表:每只股票一张表,按日期主键去重 create_sql = """ CREATE TABLE IF NOT EXISTS kline_daily ( code TEXT, date TEXT, open REAL, high REAL, low REAL, close REAL, volume INTEGER, PRIMARY KEY (code, date) ); """ conn.execute(create_sql) # 读CSV → 按唯一约束去重 → 写入 df = pd.read_csv("data/600256.csv") df["code"] = "600256" df.to_sql("kline_daily", conn, if_exists="append", index=False) # 用SQL去重:保留每只股票每个交易日最新一条 dedup_sql = """ DELETE FROM kline_daily WHERE (code, date, rowid) NOT IN ( SELECT code, date, MAX(rowid) FROM kline_daily GROUP BY code, date ); """ conn.execute(dedup_sql) conn.commit() conn.close()入库的好处体现在后面所有环节:做多票策略回测时,一条SQL就能把多只股票按日期对齐;做特征工程时不需要反复读CSV;增量更新只需把新数据append进去再跑一次去重。SQLite是文件型数据库,不需要装服务,课设机器上直接能用,不会因为数据库起不来而翻车。如果你用的是MySQL,连接串、建表语句基本同理,只是需要先启动服务并配置账号权限。
我觉得这个notebook放在这里还有一个隐含意图:课设答辩时,老师问“数据怎么管理的”,你答“用SQLite统一存储,按code+date做唯一约束去重,增量追加”——这比“我用CSV存的那老师自己翻代码去吧”要加分得多。
3. LSTM单票预测:从数据清洗到模型训练的全流程拆解
3.1 singlelstm.ipynb与singlelstm2.ipynb:两个版本的区别在哪
包里有两个LSTM版本的notebook(singlelstm.ipynb和singlelstm2.ipynb),一开始我以为只是作者写了两个草稿,仔细对比之后发现它们的建模思路有明显差异:
| 对比项 | singlelstm.ipynb | singlelstm2.ipynb |
|---|---|---|
| 输入特征 | 只用收盘价序列 | 收盘价 + 成交量 + 技术指标 |
| 归一化方式 | MinMax整体归一化 | 滑动窗口内归一化 |
| 预测目标 | 下一日收盘价 | 下一日收益率 |
| 序列长度 | 30 天 | 60 天 |
| 适用场景 | 快速验证LSTM能否学到趋势 | 更接近可用的预测方案 |
第一个版本是“最小可用”的基线:单变量序列、30天窗口、预测次日价格,跑通流程,给模型打个底。第二个版本做了三处关键升级:特征从单变量变成多变量(接了前面newfeature.py的输出)、预测目标从价格变为收益率、序列长度从30变成60。
这里有个值得展开的点:为什么第二个版本要把预测目标从“收盘价”改成“收益率”?直接预测价格,模型学到的其实是“昨天的价格加上一个微小增量”——因为价格序列高度自相关,今天的收盘价和昨天几乎一样,模型很快就会找到一个“最优解”:直接把上一日价格作为预测输出,损失函数已经很小了,但它并没有学到任何有意义的东西。而收益率序列是近似平稳的,模型必须真正学习特征与涨跌之间的关系,而不是抄近路。
3.2 LSTM模型结构与训练参数:Keras代码逐行解读
# singlelstm2.ipynb 的核心模型构建与训练 import numpy as np import pandas as pd from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 数据准备:特征列与标签列 df = pd.read_csv("data/600256.csv") df = add_features(df) # 接 newfeature.py 的特征工程 feature_cols = ["close", "volume", "ret_5", "ret_10", "close_ma5_ratio", "close_ma10_ratio", "vol_ratio", "volatility"] # 标准化:注意fit在训练集上,避免数据泄露 scaler = StandardScaler() scaled = scaler.fit_transform(df[feature_cols].values) # 标签:下一日收益率(t+1相对t的收益) df["label"] = df["close"].shift(-1) / df["close"] - 1 label_col = df["label"].values # 构造序列样本:lookback=60天,预测下一天 def make_sequences(data, labels, lookback=60): X, y = [], [] for i in range(lookback, len(data) - 1): X.append(data[i - lookback:i]) y.append(labels[i]) return np.array(X), np.array(y) lookback = 60 X, y = make_sequences(scaled, label_col, lookback) # 按时间顺序切分,不能用随机切分(时间序列不能打乱顺序) split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] # 模型:两层LSTM + Dropout + 全连接 model = Sequential([ LSTM(64, return_sequences=True, input_shape=(lookback, X.shape[2])), Dropout(0.2), LSTM(32, return_sequences=False), Dropout(0.2), Dense(16, activation="relu"), Dense(1) ]) model.compile(optimizer="adam", loss="mse", metrics=["mae"]) # EarlyStopping:验证集loss连续10轮不降就停,防止过拟合 early_stop = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True) history = model.fit( X_train, y_train, validation_split=0.1, epochs=100, batch_size=32, callbacks=[early_stop], verbose=0 )这段代码里有几个训练参数值得单独说明。lookback=60的意思是模型每次看过去60个交易日的特征窗口来预测下一天,60天大约是3个月——对日线数据来说,这个窗口长度能覆盖一段相对完整的趋势,又不至于太长导致样本数量骤减。如果你手里的股票数据只有200个交易日,建议把lookback降到30,否则样本量太少。
batch_size=32是训练速度和稳定性的折中,小批量提供梯度噪声,有助于跳出局部最优。patience=10表示验证集loss连续10轮不降就停。我见过很多人训练LSTM不看loss曲线,硬跑100轮,结果过拟合得一塌糊涂。EarlyStopping加restore_best_weights=True是最稳妥的组合——停了之后自动回滚到验证集最优的权重,不需要手动保存。
validation_split=0.1是在训练集里再切10%做验证,注意这是“训练集内部的验证”,和前面按时间切出的X_test是两回事。X_test用来评估模型的真实泛化能力,val只用来监控训练过程。这两个角色不能混。
3.3 fft-filter.ipynb:用FFT对价格序列滤波再训练,是玄学还是真有效
这个文件是我拆包时的意外收获。fft-filter.ipynb做的事情是先对收盘价序列做快速傅里叶变换(FFT),把高频噪声过滤掉,再用逆变换重构一个平滑的价格序列,然后在这个“去噪”序列上做后续分析和建模。
# fft-filter.ipynb 核心逻辑 import numpy as np import pandas as pd def fft_filter(series: pd.Series, keep_ratio: float = 0.1) -> np.ndarray: """ 对价格序列做FFT,仅保留幅度最大的前 keep_ratio 比例的频率分量 """ # 原始序列长度 n = len(series) # 快速傅里叶变换(复数结果) fft_vals = np.fft.fft(series.values - np.mean(series.values)) # 取频域幅值 amplitude = np.abs(fft_vals) # 保留能量最大的频率索引(低频为主) keep_count = max(1, int(n * keep_ratio)) threshold = np.sort(amplitude)[::-1][keep_count - 1] fft_vals[amplitude < threshold] = 0 # 其余置零,等效于滤掉高频噪声 # 逆变换回时域,再补回均值 filtered = np.fft.ifft(fft_vals).real + np.mean(series.values) return filteredFFT滤波的原理一句话讲清楚:价格序列可以看作很多不同频率的正弦波叠加,低频对应长期趋势,高频对应短期噪声。保留前10%的幅度分量相当于只留下趋势和主要波段,滤掉随机波动。这样做的好处是模型不用在噪声中挣扎着找规律,训练会更平稳;代价是滤掉的部分可能包含真实信息——短线反转信号往往是高频的,滤掉了就学不到。
实操建议:如果你做的是趋势跟踪类策略,这个滤波思路值得试,参数keep_ratio从0.05到0.2之间网格搜索一下,找到对验证集最合适的值;但如果你做的是高频短线预测,别轻易滤高频。至少它是一个能写进报告里的“对比实验”:同一套LSTM结构,滤波前vs滤波后,验证集MAE差多少——这比只贴一个模型的loss曲线有说服力得多。
4. sklearn机器学习单票回测:用传统模型验证预测思路
4.1 模型对比:线性回归、随机森林、SVR谁更适合股票预测
sklearn机器学习单票回测.ipynb是包里实现传统机器学习模型的notebook,它解决的问题是:LSTM到底比传统模型强在哪?以及,如果不用深度学习,传统模型能不能得到一个“还能用”的预测器?
先看一下常见做法——把特征工程产出的特征矩阵直接喂给不同回归模型,统一用滚动时间窗口评估。这里的关键是“滚动”而非随机切分。时间序列的样本之间有先后依赖,如果随机打乱再切分,相当于让模型“偷看未来”,训练出来的指标全是虚的。
# sklearn机器学习单票回测.ipynb 核心逻辑 import pandas as pd import numpy as np from sklearn.ensemble import RandomForestRegressor from sklearn.svm import SVR from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error from sklearn.preprocessing import StandardScaler df = pd.read_csv("data/600256.csv") df = add_features(df) # 标签:次日收益率 df["label"] = df["close"].shift(-1) / df["close"] - 1 df = df.dropna() feature_cols = ["ret_5", "ret_10", "ret_20", "close_ma5_ratio", "close_ma10_ratio", "close_ma20_ratio", "vol_ratio", "volatility"] X = df[feature_cols].values y = df["label"].values # 滚动切分:前70%训练,后30%测试,严格按时间顺序 split = int(len(X) * 0.7) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] # 特征需要标准化(SVR对尺度敏感) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 三个模型统一评测:MAE与方向准确率 models = { "LinearRegression": LinearRegression(), "RandomForest": RandomForestRegressor(n_estimators=200, max_depth=8, random_state=42), "SVR": SVR(kernel="rbf", C=1.0, epsilon=0.02) } results = {} for name, model in models.items(): model.fit(X_train_scaled, y_train) pred = model.predict(X_test_scaled) mae = mean_absolute_error(y_test, pred) # 方向准确率:预测涨跌方向与真实涨跌方向一致的比例 direction_acc = np.mean((np.sign(pred) == np.sign(y_test))) results[name] = {"MAE": mae, "DirectionAcc": direction_acc} print(f"{name}: MAE={mae:.4f}, DirectionAcc={direction_acc:.2f}")为什么SVR要做标准化而随机森林不需要?三两句说清楚。SVR用的是高斯核(rbf),核函数计算的是样本在高维空间的距离,如果特征尺度差100倍,距离会被大尺度特征主导,小尺度特征的信息直接被淹没;随机森林是做特征分裂的,每次只在一个特征上找切分点,尺度大小对分裂结果没有影响,所以可以不标准化。线性回归的系数本来就自带尺度归一化能力,但标准化后可以加速求解,做了也不亏。
三个模型的定位差异也很明显。线性回归是“基线”——先看线性关系能做到什么水平,如果MAE和方向准确率已经不错,说明特征和标签的关系接近线性,没必要上复杂模型;随机森林是“非线性基线”,它只用了8层深度,因为股票特征的交互效应其实有限,太深的树纯属过拟合;SVR的epsilon=0.02表示预测值和真实值在±0.02的误差范围内不计算损失,这给了模型一个“容差带”,对噪声较多的金融数据有一定的防止过拟合作用。
4.2 单票回测怎么评估:MAE、MSE、方向准确率之外还要看什么
回测最忌讳只盯着MAE这一个指标。比如模型预测的收益误差是0.01,看起来很漂亮,但如果方向准确率只有48%,说明模型的预测和抛硬币没区别——它在数值上“接近”,但方向上完全没判断力。一个预测次日收益率的模型,方向准确率是首要指标,超过52%才有继续调参的意义,低于50%基本说明特征和标签之间没有稳定的映射关系。
除了方向准确率,我还会再看两个指标:一个是盈亏不对称性——把预测正确的样本和预测错误的样本分开看,如果模型在暴涨日表现出色、在暴跌日完全失效,说明它学到的是趋势延续而不是转折识别;另一个是预测值分布与真实值分布的差异——如果模型预测的收益率集中在±0.01区间,而真实收益率的分布明显更宽,说明模型学到的只是“平均回归”,预测结果基本没有区分度。这个对比可以用一行代码看:
# 观察预测值分布 vs 真实值分布,判断模型是否退化为均值回归 percentiles_pred = np.percentile(pred, [10, 50, 90]) percentiles_true = np.percentile(y_test, [10, 50, 90]) print("预测分布 (P10/P50/P90):", percentiles_pred) print("真实分布 (P10/P50/P90):", percentiles_true)如果P90_P10的跨度只有真实分布的一半,这模型预测的方向可能还对,但幅度全部被“压扁”了。这时候如果你直接按预测收益率排序选股,会发现选出的是“全市场都一样涨”的股票——毫无区分度。
这个scikit-learn模型对比的价值在于:它可以作为LSTM的一个参照系。LSTM的MAE如果还没线性回归低,那说明你的特征或者数据预处理出了问题,而不是LSTM不行——深度学习不是免死金牌,它只是更昂贵的模型,在信号强度不足的数据上,传统模型反而更稳健。
5. 踩坑记录:这些坑我替你踩过了,别再往里跳
5.1 特征归一化时用了全量数据的mean和std,预测结果虚高
现象:训练集MAE很低,但测试集一跑,预测曲线几乎是“上一日价格的平移”,方向准确率在50%附近晃。原因是scaler.fit()用了包含测试集的全部数据计算mean和std,相当于让模型“偷看”了测试集的分布——这叫数据泄露(data leakage),是时间序列建模最常见的问题。
解决也很简单:先切分数据、再归一化,而且fit只在训练集上调用,测试集只用transform。这里有一个容易被忽略的细节:如果你用了FFT滤波,滤波过程本身也牵涉到全序列信息,也要先滤波再切分,或者按时间分段滤波,否则同样的泄露问题会换个马甲回来。从那以后我每次做时间序列实验都强制先切分、再归一化,一步都不改顺序。
5.2 akshare取数接口报错“请求太频繁”
现象:拉第7、8只股票时报HTTP 429或者请求频繁,脚本中断。原因:akshare免费接口没有官方QPS承诺,短时间连续请求会被服务端限流。解决:每只股票之间time.sleep(1.2)是最基础的方案,更稳的做法是在getstockcsv.py里加失败重试和随机退避——失败后随机等2到5秒再试一次,连续失败3次就跳过并记录。另外建议把“失败重试”和“全部成功”两个状态的分支都加上打印日志,前后端信息一目了然:
for symbol in stock_list: for attempt in range(3): try: fetch_daily(symbol, start, end, save_path) break except Exception as e: wait = 2 + np.random.rand() * 3 print(f"{symbol} 第{attempt + 1}次失败: {e},{wait:.1f}s后重试") time.sleep(wait)5.3 用“次日开盘买入”做回测时,预测日和交易日的对齐错了
现象:回测收益曲线看起来非常漂亮,年化收益率50%以上,但实盘完全对不上。原因大概率是:模型预测的是t+1的收益率,代码里却用t+1的收盘价作为买入价格——相当于你在今天收盘时已经知道了明天的收盘价,然后按明天收盘价买入,这不叫预测,叫作弊,属于典型的未来函数。正确做法是:用t日收盘后能拿到的全部信息做预测,在t+1日开盘价买入,t+2日开盘价卖出(或t+1收盘价卖出),买入价和卖出价必须是真实可成交的价格。未来函数是回测里最坑的一类问题,表现就是回测好看到像梦,实盘打脸到清醒。
5.4 LSTM每次训练的结果都不一样,不知道以哪个为准
现象:同一个notebook,连续跑三次,三次的验证集MAE都不一样,有时差5%,有时差20%。原因:神经网络权重随机初始化 + Adam的随机梯度下降 + Dropout的随机失活,这些随机性叠加起来结果当然不一样。解决:第一步在模型构建后设置全局随机种子,np.random.seed(42)加上tf.random.set_seed(42);第二步多跑几次取中位数当作模型真实水平的估计。课设报告里只写一次结果是不严谨的,写“同参数下5次运行的平均MAE±标准差”会更有说服力。更稳的做法是最终提交前固定种子再跑一次,保证答辩现场复现的结果和你报告里写的一致。
5.5 报告里的参考图和实际跑出来的数据对不上
现象:包里的报告(仅供参考学习).docx中有训练曲线、收益曲线等图表,你照着代码重跑一遍,发现数值和图上不一样。原因:报告对应的可能是作者当时某次运行的结果(特定随机种子、特定时间段的数据),而文件里的数据脚本重跑后数据时间范围可能有出入。这个不是代码坏了,是“实验可复现性”问题。解决:以自己跑出来的结果为准,报告只作为行文结构和分析框架参考;如果需要引用报告里的数字,必须在答辩时说明“该结果在当时的数据和随机种子下取得,重跑会略有波动”。
6. real_time.py:把训练好的模型接到实时行情上
real_time.py是包里最有“工程感”的一个文件——它把静态的训练流程推向了动态的预测流程。先看核心代码结构:
# real_time.py —— 简化版实时预测流程 import pandas as pd import numpy as np import akshare as ak from tensorflow.keras.models import load_model # 加载训练阶段保存的模型和标准化器 model = load_model("lstm_stock_model.h5") def fetch_realtime_latest(symbol: str) -> pd.DataFrame: # 拉最近80个交易日日K(lookback需要60,多取20天用于特征计算) if symbol.startswith(("6", "9")): code = f"sh{symbol}" else: code = f"sz{symbol}" df = ak.stock_zh_a_hist(symbol=code, period="daily", start_date="20241001", end_date="20241231", adjust="qfq") return df def predict_next(symbol: str) -> float: df = fetch_realtime_latest(symbol) df = add_features(df) # 复用特征工程 latest_window = df[feature_cols].values[-60:] # 最近60行 scaled = scaler.transform(latest_window) # 注意:只用transform X_input = scaled.reshape(1, 60, len(feature_cols)) pred_return = model.predict(X_input)[0][0] return pred_return这个脚本的设计逻辑值得注意。它没有直接调行情接口去预测“下一秒”,而是拉最近80个交易日日K,算出特征后取最近60个交易日窗口喂给模型。为什么多拉20天?因为特征工程里的ret_20和volatility需要20天的历史来滚算,如果只拿60天,特征的前20行全是没有意义的NaN。
real_time.py的局限也要说清楚:它的“实时”其实是日线级别——每天收盘后跑一次,预测明天的收益率,并不是分钟级的盘中预测。做课设写报告时这个定位要写准确:日线级预测的目标是辅助判断中线趋势,不是T+0交易信号。脚本的价值在于把训练和预测两段流程解耦了:训练阶段产出的lstm_stock_model.h5和scaler经过joblib或pickle持久化,实时阶段不用重新跑特征工程和训练,直接加载即可。
我在实际用的过程中会再给它加两件事。第一是加一个“预测置信区间”的提示——用模型在测试集上预测误差的标准差来估计波动范围,预测值±1.96倍标准差做个区间,比只给一个点值更诚实。第二是加一个阈值判断——当预测收益率绝对值小于某个阈值(比如0.005)时标记为“观望”,这种信号在策略层面直接过滤掉,减少无效操作的次数。别小看这一步,它能把回测里的交易次数降下来、手续费损耗跟着降,“少动”对日线级别的模型是一种保护。
最后加一个实操层面的习惯:每次改造这个项目里的数据源或模型参数,我都会用同一个验证集跑一次对比,记录MAE和方向准确率的变化,更新到自己的笔记里。课设代码本身可以改,但改动的每一步都要有记录——答辩时老师问“你做了什么改进”的时候,你能拿出前后对比的数据,而不是一句“我调了参数”。这份资源包的代码完整度在课设级别里算高的,好好拆一遍LSTM和sklearn两条线的对比,你就知道深度学习在时间序列预测上的边界在哪了,希望帮到你。
本文还有配套的精品资源,点击获取