背景痛点:为什么金融毕设总在“数据关”卡住
做金融数据分析毕设,最痛苦的往往不是写模型,而是“找数据—洗数据—对齐时间戳”这三步连环坑。:
- 选题阶段:很多同学一上来就想“预测股价”,结果连日线复权都没搞清,回测时把未来信息偷偷喂给模型,答辩时被老师一句“前视偏差”问倒。
- 数据获取:网上爬的 CSV 常常缺字段、乱排序;用学校内网又嫌 Spark 集群排队慢,本地电脑 8 G 内存一跑就蓝屏。
- 模型验证:LSTM 调参三天三夜,结果 AUC 还不如线性回归;论文里贴一堆彩色 loss 曲线,却解释不了“模型到底学到了什么波动规律”。
这些问题归结起来:缺少一条“能跑通、能解释、能落地”的端到端管道。下面把我去年毕设踩过的坑和最终方案完整复盘,给大家一个可直接复现的模板。
技术选型:为什么不是“全家桶”而是“小而美”
先放结论:本地笔记本就能跑、老师能看懂、答辩能讲清,比“堆大词”更重要。
数据框架:Pandas vs PySpark
- 校园网 Spark 集群排队 30 min 起步,PySpark 调分区、调序列化,写完脚本答辩都结束了。
- 股票日线 10 年也就 2-3 GB,Pandas 加
chunksize分块读,8 G 内存完全够用;真遇到分钟线,再考虑Dask做横向扩展。
模型算法:LightGBM vs LSTM
- LSTM 需要调学习率、隐藏层、Dropout、EarlyStopping,训练一次 40 min,解释性全靠“画 loss”。
- LightGBM 十分钟出结果,
plot_importance()直接输出因子权重,老师一眼看懂“原来你用 MACD 金叉做信号”。 - 树模型对缺失值鲁棒,不用像神经网络那样先
MinMaxScaler再fillna,步骤越少越不容易引入未来信息。
可视化与部署:Streamlit vs Flask
- Flask 要写路由、写模板,前端调 CSS 又一周。
- Streamlit 纯 Python 脚本,
st.line_chart(df)一键出图,本地streamlit run app.py即可,老师电脑也能跑,现场演示零翻车。
核心实现:一条脚本跑通“取数—清洗—特征—训练—回测”
下面代码以“沪深 300 成分股日行情”为例,目标:预测未来 5 日收益率方向(分类)。完整流程拆成 5 个函数,每个函数只做一件事,方便单元测试。
1. 数据拉取与缓存
import os, tushare as ts, pandas as pd, pickle, time CACHE = 'data/raw/' os.makedirs(CACHE, exist_ok=True) def get_daily(ts_code, start, end): """带本地缓存的日线下载,避免重复调用 API""" file = f"{CACHE}{ts_code}.pkl" if os.path.exists(file): return pd.read_pickle(file) pro = ts.pro_api('你的token') df = pro.daily(ts_code=ts_code, start_date=start, end_date=end) df['trade_date'] = pd.to_datetime(df['trade_date']) df.sort_values('trade_date', inplace=True) df.to_pickle(file) time.sleep(0.3) # 控制 QPS return df2. 缺失值与异常值处理
def clean(df): # 1. 缺失:只有上市初期可能缺,直接前向填充 df = df.ffill() # 2. 异常:涨跌幅绝对值 > 20 % 视为涨跌停录入错误,用中位数替换 for col in ['open', 'high', 'low', 'close']: median = df[col].median() df.loc[df[col].pct_change().abs() > 0.2, col] = median return df3. 技术指标特征工程
import ta # Technical Analysis 库 def add_technical(df): df['sma_10'] = ta.trend.sma_indicator(df['close'], 10) df['rsi_14'] = ta.momentum.rsi(df['close'], 14) df['macd'] = ta.trend.macd_diff(df['close']) # 更多因子可继续加,但记得保持“当根 bar 可计算”,避免未来函数 return df4. 时间序列切分 & 标签构造
def make_label(df, hold=5): """未来 hold 日收益率方向,0 跌 1 涨""" df['ret_f'] = df['close'].shift(-hold) / df['close'] - 1 df['label'] = (df['ret_f'] > 0).astype(int) return df.dropna()5. 训练与交叉验证(时间顺序)
from lightgbm import LGBMClassifier from sklearn.metrics import accuracy_score def train_model(df): feature = [c for c in df.columns if c not in ['label', 'ret_f', 'trade_date']] X, y = df[feature], df['label'] # 按时间 8:2 切分,避免信息泄漏 split_date = df['trade_date'].quantile(0.8) train_idx = df['trade_date'] <= split_date X_train, X_test = X[train_idx], X[~train_idx] y_train, y_test = y[train_idx], y[~train_idx] model = LGBMClassifier(n_estimators=5000, learning_rate=0.05, verbose=-1) model.fit(X_train, y_train) pred = model.predict(X_test) print('Accuracy:', accuracy_score(y_test, pred)) return model, feature把上面 5 个函数串起来,只需 30 行主流程:
if __name__ == '__main__': df = get_daily('000001.SZ', '20140101', '20231231') df = clean(df) df = add_technical(df) df = make_label(df, hold=5) model, feat = train_model(df)运行一次 2 分钟,准确率 56 % 左右——别嫌低,日线 5 日方向预测能过 55 % 已跑赢硬币,关键是流程干净、可解释。
性能与合规:让代码跑得动、老师信得过
- 缓存策略
- 日线更新频率低,可每晚定时拉取增量,文件名用
ts_code+last_date做版本管理,避免重复下载。
- 日线更新频率低,可每晚定时拉取增量,文件名用
- API 限速
- Tushare 免费账号 500 次/分钟,循环里加
time.sleep(0.12)稳过;Yahoo Finance 用yfinance库并发高时会被封 IP,建议单线程 + 失败重试。
- Tushare 免费账号 500 次/分钟,循环里加
- 回测前视偏差
- 任何涉及未来函数(如
shift(-1))必须在特征工程阶段就剔除;标准化用rolling().mean()而不是全局mean()。 - 划分训练/测试务必按时间先后,不能用
train_test_split的随机切分。
- 任何涉及未来函数(如
生产环境避坑:别让“好看”的结果图误导你
- 过拟合预警
- 观察训练集 AUC 与验证集差距 > 5 % 就缩减树深度或加
reg_lambda;用early_stopping_rounds=100让模型自己停下来。
- 观察训练集 AUC 与验证集差距 > 5 % 就缩减树深度或加
- 可视化误导
- Streamlit 默认把索引当横轴,若直接把价格画出来,指数增长像“直线飙升”,容易给非专业老师“稳赚”错觉。加对数坐标
st.line_chart(df.set_index('trade_date')['close'].apply(np.log))更客观。
- Streamlit 默认把索引当横轴,若直接把价格画出来,指数增长像“直线飙升”,容易给非专业老师“稳赚”错觉。加对数坐标
- 学术查重
- 代码部分不会被查,但论文正文避免大段复制网上“策略原理”。把特征构造写成“基于 10 日移动平均与 MACD 背离度量的量价因子”,用自己的话转述即可。
系统扩展:从单资产到多资产组合
单股模型跑通后,把ts_code换成一篮子沪深 300,循环训练即可得到 300 个模型;预测值作为打分,再叠加行业中性、市值中性,就能做组合权重优化。下一步可以尝试:
- 把预测概率当因子,做 Group Neutral 分层回测;
- 用
cvxpy求解均值-方差最优化,输出每日权重; - Streamlit 侧边栏加滑块,让用户自己调风险偏好,实时看组合夏普变化。
写在最后
整套流程下来,硬件只需一台 8 G 内存笔记本,软件全是开源,从数据到交互式看板 200 行代码搞定。毕业设计不是“炫技”,而是让评委老师 5 分钟看懂你做了什么、为什么这么做。希望这份“小而美”模板能帮你把精力花在讲故事而不是调环境。现在就打开编辑器,把000001.SZ换成你感兴趣的股票,跑一遍脚本,再想想:如果让你同时分析股票、基金、可转债,你会怎么复用这条管道?答案其实就藏在刚刚写好的函数接口里——把数据获取层抽象成统一get_asset,剩下的特征、训练、可视化全部复用,真正的多资产组合分析系统就已经完成了一半。祝你毕设顺利通过,也欢迎把遇到的问题和优化思路分享出来,一起把“学生级”玩具做成“科研级”工具。