From b3ea8e7b047c996519c9204faa0217082a72271f Mon Sep 17 00:00:00 2001 From: claude_dev Date: Fri, 17 Jul 2026 11:51:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(factor):=20=E5=9B=A0=E5=AD=90=E5=88=86?= =?UTF-8?q?=E6=9E=90=E7=AB=AF=E5=88=B0=E7=AB=AF=E4=BF=AE=E5=A4=8D(?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E7=BC=BA=E5=A4=B1+vt=5Fsymbol+IC/tears?= =?UTF-8?q?=E8=A7=A3=E8=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因链: 1. alphalens + scikit-learn 没装 → analyzer:76 软错误 return {error:alphalens not installed}(已 pip 装 alphalens-reloaded 0.4.6 + scikit-learn 1.7.2,requirements-lock 已锁)。 2. symbols 传 vt_symbol(600000.SSE)但 read_db_daily 查 DB 用裸代码 → bars 空 → factor/prices 全空 → alphalens infer_trading_calendar weekmask 全零 → busdaycal 崩。 3. statistics 含 date/numpy(之前 optimize 已修 result_store _json_default)。 4. alphalens-reloaded × pandas2 的 groupby.transform 兼容:demean_forward_returns tears 崩 No objects to concatenate。 适配修复(不动 vnpy/alphalens 源码): - symbols 归一:vt_symbol → 裸代码(split '.'),read_db_daily 能查到。 - IC/tears 解耦:IC 算完先存 ic_summary success,tears 独立 try(失败标 tears_error 不覆盖 IC)——IC 核心数值可用,tears 报告作为已知限制。 - error 加 traceback 字段(调试友好)。 验证: 3 symbol(600000/000001/600519) ma5 因子 IC 成功(1D/5D/10D mean/icir/t_stat count=49),浏览器 /factor/result 页表格渲染。单 symbol IC NaN 是截面用法(需≥2标的),非 bug。 --- sanguo_factor/analyzer.py | 52 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/sanguo_factor/analyzer.py b/sanguo_factor/analyzer.py index e62206c..35b7887 100644 --- a/sanguo_factor/analyzer.py +++ b/sanguo_factor/analyzer.py @@ -2,6 +2,7 @@ import sys import os import warnings +import traceback _VNPY_SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "vnpy_v4.4.0")) if _VNPY_SRC not in sys.path: sys.path.insert(0, _VNPY_SRC) @@ -66,6 +67,11 @@ def run_factor_analysis( """ from .registry import get_factor + # symbols 兼容:前端/vnpy 可能传 vt_symbol("600000.SSE"),read_db_daily 查 DB 的 key + # 是裸代码(同 cta_engine),带后缀查不到 → bars 空 → factor/prices 全空 → alphalens 崩。 + # 统一归一成裸代码(DB key)。 + symbols = [str(s).split(".")[0] for s in symbols] + # API path passes cfg=None → load default data_platform.yaml (so read_db_daily # and AlphaLabSession can find the A-share DB). if cfg is None: @@ -203,6 +209,18 @@ def run_factor_analysis( # Ensure datetime index for prices prices_df.index = pd.to_datetime(prices_df.index) + # busdaycal 调试:定位 factor/prices 数据是否空或日期不对齐 + if prices_df.empty or len(factor_dates) == 0: + fmin = factor_pd['datetime'].min() if len(factor_pd) else None + fmax = factor_pd['datetime'].max() if len(factor_pd) else None + cmin = close_pd['datetime'].min() if len(close_pd) else None + cmax = close_pd['datetime'].max() if len(close_pd) else None + ic_summary[factor_name] = { + "status": "error", + "error": f"DBG empty: factor_df.height={factor_df.height}, factor_dates={len(factor_dates)}, factor_series={len(factor_series)}, close_rows={len(close_pd)}, prices_df={prices_df.shape}, factor_dt={fmin}~{fmax}, close_dt={cmin}~{cmax}", + } + continue + # Call get_clean_factor_and_forward_returns merged_data = get_clean_factor_and_forward_returns( factor=factor_series, @@ -248,12 +266,18 @@ def run_factor_analysis( # Capture IC extraction error but continue with tears report ic_data = {"error": f"IC extraction failed: {type(ic_error).__name__}: {ic_error}"} - # Generate tears sheet + # IC 已算完(ic_data)——先存成功。因子分析的核心指标(IC/ICIR/t_stat)可用, + # 即使下面的 tears 报告因 alphalens-reloaded × pandas2 的 groupby.transform + # 兼容问题崩,也不影响 IC 数值。 + ic_summary[factor_name] = { + "status": "success", + "ic": ic_data, + } + + # Generate tears sheet(独立 try:tears 失败只标注,不覆盖上面的 IC 成功) from io import StringIO - import sys old_stdout = sys.stdout sys.stdout = StringIO() # Capture stdout to avoid display issues - try: create_full_tear_sheet( merged_data, @@ -261,27 +285,21 @@ def run_factor_analysis( group_neutral=False, by_group=False ) + factor_report_path = os.path.join(output_dir, f"{factor_name}_tears.html") + plt.savefig(factor_report_path.replace(".html", ".png")) # Save as PNG + report_paths[factor_name] = factor_report_path.replace(".png", ".html") + ic_summary[factor_name]["report"] = factor_report_path + except Exception as tears_e: + ic_summary[factor_name]["tears_error"] = f"{type(tears_e).__name__}: {tears_e}" finally: sys.stdout = old_stdout # Restore stdout - # Save the tears report - factor_report_path = os.path.join(output_dir, f"{factor_name}_tears.html") - plt.savefig(factor_report_path.replace(".html", ".png")) # Save as PNG - report_paths[factor_name] = factor_report_path.replace(".png", ".html") # Mark HTML as report - - # Store basic IC summary (simplified) — close prices sourced from DB (real) - status = "success" - ic_summary[factor_name] = { - "status": status, - "report": factor_report_path, - "ic": ic_data # Add IC statistics - } - except Exception as e: err_type = type(e).__name__ ic_summary[factor_name] = { "status": "error", - "error": f"{err_type}: {e}" + "error": f"{err_type}: {e}", + "traceback": traceback.format_exc() } return FactorReport(