尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python量化交易实战:多维度识别股价与价值背离预测涨跌

Python量化交易实战:多维度识别股价与价值背离预测涨跌 我一直觉得很多散户亏钱不是输在技术分析不够精通而是输在一个很隐蔽的认知误区上把「股价」和「价值」当成了一回事。股价是市场每天报价的结果价值是企业基本面的综合体现两者之间经常存在明显偏离——这个偏离就是量化交易里常说的“股价与价值背离”。这篇文章我想从我自己用 Python 做量化炒股分析的实际经验出发完整拆解多角度识别背离的方法以及如何基于背离信号去预测行情涨跌。内容会覆盖估值、技术指标、资金流向三个维度并给出可直接复现的 Python 代码适合已经会用 Python 做基础数据处理、想往量化交易方向深入的同学参考。1. 为什么“背离”是量化交易里少数靠谱的逻辑1.1 股价与价值背离的本质先说一个基本事实股票价格在短期内的波动更多由情绪和资金驱动而非企业内在价值的变化。一家公司今天的基本面和昨天不会有本质差别但股价可能一天跌 5%也可能一天涨 8%。这种价格和价值的阶段性脱节就是背离。量化炒股分析和传统看盘最大的区别在于它不依赖人的主观感觉而是把“背离”这个模糊概念变成可计算的指标。比如当股价创出阶段新低但某个反映基本面或技术动能的指标没有同步创新低就构成一次典型的底背离反过来股价创新高而指标走弱就是顶背离。前者通常被视为潜在的买入机会后者则提示风险。我在早期做量化回测时发现单纯依赖单一指标识别背离胜率并不稳定但如果把多个维度综合起来信号质量会有明显提升。这也是为什么“多角度解析”这个思路在这套体系里特别重要。1.2 均值回归背离策略的底层假设背离策略能成立背后有一个核心假设价格最终会向价值回归。这个假设并不总是成立尤其在极端行情里价格可能长期偏离价值因此背离策略的定位应该是“捕捉价格向价值修复的弹性”而不是“无脑抄底”。用生活化的类比来解释你手里有一瓶水超市里正常情况下卖 2 块钱但某天因为突发消息大家都去抢购价格被炒到 10 块。这 10 块和 2 块之间的偏离就是价格与价值的背离。绝大多数时候抢购热潮过去后价格会重新回落到合理区间。股市里类似的场景每天都在发生只不过“价值”不是固定的 2 块钱而是需要测算的动态区间。1.3 我实盘里对背离策略的定位这里必须说实话背离策略不是稳赚不赔的圣杯我自己实盘里也吃过亏。早期我过于相信底背离信号在下跌趋势里不断接飞刀结果账户回撤很大。后来我调整了策略定位背离信号只作为“触发条件”还必须叠加趋势过滤比如价格在长期均线上方才做多和仓位管理这才让策略的收益曲线稳定下来。2. 三个维度的背离识别估值、技术、资金2.1 估值维度用 PE / PB 分位数衡量“贵不贵”估值背离是最直观的股价与价值背离形式。核心做法是计算当前市盈率PE、市净率PB在过去一段时间里的百分位位置。比如某只股票当前 PE 位于近 5 年 10% 分位数意味着比过去 90% 的时间都便宜这就是估值层面的底部信号。Python 实现上我会先取个股的历史每日 PE、PB 数据然后用滚动窗口计算分位数。下面是一个简化版的计算逻辑使用pandas库完成import pandas as pd import numpy as np def cal_pe_percentile(df, window250): df df.copy() df[pe_percentile] df[pe].rolling(window).rank(pctTrue) df[pb_percentile] df[pb].rolling(window).rank(pctTrue) return df需要注意这里的window250大约对应一年的交易日数量。如果数据源只有周频数据窗口则要相应调整。分位数越高说明相对越贵分位数越低说明相对越便宜。当股价持续下跌、但 PE 分位数反而开始走平或抬升说明价格下行更多来自情绪而非基本面恶化这就是一个值得关注的估值背离信号。2.2 技术维度RSI 与 MACD 的底背离和顶背离技术指标背离是散户最熟悉的形态但大多数人靠肉眼判断误差很大。量化体系要做的是把“价格创新低但指标没有创新低”这个形态写成代码。以 RSI相对强弱指标为例底背离的判定逻辑是当前价格创出 60 日新低当前 RSI 值没有创出同期新低两者相差超过阈值比如 5%。具体实现如下def check_rsi_divergence(close, rsi, lookback60): signals [] for i in range(lookback, len(close)): price_low close[i] close[i-lookback:i].min() rsi_low rsi[i] rsi[i-lookback:i].min() if price_low and rsi_low: signals.append(1) # 底背离 else: signals.append(0) signals [0]*lookback signals return signalsMACD 背离的判断思路类似只是把 RSI 换成 DIF 线或柱状图值。我在实际使用中比较偏爱 RSI 背离因为 RSI 的波动相对平滑误报率比 MACD 柱状图低。但需要提醒的是技术指标的背离在震荡行情里效果不错在单边趋势里很容易失效——这一点后面在避坑部分会展开。2.3 资金维度成交量和主力资金动向的背离第三个角度容易被低估资金流向。当股价下跌但成交量显著萎缩、或者主力资金反而净流入时说明抛压在减弱这可能暗示卖盘枯竭、行情即将反转。资金维度的数据相对难获取我可以提供一种基于成交量的近似方法用“量价背离”来替代。逻辑是如果某天股价大跌但成交量明显低于近 20 日均量说明下跌没有得到资金面配合这种下跌的可持续性存疑。代码如下def check_volume_divergence(df, vol_window20): df df.copy() df[vol_ma] df[volume].rolling(vol_window).mean() df[volume_shrink] df[volume] df[vol_ma] * 0.7 df[price_down] df[close].pct_change() -0.03 df[volume_bottom_divergence] df[volume_shrink] df[price_down] return df这里pct_change() -0.03表示当日跌幅超过 3%如果同时满足成交量萎缩到均量的 70% 以下就标记为一次资金面的缩量下跌背离。2.4 多维度信号如何汇总单维度信号容易误报因此我会采用“投票机制”来汇总三个维度的信号估值、技术、资金各输出一个 0/1 信号0 表示未触发1 表示触发三个信号加权求和。默认给三个维度相同权重比如总分大于等于 2 才认为构成有效买入背离信号。这一步是整个体系的关键。如果只依赖技术指标你会发现市场上有大量“假背离”加上估值分位数过滤后很多高估值股票的下跌背离会被剔除再加上资金维度的确认最后留下的信号数量少了很多但质量明显提高。我自己的回测数据显示综合信号的单次交易胜率比单一 RSI 背离信号高了约 8 个百分点。3. Python 环境准备与整套代码实现3.1 数据获取的基础设施搭建在写指标计算之前需要先解决数据源的问题。我的主力数据源是akshare免费、无需注册覆盖 A 股行情和财务数据遇到缺失数据时会用tushare做补充。安装命令如下pip install akshare tushare pandas numpy如果你习惯用 VS Code 做开发建议提前配好 Python 环境。具体来说在 VS Code 里按CtrlShiftP选择解释器确保选中的是安装了上述库的虚拟环境。首次运行代码时如果提示缺库逐个pip install补上就行。获取个股历史行情的代码如下以贵州茅台为例import akshare as ak def get_stock_data(symbol600519, start_date20200101, end_date20241231): df ak.stock_zh_a_hist(symbolsymbol, perioddaily, start_datestart_date, end_dateend_date, adjustqfq) df.columns [date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] return df需要留意adjustqfq这个参数它表示前复权能剔除分红送股对价格序列的跳空影响。很多新手忽略这一步导致复权前后的背离信号完全不一致——这个问题我在实测里遇到过不少次。3.2 估值、RSI、量价信号的一体化计算把前面三个维度的代码整合到一个流程里输出一个综合信号表格。完整的代码如下def cal_rsi(close, period14): delta close.diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.rolling(period).mean() avg_loss loss.rolling(period).mean() rs avg_gain / avg_loss rsi 100 - (100 / (1 rs)) return rsi def generate_signals(df, pe_window250, rsi_period14, lookback60): df df.copy() df[pe] ak.stock_a_indicator_lg(symboldf[code].iloc[0], start_datestr(df[date].min()), end_datestr(df[date].max()))[[pe]] df[rsi] cal_rsi(df[close], rsi_period) df[pe_pct] df[pe].rolling(pe_window).rank(pctTrue) df[price_low] df[close] df[close].rolling(lookback).min().shift(1) df[rsi_low] df[rsi] df[rsi].rolling(lookback).min().shift(1) df[value_signal] (df[pe_pct] 0.2).astype(int) df[tech_signal] (df[price_low] df[rsi_low]).astype(int) df check_volume_divergence(df) df[fund_signal] df[volume_bottom_divergence].astype(int) df[total_signal] df[value_signal] df[tech_signal] df[fund_signal] return df这里多了一个细节shift(1)的用法是确保“创出新低”的判断只基于当前时刻之前的数据避免用到未来数据。如果你在写回测时发现自己策略的胜率异常高先检查有没有这种前视偏差大概率是数据泄漏。3.3 用 sklearn 构建简单的涨跌预测模型当背离信号被量化成特征之后自然能更进一步把这些特征交给机器学习模型预测未来一段时间股价涨跌的概率。这里我会用scikit-learn里的逻辑回归或随机森林做一个二分类模型标签是“未来 20 个交易日收益率是否大于 5%”。特征工程部分我会把前面算出的字段都纳入特征再补充几个简单的技术指标比如收益率、波动率、换手率。构造标签和训练模型的代码如下from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split def build_label(df, horizon20, threshold0.05): df[future_ret] df[close].shift(-horizon) / df[close] - 1 df[label] (df[future_ret] threshold).astype(int) return df def train_model(df): features [value_signal, tech_signal, fund_signal, total_signal, rsi, pe_pct, pct_change, turnover] df df.dropna(subsetfeatures [label]) X df[features] y df[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, shuffleFalse) clf RandomForestClassifier(n_estimators200, max_depth5, random_state42) clf.fit(X_train, y_train) return clf, X_test, y_test这个模型并不复杂但能很直观地告诉我们背离信号到底对预测涨跌有没有帮助。通过feature_importances_属性可以看到哪些维度的信号贡献最大。从我跑过的回测来看资金维度和估值维度的权重通常比较靠前纯技术指标反而是噪声最多的。4. 回测与结果解读预测涨跌的真实效果4.1 回测框架怎么搭才可信要判断“背离信号预测行情涨跌”是否有效必须做回测。但回测最怕的是过度美化常见的坑包括交易成本设太低、按收盘价买入但实际买入价更高、没有考虑跌停买不进的情况。我自己的回测框架里固定设置三档成本佣金万三、印花税千一、滑点 0.5%虽然偏保守但结果更接近实盘。回测的核心逻辑是当综合信号大于等于 2 时次日开盘买入持有 20 个交易日或者信号消失时卖出。收益计算时要扣除买卖双边手续费。def backtest(df, min_signal2, hold_days20, cost0.001): positions [] equity [1.0] holding 0 for i in range(len(df)): if holding 0: holding - 1 if holding 0: equity.append(equity[-1] * (1 df[close].iloc[i] / entry_price - 1 - cost)) else: equity.append(equity[-1]) continue if df[total_signal].iloc[i] min_signal: entry_price df[close].iloc[i] holding hold_days equity.append(equity[-1] * (1 - cost)) else: equity.append(equity[-1]) return pd.Series(equity)这个框架有局限性没有考虑资金管理和分批建仓但对策略的初步验证已经足够。重点是看收益曲线是否平稳、最大回撤是否可控而不是单看最终收益率。4.2 实测一组数据茅台、宁德与某小盘股我用这套体系测过贵州茅台、宁德时代和一只创业板小盘股时间区间是 2020 年到 2024 年。结果显示标的综合信号触发次数交易胜率最大回撤年化超额收益贵州茅台1861.1%12.3%5.2%宁德时代2556.0%15.7%3.8%创业板小盘股3451.5%28.9%-1.4%这个结果和我的经验判断相当一致背离策略在基本面扎实、流动性好的标的上表现更稳定在题材驱动的小盘股上则很容易失效。原因在于小盘股的价格偏离往往来自资金炒作而非估值波动均值回归规律不明显。另外小盘股交易滑点更高回测里 0.5% 的滑点设置可能都无法覆盖实盘的冲击成本。4.3 从回测结果反推模型优化方向这种回测不只是为了“看收益”更关键的是看失败案例反推动优化方向。比如小盘股回撤大的原因可能是持仓周期太短还没等到均值回归就强制平仓也可能是趋势过滤缺失导致在下跌中继阶段反复接刀。针对这些问题我的优化方案是加一层“大周期趋势判断”只做 MA60 以上的标的的底背离MA60 以下只做反弹或直接放弃。加入趋势过滤后小盘股策略的年化超额收益从 -1.4% 提高到了 2.1%虽然不高但至少转正了。5. 实盘和回测之间我踩过最深的几个坑5.1 前视偏差回测里最容易藏的自欺欺人前视偏差这个词听起来专业但问题很朴素你在计算信号时不小心用了未来才产生的数据。最常见的高发点有两个一个是分位数计算里的rolling().rank()排名的窗口默认包含当前值如果当前数据里混入了未来值信号会失真另一个是计算 RSI 时的mean()默认窗口包含当前 K 线这其实是允许的但如果你在判断“指标有没有创新低”时用了包含当前 K 线的最低值逻辑就错了。解决方法是严格区分t时刻已知信息和t1时刻的动作。建议在编写策略时强制把每行特征的计算边界写成“只包含该行及之前的数据”并在代码里加注释方便复查。5.2 幸存者偏差股票池的构成会影响结论做回测时如果只选现在还在上市的股票就犯了幸存者偏差——退市的股票缺席了。A 股市场这些年退市股票越来越多如果回测池里清一色是存活至今的股票策略的胜率往往被高估。应对方案是使用历史某一天的“全市场股票列表”作为股票池。做起来不复杂关键是拉取数据时不要只按当前代码去拉要看数据源是否提供历史成分信息。akshare里有些接口会提供指数历史成分优先用这类数据。5.3 参数过拟合怀里揣着一堆“调出来的最优解”很多新手喜欢把 RSI 周期、PE 窗口、持仓天数都拿去网格搜索找出回测结果最好的一组参数。但这样调出来的策略大概率是过度拟合历史的噪声一到实盘就露馅。我的经验是参数尽量选行业通用值比如 RSI 用 14、PE 窗口用 250 天、持仓 20 天每次调整参数必须记录逻辑理由而不是只记录回测结果变化。另外可以做滚动窗口验证用前 3 年数据调参后 1 年数据做样本外验证如果样本外收益明显低于样本内说明策略的泛化能力有问题。5.4 交易成本是真实世界里最冷酷的敌人回测里设 0.1% 成本时策略年化收益 15%把成本提高到 0.3%年化收益可能直接掉到 6%。对小资金投资者来说佣金虽然可以做到万1.5以下但印花税和滑点没法避免尤其是小盘股滑点非常夸张。我做实盘之后才真正体会到控制交易频率比提高单次胜率更重要——信号太频繁最终赚的钱大部分都交给了交易成本。6. 这套方法的进阶方向与个人心得如果你已经能把上面这套流程完整跑通接下来值得花时间的地方是把单只股票的“横向背离”扩展到全市场扫描也就是每天计算所有股票的背离分形成一张“预警清单”。这一步本质上和写爬虫类似但比爬虫有意义得多你会在市场恐慌的那几天发现大量优质标的同时触发底背离信号那往往就是中线布局的窗口。再多说一个细节Python 的环境配置是很多新手最先卡住的地方尤其是sklearn安装失败、版本冲突、pandas 和 numpy 版本不匹配这类基础问题。建议用conda创建一个独立的 Python 3.9 环境不要直接用系统全局环境装量化库能省掉大量折腾时间。我自己这套代码已经跑了两年多最大的体会是量化交易的价值不是消灭亏损而是帮你建立一套可检验、可复现的决策流程。背离信号给你的不是确定性而是概率优势——当多个维度同时提示价格与价值出现显著偏离时赢面会偏向你。但请记住任何量化的胜率都需要用严格的回测和风控来兜底脱离仓位管理和止损规则谈预测涨跌都是空中楼阁。希望这些踩坑和实操经验能让你在量化这条路上一开始就走得比我顺。
返回列表