默认全部读取/影子开关关闭,网站继续走旧 Tushare 链路;开启单项时只替换该类原料并在失败时回旧,问天保持旧路径。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
154 lines
5.3 KiB
Python
154 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from backend.data.numbers import finite_number
|
|
|
|
|
|
AMOUNT_THOUSAND_YUAN = 1000.0
|
|
AMOUNT_WAN_YUAN = 10000.0
|
|
VOLUME_LOT = 100.0
|
|
|
|
API_TO_DATASET = {
|
|
"trade_cal": "calendar",
|
|
"stock_basic": "stocks",
|
|
"daily": "daily",
|
|
"daily_basic": "valuation",
|
|
"index_daily": "index_daily",
|
|
"moneyflow": "moneyflow",
|
|
"stk_auction": "auction",
|
|
}
|
|
|
|
SCALE_FIELDS = {
|
|
"daily": {"vol": VOLUME_LOT, "amount": AMOUNT_THOUSAND_YUAN},
|
|
"index_daily": {"vol": VOLUME_LOT, "amount": AMOUNT_THOUSAND_YUAN},
|
|
"valuation": {"total_mv": AMOUNT_WAN_YUAN, "circ_mv": AMOUNT_WAN_YUAN},
|
|
"moneyflow": {
|
|
"buy_sm_amount": AMOUNT_WAN_YUAN,
|
|
"sell_sm_amount": AMOUNT_WAN_YUAN,
|
|
"buy_md_amount": AMOUNT_WAN_YUAN,
|
|
"sell_md_amount": AMOUNT_WAN_YUAN,
|
|
"buy_lg_amount": AMOUNT_WAN_YUAN,
|
|
"sell_lg_amount": AMOUNT_WAN_YUAN,
|
|
"buy_elg_amount": AMOUNT_WAN_YUAN,
|
|
"sell_elg_amount": AMOUNT_WAN_YUAN,
|
|
"net_mf_amount": AMOUNT_WAN_YUAN,
|
|
},
|
|
"auction": {"vol": VOLUME_LOT, "float_share": AMOUNT_WAN_YUAN},
|
|
}
|
|
|
|
|
|
def yyyymmdd(value: Any) -> str:
|
|
return str(value or "").replace("-", "")[:8]
|
|
|
|
|
|
def to_native_rows(dataset: str, rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
return [to_native_row(dataset, row) for row in rows]
|
|
|
|
|
|
def to_native_row(dataset: str, row: dict[str, Any]) -> dict[str, Any]:
|
|
if dataset == "calendar":
|
|
is_open = row.get("is_open")
|
|
return {
|
|
"exchange": str(row.get("exchange") or "SSE"),
|
|
"cal_date": yyyymmdd(row.get("cal_date")),
|
|
"is_open": 1 if is_open in (True, 1, "1", "Y", "y") else 0,
|
|
"pretrade_date": yyyymmdd(row.get("pretrade_date")) or None,
|
|
}
|
|
converted = dict(row)
|
|
converted.pop("batch_id", None)
|
|
if "volume" in converted and "vol" not in converted:
|
|
converted["vol"] = converted.pop("volume")
|
|
elif "volume" in converted:
|
|
converted.pop("volume", None)
|
|
scales = SCALE_FIELDS.get(dataset) or {}
|
|
for field, factor in scales.items():
|
|
if field in converted:
|
|
converted[field] = _unscale(converted.get(field), factor)
|
|
if dataset == "stocks":
|
|
converted.pop("updated_at", None)
|
|
return converted
|
|
|
|
|
|
def to_canonical_row(dataset: str, row: dict[str, Any]) -> dict[str, Any]:
|
|
if dataset == "calendar":
|
|
is_open = row.get("is_open")
|
|
return {
|
|
"exchange": str(row.get("exchange") or "SSE"),
|
|
"cal_date": yyyymmdd(row.get("cal_date")),
|
|
"is_open": 1 if is_open in (True, 1, "1", "Y", "y") else 0,
|
|
"pretrade_date": yyyymmdd(row.get("pretrade_date")) or None,
|
|
}
|
|
converted = dict(row)
|
|
if "volume" in converted and "vol" not in converted:
|
|
converted["vol"] = converted.pop("volume")
|
|
scales = SCALE_FIELDS.get(dataset) or {}
|
|
for field, factor in scales.items():
|
|
if field in converted:
|
|
converted[field] = _scale(converted.get(field), factor)
|
|
return converted
|
|
|
|
|
|
def row_key(dataset: str, row: dict[str, Any]) -> tuple[str, ...]:
|
|
if dataset == "calendar":
|
|
return (yyyymmdd(row.get("cal_date")),)
|
|
if dataset == "stocks":
|
|
return (str(row.get("ts_code") or "").upper(),)
|
|
if dataset == "status":
|
|
return (str(row.get("dataset") or ""), yyyymmdd(row.get("trade_date")))
|
|
return (str(row.get("ts_code") or "").upper(), yyyymmdd(row.get("trade_date")))
|
|
|
|
|
|
def project_fields(rows: list[dict[str, Any]], fields: str) -> list[dict[str, Any]]:
|
|
keys = [item.strip() for item in str(fields or "").split(",") if item.strip()]
|
|
if not keys:
|
|
return rows
|
|
return [{key: row.get(key) for key in keys} for row in rows]
|
|
|
|
|
|
def filter_stock_rows(rows: list[dict[str, Any]], params: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
payload = params or {}
|
|
ts_code = str(payload.get("ts_code") or "").strip().upper()
|
|
status = str(payload.get("list_status") or "").strip()
|
|
name = str(payload.get("name") or "").strip()
|
|
filtered = rows
|
|
if ts_code:
|
|
filtered = [row for row in filtered if str(row.get("ts_code") or "").upper() == ts_code]
|
|
if status:
|
|
filtered = [row for row in filtered if str(row.get("list_status") or status) == status]
|
|
if name:
|
|
filtered = [row for row in filtered if name.casefold() in str(row.get("name") or "").casefold()]
|
|
return filtered
|
|
|
|
|
|
def filter_calendar_rows(rows: list[dict[str, Any]], params: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
payload = params or {}
|
|
if payload.get("is_open") in (1, "1", True):
|
|
return [row for row in rows if int(row.get("is_open") or 0) == 1]
|
|
if payload.get("is_open") in (0, "0", False):
|
|
return [row for row in rows if int(row.get("is_open") or 0) == 0]
|
|
return rows
|
|
|
|
|
|
def _scale(value: Any, factor: float) -> float | None:
|
|
number = _optional_number(value)
|
|
if number is None:
|
|
return None
|
|
return number * factor
|
|
|
|
|
|
def _unscale(value: Any, factor: float) -> float | None:
|
|
number = _optional_number(value)
|
|
if number is None or factor == 0:
|
|
return None
|
|
return number / factor
|
|
|
|
|
|
def _optional_number(value: Any) -> float | None:
|
|
if value in (None, ""):
|
|
return None
|
|
number = finite_number(value, default=float("nan"))
|
|
if number != number:
|
|
return None
|
|
return number
|