rebuild(stage-5): establish market data gateway and charts

This commit is contained in:
leefer
2026-07-30 02:35:42 +08:00
parent 40ad5d6836
commit cf0ab7026f
45 changed files with 2701 additions and 46 deletions
+105
View File
@@ -0,0 +1,105 @@
from __future__ import annotations
import json
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime
from zoneinfo import ZoneInfo
from backend.data.contracts import (
DataSource,
DataUsage,
ObservationMetadata,
ProviderResult,
SnapshotState,
)
from backend.data.providers.base import ProviderError
SHANGHAI = ZoneInfo("Asia/Shanghai")
INDEX_CODES = {"000001.SH": "1.000001", "399001.SZ": "0.399001", "399006.SZ": "0.399006"}
class EastmoneyProvider:
source = DataSource.EASTMONEY
url = "https://push2delay.eastmoney.com/api/qt/stock/trends2/get"
def __init__(self, timeout: int = 6) -> None:
self._timeout = timeout
@property
def configured(self) -> bool:
return True
def calendar(self, start_date: str, end_date: str) -> ProviderResult:
raise ProviderError("The display provider is not a calendar authority")
def entities(self) -> ProviderResult:
raise ProviderError("The display provider is not an entity authority")
def daily(self, entity_type: str, identifier: str, end_date: str) -> ProviderResult:
raise ProviderError("The display provider does not supply canonical daily bars")
def minute(self, entity_type: str, identifier: str, trade_date: str) -> ProviderResult:
secid = self._secid(entity_type, identifier)
params = urllib.parse.urlencode(
{
"secid": secid,
"fields1": "f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13",
"fields2": "f51,f52,f53,f54,f55,f56,f57,f58",
"iscr": "0",
"ndays": "1",
}
)
request = urllib.request.Request(
f"{self.url}?{params}",
headers={"Accept": "application/json", "User-Agent": "XiaobaiReview/2"},
)
try:
with urllib.request.urlopen(request, timeout=self._timeout) as response:
payload = json.loads(response.read().decode("utf-8"))
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
raise ProviderError("展示行情请求失败") from exc
data = payload.get("data") or {}
rows = []
for raw in data.get("trends") or []:
fields = str(raw).split(",")
if len(fields) < 8 or " " not in fields[0]:
continue
date, time = fields[0].split(" ", 1)
if date != trade_date or not "09:30" <= time[:5] <= "15:00":
continue
rows.append(
{
"time": fields[0],
"open": fields[1],
"close": fields[2],
"high": fields[3],
"low": fields[4],
"volume": fields[5],
"amount": fields[6],
"avgPrice": fields[7],
"preClose": data.get("preClose"),
}
)
metadata = ObservationMetadata(
source=self.source,
observed_at=datetime.now(SHANGHAI),
unit="yuan/share",
adjustment="unadjusted",
freshness_seconds=0,
coverage=1 if rows else 0,
state=SnapshotState.REALTIME,
usage=DataUsage.DISPLAY,
)
return ProviderResult(tuple(rows), metadata)
@staticmethod
def _secid(entity_type: str, identifier: str) -> str:
if entity_type == "index" and identifier in INDEX_CODES:
return INDEX_CODES[identifier]
code = identifier.split(".")[0]
if entity_type == "stock" and len(code) == 6 and code.isdigit():
market = "1" if code.startswith(("5", "6", "9")) else "0"
return f"{market}.{code}"
raise ProviderError("该标的暂无展示分时数据")