feat(HEL-463): 接入剩余行情数据到 datahub
扩展盘后正式集(涨跌停/人气/龙虎榜/板块日线)与盘中观察 API(报价/指数/分时),网站 bridge 按开关接入并回退旧链路;问天改为按数据依赖跟随开关,不再整栈强制旧路径。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
16ba83ec01
commit
605f97e5df
@@ -1,3 +1,99 @@
|
||||
from datahub.adapters.base import ReservedAdapter
|
||||
from __future__ import annotations
|
||||
|
||||
ADAPTER = ReservedAdapter("tencent")
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from datahub.adapters.base import AdapterError, MarketAdapter
|
||||
from datahub.numbers import finite_number, round4
|
||||
|
||||
TENCENT_INDEX_URL = "https://qt.gtimg.cn/q=sh000001,sz399001,sz399006"
|
||||
BROWSER_UA = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
|
||||
class TencentAdapter(MarketAdapter):
|
||||
name = "tencent"
|
||||
|
||||
def __init__(self, timeout: int = 8) -> None:
|
||||
self.timeout = timeout
|
||||
|
||||
def probe(self) -> dict[str, Any]:
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
rows = self.fetch_indices()
|
||||
state = "ok" if len(rows) == 3 else "empty"
|
||||
except AdapterError as exc:
|
||||
return {
|
||||
"provider": self.name,
|
||||
"configured": True,
|
||||
"state": "error",
|
||||
"message": str(exc),
|
||||
"latency_ms": round((time.perf_counter() - started) * 1000),
|
||||
}
|
||||
return {
|
||||
"provider": self.name,
|
||||
"configured": True,
|
||||
"state": state,
|
||||
"latency_ms": round((time.perf_counter() - started) * 1000),
|
||||
}
|
||||
|
||||
def fetch(self, dataset: str, params: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
if dataset in {"indexes_quotes", "index_quotes"}:
|
||||
return self.fetch_indices()
|
||||
raise AdapterError(f"{self.name} unsupported dataset: {dataset}")
|
||||
|
||||
def normalize(self, dataset: str, rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return list(rows)
|
||||
|
||||
def fetch_indices(self) -> list[dict[str, Any]]:
|
||||
request = urllib.request.Request(
|
||||
TENCENT_INDEX_URL,
|
||||
headers={"User-Agent": BROWSER_UA, "Referer": "https://gu.qq.com/"},
|
||||
method="GET",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=self.timeout) as response:
|
||||
raw = response.read().decode("gb18030", errors="ignore")
|
||||
except Exception as exc:
|
||||
raise AdapterError(f"tencent request failed: {exc}") from exc
|
||||
result = []
|
||||
for line in raw.splitlines():
|
||||
if '="' not in line:
|
||||
continue
|
||||
fields = line.split('="', 1)[1].rsplit('";', 1)[0].split("~")
|
||||
if len(fields) < 38:
|
||||
continue
|
||||
code = fields[2]
|
||||
if code not in {"000001", "399001", "399006"}:
|
||||
continue
|
||||
try:
|
||||
quote_time = datetime.strptime(fields[30], "%Y%m%d%H%M%S").astimezone()
|
||||
except ValueError as exc:
|
||||
raise AdapterError(f"Tencent invalid quote time for {code}") from exc
|
||||
ts_code = "000001.SH" if code == "000001" else f"{code}.SZ"
|
||||
result.append(
|
||||
{
|
||||
"ts_code": ts_code,
|
||||
"code": code,
|
||||
"name": fields[1] or code,
|
||||
"price": round4(finite_number(fields[3])),
|
||||
"pct_chg": round4(finite_number(fields[32])),
|
||||
"change_amount": round4(finite_number(fields[31])),
|
||||
"open": round4(finite_number(fields[5])),
|
||||
"high": round4(finite_number(fields[33])),
|
||||
"low": round4(finite_number(fields[34])),
|
||||
"previous_close": round4(finite_number(fields[4])),
|
||||
"amount": round4(finite_number(fields[37]) * 10000),
|
||||
"quote_time_epoch": int(quote_time.timestamp()),
|
||||
"quote_time": quote_time.isoformat(timespec="seconds"),
|
||||
"source": "tencent_qt",
|
||||
}
|
||||
)
|
||||
if len(result) != 3:
|
||||
raise AdapterError(f"Tencent returned {len(result)}/3 indices")
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user