diff --git a/sanguo_factor/data_adapter.py b/sanguo_factor/data_adapter.py index 4f721bc..ef3b1bb 100644 --- a/sanguo_factor/data_adapter.py +++ b/sanguo_factor/data_adapter.py @@ -18,12 +18,13 @@ def convert_bars_to_alpha_df(bars: list[BarData]) -> pl.DataFrame: bars: List of BarData objects from Phase 1 read_db_daily Returns: - polars DataFrame with columns: vt_symbol, datetime, open, high, low, close, volume, turnover, open_interest + polars DataFrame with columns: vt_symbol, datetime, open, high, low, close, volume, turnover, open_interest, vwap Note: SPIKE CORRECTION: AlphaLab.save_bar_data stores parquet columns as datetime, vt_symbol, open, high, low, close, volume, turnover, open_interest (NOT open_price, close_price - this was corrected in S1 spike testing) + vwap: turnover/volume 派生(volume=0 置null),Alpha101 表达式依赖 """ if not bars: return pl.DataFrame(schema={ @@ -36,6 +37,7 @@ def convert_bars_to_alpha_df(bars: list[BarData]) -> pl.DataFrame: "volume": pl.Float64, "turnover": pl.Float64, "open_interest": pl.Float64, + "vwap": pl.Float64, }) return pl.DataFrame({ @@ -48,6 +50,7 @@ def convert_bars_to_alpha_df(bars: list[BarData]) -> pl.DataFrame: "volume": [float(b.volume) for b in bars], "turnover": [float(b.turnover) if b.turnover is not None else 0.0 for b in bars], "open_interest": [float(b.open_interest) if b.open_interest is not None else 0.0 for b in bars], + "vwap": [(b.turnover / b.volume) if (b.turnover and b.volume) else None for b in bars], }) diff --git a/tests/factor/test_data_adapter.py b/tests/factor/test_data_adapter.py index 66f41c2..c29fc4a 100644 --- a/tests/factor/test_data_adapter.py +++ b/tests/factor/test_data_adapter.py @@ -63,7 +63,7 @@ def test_convert_empty_bars(): assert df.height == 0 # Should still have schema defined - assert len(df.columns) == 9 # vt_symbol, datetime, open, high, low, close, volume, turnover, open_interest + assert len(df.columns) == 10 # vt_symbol, datetime, open, high, low, close, volume, turnover, open_interest, vwap def test_save_alpha_lab_data(): diff --git a/tests/factor/test_vwap_adapter.py b/tests/factor/test_vwap_adapter.py new file mode 100644 index 0000000..46049c7 --- /dev/null +++ b/tests/factor/test_vwap_adapter.py @@ -0,0 +1,33 @@ +"""vwap 派生列:turnover/volume,volume=0 → null.""" +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "vnpy_v4.4.0"))) + +from datetime import datetime +from vnpy.trader.object import BarData +from vnpy.trader.constant import Exchange, Interval +from sanguo_factor.data_adapter import convert_bars_to_alpha_df + + +def _bar(symbol, dt, close=10.0, volume=100.0, turnover=1000.0): + return BarData( + symbol=symbol, exchange=Exchange.SSE, datetime=dt, interval=Interval.DAILY, + open_price=9.9, high_price=10.1, low_price=9.8, close_price=close, + volume=volume, turnover=turnover, open_interest=0, gateway_name="T", + ) + + +def test_vwap_derived(): + df = convert_bars_to_alpha_df([_bar("600000", datetime(2024, 1, 5))]) + assert df["vwap"][0] == 10.0 # 1000 / 100 + + +def test_vwap_null_when_volume_zero(): + df = convert_bars_to_alpha_df([_bar("600000", datetime(2024, 1, 5), volume=0.0, turnover=0.0)]) + assert df["vwap"][0] is None + + +def test_empty_schema_has_vwap(): + df = convert_bars_to_alpha_df([]) + assert "vwap" in df.columns + assert df.height == 0