尧图精选

KNN股市预测源码解析:Python特征工程与回测避坑指南

🕒 发布时间:2026/10/1 22:30:25 📁 来源:尧图网络
简介这是一份基于KNN算法实现股市预测的Python项目源码包面向具备一定Python与机器学习基础、希望将分类算法应用于金融时序数据的学习者和开发者。核心思路是通过近邻样本的特征相似度对行情方向进行预估适合用于课程设计、算法对比实验或个人量化入门练习。资源共2个文件包含1个py主程序与1个md说明文档压缩包仅3KB结构精简便于直接阅读代码逻辑并快速运行验证。目前已有291人学习下载。读者拿到后可以查看完整的算法实现框架、数据预处理方式与预测流程同时借助说明文档理解参数设置和结果口径由于文件量少、无冗余依赖也适合作为改写与扩展的起点例如替换数据集、调整特征维度或对比不同K值效果。整体来看代码体量小巧但覆盖了从训练样本构建到预测输出的基本链路适合初学KNN与股市预测结合场景时快速上手。1. KNN做股市预测一次用「邻居投票」逼近股价涨跌的量化尝试如果你在找「python实现基于knn算法的股市预测项目源码.zip」这类资源大概率已经踩过一轮坑了GitHub上翻到的项目要么依赖早失效要么数据写死没法换股票要么跑完连个准确率都不打印。我的建议是别急着找现成压缩包按这篇笔记的思路用 KNN 把这个「股市预测」的完整链路自己搭一遍——从特征构造、滚动训练到回测评估每行代码都解释清楚最后你会得到一套能换数据、能调参、能说清楚「模型到底有没有用」的源码骨架。KNN 算法在股票量化分析里属于最容易被低估的那一类它没有复杂的数学推导不需要梯度下降却能靠「找历史上最相似的几天看它们之后怎么走」完成预测。对新手来说这是理解特征工程和模型评估的最佳切入点对熟手来说这是检验自己数据预处理功底的好靶子。这篇笔记不聊玄学只聊怎么用 Python 跑通一个可复现的 KNN 股市预测方案以及它在真实行情里到底值不值得投入。2. KNN 的预测逻辑与股市数据的适配边界为什么「分类涨跌」比「预测数值」更靠谱2.1 KNN 的三要素距离度量、K 值、投票规则KNNK-Nearest Neighbors的思路一句话就能讲清一个样本的标签由它在特征空间里最接近的 K 个已知样本投票决定。用在股市上就是拿今天的各项指标涨跌幅、成交量、换手率等去历史行情里找相似的日子看那些日子之后股价是涨是跌少数服从多数。落地时三个东西必须定死距离度量。最常见的是欧氏距离也就是在特征空间里算直线距离。但对股市数据不同特征的量纲差异极大——成交量可能是几百万手涨跌幅却只有几个百分点。如果不做标准化距离会被成交量这类大数值特征完全主导KNN 直接变成「看谁成交量接近」预测意义全无。所以动手写 KNN 的第一课永远是标准化后面代码里会反复强调。K 值。K 太小模型对噪声敏感某一天的异常数据就可能带偏投票结果K 太大模型把久远且相关性低的样本也纳入了投票决策边界过于平滑错过短期反转信号。经验上从 K5 起步用验证集扫 1 到 30 的奇数选出准确率最高的那个。不要靠直觉定 K后面会给扫描代码。投票规则。最朴素的是等权投票每个邻居一票。进阶做法是距离加权投票距离近的邻居权重更高。但实测下来在日线级别行情上距离加权的提升非常有限有时还不如等权稳定——因为行情数据的噪声太大「近邻更可信」这个假设本身就经常失效。所以第一版实现用等权就够了别一上来就上花活。2.2 回归预测收盘价为什么是坑KNN 回归在非平稳序列上的致命问题有读者可能要问KNN 不是也能做回归吗为什么不直接预测明天的收盘价能但我强烈不建议在股市里这么用。原因有三都是血泪经验换来的。第一股价序列是非平稳的。十年前的上证指数是 3000 点今天是 3400 点KNN 用欧氏距离找「最相似的历史日子」时数值上完全不在一个量级。你确实可以对价格做一阶差分或者用涨跌幅替代绝对价格但这一步本身就是特征工程的核心难点——很多源码包就是栽在这里拿原始价格算距离跑出来的「预测值」只是把历史价格平移了一下毫无信息量。第二回归评估指标会骗人。用均方误差MSE评估股价预测模型只要输出「贴近最近一天收盘价」MSE 就会很好看因为股价本身有惯性相邻两天差距本来就小。但这个「好成绩」对交易没有任何指导意义预测误差 5 毛钱照样不知道涨跌方向。第三交易决策需要的是方向不是数值。你不需要模型告诉你明天收盘价是 3.62 还是 3.71你需要它告诉你明天涨的概率大还是跌的概率大。所以把问题二值化——明收价高于今收价记为 1涨否则记为 0跌——比回归更贴合实际诉求准确率指标也更直白。这一章不是凑篇幅。你后面写代码的时候所有特征构造、评估方案都建立在这两个判断之上。如果方向选错代码越跑越偏最后只会得出「KNN 是玄学」的错误结论。3. 构造可复现的股市特征工程从原始行情到训练集四条必须守住的规则3.1 数据源与数据格式别用在线接口先跑通本地 CSV做行情数据最常见的选择是 tushare 这类 Python 库或者直接从财经网站下载日线 CSV。我建议第一版代码全用本地 CSV原因很实际在线接口有版本兼容问题、有权限限制、有网络波动调试时会把「代码 bug」和「数据问题」混在一起新人根本分不清是哪个环节翻车。你需要的数据列按最低标准是这五列date日期、open开盘价、high最高价、low最低价、close收盘价。如果还能拿到 volume成交量特征空间会更丰富。以东方财富或者新浪财经导出的日线数据为例CSV 表头大致长这样注意不同源的表头命名可能不一致读入后第一步就是统一列名import numpy as np import pandas as pd df pd.read_csv(stock_data.csv, encodinggbk) # 统一列名无论数据源叫什么一律改成小写英文标准名 df.columns [c.strip().lower() for c in df.columns] df df.rename(columns{ 日期: date, 开盘: open, 最高: high, 最低: low, 收盘: close, 成交量: volume }) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) # 只保留需要的列防止脏数据混进特征 df df[[date, open, high, low, close, volume]] print(df.head()) print(f数据范围: {df[date].min()} ~ {df[date].max()}, 共 {len(df)} 条)这段代码的逻辑是「先归一化数据形态再做任何计算」。统一列名和排序是两条铁律不同数据源的表头差异极大中文表头尤其坑不及时统一后面所有 pandas 操作都会报 KeyError按日期升序排列则是时间序列建模的大前提一旦乱序后面生成滞后特征时数据标签会串位。encodinggbk是一个常见的适配参数——国内很多股票软件导出的 CSV 是 GBK 编码用默认的 UTF-8 读会直接乱码或者报 UnicodeDecodeError。如果你的文件是 UTF-8 编码改成utf-8即可。3.2 特征构造用「涨跌幅 波动率」代替原始价格根治非平稳问题有了原始行情下一步是构造特征。这里的核心诉求是特征必须是平稳的也就是数值在时间维度上没有趋势。直接拿 close 价格做特征十年前 10 块钱的股票和现在 30 块钱的同一只股票在 KNN 的距离计算里会被判为「差异巨大」但它们的技术形态可能完全一致。所以第一版特征我建议只保留这几类# 1. 涨跌幅今天的收盘价相对于昨天的变化率 df[ret_1] df[close].pct_change(periods1) # 2. 多日累计算涨跌幅3日和5日动量 df[ret_3] df[close].pct_change(periods3) df[ret_5] df[close].pct_change(periods5) # 3. 振幅当天最高价与最低价之差相对收盘价的比率代表日内波动 df[amp] (df[high] - df[low]) / df[close] # 4. 量价关系成交量变化率放量缩量在KNN里是很有区分度的特征 df[vol_ratio] df[volume] / df[volume].rolling(window5).mean() # 5. 当日位置收盘价在当日区间内的相对位置(close - low) / (high - low) df[pos] (df[close] - df[low]) / (df[high] - df[low]).replace(0, np.nan) # 6. 标签明天收盘价是否高于今天是1否0 df[label] (df[close].shift(-1) df[close]).astype(int) # 清理NaN前5行和最后1行因滞后/超前操作无法计算 df df.dropna().reset_index(dropTrue) features [ret_1, ret_3, ret_5, amp, vol_ratio, pos] print(df[features [label]].describe())这里要特别说明shift(-1)生成标签的做法这是时间序列预测最容易出错的环节。close.shift(-1)表示「把下一天的收盘价搬到今天这一行」所以label的含义是「今天看明天涨不涨」。预测时我们要用截至今天收盘的数据去预测 label绝不能把明天的数据混进特征。pos特征在高开低走或一字板时high low会导致分母为零我用.replace(0, np.nan)规避了除零错误后面 dropna 会顺带清理这些异常行。你在跑自己的数据时如果发现样本量明显少了一截优先怀疑这类除零和窗口计算产生的 NaN。3.3 训练集与测试集的切分时序数据禁止随机打乱必须前训练后测试这是本项目里最关键、也最容易翻车的一步。分类问题里常见的train_test_split(X, y, test_size0.2, random_state42)——在这里不能直接用。因为股市数据是时间序列用未来数据训练、用过去数据测试等于开卷考试模型会把未来信息「泄漏」进训练集测试准确率虚高实盘一用就现原形。正确的是按时间顺序切分前 80% 的历史数据做训练后 20% 做验证并且验证集的每一天它用到的 K 个邻居都必须来自它之前的日子这才是模拟真实交易场景。split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx].copy() test_df df.iloc[split_idx:].copy() X_train train_df[features].values y_train train_df[label].values X_test test_df[features].values y_test test_df[label].values print(f训练集: {len(X_train)} 条, {train_df[date].min()} ~ {train_df[date].max()}) print(f测试集: {len(X_test)} 条, {test_df[date].min()} ~ {test_df[date].max()})split_idx的计算方式决定了两件事训练集覆盖多长历史、测试集覆盖多长时段。80/20 是常见做法但这不玄学——测试集至少要覆盖一个完整的中期趋势才能看出模型在不同行情下的表现差异。如果你的数据只有一两年的日线建议把比例调到 85/15确保测试集至少有两三个月否则评估结论的可信度极低。到这里你已经完成了本项目最核心的 60% 工作。后面 KNN 的代码反而是最机械的部分——但标准化和 K 值选择还有两个暗坑要避开下一章细说。4. 用 Python 跑通 KNN 训练与预测标准化怎么救回你的准确率K 值怎么扫不翻车4.1 标准化必须在全量数据上拟合但只能用训练集算参数KNN 对特征缩放高度敏感这一点前面已经反复强调。这里要讲的暗坑是StandardScaler 的 fit 只能在训练集上调用transform 再应用到训练集和测试集。如果先对全量数据 fit均值和方法则已经偷看了未来数据测试集的评估同样失真。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 在训练集上学习均值和方差 X_test_scaled scaler.transform(X_test) # 用训练集的参数转换测试集 # 验证一下缩放效果 print(训练集均值(近似0):, X_train_scaled.mean(axis0).round(6)) print(训练集标准差(近似1):, X_train_scaled.std(axis0).round(6))这两行代码的顺序就是铁律先 fit再 transform只 fit 训练集不碰测试集。很多开源源码包都在这里偷懒——直接scaler.fit(X_all)跑出来的回测曲线漂亮得不真实一换时间段立刻打回原形。你如果拿到一份现成的 KNN 股市预测源码第一件事就是检查它的标准化是 fit 在哪份数据上的。为什么要用 StandardScaler 而不是 MinMaxScaler两种方案在我们的特征集上都能用但 StandardScaler 对离群值的稳健性更好——涨跌幅偶尔出现极端值比如 -9.98%Z-score 标准化后这个点仍是高方差样本而 MinMax 会将所有值压到 [0,1]离群点会把正常样本压缩到极小的区间里距离计算的有效精度反而下降。4.2 K 值遍历在验证集上找最优邻居数而不是拍脑袋定 3 或 5K 值是这个项目里最值得调的参数捷径是把 1 到 30 的奇数都跑一遍用验证集准确率挑最优。注意这里说的验证集是训练集内部细分的后段数据不是之前留出的测试集——测试集要在你确定好 K 之后才能碰一次这是模型评估的基本洁癖。from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score best_k 1 best_score 0 results {} # 在训练集内再做一次时序切分模拟验证过程 val_ratio 0.15 val_size int(len(X_train_scaled) * val_ratio) X_tr X_train_scaled[:-val_size] y_tr y_train[:-val_size] X_val X_train_scaled[-val_size:] y_val y_train[-val_size:] for k in range(1, 31, 2): # 只试奇数偶数有平票风险 model KNeighborsClassifier( n_neighborsk, weightsdistance, # 距离加权K较小时尤其重要 algorithmauto, # 数据量小自动选暴力搜索即可 p2 # 欧氏距离 ) model.fit(X_tr, y_tr) y_pred_val model.predict(X_val) score accuracy_score(y_val, y_pred_val) results[k] score if score best_score: best_score score best_k k print(f最优K值: {best_k}, 验证集准确率: {best_score:.4f})weightsdistance和之前说的「等权优先」不矛盾——在 K 值扫描阶段距离加权能让小 K 的波动更平滑减少单点噪声的干扰。你没看错这里我改主意了第一版手工原型用等权没问题但进入网格搜索阶段距离加权是更好的默认选择因为它的投票对异常邻居有天然的衰减。验证集是从训练集尾部切出来的 15%它依旧是「历史数据」不会引入未来信息。这一步的本质是模拟「站在某个时间点对之后一段未知行情做预测」和真实交易场景对齐。4.3 测试集验证与预测结果输出输出一张带日期、真实涨跌、预测涨跌的明细表选定最优 K 后终于可以拿它去碰测试集了。这一步的输出不能只是一个准确率数字最好保存一份明细表——每一行是哪一天、模型预测涨还是跌、实际上涨还是跌、以及预测上涨的概率这份表是你后面所有复盘和进阶工作的原材料。final_model KNeighborsClassifier( n_neighborsbest_k, weightsdistance, algorithmauto, p2 ) final_model.fit(X_train_scaled, y_train) # 预测测试集predict_proba拿到上涨概率用于后续筛选高置信度样本 y_pred final_model.predict(X_test_scaled) y_prob final_model.predict_proba(X_test_scaled)[:, 1] test_accuracy accuracy_score(y_test, y_pred) print(f测试集准确率: {test_accuracy:.4f}) # 组装结果明细 result_df test_df[[date, close, label]].copy() result_df[pred_label] y_pred result_df[prob_up] y_prob result_df[hit] (result_df[label] result_df[pred_label]).astype(int) print(result_df.head(10)) print(f整体预测命中率: {result_df[hit].mean():.4f})predict_proba的输出比predict更有价值——它给出的是「上涨概率」连续值而不是硬分类的 0/1。进阶用法是只对prob_up 0.7或prob_up 0.3的样本做交易决策中间地带直接放弃。KNN 的置信度并不完美但在筛选信号上确实有效这一点后面第 6 章会展开验证。到这一步你手里的源码已经具备了完整的 KNN 股市预测闭环数据清洗、特征构造、时序切分、标准化、参数调优、测试评估。但先别急着庆祝接下来这章谈的五个坑每一个都是你换数据、换股票时必然会遇到的。5. KNN 股市预测避坑指南五个把模型带偏的经典翻车现场5.1 特征包含未来函数成交量里的量比算着算着就穿越了现象模型在训练集上准确率高达 0.9一上测试集立刻掉到 0.5你怀疑人生以为 KNN 根本没用。原因特征构造里出现未来函数。最常见的穿越来源是量比——很多新手用「当日成交量 / 截止当前的平均成交量」这类数据库统计口径来构造特征但拉取数据时算的是全天总量盘中等于提前知道了收盘后的成交量。另一种隐蔽的穿越是用rolling窗口计算均值时没有留意窗口是否包含未来行。解决逐项检查每个特征的生成逻辑凡是用到shift()的确认偏移方向是「取过去」而不是「取未来」。负责任的做法是把特征工程代码和标签代码放在同一段打印出 DataFrame 后手动抽查三天数据用笔算一遍特征值是否符合当天的真实信息。5.2 标准化时 fit 到了全量数据——测试集评估形同虚设现象测试集准确率异常稳定稳定到让人起疑或者测试集正确率远高于训练集明显不合常理。原因scaler.fit(X_all)偷看了全量数据的均值方差测试集的分布信息已经以参数形式存在于标准化过程里。这在严格的时间序列评估中属于信息泄露属于 KNN 项目里最隐蔽的「开卷考试」。解决严格按训练集 fit、训练集和测试集分别 transform 的顺序写代码。开一个 Code Review 环节让同事找找你的预处理代码里有没有fit出现在切分之前。这个检查不到 1 分钟能报销掉你后面所有盲目自信。5.3 K 值取偶数平票时模型随机站队现象同一份数据跑多次结果之间差一点点但说不清差在哪或者代码里没有设置随机种子每次预测输出不一致。原因K 取偶数时投票结果可能出现 4:4、5:5 这样的平票sklearn 的默认策略是随机选取一个类别导致模型输出不稳定也让你怀疑自己的源码包有 bug。解决K 值遍历时只试奇数即可从根源上规避平票。另外在模型的初始化参数里补上random_state42目的是让结果可复现——虽然 KNN 本身没有随机性但平票时的随机决策有了种子整个链路就是确定性的了。5.4 涨跌平三分类被压成二分类丢失大量「平盘」信息现象预测准确率看着有 60%但实盘交易时胜率惨不忍睹频繁止损。原因市场里有大量「几乎走平」的日子——涨跌幅在 0.2% 以内。二分类强迫模型把这类样本硬归入涨或跌误差噪声被放大更离谱的是模型学会了直接预测「涨」就能拿到 52% 准确率因为 A 股上涨天数略多于下跌天数。解决把 label 改成三分类涨幅大于 0.5% 记 1跌幅小于 -0.5% 记 -1中间区域记 0。KNN 依旧照常做多分类但评估时单独统计三类的混淆矩阵。你会发现模型对「大涨大跌」的识别准确率常常远高于对「微涨微跌」的这个信号会指导你后续的交易筛选策略。5.5 测试集覆盖时间太短刚好撞上一波单边行情现象换一只股票测试准确率从 62% 掉到 48%你认为模型报废了换个时间段再测又回到 60%来来回回折腾。原因测试集只在某一段单边牛市或熊市里评估行情有惯性KNN 在单边行情下预测非常简单——昨天涨今天就大概率涨。这种准确率代表的是「行情惯性」而非模型能力换个震荡行情立刻打回原形。解决测试集至少覆盖两种不同行情状态比如一段震荡加一段趋势。更稳妥的做法是滚动回测每次用过去 250 个交易日训练预测未来 20 个交易日然后滑动窗口重复多次把多段预测结果的准确率汇总。这个方案后一章会给一个最小实现。6. 让 KNN 预测真正可用的进阶技巧加一根均线做信号过滤再看模型还有没有救前面五章跑通了基础流程但实盘不是靠 60% 准确率就能赚钱的——60% 的预测准确率配合 1:1 的盈亏比扣掉手续费后依然是负期望。进阶的第一件事是给模型预测加一道过滤条件均线趋势框架。思路很简单KNN 负责判断「日线级别涨跌形态」均线负责判断「中期趋势方向」。均值之上只做多、均值之下只做空或空仓KNN 的输出只有在和趋势方向一致时才作为有效信号这样可以过滤掉大量逆势信号把模型从「预测准」推向「交易能赚」。# 在原先的df上增加一条20日均线 df[ma20] df[close].rolling(window20).mean() # 用之前的时序切分逻辑测试集里只保留趋势与预测方向一致的样本 result_df[ma20] test_df[close].rolling(20).mean() result_df[trend_up] (test_df[close] result_df[ma20]).astype(int) # 有效信号模型预测上涨且当前价格在20日均线之上 valid_signal result_df[(result_df[pred_label] 1) (result_df[trend_up] 1)] invalid_signal result_df[(result_df[pred_label] 1) (result_df[trend_up] 0)] print(f全部看多信号: {len(result_df)} 天) print(f趋势过滤后有效信号数: {len(valid_signal)} 天) print(f趋势过滤前命中率: {result_df[result_df[pred_label] 1][hit].mean():.4f}) print(f趋势过滤后命中率: {valid_signal[hit].mean():.4f})ma20是 20 日简单移动均线这里的 20 是交易月均交易日数属于参数里的「经验默认值」可以直接用。两个命中率的对比会给你一个非常直接的结论KNN 的预测是否具备「在趋势行情中更好的表现」。如果过滤后命中率显著提高说明模型确实学到了一定的形态识别能力如果提升不明显那你的特征或者 K 值可能还有优化空间。最后一个进阶验证把测试数据换成另一只股票或另一段时期原封不动重跑一遍。这只花你 10 分钟但它的意义是检查模型是否过度拟合了第一只股票的形态。我的习惯是拿三只不同风格的股票一只大盘蓝筹、一只中小板题材、一只金融股做交叉验证。这个过程通常会刷新你对这个源码包的认知——大多数情况下KNN 在风格相近的股票上泛化尚可跨风格就明显吃力这是特征空间不够丰富的信号而不是 KNN 本身的问题。这些年写过不少量化原型KNN 这个方向我留下的最深教训是它考验的从来不是模型而是你对数据的态度。特征是否平稳、切分是否严格、标准化有没有泄漏任何一步偷懒模型都会以「准确率虚高、实盘翻车」的方式加倍还给你。希望这份源码笔记能帮你少踩几个我从坑里爬出来才看清的坎把这套流程变成你自己的股市预测工具箱。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →