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(