Files
tick-stock-panel/backend/tests/test_enriched_refresh_memory.py

81 lines
3.6 KiB
Python

from datetime import date, timedelta
import polars as pl
import pytest
from polars.testing import assert_frame_equal
from app.indicators import pipeline
@pytest.fixture
def history(monkeypatch):
monkeypatch.setattr(pipeline, "_custom_signal_exprs", {
"signal_test_previous": (pl.col("close") > pl.col("close").shift(1).over("symbol")),
})
symbols = ["600000.SH", "300001.SZ", "688001.SH", "000001.SZ", "920001.BJ"]
rows = []
for i, symbol in enumerate(symbols):
for day in range(160):
close = 10 + i + day * 0.01 + (day % 7) * 0.2
rows.append({
"symbol": symbol, "date": date(2025, 1, 1) + timedelta(days=day),
"open": close - 0.1, "high": close + 0.3, "low": close - 0.3,
"close": close, "volume": float(1000 + day * 7), "amount": close * 1000,
"raw_close": close, "raw_high": close + 0.3, "raw_low": close - 0.3,
})
return pl.DataFrame(rows).sample(fraction=1, shuffle=True, seed=17)
@pytest.mark.parametrize("with_shares", [False, True])
def test_history_metadata_matches_original_refresh(history, tmp_path, with_shares):
symbols = history["symbol"].unique().sort().to_list()
instruments = pl.DataFrame({
"symbol": symbols[:-1], "name": ["stock", "ST stock", "stock", "stock"],
"total_shares": [2000000.0] * 4, "float_shares": [1000000.0] * 4,
})
shares = pl.DataFrame({
"symbol": [symbols[0]], "period_end": [date(2025, 2, 1)],
"announce_date": [date(2025, 3, 1)], "float_shares": [500000.0],
}) if with_shares else None
benchmark = history.filter(pl.col("symbol") == symbols[0]).with_columns(
pl.lit("000001.SH").alias("symbol"),
)
index_path = tmp_path / "kline_index_daily" / "part.parquet"
index_path.parent.mkdir()
benchmark.write_parquet(index_path)
expected = pipeline.compute_limit_signals(
pipeline.compute_signals(pipeline.attach_deviation_columns(
pipeline.compute_indicators(history.sort("symbol", "date")), tmp_path,
)),
instruments, historical_shares=shares,
)
missing = [c for c in ("name", "total_shares", "float_shares") if c not in expected.columns]
expected = expected.join(instruments.select("symbol", *missing), on="symbol", how="left")
actual = pipeline.compute_enriched_history_window(
history, tmp_path, instruments=instruments, historical_shares=shares, sym_batch=2,
include_instrument_metadata=True,
)
assert_frame_equal(actual, expected.sort("symbol", "date"), check_exact=True)
@pytest.mark.parametrize("instruments", [None, pl.DataFrame(), pl.DataFrame({"symbol": ["600000.SH"]})])
def test_history_optional_metadata_keeps_legacy_inputs(history, tmp_path, instruments):
expected = pipeline.compute_enriched_history_window(history, tmp_path, instruments, sym_batch=2)
actual = pipeline.compute_enriched_history_window(
history, tmp_path, instruments, sym_batch=2, include_instrument_metadata=True,
)
assert_frame_equal(actual, expected, check_exact=True)
def test_history_wide_sort_is_bounded_by_batch(history, tmp_path, monkeypatch):
original_sort = pl.DataFrame.sort
def bounded_sort(frame, *args, **kwargs):
if frame.width > history.width:
assert frame["symbol"].n_unique() <= 2, "full history wide sort copies the cache"
return original_sort(frame, *args, **kwargs)
monkeypatch.setattr(pl.DataFrame, "sort", bounded_sort)
result = pipeline.compute_enriched_history_window(history, tmp_path, sym_batch=2)
assert result.select("symbol", "date").equals(history.select("symbol", "date").sort("symbol", "date"))