尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

KNN时间序列相似性匹配:量化形态信号生成原理与实战

发布时间:2026/9/23 18:18:17

资讯中心
01
ARTICLE

KNN时间序列相似性匹配:量化形态信号生成原理与实战

KNN时间序列相似性匹配:量化形态信号生成原理与实战
简介本资源是一份基于KNN算法的Python股市预测实践代码包面向金融数据分析初学者、量化入门学习者及对机器学习在时序预测中应用感兴趣的开发者。它提供轻量级可运行方案帮助用户理解如何利用历史股价数据进行趋势预测并掌握tushare获取行情、pandas清洗、fastdtw度量时间序列相似性等关键环节。压缩包共2个文件1个核心预测脚本share_foresee_end.py 1份详细README.md说明文档总大小仅2KB结构简洁开箱即用。已有121人学习下载适合快速部署验证KNN在短周期日频与周频预测中的效果差异内含main函数调用示例、k值调优提示及ktype参数切换逻辑便于读者开展参数敏感性实验与结果对比分析。1. 这不是“预测股价”而是用 KNN 做时间序列相似性回溯一个被低估的量化信号生成器很多人下载这个LiYongknnSuanFapythonShiXianDeGuShiYuCe-master.zip后第一反应是“它能告诉我明天该买哪只股票”——然后运行main(000001, 5)看到一行输出就关掉终端。结果失望、删包、发帖吐槽“KNN 股市预测就是玄学”。但真相是它根本不是在预测绝对价格而是在历史行情中找“形态最像今天”的 N 段周期并统计这些相似周期后续 1~5 日的涨跌分布输出概率化信号。这本质是一个无监督的时间序列模板匹配引擎和 MACD、RSI 同属技术分析逻辑层但用距离度量替代了指标公式。它不依赖任何财务因子只吃 OHLCV 数据不训练模型权重只做最近邻检索不承诺盈亏只提供“过去 3 年里出现过 7 次和今天 K 线组合高度相似的走势其中 5 次后 3 日上涨超 2%”——这才是share_foresee_end.py真正在干的事。适合想快速验证“形态复现率”假设的量化新手、想给策略加一层辅助过滤的实盘交易员以及需要可解释性信号而非黑匣子输出的研究者。如果你期待它输出“明日收盘价12.34 元”那确实会翻车但若把它当做一个轻量级、可调试、可溯源的形态信号发生器它比很多封装过深的“AI 预测平台”更透明、更可控。2. 从数据获取到信号生成完整链路拆解与关键参数含义2.1 数据源选择与 tushare 接口适配逻辑项目依赖tushare获取 A 股日线数据但原文未说明版本兼容性。实测发现tushare 2.x旧版接口已停用必须升级至 tushare 4.xpro 版且需注册获取 token。原始README.md中的ts.get_k_data()已失效实际代码中share_foresee_end.py使用的是pro_api方式调用。关键适配点如下# share_foresee_end.py 中实际使用的数据获取片段已修正 import tushare as ts ts.set_token(your_token_here) # 必须替换为你在 tushare 官网申请的 token pro ts.pro_api() def get_stock_data(ts_code, start_date20180101, end_dateNone): 获取指定股票的日线数据前复权 ts_code: 000001.SZ 格式注意后缀 start_date: YYYYMMDD 字符串 end_date: 默认为今日 返回: pandas.DataFrame含 open, high, low, close, vol 字段 df pro.daily(ts_codets_code, trade_date, start_datestart_date, end_dateend_date) df df.sort_values(trade_date).reset_index(dropTrue) # 补充前复权处理tushare pro 默认返回前复权数据但需确认 # 若需后复权应调用 adj_factor 并手动计算 return df[[trade_date, open, high, low, close, vol]]提示ts_code必须带交易所后缀如000001.SZ或600000.SH否则pro.daily()返回空 DataFrame。这是新手踩坑最高频点——直接输000001会静默失败不报错但df为空后续所有计算基于空数据最终knn距离全为infmain()报ValueError: attempt to get argmin of an empty sequence。2.2 特征工程为什么用 DTW 而不是欧氏距离KNN 在时间序列场景下若直接用close列做欧氏距离会严重受起始点偏移影响。比如两段完全相同的上涨波形若一段从 10 元启动、另一段从 15 元启动欧氏距离巨大但形态本质一致。项目引入fastdtw库正是为解决此问题from fastdtw import fastdtw from scipy.spatial.distance import euclidean def dtw_distance(series_a, series_b): 计算两段价格序列的 DTW 距离归一化后 series_a, series_b: 一维 numpy array长度需一致代码中会 pad 或截断 # 关键预处理z-score 归一化消除量纲差异 a_norm (series_a - np.mean(series_a)) / (np.std(series_a) 1e-8) b_norm (series_b - np.mean(series_b)) / (np.std(series_b) 1e-8) distance, _ fastdtw(a_norm, b_norm, disteuclidean) return distance # 在 knn_search 函数中调用 distances [] for i in range(len(history_windows)): d dtw_distance(test_window, history_windows[i]) distances.append(d)参数说明fastdtw的radius参数默认为1表示局部约束窗口大小。增大 radius 可提升匹配精度但增加计算量实测radius2对 A 股日线已足够radius5会使单次搜索耗时翻倍disteuclidean是 DTW 的底层距离函数不可替换为cosineDTW 要求度量满足三角不等式余弦不满足归一化必须做否则高价股如贵州茅台与低价股如 ST 股无法公平比较——这是fastdtw在金融时序中被误用的重灾区。2.3 KNN 检索与信号生成main()函数的三层逻辑main(ts_code, k, ktypeD)表面只接收三个参数但内部执行三阶段操作阶段功能关键输出可调参数1. 窗口切片将历史数据按ktype切分为固定长度窗口默认ktypeD→ 20 日ktypeW→ 10 周test_window: 当前最新窗口history_windows: 所有历史窗口列表window_len在share_foresee_end.py第 42 行硬编码为20日或10周需手动修改2. DTW 距离计算对test_window与每个history_windows[i]计算 DTW 距离distances: 长度为 N 的距离数组radius参数影响精度/速度平衡3. 信号聚合取距离最小的k个邻居统计其后续forecast_days3日的涨跌方向close[forecast_days] close[0]up_ratio: 上涨邻居占比avg_return: 平均收益率std_return: 收益率标准差forecast_days在main()函数内固定为3不可外部传入注意ktypeW并非简单按周聚合而是将日线数据先 resample 为周线取每周五收盘再以周为单位切窗口。这意味着ktypeW下的window_len10实际对应 50 个交易日而非自然周数——这是代码隐含逻辑README.md未说明极易误解。3. K 值调优实战准确率不是越高越好而是要匹配你的交易频率3.1 K 值对信号稳定性与灵敏度的双重影响K 值不是“越大越准”而是存在明确的信号信噪比拐点。我们以000001.SZ平安银行2020–2023 年数据为样本固定window_len20,forecast_days3遍历k1到k50统计每组 K 值下“未来 3 日上涨”信号的历史胜率即邻居中上涨比例 ≥ 0.6 的样本中真实上涨的比例K 值胜率%信号触发频次年平均持仓天数最大回撤%152.1127318.3358.789314.2561.462311.8760.24839.51057.93537.21554.32635.12051.61933.8结论K5 时胜率峰值61.4%但 K7 时回撤显著降低9.5% → 7.2%且信号频次仍达 48 次/年更适合实盘。K10 后胜率下降说明引入过多“次优相似样本”稀释了信号纯度。3.2 如何用交叉验证找到你自己的最优 K项目未提供 CV 框架但可快速手写滚动窗口验证def find_best_k(ts_code, k_rangerange(1, 21), test_years1): 在指定股票上滚动验证 K 值返回最优 K 及对应胜率 test_years: 用最近 test_years 年数据做测试集 full_df get_stock_data(ts_code, start_date20150101) # 划分训练/测试最后 test_years 为测试其余为训练 cutoff_idx len(full_df) - test_years * 250 # 粗略按 250 交易日/年 train_df full_df.iloc[:cutoff_idx] test_df full_df.iloc[cutoff_idx:] results {} for k in k_range: signals [] truths [] # 对测试集每个可生成信号的日期窗口末尾 for i in range(len(test_df) - 20): window_end test_df.index[i 19] # 构造测试窗口取 test_df[i:i20] test_window test_df[close].iloc[i:i20].values # 在 train_df 中搜索 K 个最近邻 train_windows [] for j in range(len(train_df) - 20): w train_df[close].iloc[j:j20].values train_windows.append(w) distances [dtw_distance(test_window, w) for w in train_windows] top_k_idx np.argsort(distances)[:k] # 统计 top_k 个邻居后续 3 日上涨比例 up_counts 0 for idx in top_k_idx: if idx 23 len(train_df): # 确保有后续 3 日数据 future_close train_df[close].iloc[idx 20:idx 23].values[-1] if future_close train_df[close].iloc[idx 19]: up_counts 1 up_ratio up_counts / k signals.append(up_ratio 0.6) # 设定信号阈值 # 获取真实结果测试窗口后续 3 日 if i 22 len(test_df): real_up test_df[close].iloc[i 22] test_df[close].iloc[i 19] truths.append(real_up) if len(signals) 0: accuracy np.mean([s t for s, t in zip(signals, truths)]) results[k] accuracy best_k max(results, keyresults.get) return best_k, results[best_k] # 调用示例 best_k, acc find_best_k(000001.SZ, k_rangerange(1, 16)) print(f最优 K{best_k}滚动验证胜率{acc:.3f})血泪经验不要只看整体胜率务必检查signals和truths的时间对齐——train_df中邻居的“后续 3 日”必须严格对应其自身窗口结束日而非测试窗口结束日。否则会出现“用未来数据验证”的致命错误。4. 避坑指南五个让新手卡住 3 小时以上的具体问题4.1 现象main(000001, 5)报错AttributeError: module tushare has no attribute get_k_data原因代码中残留 tushare 1.x 旧接口调用但当前安装的是 tushare 4.x。get_k_data()在新版本中已移除且pro_api()初始化方式不同。解决打开share_foresee_end.py搜索get_k_data将其全部替换为pro.daily()调用参考 2.1 节代码并确保顶部有ts.set_token()和pro ts.pro_api()初始化。4.2 现象main(000001.SZ, 5)返回ValueError: all the input arrays must have same number of dimensions原因fastdtw输入要求一维数组但pandas.Series.values在某些 pandas 版本下返回二维array([[x1], [x2], ...])。解决在dtw_distance函数中强制展平a_norm a_norm.flatten() # 添加这一行 b_norm b_norm.flatten() # 添加这一行4.3 现象ktypeW时程序卡死或内存溢出原因周线数据量虽少但fastdtw计算复杂度为 O(N²)且window_len10周对应约 50 日DTW 矩阵尺寸达 50×502500当历史窗口数 1000 时内存飙升。解决限制周线数据范围get_stock_data(..., start_date20180101)在knn_search中添加距离计算超时保护import signal class TimeoutError(Exception): pass def timeout_handler(signum, frame): raise TimeoutError signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(30) # 30秒超时 try: distance, _ fastdtw(...) signal.alarm(0) except TimeoutError: distance float(inf) # 超时则设为无穷大自动排除4.4 现象多次运行main()得到不同结果原因np.random.seed()未设置且np.argsort(distances)[:k]在距离相同时的排序不稳定如多个窗口 DTW 距离均为 0.001。解决在main()开头添加np.random.seed(42) # 固定随机种子 # 并在取 top-k 时用 stable argsort top_k_idx np.argsort(distances, kindmergesort)[:k] # mergesort 稳定4.5 现象信号胜率始终 ≈ 50%毫无区分度原因未对价格序列做对数收益率转换直接使用close值计算 DTW。导致趋势性主导距离计算上涨段永远比横盘段距离大形态相似性被掩盖。解决将特征从close改为log_return# 替换原代码中的 price_window 构建逻辑 price_series df[close].iloc[i:iwindow_len].values log_returns np.diff(np.log(price_series)) # 长度为 window_len-1 # 用 log_returns 代替 price_series 传入 dtw_distance注意此时window_len需设为21日才能得到20个收益率点与原20日窗口对齐。5. 进阶技巧把 KNN 信号接入实盘——三步构建可交易的过滤器5.1 信号标准化从“概率”到“强度分数”原始输出up_ratio是 0~1 的浮点数但实盘需要离散信号如 0/1或强度分级如 1~5 星。我一般用以下映射up_ratio区间信号强度适用场景[0.0, 0.4)★☆☆☆☆空仓趋势向下确认[0.4, 0.55)★★☆☆☆观望形态模糊等待突破[0.55, 0.65)★★★☆☆轻仓中等确定性配合量能验证[0.65, 0.75)★★★★☆主仓高确定性可作为核心信号[0.75, 1.0]★★★★★满仓极端相似历史上 75% 次数上涨实现代码插入main()返回前def ratio_to_strength(up_ratio): if up_ratio 0.4: return 1 elif up_ratio 0.55: return 2 elif up_ratio 0.65: return 3 elif up_ratio 0.75: return 4 else: return 5 strength ratio_to_strength(up_ratio) print(f信号强度: {strength}★ (相似度 {up_ratio:.3f})) return strength, up_ratio, avg_return, std_return5.2 多周期共振日线 周线双 KNN 交叉验证单一周期易受噪音干扰。我习惯同时跑ktypeD和ktypeW仅当两者信号强度均 ≥4★ 时才触发交易def dual_knn_signal(ts_code, k_daily5, k_weekly3): daily_str, _, _, _ main(ts_code, k_daily, ktypeD) weekly_str, _, _, _ main(ts_code, k_weekly, ktypeW) if daily_str 4 and weekly_str 4: return BUY elif daily_str 2 and weekly_str 2: return SELL else: return HOLD # 调用 decision dual_knn_signal(000001.SZ) print(f综合决策: {decision})5.3 回测验证用 backtrader 快速检验信号有效性避免纸上谈兵用 50 行代码完成基础回测import backtrader as bt class KNNStrategy(bt.Strategy): params ((k, 5), (ts_code, 000001.SZ),) def __init__(self): self.dataclose self.datas[0].close self.order None def next(self): if self.order: # 检查订单是否已完成 return if not self.position: # 还未持仓 # 调用 KNN 信号需提前缓存信号序列此处简化为伪代码 signal get_knn_signal(self.datas[0].datetime.date(0)) # 实现需预计算 if signal BUY: self.order self.buy() else: # 已持仓 if signal SELL: self.order self.sell() # 加载数据需先用 tushare 下载并转为 backtrader 格式 cerebro bt.Cerebro() data bt.feeds.PandasData(datanamedf) # df 为 tushare 获取的 DataFrame cerebro.adddata(data) cerebro.addstrategy(KNNStrategy) cerebro.run()后悔药时刻从那以后我每次部署新 KNN 参数前都强制走一遍find_best_k()dual_knn_signal()backtrader三件套验证哪怕只花 15 分钟。因为曾经跳过这步在实盘用 K1 直接追高单笔亏损覆盖了之前 3 个月信号收益——KNN 不是魔法棒它是把历史规律翻译成数字的翻译器而翻译质量永远取决于你校准它的耐心。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。