feat(selection): add Zhixing line indicators to selection chart and update related components

This commit is contained in:
yuxuanhui
2026-09-01 22:57:58 +08:00
parent 3da90f7df8
commit f41ad0915e
10 changed files with 100 additions and 25 deletions
@@ -9,7 +9,7 @@ from typing import Literal, cast
import pandas as pd
from ..domain.indicators import compute_kdj
from ..domain.indicators import compute_kdj, compute_zhixing_lines
from ..domain.ports import MarketDataReader
SELECTION_CHART_LIMIT = 250
@@ -21,7 +21,7 @@ class SelectionChartNotFound(LookupError):
@dataclass(frozen=True, slots=True)
class SelectionChartPoint:
"""One date-aligned OHLCV and KDJ point in the public chart vocabulary."""
"""One date-aligned OHLCV, KDJ, and Zhixing-line point."""
trade_date: date
open: float | None
@@ -32,6 +32,8 @@ class SelectionChartPoint:
k: float | None
d: float | None
j: float | None
trend_white: float | None
trend_yellow: float | None
@dataclass(frozen=True, slots=True)
@@ -46,7 +48,7 @@ class SelectionChart:
class GetSelectionChart:
"""Load one qfq history, compute KDJ, then bound the response series."""
"""Load one qfq history, compute KDJ and Zhixing lines, then bound the series."""
def __init__(self, reader: MarketDataReader) -> None:
"""Inject the market-data reader owned by the selection context."""
@@ -54,7 +56,7 @@ class GetSelectionChart:
self.reader = reader
def execute(self, ts_code: str, target_trade_date: date) -> SelectionChart:
"""Return at most 250 points without changing KDJ's full-history state.
"""Return at most 250 points without changing full-history indicator state.
Args:
ts_code: Tushare stock identifier selected by the user.
@@ -83,6 +85,7 @@ class GetSelectionChart:
}
)
kdj = compute_kdj(frame)
white, yellow = compute_zhixing_lines(frame["close"])
start = max(0, len(bars) - SELECTION_CHART_LIMIT)
points = tuple(
SelectionChartPoint(
@@ -95,6 +98,8 @@ class GetSelectionChart:
k=_finite_or_none(kdj.iloc[index]["K"]),
d=_finite_or_none(kdj.iloc[index]["D"]),
j=_finite_or_none(kdj.iloc[index]["J"]),
trend_white=_finite_or_none(white.iloc[index]),
trend_yellow=_finite_or_none(yellow.iloc[index]),
)
for index, bar in enumerate(bars)
if index >= start
@@ -147,7 +147,7 @@ class SelectionStockResponse(BaseModel):
class SelectionChartPointResponse(BaseModel):
"""One date-aligned qfq OHLCV and KDJ point."""
"""One date-aligned qfq OHLCV, KDJ, and Zhixing-line point."""
trade_date: date
open: float | None
@@ -158,6 +158,8 @@ class SelectionChartPointResponse(BaseModel):
k: float | None
d: float | None
j: float | None
trend_white: float | None
trend_yellow: float | None
class SelectionChartResponse(BaseModel):
@@ -268,7 +270,7 @@ def get_selection_chart(
target_trade_date: date,
service: Annotated[GetSelectionChart, Depends(get_selection_chart_service)],
) -> SelectionChartResponse:
"""Return one stock's bounded qfq OHLCV and KDJ history."""
"""Return one stock's bounded qfq OHLCV, KDJ, and Zhixing-line history."""
try:
chart = service.execute(ts_code, target_trade_date)
@@ -482,6 +484,8 @@ def _chart_response(chart: SelectionChart) -> SelectionChartResponse:
k=point.k,
d=point.d,
j=point.j,
trend_white=point.trend_white,
trend_yellow=point.trend_yellow,
)
for point in chart.points
],