"""Application tests for bounded qfq chart series.""" from datetime import date, timedelta import pandas as pd import pytest from zhixing_server.modules.selection.application.chart import ( GetSelectionChart, SelectionChartNotFound, ) from zhixing_server.modules.selection.domain.indicators import ( compute_kdj, compute_zhixing_lines, ) from zhixing_server.modules.selection.domain.models import SelectionBar, StockHistory class FakeReader: """Return one deterministic history through the requested date.""" def __init__(self, history: StockHistory) -> None: self.history = history self.request: tuple[str, date] | None = None def load_history(self, ts_code: str, target_trade_date: date) -> StockHistory: self.request = (ts_code, target_trade_date) return self.history def _history(count: int) -> StockHistory: start = date(2025, 1, 1) bars = tuple( SelectionBar( trade_date=start + timedelta(days=index), open=10.0 + index / 100, high=11.0 + index / 100, low=9.0 + index / 100, close=10.5 + index / 100, volume=1000.0 + index, ) for index in range(count) ) return StockHistory(ts_code="000001.SZ", name="平安银行", bars=bars) def test_chart_computes_on_full_history_before_returning_last_250_points() -> None: history = _history(260) reader = FakeReader(history) target = history.bars[-1].trade_date chart = GetSelectionChart(reader).execute(history.ts_code, target) assert reader.request == ("000001.SZ", target) assert chart.source_adj == "qfq" assert len(chart.points) == 250 assert chart.points[0].trade_date == history.bars[10].trade_date assert chart.points[-1].trade_date == target frame = pd.DataFrame( { "low": [bar.low for bar in history.bars], "high": [bar.high for bar in history.bars], "close": [bar.close for bar in history.bars], } ) expected = compute_kdj(frame).iloc[-1] expected_white, expected_yellow = compute_zhixing_lines(frame["close"]) assert chart.points[-1].k == pytest.approx(float(expected["K"])) assert chart.points[-1].d == pytest.approx(float(expected["D"])) assert chart.points[-1].j == pytest.approx(float(expected["J"])) assert chart.points[-1].trend_white == pytest.approx(float(expected_white.iloc[-1])) assert chart.points[-1].trend_yellow == pytest.approx(float(expected_yellow.iloc[-1])) def test_chart_filters_future_rows_and_preserves_nullable_points() -> None: history = _history(12) target = history.bars[-2].trade_date incomplete = SelectionBar( trade_date=history.bars[3].trade_date, open=history.bars[3].open, high=None, low=history.bars[3].low, close=None, volume=None, ) history = StockHistory( ts_code=history.ts_code, name=history.name, bars=history.bars[:3] + (incomplete,) + history.bars[4:], ) chart = GetSelectionChart(FakeReader(history)).execute(history.ts_code, target) assert chart.points[-1].trade_date == target assert all(point.trade_date <= target for point in chart.points) assert chart.points[3].high is None assert chart.points[3].volume is None assert chart.points[3].k is None assert chart.points[3].d is None assert chart.points[3].j is None def test_chart_rejects_empty_history() -> None: reader = FakeReader(StockHistory(ts_code="000001.SZ", name="平安银行")) with pytest.raises(SelectionChartNotFound, match="chart data not found"): GetSelectionChart(reader).execute("000001.SZ", date(2026, 8, 8))