简介:基于机器学习的股票预测与分析毕业设计项目,面向计算机相关专业正在准备毕设的学生,以及需要项目实战练习的学习者,也可直接用于课程设计或期末大作业。项目以Python为主要开发语言,围绕股票数据清洗、特征构建、模型训练与结果可视化等环节展开,配套源码与文档说明均经过严格调试,可确保运行,能帮助读者快速搭建完整的股票预测分析流程。压缩包共包含2000个文件,其中1675个PNG图片展示预测对比与走势分析图表,156个CSV文件保存历史行情数据,112个NPZ文件与39个PTH文件对应预处理数据及模型权重,另有少量PY源码与XML配置,整体约693MB,目录结构清晰便于检索。目前已有614人学习下载,尤其适合需要参考完整项目方案、快速上手机器学习实战的毕业设计使用者。
1. 机器学习预测股价:为什么有人拿高分,有人只能靠堆模型
同样是“Python实现基于机器学习的股票预测和分析源码+文档说明”这个题目,有的毕业设计能让评委点头,有的只能换来一句“再想想”。差别通常不在模型有多高级,而在三件事:有没有把预测问题严格定义成可训练的监督学习任务;有没有避开数据泄露这类看不见的作弊;文档说明能不能讲清楚每一个选择背后的理由。这篇文章就按这个顺序,把从行情数据到特征、模型、回测、避坑、文档的完整路径拆开。适合打算用它做毕设或课程设计的学生,也适合想验证机器学习在金融时序数据上到底几斤几两的开发者。读完之后,你能得到一套可以直接改参数复现的代码框架,以及几条用真金白银换来的踩坑经验。
2. 数据与标签:先把预测问题定义成能训练的样子
股票预测里的“预测”在机器学习中必须有具体定义。不是“猜明天涨不跌”,而是“用历史K线生成一组特征,预测未来N个交易日的收益是否超过某个阈值”。把这句话拆开,数据范围、标签、特征窗口和训练切分就是四个必须自己定的参数,也是数据泄露最常发生的地方。很多同学上来就拿全部历史数据算特征,最后模型在测试集上曲线完美,复盘才发现某列特征把未来信息带进了训练集——这类问题要在一开始就断掉。
2.1 最小数据口径:行情字段与日期对齐
常见做法是用第三方行情接口拉日线,字段一般包含date、open、high、low、close、volume。做毕设不需要分钟线,日线就够用,噪声比分钟线小,技术指标的计算也更稳定。拉下来第一件事不是算特征,而是统一列名、排序、去重,因为不同接口返回的字段命名差异很大,rolling和pct_change这类窗口函数对数据顺序极其敏感,顺序一旦错乱,整个特征矩阵都会跟着错。
import pandas as pd def fetch_daily(stock_code, start_date, end_date): # 用你申请过token的行情服务替换下面这行 df = quotes_api.daily( stock_code, start_date=start_date, end_date=end_date, adjust="qfq" # 前复权:消除分红送股造成的价格跳空 ) # 统一列名,后面所有特征脚本只认这六列 df = df.rename(columns={ "trade_date": "date", "open": "open", "high": "high", "low": "low", "close": "close", "vol": "volume" }) df = df[["date", "open", "high", "low", "close", "volume"]].copy() df["date"] = pd.to_datetime(df["date"]) df.sort_values("date", inplace=True) df.drop_duplicates(subset=["date"], keep="last", inplace=True) df.reset_index(drop=True, inplace=True) return df data = fetch_daily("your_stock_code", "2018-01-01", "2024-12-31") print(data.shape) print(data.head())这段代码的核心是前复权和日期对齐。前复权会对历史价格做向后修正,模拟出“不考虑分红送股”的连续价格序列;如果不做,除权日会出现一根假的大阴线,后续计算的收益率全部失真。drop_duplicates是防数据源重复推送同一天的数据,sort_values保证窗口函数按时间正确滑动。注意,stock_code要换成你实际有行情权限的标的代码;start与end建议至少拉5年以上日线,也就是1200根左右的K线,样本太少时后续训练集和验证集会非常紧张。
参数上,adjust选“qfq”是股票类接口最常用的设置,期货或其他品种接口可能没有这个选项,直接跳过即可。日期区间建议覆盖牛熊两种状态,纯牛市数据会导致模型学不到下跌样本,测试结果虚高。
提示:不同交易市场的交易日历不一样,不要用自然日去对齐行情。最简单可靠的方式是直接以标的有成交量的日期为准,停牌日没有行情数据,自然不会被算进序列。
2.2 分类标签还是回归标签:未来收益率阈值
标签构造有两种路线:预测“未来5日收益率具体是多少”属于回归,预测“未来5日收益率是否超过2%”属于分类。我建议毕业设计选分类。市场噪声太大,回归模型的损失函数容易被少数暴涨暴跌样本牵引,模型整天忙着拟合那些极端行情,最后在普通交易日上的预测一塌糊涂。分类只要一个相对稳定的边界,并且天然和“买入/不买入”的决策对齐。
def build_label(df, horizon=5, up_threshold=0.02): # 未来第horizon个交易日的收盘价 future_close = df["close"].shift(-horizon) future_return = future_close / df["close"] - 1 label = (future_return > up_threshold).astype(int) return label df["label"] = build_label(df, horizon=5, up_threshold=0.02) print(df["label"].value_counts(normalize=True))shift(-horizon)这个操作把每个交易日对应未来第N天的收盘价拉到当前行,从而算出未来一段时间的收益率。需要注意的是,DataFrame最后horizon行的future_close会是NaN,也就是说末尾几天的标签是空的,后面用dropna统一清掉。build_label返回的label是int型,0表示未来收益率未超过阈值,1表示超过。
参数设置上,horizon=5对应预测未来5个交易日,也就是一周的涨跌;如果改成20,就变成了月线级别的预测。up_threshold=0.02的含义是涨幅超过2%才算正类。这个值不是拍脑袋定的,要先打印label分布:如果正例占比超过70%,说明阈值设得太低,或者样本区间处于大牛市,模型会学到“无脑看涨”的捷径。这种情况下,可以把阈值提高到0.03或0.05,让正负样本接近均衡一些。类别分布直接决定了AUC指标的参考价值,务必看一眼再往下走。
2.3 特征工程:四组特征让K线变成模型输入
特征要围绕行情自身的动量、趋势偏离、波动性和量价关系展开。这里给一个最小可用特征集合,里面的每个特征都只用当期以及之前的数据计算,没有把未来信息写进特征矩阵。特征不是越多越好,毕设论文里能和老师讲清楚业务含义的特征,比堆几十个指标更有说服力。
def build_features(df): feats = pd.DataFrame(index=df.index) close = df["close"] # 第一组:动量,过去1/5/20日的涨跌幅 for n in [1, 5, 20]: feats[f"ret_{n}d"] = close.pct_change(periods=n) # 第二组:趋势偏离,收盘价离短中期均线的距离 feats["ma5"] = close.rolling(5).mean() feats["ma20"] = close.rolling(20).mean() feats["bias_ma20"] = close / feats["ma20"] - 1 # 第三组:波动率,20日收益率标准差 feats["std_20"] = close.pct_change().rolling(20).std() # 第四组:量价配合,量比与当日收益率的交互 vol_ma20 = df["volume"].rolling(20).mean() feats["vol_ratio"] = df["volume"] / vol_ma20 feats["ret_1d"] = close.pct_change() feats["ret_vol"] = feats["ret_1d"] * feats["vol_ratio"] return feats features = build_features(df) master = pd.concat([df[["date", "close", "label"]], features], axis=1) master = master.dropna().reset_index(drop=True)这个函数里,pct_change和rolling都是天然只依赖过去数据的窗口算子,不会造成未来数据混入。ret_1d在第四组里又算了一次,是因为它既作为动量特征单独存在,又参与ret_vol的计算;如果你想省一列,直接复用第一组的ret_1d也可以。bias_ma20衡量的是当前价格偏离20日均线的百分比,值越大说明短期涨得越急,值越负说明跌破月线。
参数上,5日和20日是技术分析里的常规短中期均线窗口,想加10日均线也很简单,再加一行rolling(10).mean()就行。std_20反映过去20个交易日的波动程度,波动率高的阶段通常意味着市场情绪不稳定,模型在这个阶段容易给出漂移较大的概率。所有rolling特征都会在数据头部产生NaN,所以master必须dropna;丢掉的只是前20行样本,对5年日线的影响可以忽略。
2.4 切分训练与测试:随机抽样在这里会作弊
拿到master之后,最危险的一步就是切分数据集。机器学习标准流程里的train_test_split默认是随机抽样,但股票数据是强时序依赖的,随机抽样会把未来数据混进训练集,模型在验证阶段相当于开卷考试,回测曲线漂亮到不可思议,实盘一跑就现原形。真正合理的方式是按日期二分,或者用时间序列交叉验证。
split_date = "2022-12-31" train_set = master[master["date"] <= split_date].copy() test_set = master[master["date"] > split_date].copy() print(f"train: {len(train_set)} rows, test: {len(test_set)} rows")以某个日期为界,之前的全部做训练,之后的全部做测试,这是股票预测最简单也不会作弊的验证方式。需要注意,这个切分只保证行层面没有未来数据,后续在特征标准化时,必须先在训练集上fit统计量,再transform测试集,不能在全体数据上一次性做标准化,否则测试集的均值方差又泄露进了训练过程。
比例上,常见取8:2或7:3。但我更建议先看行情走势再定切分点,让测试集尽量包含上涨、下跌、震荡三种状态;如果测试集恰好是一段单边上涨,任何模型预测“继续涨”都能拿到好收益,这种结果对毕业设计答辩来说站不住脚。更稳妥的做法是保留最近15%到20%的样本做最终测试,同时留一段2020年前后的震荡区间做中间验证,论文里把两段结果都报告出来,可信度会高很多。
3. 选模型与验证协议:从基线模型到随机森林与XGBoost的横向对比
这一章解决“用什么模型”和“怎么证明模型有效”两个问题。常见的误区是一上来就无脑上LSTM,好像不用深度学习就不够炫。实际上,对于日线级别、几千行样本、十几个特征的数据量,树模型和简单线性模型有明显优势:训练快、可解释性好、不容易因为序列长度的设置而崩溃。LSTM当然可以做,但在这个数据体量下需要更长的调参周期,并不适合作为第一个跑通全流程的主模型。
3.1 为什么先用逻辑回归做基线
逻辑回归是一个极好的基线模型:特征线性加权、输出概率、训练耗时只有几秒。如果逻辑回归在测试集上的AUC能到0.58以上,说明特征里确实有预测信号,后面换更强的模型才有意义;如果逻辑回归在验证阶段接近随机猜测,问题大概率出在特征构造或标签定义上,而不是模型不够强。把逻辑回归作为第一个跑通全流程的模型,能让你在写代码、调接口阶段少走很多弯路。
逻辑回归需要一个额外处理:特征标准化。因为它的损失函数对特征量纲敏感,像ret_20d这种百分数特征和vol_ratio这种倍数特征如果放在一起不加处理,梯度更新会被大数值特征主导。用sklearn的Pipeline,把StandardScaler和LogisticRegression串起来,能保证每一折交叉验证都在训练集上独立计算均值方差,不会跨折泄露。
3.2 用时间序列交叉验证横向对比三种模型
横向对比脚本可以用TimeSeriesSplit实现,这是K折交叉验证的时序版本,专门解决“后面数据不能出现在前面训练”的问题。这里以随机森林和逻辑回归为例,XGBoost替换起来也一样。注意,训练每折之前千万别做全量标准化,Pipeline会把这件事挡在每一折内部,这是它最大的价值。
from sklearn.model_selection import TimeSeriesSplit, cross_val_score from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier X = master.drop(columns=["date", "close", "label"]) y = master["label"] tscv = TimeSeriesSplit(n_splits=5, gap=5) models = { "LR": make_pipeline( StandardScaler(), LogisticRegression(max_iter=1000) ), "RF": RandomForestClassifier( n_estimators=300, max_depth=8, min_samples_leaf=10, random_state=42 ), } for name, model in models.items(): scores = cross_val_score(model, X, y, cv=tscv, scoring="roc_auc") print(f"{name}: AUC={scores.mean():.4f} ± {scores.std():.4f}")TimeSeriesSplit(n_splits=5)会把数据顺序切分成5段,每次用前面若干段训练、紧接着后一段做验证。gap=5的含义是训练集和验证集中间空出5个交易日,防止horizon=5的标签窗口把验证集信息意外带进训练集。随机森林里的random_state=42是固定随机种子,没有它,同一段代码跑两次结果可能不同,这在实验中很不严谨。
参数上,随机森林的n_estimators=300已经能覆盖几千行样本的随机性差异,再增大到500只增加训练时间,对AUC的提升非常有限。max_depth=8用于限制树的深度,太深的树容易把历史噪声背下来,min_samples_leaf=10则要求每个叶子节点至少10个样本才能分裂,这两个约束合在一起,就是在告诉模型“不要过度记住个别异常日”。输出看AUC均值和标准差,均值高说明有预测能力,标准差低说明结果稳定,两者都重要。
3.3 指标不只是准确率:AUC、精确率与回测胜率怎么配合看
股票预测的标签天然不平衡,假设样本里有75%是上涨标签,模型什么都不学,全部预测“上涨”,准确率也有75%,显得很厉害,实际上毫无交易价值。所以论文里不能只用准确率一个指标,至少同时报告AUC、精确率和回测胜率。AUC衡量模型对正负样本的排序能力,精确率告诉你在所有“预测会上涨”的样本里有多少真正上涨,而回测胜率则直接反映按照信号交易时的真实命中率。
| 指标 | 关注点 | 在股票预测里的意义 |
|---|---|---|
| 准确率 | 整体对错比例 | 容易被多数类迷惑,不能单独使用 |
| AUC | 正负样本排序能力 | 判断正例概率是否高于负例,结果稳健 |
| 精确率 | 正例预测的可靠性 | “预测上涨”的样本中真正上涨的比例 |
| 回测胜率 | 交易实际效果 | 按信号交易时盈利交易占比 |
这个表可以直接放进文档说明里。AUC在0.55到0.62之间,是这个领域很常见的结果——算不上印钞机,但说明特征确实捕捉到了微弱规律;如果连0.5都不如,那基本等同于随机猜测,需要回头检查数据预处理。精确率配合回测胜率一起看,能有效避免“AUC 0.6但交易却亏钱”的错觉:因为AUC高只代表排序好,不代表你选的阈值恰好落在有利区间。
4. 源码落地:训练、预测与回测的最小完整脚本
前面几章把零件准备好了,这一章把它们串成一个能从头跑到尾的完整脚本。流程是:按日期切分训练集与测试集,训练随机森林,输出预测概率,生成交易信号,再做事件式回测。最后附一段文档说明的写作骨架,让你知道源码之外该写什么。
4.1 完整训练脚本:固定随机种子并输出特征重要性
正式训练之前,先固定随机种子。随机森林内部有随机抽样过程,不固定种子的话,同一份数据每次训练出来特征重要性和预测概率都不完全一致,这是玄学还是科学,全靠这个种子说了算。下面的脚本同时输出特征重要性,这个结果是文档说明里“特征有效性分析”的现成素材。
from sklearn.ensemble import RandomForestClassifier split_date = "2022-12-31" train_set = master[master["date"] <= split_date].copy() test_set = master[master["date"] > split_date].copy() feature_cols = [c for c in master.columns if c not in ["date", "close", "label"]] X_train = train_set[feature_cols] y_train = train_set["label"] X_test = test_set[feature_cols] y_test = test_set["label"] model = RandomForestClassifier( n_estimators=300, max_depth=8, min_samples_leaf=10, random_state=42, ) model.fit(X_train, y_train) importance = pd.Series( model.feature_importances_, index=feature_cols, ).sort_values(ascending=False) print(importance.head(10))feature_cols通过列表推导式从master里排除日期、收盘价和标签,剩下的全是特征。这一步看起来简单,实际很容易翻车:如果你之前构造特征时不小心把close本身留在了特征里,模型会直接拿当前收盘价预测未来涨跌,训练时拟合极好,测试时也可能会出现虚假的高分。因此建议每次训练前打印一次feature_cols,人工扫一眼有没有混入不该有的列。
从输出结果能直观看到,哪些特征对预测的贡献最大。经验上,short-term动量特征如ret_5d和波动率std_20通常会排前面,而量比这类特征在不同标的上表现很不稳定。如果你的特征重要性第一名是某个毫不起眼的列,也要警惕是不是数据构造时出了bug。
4.2 预测概率与阈值决策
分类器的predict()只输出0或1,但predict_proba()能输出概率。阈值这个参数非常有用:拿0.6当门槛,意味着模型只有六成以上把握时才发出买入信号。这会明显减少交易次数,但留下来的信号通常更可靠,回测胜率也会往上走。
test_set = test_set.copy() test_set["pred_prob"] = model.predict_proba(X_test)[:, 1] test_set["signal"] = (test_set["pred_prob"] >= 0.6).astype(int) print(test_set["signal"].value_counts())model.predict_proba返回的是二维数组,第一列是负类概率、第二列是正类概率,所以[:, 1]取正类。test_set里新增的pred_prob列相当于给每个测试日打分,signal列则根据阈值转成0/1决策。如果signal几乎全是0,说明模型在测试期找不到足够强的上涨信号,这也是一种有效结论。
阈值0.6只是一个起点。实际项目里我一般会把0.5到0.7区间都跑一遍,记录每个阈值下的交易次数、胜率和累计收益,然后挑一个更合理的点。阈值的调整不改变模型本身,它属于“决策层”的参数,放在模型之后单独优化,能让实验逻辑更清晰。
4.3 事件式回测:胜率、累计收益与最大回撤
回测方法各有取舍,毕业设计最常用的是事件式回测:每次出现买入信号,持有到未来第horizon个交易日,按那时收盘价结算,扣除手续费和滑点。这个逻辑能直接回答“模型选的日期是否真的有超额收益”。
def event_backtest(result_df, horizon=5, fee_rate=0.0002, slip_rate=0.0005): df = result_df.copy() future_close = df["close"].shift(-horizon) df["future_ret"] = future_close / df["close"] - 1 df["cost"] = fee_rate * 2 + slip_rate # 双边手续费再加滑点 df["net_ret"] = df["future_ret"] - df["cost"] traded = df[df["signal"] == 1].dropna(subset=["net_ret"]) if len(traded) == 0: return {"交易次数": 0, "胜率": 0, "平均收益": 0, "累计收益": 0} win_rate = (traded["net_ret"] > 0).mean() avg_ret = traded["net_ret"].mean() cum_ret = (1 + traded["net_ret"]).prod() - 1 return { "交易次数": len(traded), "胜率": round(win_rate, 4), "平均收益": round(avg_ret, 4), "累计收益": round(cum_ret, 4), } event_backtest(test_set)这个回测有两个关键点。一是shift(-horizon)取未来收盘价,和构建标签时用的是同一种位移方式,保证“预测的是未来N日收益”这一口径前后一致。二是cost的算法,fee_rate * 2表示买入和卖出各付一次单边手续费,slip_rate是滑点假设,代表你按收盘价下单时实际成交价会有偏差。把这两个扣掉之后,net_ret才是真正落到口袋里的预期收益。
参数上,fee_rate=0.0002对应单边万二的手续费,slip_rate=0.0005对应千分之五的滑点——这是比较温和但合理的估计。如果换手很频繁,可以把滑点调高到0.001再看一次回测结果,看看收益会不会变成亏损。如果收益率对交易成本极其敏感,论文里可以写“该策略在高频场景下不具备成本优势”。
注意:这里的事件式回测是“信号有效性验证”,不是资金曲线模拟。它假设每次交易使用等额资金,且信号之间不重叠。想做资金曲线,需要按每个交易日持仓比例折算,这是第6章的内容。
4.4 文档说明:把实验结论写成可以答辩的骨架
拿到源码工程的运行结果之后,文档说明建议按六个章节组织:问题定义与背景、数据集与预处理、特征工程说明、模型选择与参数记录、回测结果与风险讨论、结论与展望。每一章都能从前面的脚本输出里找到素材:特征重要性表放进特征工程章,AUC对比表放进模型章,事件回测结果表放进回测章。
这里有一个能加分的做法:把“失败实验”也写进文档。比如,先展示随机切分得到的虚高AUC,再展示时间序列切分后的真实AUC,说明你理解数据泄露问题。这比只放一个漂亮结果更让老师信服,因为股票预测本来就是一个低信噪比问题,承认边界反而体现水平。
5. 避坑清单:数据穿越、类别失衡和交易成本是最常见的三个坑
这一章列出的都是做这个题目时大概率会遇到的问题,每一条都按“现象→原因→解决”的顺序写。能避开的坑提前避开,避不开的要知道怎么排查。
5.1 回测漂亮到离谱,实盘立刻翻车:数据穿越
现象:模型在测试集上的累计收益率曲线几乎45度向上,仿佛找到了印钞机,但换到另一段行情或者实盘就完全失效。原因:最常见的是用了train_test_split随机切分,或在全量数据上做了标准化,让未来数据参与了训练或统计量计算。解决:所有切分必须严格按日期推进,时间序列交叉验证或按日期二分,归一化统计量只能在训练集上fit,测试集只做transform。整篇论文里,这条是最可能被答辩老师追问的。
5.2 预测结果几乎全是上涨,模型成了“死多头”
现象:预测信号里买入占比超过80%,回测交易次数巨大,胜率却接近掷硬币。原因:标签阈值设太低,或者样本区间处于长期上涨阶段,正例占比超过70%,模型学到“都预测上涨”这个捷径。解决:先打印label分布,把up_threshold从0.02提到0.03或0.05,观察正负比例变化;如果仍然不平衡,再考虑对训练集做下采样。记住的一点是:先调标签,再调模型,顺序不要反。
5.3 停牌、涨跌停数据毁掉特征:极端值是怎么来的
现象:某一天收益率变成几百倍,或者vol_ratio冲到几十,模型预测概率直接两极分化。原因:停牌日的成交量为0,部分数据源还会出现close为0或与前一交易日相同,这会让pct_change计算出异常值;涨跌停日的成交量经常过度放大或萎缩,量比指标失真。解决:在预处理阶段先剔除成交量为0的行,再把ret_1d截断到正负11%以内,把vol_ratio截断到一定倍数。这属于特征清洗,要在算特征之前完成。
5.4 毛收益看着不错,扣掉成本就亏:交易成本吃利润
现象:策略胜率55%,单笔平均收益也不错,但净收益是负数。原因:交易频率太高,平均持仓只有两三天,手续费和滑点吃掉了大部分毛收益。解决:回测里必须加入成本模型,具体来说把双边万五以上的费用计入;然后提高阈值降低交易次数,比如从0.5提高到0.6;如果成本敏感度分析显示亏损,那就如实写成“信号有效性不足”,这仍然是合格的结论。毕业设计不要求你造出印钞机,但要求你诚实面对回测结果。
6. 进阶用法:阈值寻优、滚动窗口与资金曲线验证
到这一步,基础流程已经跑通,剩下的问题是如何让这个题目更有完成度。我建议做三件事:阈值网格寻优、滚动训练窗口、资金曲线验证。这三件事都不难,但能把作品和普通课程设计拉开差距。
阈值寻优的核心是“决策边界”与“模型”解耦。模型只负责输出概率,买不买是另一层决策,两件事混在一起讲永远说不清楚。把0.5到0.7的阈值每隔0.05扫一遍,记录每个阈值下的交易次数、胜率和累计收益,你就有了一个决策层参数的有效性表。
import numpy as np best_metrics = [] for threshold in np.arange(0.5, 0.71, 0.05): test_set["signal"] = (test_set["pred_prob"] >= threshold).astype(int) metrics = event_backtest(test_set, horizon=5) metrics["threshold"] = round(threshold, 2) best_metrics.append(metrics) result_table = pd.DataFrame(best_metrics).sort_values("累计收益", ascending=False) print(result_table)你会看到,阈值太低时交易次数多、成本侵蚀收益;阈值太高时交易次数太少,样本不足导致结果不稳定。最终选择哪个阈值,不只看累计收益,还要看交易次数是否足够支撑统计结论。这张表放进论文里,占一页纸,却能直观体现你对决策边界的理解。
滚动训练更贴近真实场景:不要把模型一次性训练完然后一直用,而是设定一个固定长度的训练窗口,比如最近500根K线,每20个交易日重新训练一次,再对最新一天给出预测。这种方法能减轻市场风格漂移对模型的影响,回测结果也更接近实际部署。实现上,用一个for循环控制窗口滑动即可,计算量也不大。
最后是资金曲线。事件式回测只能验证信号质量,资金曲线需要按每个交易日把信号映射成仓位变化:有信号那天买入,持仓第5天卖出,过程中可能多个信号重叠,要把资金拆到不同仓位。画两条线:一条是模型策略净值,另一条是买入持有基准净值。如果模型净值能稳定跑赢基准,同时最大回撤控制在合理范围,这个作品的完成度就非常高了。
我自己在带A同学做这个题目时,他最后的亮点恰恰文档里的失败实验和阈值敏感性表,这些老老实实的内容比一个虚高的准确率更能打动人。做完这套验证闭环,你应该明白一件事:股票预测这个方向,真正有长期价值的是严谨的数据切分、特征分析和回测框架,而不是某个模型参数带来的短期幻觉。别掉进“准确率很高等于能赚钱”的坑里,把每一步验证做到无懈可击,这个题目才真正被吃透了。希望帮到你。
本文还有配套的精品资源,点击获取