fix(factor): tears对秩类离散因子必空根治(qcut duplicates+quantiles=10) [vps]
CI/CD / test (push) Successful in 1s
CI/CD / nas-deploy (push) Successful in 6s
CI/CD / nas-verify (push) Successful in 8s

alpha16实锤(08-30用户验收factor_b7f3a2e8无图表):cs_rank(ts_cov(cs_rank,cs_rank,5))
截面值高度并列(中位日25值仅6唯一,-19.5×12/-5×9)→alphalens quantize_factor的
pd.qcut(x,q)不传duplicates(默认raise)→Bin edges must be unique→no_raise吞成全NaN
→binning丢100%(303/303日期全抛)→merged空→IC全'No valid IC values'+tears
IndexError+报告空=页面无图表。该形态下此类因子tears永远不可能成功。

修复三件:
①tears_data.py加pd.qcut duplicates='drop'幂等patch(与demean pandas2 patch同位,
_SANGUO flag防重入)——并列值并入少数分位,quantile_keys动态短化前端已容错
②analyzer get_clean调用前import tears_data激活模块级补丁(原import在IC后=补丁
晚到)+quantiles默认5→10显式传参(对齐tears页D1..D10设计与排行榜decile口径)
③build_tears_data qkeys防御:<2组多空退化零序列/0组空Series,不再IndexError

+回归测试:离散并列因子走真实alphalens binning(原测试自合成factor_quantile
绕过该路径=漏掉此bug的原因);RED→GREEN双向验证(无patch merged空(0,3)97.5%
bin丢/有patch绿)。factor+api 123绿;NAS容器30只股端到端:binning丢0.0%、
merged 7732行、tears三周期出数(ic_mean 5D=-0.0014)
This commit is contained in:
2026-08-30 09:36:21 +08:00
parent 32d17676b3
commit 8eba3e1cf1
3 changed files with 73 additions and 3 deletions
+26 -2
View File
@@ -26,6 +26,24 @@ try:
except ImportError:
pass
# 同款幂等补丁:alphalens quantize_factor 的 pd.qcut(x, q) 不传 duplicates
# (默认 'raise')——秩类因子(cs_rank/ts_rank 族,如 alpha16)截面值高度并列,
# 分位边界必重合 → ValueError: Bin edges must be unique → 被 no_raise 吞成
# 全 NaN → binning 丢 100% → merged 空 → IC 全空 + tears 崩(NAS 2026-08-30
# 实锤:alpha16 303/303 日期全抛,中位日 25 值仅 6 唯一)。改默认 duplicates='drop'
# 后并列值并入少数分位(quantile_keys 变短,前端已按实际 keys 动态渲染)。
_orig_qcut = pd.qcut
def _qcut_dup_drop(*args, **kwargs):
kwargs.setdefault("duplicates", "drop")
return _orig_qcut(*args, **kwargs)
if not getattr(pd.qcut, "_sanguo_dup_drop", False):
_qcut_dup_drop._sanguo_dup_drop = True
pd.qcut = _qcut_dup_drop
_TRADING_DAYS = 252
@@ -97,8 +115,14 @@ def build_tears_data(merged_data, periods: tuple = (1, 5, 10)) -> dict:
quantile_nav[str(q)] = [round(float(v), 6) for v in _nav(daily_q)]
quantile_annual[str(q)] = round(_annualized(daily_q, p), 4)
# 多空 = 最高分位 − 最低分位(每日,demeaned 超额口径同 tearsheet)
ls_daily = qd[qkeys[-1]] - qd[qkeys[0]]
# 多空 = 最高分位 − 最低分位(每日,demeaned 超额口径同 tearsheet)
# 离散因子 duplicates='drop' 后极端形态可能仅 1 组:多空退化为零序列。
if len(qkeys) >= 2:
ls_daily = qd[qkeys[-1]] - qd[qkeys[0]]
elif qkeys:
ls_daily = qd[qkeys[0]] * 0.0
else:
ls_daily = pd.Series(dtype=float)
ls_nav = _nav(ls_daily)
n = len(ic)