rebuild(runtime): govern market operations and job truth

This commit is contained in:
leefer
2026-07-30 10:14:29 +08:00
parent 4fc8691eee
commit d8f0dd930c
39 changed files with 2224 additions and 79 deletions
+116
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
import re
import threading
import urllib.error
import urllib.request
@@ -89,6 +90,14 @@ class IfindProvider:
) -> dict[str, ProviderResult | dict[str, Any]]:
raise ProviderError("iFinD is not the post-close snapshot authority")
def realtime_market_inputs(
self,
trade_date: str,
previous_trade_date: str,
identifiers: tuple[str, ...],
) -> dict[str, ProviderResult | dict[str, Any]]:
raise ProviderError("iFinD不承担全市场盘中快照计算")
def sector_members(self, representative: str, trade_date: str) -> ProviderResult:
raise ProviderError("iFinD is not the Shenwan constituent authority")
@@ -145,6 +154,76 @@ class IfindProvider:
),
)
def event_reasons(self, trade_date: str) -> ProviderResult:
date = datetime.fromisoformat(_display(trade_date)).date()
display = f"{date.year}{date.month}{date.day}"
queries = {
"limit_up": (
f"{display}涨停股票,股票代码、股票简称、涨停原因、"
"首次涨停时间、最终涨停时间、开板次数"
),
"broken": (
f"{display}曾涨停但收盘未涨停的股票,股票代码、股票简称、"
"涨停原因、首次涨停时间、开板次数"
),
"limit_down": f"{display}跌停股票,股票代码、股票简称、跌停原因",
}
rows: list[dict[str, Any]] = []
completed = 0
for event_type, query in queries.items():
try:
payload = self._request(
"smart_stock_picking",
{"searchstring": query, "searchtype": "stock"},
)
except ProviderError:
continue
completed += 1
for raw in _result(
payload, "event", "not_applicable", SnapshotState.FINAL
).rows:
identifier = _event_identifier(raw)
if not identifier:
continue
reason_tokens = (
("跌停原因", "风险线索", "原因")
if event_type == "limit_down"
else ("涨停原因类别", "涨停原因", "触板逻辑", "原因")
)
rows.append(
{
"event_type": event_type,
"identifier": identifier,
"reason": str(_event_field(raw, reason_tokens) or "").strip(),
"first_time": _event_time(
_event_field(
raw, ("首次涨停时间", "首次触板时间", "首次封板时间")
)
),
"last_time": _event_time(
_event_field(
raw, ("最终涨停时间", "最后涨停时间", "最后封板时间")
)
),
"open_times": _event_integer(
_event_field(raw, ("开板次数", "打开涨停次数"))
),
}
)
return ProviderResult(
tuple(rows),
ObservationMetadata(
source=self.source,
observed_at=datetime.now(SHANGHAI),
unit="event",
adjustment="not_applicable",
freshness_seconds=0,
coverage=completed / len(queries),
state=SnapshotState.FINAL,
usage=DataUsage.CALCULATION,
),
)
def screener_inputs(self, trade_dates: tuple[str, ...]) -> dict[str, ProviderResult | None]:
raise ProviderError("iFinD尚未批准用于盘后因子批量计算")
@@ -295,3 +374,40 @@ def _error_code(payload: dict[str, Any]) -> int:
return int(payload.get("errorcode", payload.get("code", 0)) or 0)
except (TypeError, ValueError):
return -1
def _event_field(row: dict[str, Any], tokens: tuple[str, ...]) -> Any:
for token in tokens:
for key, value in row.items():
if token in str(key) and value not in (None, ""):
return value
return None
def _event_identifier(row: dict[str, Any]) -> str:
value = _event_field(row, ("股票代码", "证券代码", "代码", "thscode"))
text = str(value or "").strip().upper()
match = re.search(r"(?<!\d)(\d{6})(?!\d)", text)
if not match:
return ""
code = match.group(1)
if re.fullmatch(r"\d{6}\.(?:SH|SZ|BJ)", text):
return text
suffix = "BJ" if code.startswith(("4", "8", "9")) else "SH" if code.startswith("6") else "SZ"
return f"{code}.{suffix}"
def _event_time(value: Any) -> str:
text = str(value or "").strip()
match = re.search(r"(?<!\d)(\d{1,2}):(\d{2})(?::\d{2})?(?!\d)", text)
if match:
return f"{int(match.group(1)):02d}:{match.group(2)}"
compact = re.search(r"(?<!\d)(\d{2})(\d{2})(\d{2})(?!\d)", text)
return f"{compact.group(1)}:{compact.group(2)}" if compact else ""
def _event_integer(value: Any) -> int | None:
try:
return max(0, int(float(value))) if value not in (None, "") else None
except (TypeError, ValueError):
return None