feat(selection): implement gold brick resonance strategy with evaluation and logging enhancements

This commit is contained in:
yuxuanhui
2026-09-05 10:22:08 +08:00
parent 9163590070
commit 79476252b1
20 changed files with 1060 additions and 97 deletions
@@ -14,10 +14,16 @@ from zhixing_server.modules.selection.application.chart import (
SelectionChart,
SelectionChartNotFound,
)
from zhixing_server.modules.selection.application.evaluate_gold_brick import (
EvaluateGoldBrick,
)
from zhixing_server.modules.selection.application.run import (
RunZhixingB1,
)
from zhixing_server.modules.selection.domain.models import SelectionSignal
from zhixing_server.modules.selection.domain.models import (
SelectionSignal,
SelectionStrategyName,
)
from zhixing_server.modules.selection.domain.pattern_scoring import (
PatternScore,
ZhixingB1PatternScorer,
@@ -45,7 +51,7 @@ selection_router = APIRouter()
_SELECTION_POOL_CACHE_LOCK = threading.Lock()
_SELECTION_POOL_CACHE: dict[tuple[str, int], SelectionPostgresPool] = {}
StrategyValue = Literal["zhixing_b1"]
StrategyValue = SelectionStrategyName
SelectionStatusValue = Literal[
"no_data",
"running",
@@ -216,6 +222,7 @@ def get_selection_service(
return RunZhixingB1(
reader,
store,
evaluators={"gold_brick": EvaluateGoldBrick(reader)},
pattern_case_loader=pattern_case_loader,
pattern_scorer=ZhixingB1PatternScorer(),
pattern_scoring_enabled=settings.selection_pattern_scoring_enabled,
@@ -324,7 +331,9 @@ def get_selection_run(
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 10,
search: Annotated[str | None, Query(max_length=100)] = None,
category: Literal["pullback", "oversold", "original"] | None = None,
category: Literal[
"pullback", "oversold", "original", "resonance"
] | None = None,
sort: Literal["code", "score_desc", "score_asc"] = "code",
) -> SelectionResultsResponse:
"""Return one run for asynchronous polling."""
@@ -347,7 +356,9 @@ def get_selection_results(
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 10,
search: Annotated[str | None, Query(max_length=100)] = None,
category: Literal["pullback", "oversold", "original"] | None = None,
category: Literal[
"pullback", "oversold", "original", "resonance"
] | None = None,
sort: Literal["code", "score_desc", "score_asc"] = "code",
) -> SelectionResultsResponse:
"""Return the current persisted result for a strategy and optional date."""
@@ -410,7 +421,13 @@ def _run_response(run: SelectionRun, *, query: SelectionResultQuery) -> Selectio
reason=item.reason,
)
for item in run.items
if item.status in {"insufficient_history", "missing_target_bar", "data_error"}
if item.status
in {
"insufficient_history",
"missing_target_bar",
"missing_turnover_rate",
"data_error",
}
],
stocks=[
SelectionStockResponse(
@@ -496,7 +513,7 @@ def _result_query(
page: int,
page_size: int,
search: str | None,
category: Literal["pullback", "oversold", "original"] | None,
category: Literal["pullback", "oversold", "original", "resonance"] | None,
sort: Literal["code", "score_desc", "score_asc"],
) -> SelectionResultQuery:
"""Normalize HTTP query values before handing them to the selection port."""