kNN股票预测实战:fastdtw相似度计算与避坑指南
简介一份基于k近邻算法实现的股市预测源代码其核心思想是通过历史样本的相似性来估计未来走势适用于对机器学习与量化交易感兴趣的开发者帮助读者利用历史行情数据预测未来价格。压缩包共包含两个文件一个Python脚本用于核心预测逻辑一个Markdown文档说明环境配置与调用方法整体体积仅两KB大小文件精简、便于阅读和二次修改。代码集成了pandas、tushare、numpy、scipy、fastdtw等流行库通过main函数传入股票代码和k近邻参数即可运行并支持以周为单位的长周期预测便于用户对比不同参数下的准确率。目前已有121人学习下载无论对于机器学习初学者还是量化分析爱好者都具有参考价值适合作为kNN算法在金融领域应用的入门实例也可在此基础上调整特征与参数构建更稳健的预测模型。1. 拿 kNN 去预测股票先接受它会错才有讨论价值股市预测这个题目用 Python 和 kNN 算法去碰属于典型的「听起来不太靠谱、拆开看却很有教学价值」的资源。原因很简单kNN 不假设数据服从某种分布它只是朴素地认为「历史上和当前最像的时刻接下来的走势也大概率像」。这个逻辑在量化分析里叫相似性匹配比单纯的均线金叉多了一层可解释性也比深度学习少了一个黑匣子。这份资源的核心代码集中在share_foresee_end.py依赖 pandas、tushare、numpy、scipy、fastdtw 这些库交互方式极简——在命令行里调用main(股票代码, k)就能拿到预测结果。它适合两类人。第一类是刚学完机器学习基础、想找个真实金融数据集练手的新手kNN 在这里没有复杂的网络结构所有中间结果都能打印出来一步步看第二类是做股票量化分析但不想一上来就上 LSTM 这类重模型的从业者想先用一个可解释的基线模型摸摸数据脾气。我拆完这份资源之后最直接的感受是它的价值不在预测准确率而在把「时间序列怎么喂给 kNN」这件事完整地演示了一遍——这里面的坑比算法本身多得多。2. 环境准备与数据链路按依赖库的角色排好分工2.1 七个库分别干什么先把这份资源用到的依赖库理一遍因为它们的组合方式直接决定了你后面会踩什么坑。pandas 是主力负责所有 DataFrame 操作从行情数据到特征表都靠它numpy 做数值计算kNN 里的矩阵运算和归一化都离不开tushare 是数据源拉历史日线数据就靠它这也是整条链路里最容易翻车的一环。scipy 虽然在这份资源里不是主角但 knn 的某些距离计算比如调用scipy.spatial.distance或者统计校验会用到它fastdtw 是这份资源的一个亮点用于计算两条时间序列之间的动态时间弯曲距离后面会展开说。剩下的 operator 和 math 属于工具型库。operator 提供itemgetter这类函数式工具在排序近邻样本时特别顺手——你按距离排完序要找距离最小的 k 个样本的标签用operator.itemgetter要比写 lambda 快且清晰。math 库主要是提供sqrt、pow这类基础数学函数如果距离计算是手写的而不是完全交给 fastdtw就会用到它。这些库的角色如果一句话概括tushare 负责把数据取进来pandas 负责把数据整理成 kNN 能吃的形状fastdtw 负责回答「哪两段历史行情最像」剩下的都是辅助。角色分清楚之后你遇到报错时排查方向就明确多了——数据源报错去查 tushare维度报错去查 pandas 的 shape距离结果异常去查 fastdtw 的输入格式。2.2 从安装到首次获取数据这份资源的使用说明要求在交互式命令行中运行脚本所以第一件事就是装依赖。我用虚拟环境安装的完整命令如下python -m venv venv source venv/bin/activate pip install pandas numpy scipy tushare fastdtw参数说明venv是虚拟环境目录名这里用source venv/bin/activate激活Windows 下对应的是venv\Scripts\activate。这几个库建议一次装齐否则跑到一半发现缺库再安装的话可能因为版本变动影响已经算好的数据格式。装完之后先别急着跑 kNN先验证数据链路通不通。tushare 有两种接口风格老版本的get_hist_data不需要 token但接口已经停了现在推荐的是 tushare pro 的pro.daily需要注册后拿 token。我通常先跑这一段确认数据能拉下来import tushare as ts import pandas as pd ts.set_token(你的token) pro ts.pro_api() df pro.daily( ts_code600036.SH, start_date20200101, end_date20231231 ) print(df.head(10)) print(df.shape) # 确认拿到多少行逻辑说明ts.set_token只需要执行一次token 会缓存在本地pro.daily返回的是最近日期在最前面的数据这点和很多人的直觉相反后面构造训练集时要特别注意排序方向。实测中ts_code600036.SH的格式是「代码 交易所后缀」上海是.SH深圳是.SZ写错会直接报错或者返回空数据。如果你不想注册 tushare pro还有一个备选方案是用akshare替代数据源但需要注意接口字段名有差异。这份资源本身是围绕 tushare 写的所以建议先按它的路子走通再考虑替换。2.3 把行情数据加工成 kNN 能吃的形状kNN 算法的输入是「特征矩阵 标签」股票数据本身是时间序列不能直接塞。资源和 README 里没有给全特征构造的代码但按照这个场景下最常见的做法我会把一段连续的历史行情切成长度为window的序列作为特征用序列末尾之后若干天的涨跌方向作为标签。这里的核心代码结构如下def build_dataset(df_close, window20, horizon5): data_X, data_y [], [] for i in range(len(df_close) - window - horizon): seq df_close[i : i window] # 特征:最近 window 天的收盘价 future df_close[i window : i window horizon] label 1 if future[-1] df_close[i window - 1] else 0 # 标签:未来 horizon 天内是否上涨 data_X.append(seq) data_y.append(label) return np.array(data_X), np.array(data_y)逻辑说明这个函数把一个一维收盘价序列切成长度为window20的滑动窗口horizon5表示看未来 5 天的涨跌。标签的定义是「未来第 5 天是否比第 20 天高」这是一个二分类定义0 和 1 分别代表下跌和上涨。为什么用窗口的后沿做基准而不是用未来 5 天的均价是因为 kNN 要预测的是方向而不是具体点位——方向定义越简单模型越不容易被噪声带偏。参数调法上window取 20 对应约一个月的交易日horizon取 5 对应一周。这两个参数你可以按自己的持仓周期改做短线的把window缩到 5做趋势的把horizon拉到 20。但改参数之前一定要重新思考标签的定义是否还成立——horizon比window还大时特征和标签的时间窗口会重叠这种重叠在时间序列预测里是有争议的因为它会引入前视偏差。3. 相似度怎么算fastdtw 在这里比欧氏距离好在哪3.1 K 线序列对齐的难点kNN 的核心动作是「找最近的 k 个邻居」而这个「最近」完全取决于距离度量。如果你直接用欧氏距离去算两条股价序列的相似度会遇到一个很尴尬的情况两条序列只是时间轴上有伸缩比如一段是先横盘后拉升另一段是先拉升后横盘它们实际走势形态很像但欧氏距离算出来却很远。这正是这份资源引入 fastdtw 的原因。动态时间弯曲DTW的思路是允许两个时间点在一定的窗口范围内错位对齐找到一条总体代价最小的匹配路径。而 fastdtw 是在这个基础上的加速版本它先把序列粗粒度化再逐层精修路径大幅降低了原始 DTW 的 O(n²) 复杂度。放到 kNN 场景里它的直接收益是当历史行情包含了不同速度的同类走势时DTW 能把它们正确地归为「相似」。拿具体的例子说同样是「跌 3 天再涨 2 天」的形态一次走了 5 天另一次走了 7 天欧氏距离会因为长度不同直接卡死需要先插值对齐fastdtw 则直接接受两条长度不同的序列内部完成对齐。这在真实股市数据里太常用了因为市场节奏不可能每次都一样。3.2 用 fastdtw 计算邻居距离的完整流程在 kNN 的预测函数里每个待预测样本要和历史上每一个样本算一次距离。这份资源的核心逻辑可以简化为from fastdtw import fastdtw import numpy as np from operator import itemgetter def knn_predict_one(test_seq, train_X, train_y, k): distances [] for i, train_seq in enumerate(train_X): distance, _ fastdtw(test_seq, train_seq) distances.append((i, distance)) distances.sort(keyitemgetter(1)) neighbors distances[:k] votes [train_y[i] for i, _ in neighbors] pred 1 if sum(votes) k / 2 else 0 return pred, distances逻辑说明fastdtw(test_seq, train_seq)返回两个值第一个是最小累积距离第二个是路径我们在 kNN 里只用距离值。itemgetter(1)的意思是按距离那一列排序而不是按样本索引排。最后sum(votes) k / 2是多数投票的简化写法等于说「k 个邻居里如果超过一半在未来一段时间是上涨的就预测上涨」。这段代码有一个必须注意的对齐问题test_seq和train_seq必须来自同一只股票因为不同股票的绝对价格水平差异巨大直接混在一起算 DTW 距离会完全被高价股的数值主导。这份资源的main函数里也是单只股票内部做历史相似性匹配这一点是算法的前提不是可选项。3.3 k 值和其他参数的联动关系k 这个参数在资源的使用说明中被重点强调说它「可能引起准确率和估计结果的变化」。实际用下来的经验是k 影响的是投票池的大小k 太小比如 1 或 2容易过拟合到单次偶然的相似走势上k 太大比如 50 以上则会让历史上一堆不太相关的样本也挤进来投票把信号稀释掉。我一般会从k5开始以 3 为步长往上扫画一条 k 与准确率的折线图来选。另外window和ktype是携手工作的。资源说明里提到main(股票代码, k, ktypeW)可以按周为单位预测。这个参数的实际意义在于当它等于默认值时用日线数据的滑动窗口去找相似历史当它等于W时数据先被重采样成周线然后用同样逻辑做匹配。周线数据的噪声小但样本量也少了差不多 5 倍k 值要相应调小——我在日线上试出来最优 k15切到周线后 k5 反而更好。这就是为什么你拿到代码后不能直接套用别人给的最佳 k 值。4. 避坑手记数据对齐、归一化和 k 值玄学4.1 踩坑一tushare 拉回来的数据是倒序的预测结果整体偏移现象第一次跑通代码后发现预测准确率长期徘徊在 45% 上下而且预测上涨的样本明显偏多。把预测结果和真实走势对齐画出来发现标签整体错位了一天。原因tushare pro 的daily接口默认按日期倒序返回最新的数据在最上面。如果直接按行号切窗口切出来的序列是时间反演序kNN 找出来的「相似历史」根本不在同一个时间方向上。这个问题在资源说明里没有提但几乎所有第一次用 tushare pro 的人都会撞上。解决在构造数据集之前强制按日期升序遍历。加一行df df.sort_values(trade_date)然后再做窗口切分。从那以后我每次拉完数据第一件事就是打印头和尾各 5 行日期确认时间方向这个习惯保留到了现在。4.2 踩坑二不同股票混在一起算邻居结果被高价股绑架现象把main的参数从平安银行改成贵州茅台之后kNN 找出来的邻居几乎全是贵州茅台自己的历史片段而且距离小得离谱。原因kNN 的距离度量没有归一化处理时绝对价格水平直接决定了距离大小。茅台股价 1700 元平安银行 11 元前者的波动幅度是后者的上百倍计算出来的 DTW 距离完全被高价股的数值变化覆盖。这不是 fastdtw 的问题而是所有距离类算法面对未归一化数据时的通病。解决在计算距离之前对每个序列做 z-score 归一化也就是(x - mean) / std。这样做之后模型关注的是「走势形态」而不是「绝对价格」不同股票之间的相似性才有了可比性。注意归一化要在单只股票内部做不能全市场混在一起算均值和标准差否则又引入了横截面偏差。4.3 踩坑三k 取偶数导致投票平票预测结果悬空现象k 取 10 或 20 时偶尔出现预测概率正好 0.5 的情况代码按大于一半判涨但换个运行环境可能判跌结果不稳定。原因多数投票在 k 为偶数时天然存在平票可能这在分类问题里是老问题但在时间序列 kNN 里容易被忽视因为大家习惯性地以为 k 小一点就不会平票。解决最简单的做法是把 k 固定为奇数另一种做法是平票时回退到距离更近的邻居的标签而不是强行判涨。我在自己的脚本里加了一个平票回退逻辑如果涨跌票数相同比较距离最近的前两个邻居里谁的距离更小就采纳谁的标签。这样比随机打破平票要稳。4.4 踩坑四fastdtw 在长序列上慢到怀疑人生现象把window调到 60 以上同时数据集覆盖三年行情时一次预测要几十秒扫 k 参数的时候整个下午就没了。原因fastdtw 虽然比原始 DTW 快但它的复杂度仍然和序列长度近似相关。window60的 60 维序列在样本量几千的情况下每预测一次都要计算几千次距离这个乘法积累起来很可观。解决两个手段并用。第一对训练集做下采样不要每一条历史都参与计算先按某种规则抽出一部分候选比如每隔 3 条取 1 条这样距离计算量直接降到三分之一第二把window控制在 20 以内因为 kNN 的窗口太大时真正决定走势的其实是最近几天的变化更早的价格信息只是基线对相似性判断贡献很小。这也是资源使用说明里建议多改 k 值而不是多改 window 值的隐含原因——改 window 的计算代价比改 k 高得多。4.5 踩坑五复现时缺库没装全报错信息指向不清现象直接运行python share_foresee_end.py报ModuleNotFoundError: No module named fastdtw但资源说明明明写了需要这些库安装之后又报另一个库缺版本。原因资源本身没有提供 requirements.txtREADME 里也只是列出了库名没有锁版本。fastdtw 这个库在 PyPI 上存在但更新少它依赖的 numpy 版本如果太新某些接口会有兼容问题。解决我当时的做法是重新建一个干净的虚拟环境按pip install pandas numpy scipy tushare fastdtw的顺序逐个装每装一个就import验证一次。全部验证通过后再跑主脚本。这个笨办法比一次性装完再排错要快得多。另外尽量用 Python 3.8 到 3.10 之间的版本太新的 Python 有些包还没跟上。这个经验不针对这份资源而是所有依赖古老库的机器学习项目的通病——环境和代码逻辑要分开排查。5. 复现后的验证技巧用回测口径判断这次预测算不算数5.1 别拿历史全量做训练留出最后一段做检验kNN 没有训练过程所以没有经典的 train/test 划分焦虑但如果你把 2015 到 2023 年的数据全拿来当训练集去预测 2024 年的走势这个操作是合理的反过来如果你在 2023 年 11 月做了一次预测然后把 2023 年 12 月的数据也放进训练集这就是典型的前视偏差。我验证这份资源时的做法是把时间序列按 8:2 切开前 80% 作为历史库后 20% 作为验证段在验证段上逐日模拟「用当时能看到的所有历史做预测」再和真实走势对比。这个逐日模拟的循环结构很重要它模拟的是真实交易场景预测 1 月 5 日时你只有 1 月 4 日及之前的数据。代码里要让训练集边界跟着验证点滚动推进不能一次性把验证段之后的未来数据也放进历史库。能在小数据上跑通 kNN 不稀奇把这个滚动回测的时间边界处理对才说明你真的理解了这个算法在时间序列上的用法。5.2 准确率之外还要看预测的分布是否均匀我跑完这份资源之后整理了一张表格记录不同 k 值下的表现包括准确率、预测上涨的比例、以及预测上涨时真实上涨的条件概率。只看准确率时 k15 是 58%k5 是 52%但进一步看k15 的预测结果里 70% 都在喊「涨」这在实际决策里几乎没有区分度而 k5 虽然整体准确率低但遇到它喊「涨」的时候真实上涨概率接近 63%。这说明 k5 更适合做交易参考虽然它整体会错得更多但它在给出信号时更有信息量。从那以后我每次跑这类预测模型都强制自己走一遍三件事查时间方向、查归一化范围、查训练集时间边界。这三件事看着基础但每一次预测结果「玄学般地不对」时回头检查基本都是它们三个之一在出问题。这份资源的价值也正在于此——它让我踩过了这些坑也希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →