From ad5992291935a39624bb6bc4346ca09ceb2c7f91 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sat, 15 Aug 2026 07:08:21 +0800 Subject: [PATCH] =?UTF-8?q?test(factor):=20test=5Fanalyzer=E4=B8=89?= =?UTF-8?q?=E4=B8=AAnever-ran=E6=B5=8B=E8=AF=95=E4=BF=AE=E5=A4=8D=E2=80=94?= =?UTF-8?q?=E2=80=94=E8=A1=A5read=5Fdb=5Fdaily=E5=81=87bars(analyzer?= =?UTF-8?q?=E7=9C=9F=E8=AF=BB=E5=BA=93=E5=BB=BAprices,=E7=BC=BAbars?= =?UTF-8?q?=E8=A7=A6=E5=8F=91DBG=E7=A9=BA=E5=AE=88=E5=8D=AB=E7=9F=AD?= =?UTF-8?q?=E8=B7=AFtears/IC)+factor=E6=97=A5=E6=9C=9F=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=E7=94=9F=E4=BA=A7aware=E5=8F=A3=E5=BE=84(Asia/Shanghai);?= =?UTF-8?q?=E6=AD=A4=E6=89=B9=E6=B5=8B=E8=AF=95=E5=8E=9F=E5=9C=A8=E4=BB=BB?= =?UTF-8?q?=E4=BD=95=E7=8E=AF=E5=A2=83=E9=83=BD=E6=B2=A1=E8=B7=91=E8=BF=87?= =?UTF-8?q?(Mac=E7=BC=BA=E4=BE=9D=E8=B5=96skip/=E5=AE=B9=E5=99=A8=E7=BC=BA?= =?UTF-8?q?pytest/CI=E5=8F=AA=E8=B7=91data=5Fplatform),Mac=E8=A1=A5?= =?UTF-8?q?=E9=BD=90lock=E4=BE=9D=E8=B5=96=E5=90=8E=E6=9A=B4=E9=9C=B2;?= =?UTF-8?q?=E5=85=A8=E9=87=8F900=E7=BB=BF=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/factor/test_analyzer.py | 45 +++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/tests/factor/test_analyzer.py b/tests/factor/test_analyzer.py index 704181f..7adfcad 100644 --- a/tests/factor/test_analyzer.py +++ b/tests/factor/test_analyzer.py @@ -6,6 +6,22 @@ sys.path.insert(0, _VNPY_SRC) from unittest.mock import Mock, patch, MagicMock import tempfile +from datetime import datetime +from types import SimpleNamespace +from zoneinfo import ZoneInfo + +_SH = ZoneInfo("Asia/Shanghai") + + +def _fake_bars(symbol, days, price=100.0): + """read_db_daily 假 bars。analyzer 会单独调 read_db_daily 取 close 建 prices, + 且 prices 日期须与 factor 日期对齐(aware, Asia/Shanghai)——不喂 bars 会触发 + DBG empty 守卫直接 continue,tears/IC 永不被调到(2026-08-15 前这批测试在 + 任何环境都没跑过:Mac 缺依赖 skip/容器缺 pytest/CI 只跑 data_platform)。""" + return [ + SimpleNamespace(datetime=datetime(2024, 1, d), vt_symbol=symbol, close_price=price) + for d in days + ] def test_run_factor_analysis_returns_report(): @@ -113,12 +129,17 @@ def test_run_factor_analysis_calls_tears(tmp_path): with patch("sanguo_factor.analyzer.AlphaLabSession") as MS, \ patch("sanguo_factor.analyzer.get_clean_factor_and_forward_returns") as MC, \ - patch("sanguo_factor.analyzer.create_full_tear_sheet") as MT: + patch("sanguo_factor.analyzer.create_full_tear_sheet") as MT, \ + patch("sanguo_data.datareader.read_db_daily", + return_value=_fake_bars("600000", (2, 3, 4, 5, 8))): import polars as pl + # 非空 + aware 日期(生产 compute_factors 会 localize;naive 会被 prices 对齐 + # isin 过滤成空触发 DBG 守卫) + _days = (2, 3, 4, 5, 8) MS.return_value.compute_factors.return_value = pl.DataFrame({ - "datetime": [], - "vt_symbol": [], - "ma5": [] + "datetime": [datetime(2024, 1, d, tzinfo=_SH) for d in _days], + "vt_symbol": ["600000"] * len(_days), + "ma5": [0.5] * len(_days), }) MC.return_value = MagicMock() report = run_factor_analysis( @@ -144,7 +165,7 @@ def test_run_factor_analysis_extracts_ic_values(tmp_path): from sanguo_factor.analyzer import run_factor_analysis # Create mock factor_data with MultiIndex (datetime, asset) and IC columns - dates = pd.date_range("2024-01-01", periods=10, freq="D") + dates = pd.date_range("2024-01-01", periods=10, freq="D", tz="Asia/Shanghai") assets = ["AAPL", "GOOGL"] index = pd.MultiIndex.from_product([dates, assets], names=["datetime", "asset"]) @@ -162,7 +183,7 @@ def test_run_factor_analysis_extracts_ic_values(tmp_path): "10D": [0.12, 0.10, 0.13, 0.11, 0.12, 0.11, 0.12, 0.10, 0.11, 0.12] }, index=dates) - # Mock polars DataFrame + # Mock polars DataFrame(datetime 用带时区 ISO 串,与 _fake_bars localize 后对齐) mock_pl_df = MagicMock() mock_pl_df.to_pandas.return_value = pd.DataFrame({ "datetime": [d.isoformat() for d in dates for _ in assets], @@ -174,7 +195,9 @@ def test_run_factor_analysis_extracts_ic_values(tmp_path): with patch("sanguo_factor.analyzer.AlphaLabSession") as MS, \ patch("sanguo_factor.analyzer.get_clean_factor_and_forward_returns") as MC, \ patch("sanguo_factor.analyzer.create_full_tear_sheet") as MT, \ - patch("sanguo_factor.analyzer.factor_information_coefficient") as MIC: + patch("sanguo_factor.analyzer.factor_information_coefficient") as MIC, \ + patch("sanguo_data.datareader.read_db_daily", + return_value=_fake_bars("AAPL", range(1, 11)) + _fake_bars("GOOGL", range(1, 11))): # Mock compute_factors to return polars DataFrame MS.return_value.compute_factors.return_value = mock_pl_df @@ -221,10 +244,10 @@ def test_run_factor_analysis_ic_extraction_fails_gracefully(tmp_path): from sanguo_factor.analyzer import run_factor_analysis import pandas as pd - # Mock polars DataFrame + # Mock polars DataFrame(datetime 带时区,与 _fake_bars 对齐防 DBG 空守卫) mock_pl_df = MagicMock() mock_pl_df.to_pandas.return_value = pd.DataFrame({ - "datetime": ["2024-01-01"], + "datetime": ["2024-01-01T00:00:00+08:00"], "vt_symbol": ["AAPL"], "ma5": [0.5], "close": [100.0] @@ -233,7 +256,9 @@ def test_run_factor_analysis_ic_extraction_fails_gracefully(tmp_path): with patch("sanguo_factor.analyzer.AlphaLabSession") as MS, \ patch("sanguo_factor.analyzer.get_clean_factor_and_forward_returns") as MC, \ patch("sanguo_factor.analyzer.create_full_tear_sheet") as MT, \ - patch("sanguo_factor.analyzer.factor_information_coefficient") as MIC: + patch("sanguo_factor.analyzer.factor_information_coefficient") as MIC, \ + patch("sanguo_data.datareader.read_db_daily", + return_value=_fake_bars("AAPL", (1, 2))): MS.return_value.compute_factors.return_value = mock_pl_df