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
@@ -21,11 +21,14 @@ from backend.data.providers.tushare_client import TushareClient
|
||||
|
||||
LOGGER = logging.getLogger("xiaobai.datahub")
|
||||
ShadowSink = Callable[[dict[str, Any]], None]
|
||||
EMPTY_FAIL_DATASETS = {"stocks", "daily", "index_daily", "valuation", "moneyflow", "auction"}
|
||||
EMPTY_FAIL_DATASETS = {
|
||||
"stocks", "daily", "index_daily", "valuation", "moneyflow", "auction",
|
||||
"limit_events", "sector_daily",
|
||||
}
|
||||
|
||||
|
||||
def looks_like_heaven(module_name: str, filename: str = "") -> bool:
|
||||
"""问天调用栈识别。问天未永久冻结,只是本阶段仍走旧 Tushare 链路。"""
|
||||
"""问天调用栈识别(诊断用)。问天按数据集依赖接入,不再整栈强制旧链路。"""
|
||||
path = filename.replace("\\", "/")
|
||||
return module_name.startswith("backend.features.heaven") or "/features/heaven/" in path
|
||||
|
||||
@@ -96,8 +99,8 @@ class DatahubBridge:
|
||||
legacy_query: Callable[..., list[dict[str, Any]]],
|
||||
) -> list[dict[str, Any]]:
|
||||
dataset = API_TO_DATASET.get(api_name)
|
||||
# 问天允许后续纳入 datahub;首批只读接入仍保持旧链路,避免误切。
|
||||
if not dataset or self.heaven_guard():
|
||||
# 问天按实际数据依赖接入:已映射到 hub 的 API 跟随开关;未映射的继续旧链路。
|
||||
if not dataset:
|
||||
return legacy_query(api_name, params, fields)
|
||||
flags = self.settings.flags(dataset)
|
||||
if not flags.read and not flags.shadow:
|
||||
@@ -108,7 +111,7 @@ class DatahubBridge:
|
||||
hub_error: str | None = None
|
||||
hub_canonical: list[dict[str, Any]] = []
|
||||
try:
|
||||
response = self._fetch_dataset(dataset, params or {})
|
||||
response = self._fetch_dataset(dataset, params or {}, api_name=api_name)
|
||||
hub_canonical = self._extract_rows(dataset, response, params or {})
|
||||
hub_rows = to_native_rows(dataset, hub_canonical)
|
||||
hub_meta = dict(response.meta)
|
||||
@@ -136,7 +139,7 @@ class DatahubBridge:
|
||||
return project_fields(hub_rows, fields)
|
||||
return legacy_query(api_name, params, fields)
|
||||
|
||||
def _fetch_dataset(self, dataset: str, params: dict[str, Any]) -> DatahubResponse:
|
||||
def _fetch_dataset(self, dataset: str, params: dict[str, Any], api_name: str = "") -> DatahubResponse:
|
||||
date = yyyymmdd(params.get("trade_date") or params.get("date"))
|
||||
start = yyyymmdd(params.get("start_date") or params.get("from") or date)
|
||||
end = yyyymmdd(params.get("end_date") or params.get("to") or date)
|
||||
@@ -153,6 +156,10 @@ class DatahubBridge:
|
||||
"valuation": self.client.valuation,
|
||||
"moneyflow": self.client.moneyflow,
|
||||
"auction": self.client.auction,
|
||||
"limit_events": self.client.limit_events,
|
||||
"popularity": self.client.popularity,
|
||||
"dragon_tiger": self.client.dragon_tiger,
|
||||
"sector_daily": self.client.sectors,
|
||||
}
|
||||
fetcher = fetchers[dataset]
|
||||
query: dict[str, Any] = {}
|
||||
@@ -167,6 +174,23 @@ class DatahubBridge:
|
||||
query["to"] = end
|
||||
if dataset == "daily":
|
||||
query["adjust"] = "none"
|
||||
if dataset == "limit_events":
|
||||
limit_type = str(params.get("limit_type") or "").strip().upper()
|
||||
if limit_type:
|
||||
query["limit_type"] = limit_type
|
||||
if dataset == "popularity":
|
||||
if api_name == "ths_hot":
|
||||
query["source"] = "ths"
|
||||
elif api_name == "dc_hot":
|
||||
query["source"] = "dc"
|
||||
if dataset == "sector_daily":
|
||||
family = {
|
||||
"ths_daily": "ths",
|
||||
"dc_index": "dc",
|
||||
"sw_daily": "sw",
|
||||
}.get(api_name, "")
|
||||
if family:
|
||||
query["family"] = family
|
||||
return self._paginate(fetcher, query)
|
||||
|
||||
def _paginate(self, fetcher: Callable[..., DatahubResponse], params: dict[str, Any]) -> DatahubResponse:
|
||||
|
||||
@@ -60,6 +60,27 @@ class DatahubClient:
|
||||
def auction(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/auction", params)
|
||||
|
||||
def limit_events(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/limit-events", params)
|
||||
|
||||
def popularity(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/popularity", params)
|
||||
|
||||
def dragon_tiger(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/dragon-tiger", params)
|
||||
|
||||
def sectors(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/sectors", params)
|
||||
|
||||
def quotes_latest(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/quotes/latest", params)
|
||||
|
||||
def index_quotes(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/indexes/quotes", params)
|
||||
|
||||
def intraday_points(self, **params: Any) -> DatahubResponse:
|
||||
return self.get("/v1/intraday/points", params)
|
||||
|
||||
def dataset_status(self, date: str) -> DatahubResponse:
|
||||
return self.get("/v1/datasets/status", {"date": date})
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ API_TO_DATASET = {
|
||||
"index_daily": "index_daily",
|
||||
"moneyflow": "moneyflow",
|
||||
"stk_auction": "auction",
|
||||
"limit_list_d": "limit_events",
|
||||
"ths_hot": "popularity",
|
||||
"dc_hot": "popularity",
|
||||
"hm_detail": "dragon_tiger",
|
||||
"ths_daily": "sector_daily",
|
||||
"dc_index": "sector_daily",
|
||||
"sw_daily": "sector_daily",
|
||||
}
|
||||
|
||||
SCALE_FIELDS = {
|
||||
@@ -35,6 +42,16 @@ SCALE_FIELDS = {
|
||||
"net_mf_amount": AMOUNT_WAN_YUAN,
|
||||
},
|
||||
"auction": {"vol": VOLUME_LOT, "float_share": AMOUNT_WAN_YUAN},
|
||||
"limit_events": {
|
||||
"limit_amount": AMOUNT_WAN_YUAN,
|
||||
"float_mv": AMOUNT_WAN_YUAN,
|
||||
"total_mv": AMOUNT_WAN_YUAN,
|
||||
},
|
||||
"dragon_tiger": {
|
||||
"buy_amount": AMOUNT_WAN_YUAN,
|
||||
"sell_amount": AMOUNT_WAN_YUAN,
|
||||
"net_amount": AMOUNT_WAN_YUAN,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +84,16 @@ def to_native_row(dataset: str, row: dict[str, Any]) -> dict[str, Any]:
|
||||
converted[field] = _unscale(converted.get(field), factor)
|
||||
if dataset == "stocks":
|
||||
converted.pop("updated_at", None)
|
||||
if dataset == "popularity":
|
||||
# keep hub source; callers filter ths/dc themselves when needed
|
||||
if converted.get("ts_name") and not converted.get("name"):
|
||||
converted["name"] = converted.get("ts_name")
|
||||
if dataset == "dragon_tiger":
|
||||
if converted.get("ts_name") and not converted.get("name"):
|
||||
converted["name"] = converted.get("ts_name")
|
||||
if dataset == "sector_daily":
|
||||
if converted.get("pct_change") is not None and converted.get("pct_chg") is None:
|
||||
converted["pct_chg"] = converted.get("pct_change")
|
||||
return converted
|
||||
|
||||
|
||||
@@ -96,6 +123,30 @@ def row_key(dataset: str, row: dict[str, Any]) -> tuple[str, ...]:
|
||||
return (str(row.get("ts_code") or "").upper(),)
|
||||
if dataset == "status":
|
||||
return (str(row.get("dataset") or ""), yyyymmdd(row.get("trade_date")))
|
||||
if dataset == "limit_events":
|
||||
return (
|
||||
str(row.get("ts_code") or "").upper(),
|
||||
yyyymmdd(row.get("trade_date")),
|
||||
str(row.get("limit_type") or ""),
|
||||
)
|
||||
if dataset == "popularity":
|
||||
return (
|
||||
str(row.get("ts_code") or "").upper(),
|
||||
yyyymmdd(row.get("trade_date")),
|
||||
str(row.get("source") or ""),
|
||||
)
|
||||
if dataset == "dragon_tiger":
|
||||
return (
|
||||
str(row.get("ts_code") or "").upper(),
|
||||
yyyymmdd(row.get("trade_date")),
|
||||
str(row.get("hm_name") or ""),
|
||||
)
|
||||
if dataset == "sector_daily":
|
||||
return (
|
||||
str(row.get("ts_code") or "").upper(),
|
||||
yyyymmdd(row.get("trade_date")),
|
||||
str(row.get("family") or ""),
|
||||
)
|
||||
return (str(row.get("ts_code") or "").upper(), yyyymmdd(row.get("trade_date")))
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ DATASETS = (
|
||||
"valuation",
|
||||
"moneyflow",
|
||||
"auction",
|
||||
"limit_events",
|
||||
"popularity",
|
||||
"dragon_tiger",
|
||||
"sector_daily",
|
||||
"quotes",
|
||||
"index_quotes",
|
||||
"intraday",
|
||||
"status",
|
||||
)
|
||||
|
||||
@@ -28,6 +35,13 @@ ENV_DATASET = {
|
||||
"valuation": "VALUATION",
|
||||
"moneyflow": "MONEYFLOW",
|
||||
"auction": "AUCTION",
|
||||
"limit_events": "LIMIT_EVENTS",
|
||||
"popularity": "POPULARITY",
|
||||
"dragon_tiger": "DRAGON_TIGER",
|
||||
"sector_daily": "SECTOR_DAILY",
|
||||
"quotes": "QUOTES",
|
||||
"index_quotes": "INDEX_QUOTES",
|
||||
"intraday": "INTRADAY",
|
||||
"status": "STATUS",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user