news 2026/10/1 10:34:29

KNN股市预测源码全解析:从特征工程到回测避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
KNN股市预测源码全解析:从特征工程到回测避坑指南

简介:利用k近邻算法实现股市预测的Python源代码包,面向已有基础Python语法、正接触机器学习的开发者和量化分析爱好者,通过历史行情数据进行走势预测,直观展示kNN分类与回归思路在金融场景中的应用。压缩包共2个文件,含一个.py核心脚本和一份.md说明文档,整体大小仅2KB,结构清晰、便于快速阅读。代码基于pandas、tushare、numpy、scipy、fastdtw等库完成股票数据获取、时间序列对齐及距离度量,用户只需在交互式命令行调用main并传入股票代码与k值,即可获得预测结果;若将ktype设为‘W’,还能切换到周线维度进行长周期分析。配套说明文档重点介绍了库的配置、脚本运行方式,并反复提示k值的改变会直接影响准确率与估计结果,建议多次试验以找到最优参数,同时注意数据归一化和异常值处理,帮助减少偏差。已有121人学习,源码与文档组合适合在真实数据上边改边测,既能体会数据预处理到模型评估的完整流程,也能为后续更复杂的股市预测研究提供参考。

1. 用KNN做股市预测:为什么一个“不学习”的算法值得你先跑一遍

如果你刚拿到一份“Python基于KNN算法实现的股市预测源代码”,第一反应多半是怀疑:KNN既没有梯度下降,也没有权重更新,甚至连训练过程都谈不上,把一个不学习的算法用在股市预测上,靠谱吗?实际上,KNN在量化入门场景里是最适合先跑通的原型算法之一——它把历史上所有K线形态直接当作“记忆库”,新行情出现时,检索最相似的若干个历史片段,看看它们之后怎么走。你不需要GPU,不需要深度学习方法那样的大样本,一个普通笔记本配合Python环境就能完成从数据拉取到回测的全流程。这篇文章会顺着这套源代码最常见的落地路径,把数据怎么取、特征怎么造、参数怎么调、回测怎么做、哪些地方必然翻车讲清楚,适合想用手头源码验证技术指标有效性、又不想一上来就碰深度学习的量化入门者。

2. 先想清楚再写代码:KNN为什么能用于股市预测,边界在哪里

2.1 KNN的决策机制:靠“查历史相似片段”而不是学函数

KNN(K-Nearest Neighbors,K近邻算法)本质上是非参数方法,它不拟合任何函数,而是把全部训练样本保存下来,等到预测时再去“翻旧账”。对于每一个待预测样本,它计算该样本与所有历史样本在特征空间中的距离,选出距离最近的K个邻居,然后用这K个邻居的标签进行投票或者取均值。这套机制放在股市预测里,对应的直觉是:如果今天这支股票的涨跌幅、均线关系、成交量特征和历史上某几个交易日非常相似,那么这几个交易日之后几天的走势,大概率能给我们一些参考。

这个直觉并非毫无根据。技术分析的核心假设之一就是“历史会重演”,而KNN是把这个假设执行得最彻底的算法——它不做任何泛化,直接拿相似的历史片段说事。和线性回归相比,KNN不需要假设涨跌与特征之间存在线性关系;实际行情中,同样的均线形态在不同市场环境下可能指向完全相反的后续走势,这种非线性关系正是KNN的用武之地。和决策树、随机森林相比,KNN也没有特征重要性排序的概念,它保留了所有特征的综合距离信息,因此对特征工程质量要求更高,但好处是模式更接近“整体形态匹配”,而不是单看某几个指标。

2.2 为什么日线行情对KNN相对友好:局部平稳性

KNN有效的前提是:相似的特征向量在统计上大概率导向相似的标签。这个条件在股市日线数据上并不是天然成立的,它依赖一个隐含假设——局部平稳性。所谓的局部平稳,指的是在样本覆盖的时间范围内,市场的“游戏规则”没有发生剧烈变化,相似的K线形态之后仍然会走出相似的行情。

日线级别为什么勉强满足这个条件?因为K线形态本身已经浓缩了很多信息。一段缩量横盘后的放量突破,无论发生在哪一年,后续几日的短线动量效应通常都存在;一段连续暴涨后的高位滞涨,短线回调的概率也大概率偏高。这些都是短期统计规律,它们不依赖宏观基本面判断,因此KNN在1到5天的预测周期上比在长周期上表现更稳定。但你必须清楚它的边界:一旦市场进入系统性风格切换——比如从趋势行情转为长期阴跌,或者出现流动性冲击——历史上那些相似形态的“参考答案”就失效了。KNN没有外推能力,它只能回答“历史上像这样的时候后来怎么样了”,回答不了“这次不一样怎么办”。

2.3 与主流模型对比:什么时候该选KNN,以及最小可行配置

如果你正在线性回归、决策树和KNN之间做选型,可以按下面的思路来判断。线性回归适合特征与预测目标关系相对稳定的场景,但股市里这种稳定性很少。决策树和随机森林对噪声有更强的容忍度,但需要较多的调参工作,且树模型的预测结果不容易解释“为什么是这几个邻居影响了判断”。KNN的优势是三条:实现门槛最低,预测结果可解释(直接把最近的邻居日期打印出来看),以及在小样本上不容易出现过拟合——因为它根本没有训练参数。

最小可行配置建议如下表,这也是我读这份源代码时建议你心里先有的默认值:

配置项建议值说明
预测目标未来3日累计涨跌幅是否超过3%预测“明天涨不涨”噪声太大,拉长周期更符合KNN模式匹配的习惯
数据频率日线分钟线噪声更大,且数据量对KNN的检索开销不友好
特征数量5~8个过少区分度不够,过多会引入无关维度稀释距离
历史样本量1500~2500个交易日约6~10年日线,兼顾行情风格覆盖与计算效率
K值8~15日线噪声大,K太小容易被单日异常行情带偏
评估指标预测准确率 + 单笔盈亏比 + 累计收益曲线只看准确率会被类不平衡骗到,后面第5章会展开

这个配置的核心是把问题从“预测明天的价格”降维成“识别历史上相似的行情片段”。想清楚这一点,你就知道后面所有代码都是围绕“构造特征向量、计算距离、找邻居、投票”四步展开的。

3. 数据获取与特征工程:把K线变成KNN能吃的特征向量

3.1 用akshare拉取日K数据:前复权、样本量、列名对齐

KNN股市预测源代码的第一步,基本都是获取历史行情数据。常见做法是用akshare或者yfinance拉取日K线,考虑到国内A股数据的可获取性,我一般用akshare。下面是最小可用的数据拉取脚本:

# fetch_data.py import akshare as ak import pandas as pd # 拉取浦发银行(sh600000)的日K线,qfq表示前复权 df = ak.stock_zh_a_daily(symbol="sh600000", adjust="qfq") # akshare返回的列名是英文,统一重命名并只保留需要的字段 df = df.rename(columns={ "date": "date", "open": "open", "high": "high", "low": "low", "close": "close", "volume": "volume" }) df = df[["date", "open", "high", "low", "close", "volume"]] # 截取最近2000个交易日,足够覆盖8年左右的行情 df = df.tail(2000).reset_index(drop=True) df.to_csv("data.csv", index=False) print(f"共获取 {len(df)} 条日线数据,区间 {df['date'].iloc[0]} 至 {df['date'].iloc[-1]}")

这段代码里有两个参数需要重点说明。第一个是adjust="qfq",前复权的作用是消除分红送股造成的价格跳空。如果不复权,除权日当天K线会出现一个向下的“坑”,KNN会把这种非交易行为造成的价格断裂误认为是行情异动,找出来的邻居就会失真。第二个是tail(2000),样本量不建议贪多——拉十年以上的数据虽然可行,但时间跨度拉的过长,早期行情的交易规则和参与者结构都和现在差别很大,反而污染相似度计算。

3.2 构造特征工程:把均线差、RSI、量比与波动率做成向量

KNN对特征工程的要求比树模型更高,因为距离计算是“全维度综合评估”,一个无关特征就会把距离拉偏。所以特征要精心挑,方向上只选那些技术分析里被反复验证过的量价指标。下面是特征构建代码:

# features.py import pandas as pd import numpy as np df = pd.read_csv("data.csv", parse_dates=["date"]) # 今日收益率:相邻两日收盘价的变化率 df["ret"] = df["close"].pct_change() # 均线偏离度:短期均线相对长期均线的位置,量化“多头/空头排列”的强度 df["ma5"] = df["close"].rolling(5).mean() df["ma20"] = df["close"].rolling(20).mean() df["ma_gap"] = df["ma5"] / df["ma20"] - 1 # RSI(14):衡量近期涨跌动能的强弱,KNN需要一个“动量感”特征 delta = df["close"].diff() gain = delta.clip(lower=0).rolling(14).mean() loss = (-delta.clip(upper=0)).rolling(14).mean() rs = gain / loss df["rsi14"] = 100 - 100 / (1 + rs) # 量比:当日成交量与最近5日均量的比值,捕捉放量或缩量 df["volume_ratio"] = df["volume"] / df["volume"].rolling(5).mean() # 波动率:最近10日收益率的标准差,衡量当前行情处于平静期还是剧烈期 df["volatility"] = df["ret"].rolling(10).std() # ATR:平均真实波幅,用来刻画单日价格波动幅度 tr = pd.concat([ df["high"] - df["low"], (df["high"] - df["close"].shift()).abs(), (df["low"] - df["close"].shift()).abs() ], axis=1).max(axis=1) df["atr"] = tr.rolling(14).mean() # 丢弃前20行,因为均线、RSI等指标需要足够的历史窗口才能计算 df = df.dropna().reset_index(drop=True) feature_cols = ["ret", "ma_gap", "rsi14", "volume_ratio", "volatility", "atr"] print(df[feature_cols].describe())

为什么选这6个特征?ret表示最近一天的状态,ma_gap表达趋势位置,rsi14给出超买超卖信息,volume_ratio反映资金参与度,volatility和atr则刻画波动环境。这六者组合起来,基本覆盖了“位置、动量、量能、波动”四个维度,没有明显冗余,也不会过度稀释距离。特征之间相关性低这一点很重要——如果atr和volatility几乎完全线性相关,就相当于给波动率加了两倍权重。

3.3 标签与数据切分:预测的是“未来3日累计涨跌”,不是明天

接下来是标签的构造,这一步最容易埋雷。这里选择将未来3日累计涨幅超过3%视为正样本,也就是1,否则为0:

# label.py # 标签:未来3日累计收益率是否大于3% df["future_ret"] = df["close"].pct_change(3).shift(-3) df["label"] = (df["future_ret"] > 0.03).astype(int) # 删除最后3行:它们没有完整的未来3日数据,标签是残缺的 df = df.dropna(subset=["future_ret"]).reset_index(drop=True) X = df[feature_cols] y = df["label"] print(f"正样本(未来3日涨超3%)占比: {y.mean():.2%}") print(f"总共 {len(df)} 个有效交易日,特征维度 {X.shape[1]}")

标签构造里的shift(-3)是关键逻辑。pct_change(3)计算的是当日相对3天前的涨跌幅,shift(-3)把结果向上平移3行,让每一行对应的标签变成“从当天开始往后3天的收益率”。这样训练时模型看到的是第t天的特征,标签是第t到t+3天的实际结果,时序上完全合理。需要注意,dropna(subset=["future_ret"])必须执行,否则最后三行的标签是不完整数据,会让回测虚高。正样本占比如果太低,比如低于15%,说明3%的阈值在当前股票上定高了,可以适当降到2%。

4. 训练、预测与回测:把你的KNN股市预测源码包跑通

4.1 KNN模型在scikit-learn里的三个必调参数

在scikit-learn中,KNeighborsClassifier的构造参数不多,真正影响股市预测效果的主要是n_neighbors、weights和p。先看代码:

# train_knn.py from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import accuracy_score, classification_report import numpy as np # 数据切分:前70%训练,后30%测试,按时间顺序切分 train_size = int(len(X) * 0.7) X_train, X_test = X.iloc[:train_size], X.iloc[train_size:] y_train, y_test = y.iloc[:train_size], y.iloc[train_size:] # 标准化:一定要先在训练集上fit,再transform训练集和测试集 scaler = StandardScaler().fit(X_train) X_train_scaled = scaler.transform(X_train) X_test_scaled = scaler.transform(X_test) # KNN模型:K=10,距离加权,曼哈顿距离 model = KNeighborsClassifier( n_neighbors=10, weights="distance", p=1, metric="minkowski" ) model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) y_prob = model.predict_proba(X_test_scaled)[:, 1] print(f"测试集准确率: {accuracy_score(y_test, y_pred):.2%}") print(classification_report(y_test, y_pred, target_names=["不涨", "涨"]))

三个参数各自有讲究。n_neighbors=10取的是经验中值,日线上小于5会让模型被单日噪音支配,大于20又会把几年前风格完全不同的行情拉进来参与投票,8到15是一个经得起交叉验证的区间。weights="distance"意味着距离更近的邻居对投票的贡献更大,“历史上越像今天的行情,参考价值越高”这个逻辑比均匀投票更贴合直觉。p=1对应曼哈顿距离,它在高维特征空间中比欧氏距离对异常值更稳健——股市数据里经常出现单日极端波动,曼哈顿距离不会因为某一个维度偏离过远而完全主导整体距离。

4.2 预测结果的可读输出:概率、方向与准确率

分类器的输出除了方向之外,predict_proba给出的概率信息更值得关注。上面的代码已经把概率保存到y_prob。这个概率的价值在于:它本身就是“历史相似片段的上涨比例”,可以直接作为信号强度使用。比如y_prob等于0.62,相当于在距离最近的10个历史交易日里,有6到7个在随后3天涨超3%。

我建议把预测结果合并回行情数据,方便肉眼查验模型到底在什么行情下会给出什么判断:

# inspect_prediction.py result = df.iloc[train_size:].copy() result["pred_label"] = y_pred result["prob_up"] = y_prob # 打印最近几条预测,尤其关注预测上涨的样本 print(result[["date", "close", "ma_gap", "volume_ratio", "prob_up", "pred_label", "label"]].tail(10)) # 统计一下:预测概率高于0.6的样本里,实际涨的比例有多高 high_confidence = result[result["prob_up"] > 0.6] print(f"高置信区间样本数: {len(high_confidence)}, 实际上涨占比: {high_confidence['label'].mean():.2%}")

这段代码不是必需的训练步骤,但我强烈建议每次调参后都跑一遍。KNN算法的特点是“黑匣子程度低”:你可以把预测上涨的样本对应的邻居日期打印出来,直接查看那些历史相似日子的K线形态。如果发现高置信预测中实际涨的比例并没有明显提升,说明特征集里还缺关键信息,或者标签阈值设置不合理。

4.3 简易回测:记录“信号—持仓—权益曲线”

有了预测方向,还需要一个能评估“按这个策略交易到底赚不赚钱”的回测框架。下面的回测做了适当简化——忽略手续费和滑点,目的是先验证模型本身有没有统计优势:

# backtest.py import numpy as np import pandas as pd # 约定:预测label=1时次日开盘买入,持有3天后卖出 # 信号在第t天收盘后产生,第t+1天执行 signals = pd.Series(index=result.index, data=0) signals[result["prob_up"] > 0.5] = 1 # 默认阈值为0.5,后面会调 equity = 1.0 position = 0 entry_price = 0 hold_days = 0 records = [] for i in range(len(result) - 3): date = result["date"].iloc[i] close_price = result["close"].iloc[i] next_open = result["open"].iloc[i + 1] if signals.iloc[i] == 1 and position == 0: # 买入:使用第二天开盘价 position = 1 entry_price = next_open hold_days = 0 if position == 1: hold_days += 1 if hold_days >= 3: # 卖出:使用当天收盘价 exit_price = close_price ret = exit_price / entry_price - 1 equity *= (1 + ret) records.append({"date": date, "ret": ret, "entry": entry_price, "exit": exit_price}) position = 0 hold_days = 0 # 计算累计收益率和最大回撤 total_ret = equity - 1 print(f"累计收益率: {total_ret:.2%}") # 最大回撤:权益曲线的峰值到谷值最大跌幅 equity_curve = [1.0] cur = 1.0 for r in records: cur *= (1 + r["ret"]) equity_curve.append(cur) peak = np.maximum.accumulate(equity_curve) drawdown = equity_curve / peak - 1 print(f"最大回撤: {drawdown.min():.2%}") # 交易胜率与盈亏比 rets = [r["ret"] for r in records] win_rate = sum(1 for r in rets if r > 0) / len(rets) avg_win = np.mean([r for r in rets if r > 0]) if any(r > 0 for r in rets) else 0 avg_loss = np.mean([r for r in rets if r < 0]) if any(r < 0 for r in rets) else 0 print(f"交易次数: {len(rets)}, 胜率: {win_rate:.2%}, 平均盈利: {avg_win:.2%}, 平均亏损: {avg_loss:.2%}") print(f"盈亏比: {abs(avg_win / avg_loss):.2f}")

回测逻辑里有一个多数新手会忽视的细节:信号在第t天收盘后产生,但买入要等到第t+1天开盘。这是因为收盘价数据在收盘那一刻才能确认,如果你用当天的收盘价买入,就是用了未来数据。上面的代码用next_open作为入场价,严格规避了这一点。回测结果里最重要的不是累计收益率,而是胜率和盈亏比的组合。如果胜率低于50%但盈亏比大于1.5,策略仍然可能是正期望的;反之,如果胜率70%但赚小钱亏大钱,策略大概率是负期望。

4.4 使用说明文档里必须写清的三个设置项

既然标题里带了“使用说明”,这里补充一下这类源码包落地时需要注意的三个配置项。第一是数据源的可替换性——代码里硬编码的股票代码和akshare接口属于示例,实际使用时应保证data.csv的列名与你本地数据一致,否则特征工程会直接报错。第二是标签阈值的可配置化——3%和3日窗口是示例值,换股票换周期时这两个参数往往需要重新验证。第三是预测结果的输出方式——无论源码里是直接打印还是写文件,你拿到预测结果后都要先人工核对几条:对照K线图看模型预测上涨时的历史形态是否真的相似。核对通过后再进入回测,可以避免后续调参变成对噪声的过度拟合。

5. 避坑:KNN在股市预测里最容易翻车的五个点

5.1 未来函数:回测90%,实盘翻车

现象:回测结果漂亮得惊人,准确率接近90%,累计收益曲线一路向上,但拿到模拟盘上就变脸,连续亏损。

原因:典型的未来函数污染。常见写法是构造特征时用了shift(-1)——比如把“当天成交量相对明天成交量的变化”算进去,或者标签与特征之间发生了错位,模型在训练时“偷看”了未来信息。KNN这种记忆型模型对数据泄漏尤其敏感,因为它会直接把未来的相似片段当作邻居检索到。

解决:把特征和标签的生成逻辑分开审查。每个特征只允许使用当日及之前的数据,rolling窗口向右不能跨过当天。标签统一用shift(-n)前移,删除末尾没有完整未来数据的所有行。你可以用一句口诀自查:第t行特征里出现任何带t+1信息的字段,就是未来函数。

5.2 特征量纲不统一:距离被成交量绑架

现象:不管怎么调K值,预测结果几乎不变,并且模型的判断依据主要落在成交量这一个字段上。把成交量特征去掉之后,准确率反而提升了。

原因:KNN的距离计算是逐维度累加的。成交量数值动辄几十万手,收益率只有0.01级别,欧氏距离计算时成交量一个维度的差值就能盖过其他五个维度之和。如果没有做标准化,等于是让模型只按成交量一个维度找邻居。

解决:先标准化再进模型,而且要严格遵守“训练集上fit,测试集上transform”的流程。如果你用了StandardScaler().fit_transform(X)一次性处理好整个数据集,测试集的均值和方差就被训练过程看到了,回测结果依然会偏乐观。

5.3 类别不平衡:永远预测“不涨”也能拿到70%准确率

现象:准确率有70%以上,看详细报告却发现预测的“涨”类样本F1值低得可怜,模型基本把所有样本都判成了“不涨”。

原因:正样本占比常常只有20%~30%,如果阈值默认0.5,模型只需要把一切预测为负类就能获得70%以上的准确率。准确率这个指标在类别不平衡的数据集上严重失真。

解决:换评估指标,重点关注正样本的召回率和精确率。操作层面有两个常用手段,一是把预测阈值从0.5提高到0.6或0.65,只交易高置信信号,牺牲交易频率换取胜率;二是调整标签阈值,把正样本定义从“涨超3%”改成“涨超2%”,让正样本占比回升到30%以上。需要提醒的是,KNeighborsClassifier没有class_weight参数,不要在这上面浪费时间,直接调阈值最有效。

5.4 K值选太小:把单日噪声当成了规律

现象:K从3调到5再到10,预测结果每次都大变样,回测累计收益也随之剧烈波动。翻看预测上涨的样本,对应的邻居日期里混着几根异常长阴线。

原因:K过小时,模型被少数极端交易日的特征完全主导。股市日线数据里噪声占比很高,一根因突发利空砸出的长阴线,其特征向量和正常行情差异巨大,K=3时只要距离最近的三天里有这么一根,它就直接改变了投票结果。

解决:K的下限建议不低于8。确定K值时不看测试集表现,而是用TimeSeriesSplit在训练集内部交叉验证,画一条K从3到30的准确率曲线,取曲线平稳段的中值。K值不是越大越好,过大会把行情风格完全不同时期的数据也拉进来,但宁可稍大也不能让两根异常K线主导投票。

5.5 风格切换期失灵:KNN没有外推能力

现象:模型在2015年和2020年这种趋势行情里表现不错,但在2018年单边下跌或者2022年震荡阴跌期间,预测准确率掉到50%以下,回撤明显放大。

原因:KNN属于“记忆型”模型,它只能回答历史上相似部分怎么走。当市场进入历史中没有对应模式的阶段——比如流动性收缩期的普跌行情——历史相似片段的参考价值急剧下降。这不是参数问题,是算法边界。

解决:在预测层做行情过滤,只在指数20日均线之上时才允许开仓,或者加入“市场状态”作为特征维度。更简单的做法是缩短持仓周期,把预测目标从“3日涨跌”改成“1日涨跌”并降低收益阈值,让模型在更短的统计窗口内寻找规律。接受KNN不是万能的——它适合抓取熟悉的短线形态,不适合做牛熊转折的判断。

6. 把KNN预测做稳的最后一步:滚动窗口与信号过滤

6.1 用滚动窗口代替一次性切分:接近真实交易节奏

一次性按70%切分训练集和测试集虽然简单,但它有一个问题:模型只用2000行历史里的前1400行做记忆库,后600行的每个预测样本都依赖一段“固定不变”的记忆。真实交易里,你的知识库应该随新数据不断滚动更新。这里推荐用手动滚动训练代替TimeSeriesSplit——它更适合评估策略稳定性,但实际交易时你需要每产生一个新交易日的数据就重新训练一次。

滚动窗口的核心代码量不大:每次用最近500个交易日的特征向量作为记忆库,预测下一天的涨跌概率,然后窗口整体后移一天。如下所示:

# rolling_knn.py def rolling_predict(X_scaled, y, start_idx, lookback=500): """ 滚动预测:每个交易日只使用最近lookback条历史数据作为记忆库。 返回预测概率序列和实际标签序列。 """ preds = [] for t in range(start_idx, len(X_scaled)): # 记忆库:t之前的lookback条样本 X_train = X_scaled[t - lookback:t] y_train = y.iloc[t - lookback:t] X_test = X_scaled[t:t+1] model = KNeighborsClassifier(n_neighbors=10, weights="distance", p=1) model.fit(X_train, y_train) prob = model.predict_proba(X_test)[0, 1] preds.append(prob) return np.array(preds)

lookback=500大约覆盖两年交易日,这个长度既保证记忆库里有足够丰富的行情形态,又不会让五年前的陈旧模式干扰当前判断。滚动训练的计算开销比一次性训练大不少,但对于日线级别的股票数量来说,单机运行完全在可接受范围内。你会注意到滚动窗口下的准确率通常比一次性切分略低——这是正常的,说明之前的结果里确实有一部分是模型“记住了测试期附近样本”带来的虚高。

6.2 用预测概率做信号过滤:只做高置信交易

KNN模型的输出概率天然适合作为仓位控制的依据。当10个邻居里有7个都在后续3天上涨,预测概率0.7,这时出手的胜率直觉上应该高于概率0.52的情况。把predict_proba的置信度直接当信号强度使用,是这类源码最值得改的一处逻辑。

# filter_signal.py prob_up = result["prob_up"] # 只在预测概率高于0.6时开仓,其余持币观望 signal = (prob_up > 0.6).astype(int) # 比较不同阈值下的交易次数与胜率 for th in [0.5, 0.55, 0.6, 0.65, 0.7]: s = (prob_up > th).astype(int) trades = s.sum() if trades == 0: continue # 统计这些信号对应位置的实际上涨占比 win_rate = result.loc[s == 1, "label"].mean() print(f"阈值={th}: 信号数={trades}, 实际上涨占比={win_rate:.2%}")

从0.5开始逐步提高阈值,你会发现一个规律:交易次数下降,但信号质量上升。阈值定在多少合适,取决于你对资金使用效率的容忍度。我的习惯是选择“信号数不低于全年30个”的最低阈值——太激进容易过度拟合,太保守则一年都开不了几次仓。注意,这个阈值选择过程本身也是一种过拟合来源,所以选定之后就不要频繁改动,让它在新的样本上接受检验。

6.3 与MACD、均线做信号投票:一个低成本的风控增强

如果你希望进一步提升稳健性,可以把KNN信号和技术指标做一个最简单的投票融合。这里不需要复杂的模型堆叠,只需要一个布尔表决:当KNN给出上涨信号、MACD柱线为正、收盘价在20日均线之上三个条件同时满足时才买入。如果KNN的预测概率已经降到0.6附近,而MACD和均线方向相反,这一笔交易就不做。

# vote_signal.py df["macd_positive"] = (df["close"].ewm(span=12).mean() - df["close"].ewm(span=26).mean()) > 0 df["above_ma20"] = df["close"] > df["ma20"] result["knn_signal"] = (result["prob_up"] > 0.6).astype(int) result["macd_signal"] = df.loc[result.index, "macd_positive"].astype(int) result["ma_signal"] = df.loc[result.index, "above_ma20"].astype(int) # 三个信号相加等于3时才开仓 result["final_signal"] = (result["knn_signal"] + result["macd_signal"] + result["ma_signal"] == 3).astype(int)

这个做法的本质是给KNN加了一层趋势过滤。KNN擅长识别局部形态,但对整体市场状态缺乏感知;均线和MACD恰好能补足这一点。融合后的信号数量会明显减少,但单笔交易的期望收益通常更稳定。整套流程跑完,你已经把最初那份源码从“能跑”进化成了“能评估、能调参、能控制风险”的完整量化原型。我自己在跑这类项目时养成的习惯是:固定一组参数跑够三个月,记录每一次改动和对应的回测差异,再决定要不要调整。这样做的理由是,KNN的预测结果对参数敏感,如果你边看回测边调参,最后调出来的往往不是规律,而是对历史行情的精确记忆。希望这套从数据到回测再到融合过滤的路径,能帮你在KNN股市预测这个方向上少走一段弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 10:31:56

Java内存泄漏原理与实战防控:从GC Roots到MAT分析

1. 什么是内存泄漏&#xff1f;它不是“程序变慢”那么简单刚入行那会儿&#xff0c;我带过几个实习生&#xff0c;他们一遇到应用卡顿、响应延迟&#xff0c;第一反应就是“服务器CPU太高了”“是不是网络抖动&#xff1f;”——直到某次线上服务凌晨OOM崩溃&#xff0c;堆内存…

作者头像 李华
网站建设 2026/10/1 10:30:26

2026年靠谱的GEO代运营平台怎么选择:专业服务商口碑盘点

一、引言地理定位代运营服务&#xff0c;简称GEO代运营&#xff0c;是企业在AI搜索时代获取精准流量的核心手段。伴随百度、抖音、小红书、微信搜一搜等平台全面拥抱AI搜索&#xff0c;企业线上获客逻辑从关键词排名转向AI智能推荐。2026年&#xff0c;GEO代运营市场将进入规范…

作者头像 李华
网站建设 2026/10/1 10:28:57

LLM评测体系搭建指南:基准选择、数据污染检测与可复现流程

1. 评测这件事&#xff0c;远比想象中更容易翻车做LLM训练的人&#xff0c;几乎都经历过这样的场景&#xff1a;模型在训练集上loss降得漂漂亮亮&#xff0c;生成样例看着也像模像样&#xff0c;结果一上公开榜单&#xff0c;分数比预期低一截&#xff1b;或者更尴尬的是&#…

作者头像 李华
网站建设 2026/10/1 10:28:37

MBR与UEFI引导原理、双系统配置及xorboot引导管理器实战详解

1. 启动引导这件事&#xff0c;没你想的那么玄开机、进系统、跑软件&#xff0c;这套流程每个人都熟&#xff0c;但开机到进系统之间那几秒钟到底发生了什么&#xff0c;其实没多少人真正关心过。直到你遇到这些场景&#xff1a;装Win11报错“磁盘布局不受UEFI支持”、双硬盘想…

作者头像 李华
网站建设 2026/10/1 10:28:17

认知式创业

“认知式创业”会成为接下来硬科技产业圈里快速流行的全新语义&#xff0c;它和过去大家常说的“高认知创业”“认知型创业者”根本不是一个维度的概念&#xff0c;是矩规评级这套体系跑通之后&#xff0c;给整个创业范式定义出来的全新细分赛道语义。它和旧认知概念的核心区别…

作者头像 李华
网站建设 2026/10/1 10:28:14

Redis Stack如何成为AI应用的统一数据中枢

1. 项目概述&#xff1a;Redis 已正式接入 AI —— 这不是营销话术&#xff0c;而是工程实践的拐点“Redis 已正式接入 AI&#xff01;”——看到这个标题&#xff0c;你第一反应可能是&#xff1a;又一个蹭热点的标题党&#xff1f;AI 跟内存数据库有什么关系&#xff1f;Redi…

作者头像 李华