feat(selection): 补充策略执行结果查询链路
This commit is contained in:
@@ -11,12 +11,17 @@ import psycopg
|
||||
from ....bootstrap.config import Settings
|
||||
from ..domain.models import SelectionBar, SelectionDailyBasic, StockHistory
|
||||
from ..domain.ports import MarketDataReaderError
|
||||
from ..domain.runs import SelectionExecutionSource, SelectionStock
|
||||
|
||||
|
||||
class SelectionReaderError(MarketDataReaderError):
|
||||
"""Database read failure with stock and target-date context."""
|
||||
|
||||
|
||||
class SelectionMarketDataNotReady(MarketDataReaderError):
|
||||
"""The requested date has no market-data batch eligible for selection."""
|
||||
|
||||
|
||||
_HISTORY_QUERY = """
|
||||
SELECT
|
||||
bar.ts_code,
|
||||
@@ -41,6 +46,41 @@ WHERE bar.ts_code = %s
|
||||
ORDER BY bar.trade_date ASC
|
||||
"""
|
||||
|
||||
_SOURCE_QUERY = """
|
||||
SELECT id, target_count, valid_count, coverage
|
||||
FROM market_sync_batch
|
||||
WHERE target_trade_date = %s
|
||||
AND strategy_eligible = true
|
||||
AND status IN ('success', 'partial_success')
|
||||
ORDER BY finished_at DESC NULLS LAST, created_at DESC, id DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
|
||||
_ELIGIBLE_STOCKS_QUERY = """
|
||||
SELECT stock.ts_code, stock.name
|
||||
FROM market_stock AS stock
|
||||
WHERE stock.is_active = true
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM market_daily_bar AS bar
|
||||
WHERE bar.ts_code = stock.ts_code
|
||||
AND bar.trade_date = %s
|
||||
AND bar.source_adj = 'qfq'
|
||||
AND bar.open IS NOT NULL
|
||||
AND bar.high IS NOT NULL
|
||||
AND bar.low IS NOT NULL
|
||||
AND bar.close IS NOT NULL
|
||||
AND bar.vol IS NOT NULL
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM market_daily_basic AS basic
|
||||
WHERE basic.ts_code = stock.ts_code
|
||||
AND basic.trade_date = %s
|
||||
)
|
||||
ORDER BY stock.ts_code
|
||||
"""
|
||||
|
||||
|
||||
def _as_date(value: object) -> date:
|
||||
"""Convert a PostgreSQL date-like scalar to a date."""
|
||||
@@ -122,6 +162,64 @@ class PostgresMarketDataReader:
|
||||
daily_basic={trade_date: daily_basic[trade_date] for trade_date in sorted(daily_basic)},
|
||||
)
|
||||
|
||||
def load_execution_source(
|
||||
self,
|
||||
strategy: str,
|
||||
target_trade_date: date,
|
||||
) -> SelectionExecutionSource:
|
||||
"""Load the qualified market-data snapshot for a strategy run.
|
||||
|
||||
Args:
|
||||
strategy: Supported strategy identity. The current reader accepts
|
||||
``zhixing_b1`` and keeps the parameter explicit for future
|
||||
strategy-specific eligibility rules.
|
||||
target_trade_date: Historical trading date to evaluate.
|
||||
|
||||
Returns:
|
||||
The eligible stock snapshot and its source synchronization facts.
|
||||
|
||||
Raises:
|
||||
SelectionMarketDataNotReady: If no eligible synchronization batch
|
||||
or complete active stock exists for the requested date.
|
||||
SelectionReaderError: If PostgreSQL cannot complete the read.
|
||||
"""
|
||||
|
||||
if strategy != "zhixing_b1":
|
||||
raise SelectionMarketDataNotReady(f"unsupported selection strategy: {strategy}")
|
||||
try:
|
||||
with psycopg.connect(self.database_url) as connection:
|
||||
source_row = connection.execute(_SOURCE_QUERY, (target_trade_date,)).fetchone()
|
||||
if source_row is None:
|
||||
raise SelectionMarketDataNotReady(
|
||||
f"market data is not strategy-eligible for {target_trade_date.isoformat()}"
|
||||
)
|
||||
stock_rows = connection.execute(
|
||||
_ELIGIBLE_STOCKS_QUERY,
|
||||
(target_trade_date, target_trade_date),
|
||||
).fetchall()
|
||||
except SelectionMarketDataNotReady:
|
||||
raise
|
||||
except psycopg.Error as exc:
|
||||
raise SelectionReaderError(
|
||||
f"failed to load selection source at {target_trade_date.isoformat()}"
|
||||
) from exc
|
||||
|
||||
stocks = tuple(
|
||||
SelectionStock(ts_code=str(row[0]), name=str(row[1] or "")) for row in stock_rows
|
||||
)
|
||||
if not stocks:
|
||||
raise SelectionMarketDataNotReady(
|
||||
f"no eligible stocks have complete market data for {target_trade_date.isoformat()}"
|
||||
)
|
||||
return SelectionExecutionSource(
|
||||
market_sync_batch_id=str(source_row[0]),
|
||||
target_trade_date=target_trade_date,
|
||||
target_count=int(source_row[1]),
|
||||
valid_count=int(source_row[2]),
|
||||
coverage=Decimal(str(source_row[3])),
|
||||
stocks=stocks,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _map_row(
|
||||
row: tuple[object, ...],
|
||||
|
||||
Reference in New Issue
Block a user