
之前分析了pv_corr如何解释量价背离现象https://blog.csdn.net/liliang199/article/details/166104461价量相关性因子pv_corr理解为价量相关性/量价同步性量化指标经常被用来解释量价背离异象。这里尝试分析价量相关性因子以及通过代码示例如何对其进行筛选和对比分析。1 价量相关因子这里示例价量相关的定义以及如何在因子框架中评估价量相关因子。1.1 量价相关性1价量相关程度成交量常被视为信息到达、交易活跃度、流动性和投资者情绪的代理。价量相关性pv_corr反映价格变动与成交量变化的协同程度正相关放量上涨、缩量下跌通常代表趋势确认、信息驱动、资金参与度高。负相关放量下跌、缩量上涨可能代表恐慌抛售、流动性冲击、反转或背离。短长窗差通过corr20 - corr60捕捉价量关系是否在增强类似相关性动量。一阶差分diff(5)捕捉短期变化可能是情绪或流动性冲击的快速反应。价量相关性因子刻画市场参与度与价格方向之间的动态耦合关系以及这种关系的趋势和变化。2价量相关因子后续代码实践给出三种价量相关因子对应上述价格变动与成交量变化的不同相关程度。pv_corr_avg价量相关性水平rolling corr(log ret, Δln vol, 60d)pv_corr_trend价量相关性趋势corr(20d) - corr(60d)短窗减长窗pv_corr_diff价量相关性一阶差分pv_corr_avg.diff(5)5 日变化其中价量相关性定义为即每只股票过去w天里对数收益与成交量对数变化的滚动 Pearson 相关系数。1.2 对数差分滚动在实践中通常会对原始价量数据做如下预处理以获得更好的线形和平稳性。1价格用对数收益近似连续复利平稳性更好。2成交量用对数差分成交量非负、右偏取对数后更接近正态差分表示增长率。3滚动相关避免单日噪声捕捉一段时间内的稳定关系。1.3 IC/ICIR/RankIC通常会使用ICIR、RankIC、t_stat等指标、分组价差等指标监测pv_corr因子是否真实有效。IC因子值与未来收益的横截面相关系数。IC 为正说明因子值越高未来收益越高。ICIRIC 均值除以 IC 标准差衡量因子预测能力的稳定性类似信息比率。RankIC用秩相关代替 Pearson 相关对极端值更稳健。t_stat检验 IC 是否显著不为零。分组价差按因子分 5 组看最高组减最低组的收益检验单调性和多空收益。1.4 冻结前向框架这是因子研究中的防过拟合方法比如1样本内 2022-12-31用来选因子方向、判断是否有效。2样本外 2023-01-01~2025-12-31固定方向后测试。避免“先看结果再定方向”的前视偏差。3recent_sign_flip用于检查近年是否出现风格切换或因子失效。1.5 Beta中性对冲策略收益可能暴露于市场 beta。在后续代码中引入了realized_beta、rolling_hedge_beta、hedge处理策略收益。1realized_beta事后 beta。2rolling_hedge_beta滚动历史 beta因果估计避免未来信息。3hedge从策略收益中减去 beta 倍市场收益。这样得到的是市场中性后的 alpha 表现更接近真实选股能力。1.6 纯多头zposA 股做空成本高、限制多因此多空仅用于潜力评估一般用zpos构建纯多头组合。因子标准化后正 z 值股票获得更高权重负 z 值可能被截断或低配。这更贴近实际可交易组合。1.7 与skewness/idio_vol关系在选择因子时因子库中的因子不是越多越好可能需要与已有因子进行横截面冗余分析。以此评估新因子是否是已有因子的变体以及新因子的适用性。比如在实际测试中被发现并验证的有效因子有1skewness偏度异象高偏度股票类似彩票可能被高估未来收益低。3idio_vol特质波动率异象高特质波动股票未来收益往往较低。3vol_ratio量比类因子反映成交量相对变化。价量相关性可能与这些因子相关- 与波动率相关高波动常伴随成交量放大。- 与偏度相关极端收益可能伴随异常成交量。- 与量比相关都使用成交量信息。因此最后必须做横截面冗余分析确认新因子是否只是已有因子的变体。2 代码实践分析这里使用一个因子候选探测与样本外验证脚本比较三个价量相关性变体因子。判断它们是否能超越已有因子skewness/idio_vol是否值得纳入因子库。成交量与价格变化的动态耦合关系可能包含趋势确认、情绪、流动性和反转信息但必须证明它独立于已知的偏度、特质波动和量比异象因为因子不是越多越好。# ***** 本代码由AI生成仅用于教学和示例用途 ****** P10 — 价量相关性 (price-volume correlation) 三变体探测. 三个候选因子, 与 incumbent skewness / idio_vol 在同一套 four-lens(全窗口 2023 翻转) 冻结前向框架下比较: * pv_corr_avg : 价量相关性水平 rolling corr(log ret, Δln vol, 60d) * pv_corr_trend : 趋势 corr(20d) - corr(60d) (短-长, 相关性上行) * pv_corr_diff : 一阶差分 pv_corr_avg.diff(5) (5 日变化) 量价相关性定义: corr(涨跌 log 收益, 成交量对数差分 Δln vol), 捕捉价格与 成交量的协同/背离. 符号不硬编码, 由冻结样本内 ICIR 决定做多/做空方向. 口径与 P9(量价/情绪) 一致: 冻结 ≤2022-12-31 → OOS 2023-01-01~2025-12-31, 纯多头 zpos 60d 因果对冲, horizon20 (rebal20). FACTOR_REGISTRY 不修改 (本实验仅探测, 不注册). import argparse import json import os import sys import numpy as np import pandas as pd sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import ic_weighted_test as base # noqa: E402 import beta_neutral_test as btn # noqa: E402 from idio_vol_test import idio_volatility_factor # noqa: E402 from mfm.combine import combine_factors_icir_weight from mfm.data import load_data, load_members, universe_mask from mfm.factors import compute_factors, cross_sectional_normalize from mfm.pipelines.csi300_screen import CSI300ScreenConfig from mfm.validation import validate_factors NAMES (skewness, idio_vol, pv_corr_avg, pv_corr_trend, pv_corr_diff) STOCK (pv_corr_avg, pv_corr_trend, pv_corr_diff) def pv_corr(prices, volumes, window): Rolling Pearson corr of log-return vs log-volume-change (量价相关性). ret np.log(prices / prices.shift(1)) dvol np.log(volumes / volumes.shift(1)) minp max(window // 2, 10) return ret.rolling(window, min_periodsminp).corr(dvol) def xs_corr_series(a, b): Time-series of per-date cross-sectional Pearson corr (pairwise-complete). return a.corrwith(b, axis1) def main() - None: ap argparse.ArgumentParser(description__doc__.splitlines()[0]) ap.add_argument(--tag, requiredTrue) ap.add_argument(--instruments, defaultdata/cn_data/instruments/csi300.txt) ap.add_argument(--start, requiredTrue) ap.add_argument(--end, requiredTrue) ap.add_argument(--freeze, default2022-12-31) ap.add_argument(--oos-start, default2023-01-01) ap.add_argument(--outdir, default./tmp/pvcorr) args ap.parse_args() os.makedirs(args.outdir, exist_okTrue) cfg CSI300ScreenConfig( startargs.start, endargs.end, instruments_pathargs.instruments, out_dirargs.outdir, ) print(f[1/6] loading ({args.tag})...) prices_full, volumes_full load_data( sourceqlib, h5_pathcfg.h5_path, startcfg.start, endcfg.end, max_stockscfg.max_stocks ) segs load_members(cfg.instruments_path) mask_full universe_mask(segs, prices_full.index, prices_full.columns) member_cols mask_full.columns[mask_full.any(axis0)] print(f Universe avg members: {mask_full.sum(axis1).mean():.0f}; fever-member cols: {len(member_cols)} of {mask_full.shape[1]}) prices prices_full[member_cols] volumes volumes_full.reindex(columnsmember_cols) mask mask_full[member_cols] returns prices.pct_change(fill_methodNone) dates prices.index print([2/6] computing pvcorr variants incumbents...) pvc20 pv_corr(prices, volumes, 20) pvc60 pv_corr(prices, volumes, 60) inc compute_factors(prices, volumes, factor_names[skewness, vol_ratio]) facs { skewness: inc[skewness].where(mask), idio_vol: idio_volatility_factor(prices).where(mask), pv_corr_avg: pvc60.where(mask), pv_corr_trend: (pvc20 - pvc60).where(mask), pv_corr_diff: pvc60.diff(5).where(mask), } print([3/6] four-lens (full window 2023 flip)...) val validate_factors(facs, prices, horizoncfg.horizon, n_groups5) hold dates[dates 2023-01-01] val_h validate_factors( {n: facs[n].loc[hold] for n in NAMES}, prices.loc[hold], horizoncfg.horizon, n_groups5, ) four {} for n in NAMES: s val[ic_summary][n] r val[rank_ic_summary][n] sp val[spread_summary][n] h val_h[ic_summary][n] four[n] { icir: float(s[icir]), t_stat: float(s[t_stat]), rank_icir: float(r[icir]), spread_annualized: float(sp[annualized]), spread_sharpe: float(sp[sharpe]), spread_win_rate: float(sp[win_rate]), recent_icir: float(h[icir]), recent_sign_flip: bool(np.sign(s[icir]) ! np.sign(h[icir])), } print(f {n:14s} ICIR{four[n][icir]:.4f} t{four[n][t_stat]:.2f} frankICIR{four[n][rank_icir]:.4f} | spread{four[n][spread_annualized]:.2%} fsharpe{four[n][spread_sharpe]:.2f} | 2023 {four[n][recent_icir]:.4f} fflip{four[n][recent_sign_flip]}) print(f[4/6] frozen OOS monetization (sign {args.freeze})...) cut dates[dates args.freeze] val_is validate_factors( {n: facs[n].loc[cut] for n in NAMES}, prices.loc[cut], horizoncfg.horizon, n_groups5, ) signs {n: 1.0 if val_is[ic_summary][n][icir] 0 else -1.0 for n in NAMES} oos_idx dates[dates args.oos_start] bm returns.where(mask).mean(axis1).shift(-1).loc[oos_idx] ret_next returns.shift(-1).loc[oos_idx] rebal oos_idx[:: cfg.rebal] zero pd.Series(0.0, indexbm.index) money {} for n in NAMES: norm cross_sectional_normalize(facs[n].loc[oos_idx]) comp combine_factors_icir_weight({n: norm}, {n: signs[n]}).where(mask.loc[oos_idx]) book btn.build_scheme_returns(comp, ret_next, rebal, cfg, zpos) bep btn.realized_beta(book, bm) broll btn.rolling_hedge_beta(book, bm) hc base.calc_metrics(btn.hedge(book, bm, broll), zero) money[n] { sign: signs[n], book_beta_expost: bep, hedged_causal_ann: float(hc[annual_return]), hedged_causal_sharpe: float(hc[sharpe_ratio]), hedged_causal_maxdd: float(hc[max_drawdown]), } print(f [{n}] sign {signs[n]:.0f} book_beta{bep:.2f} fhedged_caus ann{hc[annual_return]:.2%} sharpe{hc[sharpe_ratio]:.2f} fmaxDD{hc[max_drawdown]:.1%}) print([5/6] head-to-head vs skewness (P1 rule)...) sk four[skewness] skm money[skewness] print(f incumbent skewness: ICIR{sk[icir]:.4f} rankICIR{sk[rank_icir]:.4f} fspreadSharp{sk[spread_sharpe]:.2f} hedgedSharp{skm[hedged_causal_sharpe]:.2f}) verdict {} for n in STOCK: f four[n] fm money[n] cost_win abs(f[icir]) abs(sk[icir]) and abs(f[rank_icir]) abs(sk[rank_icir]) spread_adj f[spread_sharpe] * fm[sign] money_win spread_adj sk[spread_sharpe] and fm[hedged_causal_sharpe] skm[hedged_causal_sharpe] adopt cost_win and money_win and not f[recent_sign_flip] verdict[n] {cost_win: cost_win, money_win: money_win, flip: f[recent_sign_flip], adopt: bool(adopt)} print(f {n:14s} cost{cost_win} money{money_win} flip{f[recent_sign_flip]} f- adopt{YES if adopt else NO}) print([6/6] cross-sectional redundancy vs incumbents (full-window mean)...) vol_ratio inc[vol_ratio].where(mask) xs {} for n in STOCK: xs[n] { vs_skewness: float(xs_corr_series(facs[n], facs[skewness]).mean()), vs_idio_vol: float(xs_corr_series(facs[n], facs[idio_vol]).mean()), vs_vol_ratio: float(xs_corr_series(facs[n], vol_ratio).mean()), } print(f {n:14s} corr vs skewness{xs[n][vs_skewness]:.3f} fidv{xs[n][vs_idio_vol]:.3f} vol_ratio{xs[n][vs_vol_ratio]:.3f}) out { tag: args.tag, freeze: args.freeze, oos_window: [str(oos_idx[0].date()), str(oos_idx[-1].date())], oos_days: int(len(oos_idx)), four_lens: four, money: money, verdict: verdict, xs_corr: xs, } path os.path.join(args.outdir, fpvcorr_{args.tag}.json) with open(path, w) as fh: json.dump(out, fh, indent1, ensure_asciiFalse) print(f\nSaved - {path}) if __name__ __main__: main()代码如上这里先用滚动价量相关性构造三个量价关系变体再通过四透镜验证、冻结样本内定方向、样本外纯多头 zpos 因果 beta 对冲回测最后与skewness/idio_vol比较预测力、收益、稳健性和冗余度判断是否值得采纳。以下是代码整体流程的梳理过程。2.1 因子实践目标这里构造三个候选因子对应代码中如下部分。[2/6] computing pvcorr variants incumbents...这里再次复述其计算来历。1pv_corr_avg价量相关性水平rolling corr(log ret, Δln vol, 60d)2pv_corr_trend价量相关性趋势corr(20d) - corr(60d)短窗减长窗3pv_corr_diff价量相关性一阶差分pv_corr_avg.diff(5)5 日变化符号不硬编码由样本内ICIR决定做多还是做空方向。2.2 数据与股票池数据和股票池对应代码中数据加载部分“[1/6] loading ({args.tag})...”。这里使用qlib加载价格和成交量。使用csi300.txt加载沪深 300 成分股历史。universe_mask处理成分股变动只保留曾经属于成分股的股票。价格、成交量、mask 对齐后计算因子。2.3 计算因子计算因子核心函数示例如下这里价格用对数收益近似连续复利平稳性更好。成交量用对数差分差分表示增长率非负、右偏取对数后更接近正态。滚动避免单日噪声捕捉一段时间内的稳定关系。def pv_corr(prices, volumes, window):ret np.log(prices / prices.shift(1))dvol np.log(volumes / volumes.shift(1))return ret.rolling(window, min_periodsminp).corr(dvol)同时计算已有因子- skewness收益偏度。- idio_vol特质波动率。- vol_ratio量比类因子用于冗余分析。候选因子统一用.where(mask)限制在股票池内。2.4 四透镜验证四透镜验证对应代码中如下部分[3/6] four-lens (full window 2023 flip)...四透镜验证在这里指全窗口2023翻转val validate_factors(facs, prices, horizoncfg.horizon, n_groups5)hold dates[dates 2023-01-01]val_h validate_factors(...)输出每个因子的- ICIRIC 均值 / IC 波动衡量预测稳定性。- t_statIC 显著性。- rank_icirRankIC 的 ICIR对异常值更稳健。- spread_annualized分组多空价差年化。- spread_sharpe多空价差 Sharpe。- spread_win_rate价差胜率。- recent_icir2023 年以后 ICIR。- recent_sign_flip全窗口与近期 ICIR 符号是否翻转。这一步是在看因子是否有稳定预测力以及近年是否失效或风格切换。2.5 方向货币化冻结样本内定方向样本外货币化对应代码中如下部分[4/6] frozen OOS monetization (sign {args.freeze})...cut dates[dates args.freeze] # 默认 2022-12-31val_is validate_factors(...)signs {n: 1.0 if val_is[ic_summary][n][icir] 0 else -1.0 for n in NAMES}...过程说明如下- 只用 2022-12-31的样本内数据决定因子方向。- OOS(Out-of-Sample, 样本外) 从2023-01-01到2025-12-31。- 因子方向固定避免未来信息。然后对每个因子进行如下处理- 横截面标准化。- 用combine_factors_icir_weight按符号加权单因子时基本等价于方向调整。- 用build_scheme_returns(..., zpos)构建纯多头 z-score 仓位。- 计算事后 beta、滚动因果对冲 beta。- 用hedge做市场 beta 中性得到对冲后年化、Sharpe、最大回撤。其核心思想A股做空受限所以用纯多头zpos再用滚动beta对冲市场风险保留alpha。2.6 与incumbent skewness对比与已有因子skewness的对比对应代码中如下部分5/6] head-to-head vs skewness (P1 rule)...针对每个因子做如下代码所示处理cost_win abs(f[icir]) abs(sk[icir]) and abs(f[rank_icir]) abs(sk[rank_icir])spread_adj f[spread_sharpe] * fm[sign]money_win spread_adj sk[spread_sharpe] and fm[hedged_causal_sharpe] skm[hedged_causal_sharpe]adopt cost_win and money_win and not f[recent_sign_flip]处理逻辑如下cost_win预测能力维度ICIR 和 RankICIR 绝对值都超过 skewness。money_win经济收益维度方向调整后的多空 Sharpe 和对冲后 Sharpe 都超过 skewness。flip稳健性维度2023 没有符号翻转。adopt三者同时满足才建议采纳。2.7 冗余分析最后计算候选因子与已有因子的横截面平均相关性具体为1vs skewness2vs idio_vol3vs vol_ratio如果相关性很高说明增量信息有限如果低且表现好说明可能是独立 alpha 来源。3 代码优点与潜在风险3.1 优点1有样本内/样本外冻结框架避免前视。2同时看 IC、RankIC、分组价差、对冲后收益维度全面。3使用滚动因果 beta 对冲符合实盘因果性。4检查 2023 符号翻转关注因子失效。5做与 incumbent 的冗余相关强调增量信息。3.2 潜在风险1多重检验同时测试三个变体可能数据挖掘需 FDR 或 Bonferroni 校正。2参数敏感20/60/5/20 等窗口可能过拟合需参数稳健性检验。3交易成本未计换手、冲击成本、涨跌停、停牌未体现。4纯多头约束zpos 方案的具体权重规则会影响结果。5对冲不完整只对冲市场 beta未做行业、风格、市值中性。6相关性因子共线价量相关性与波动率、成交量因子可能高度相关。7样本外时间较短2023-2025 仅三年风格切换可能未充分覆盖。8符号确定风险若样本内 ICIR 不显著方向可能由噪声决定。reference---pv_corr为什么能简要解释量价背离现象https://blog.csdn.net/liliang199/article/details/166104461金工定期报告新价量相关性因子绩效月报https://stock.finance.sina.com.cn/stock/view/paper.php?symbolsh000001reportid817751580323量价趋势、信息不对称与股票收益率基于中国A股市场的实证研究https://xtglxb.sjtu.edu.cn/EN/Y2023/V32/I4/774#1沪深股市交易量与收益率及其波动的相关性来自实证分析的证据https://ccj.pku.edu.cn/Article/info?aid301275667The Cross-Section of Volatility and Expected Returnshttps://onlinelibrary.wiley.com/doi/10.1111/j.1540-6261.2006.00836.x#1多因子Beta对冲工具的管理分析https://blog.csdn.net/liliang199/article/details/165348356