fix(HEL-494): 数据中枢独占调度,主网站不再回退旧接口
主网站只向中枢要业务数据;来源选择、切源、补数全部在中枢内部完成,失败不再走东财/腾讯/Tushare 保底。 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
ef13d6feb5
commit
0b8419abca
@@ -250,6 +250,11 @@ class EastmoneyAdapter(MarketAdapter):
|
||||
secid = INDEX_SECIDS[code]
|
||||
entity = "index"
|
||||
identifier = code
|
||||
elif code.startswith("BK") or code.endswith((".TI", ".SI")):
|
||||
symbol = code.split(".")[0]
|
||||
secid = f"90.{symbol}"
|
||||
entity = "board"
|
||||
identifier = symbol
|
||||
else:
|
||||
symbol = code.split(".")[0]
|
||||
market = "1" if symbol.startswith(("5", "6", "9")) else "0"
|
||||
@@ -294,6 +299,108 @@ class EastmoneyAdapter(MarketAdapter):
|
||||
"source": "eastmoney_trends2",
|
||||
}
|
||||
|
||||
def fetch_shenwan_quote(self, ts_code: str) -> dict[str, Any]:
|
||||
code = str(ts_code or "").split(".")[0]
|
||||
if not code:
|
||||
raise AdapterError("Invalid Shenwan code")
|
||||
payload = self._get_json(
|
||||
EASTMONEY_INDEX_URL,
|
||||
{
|
||||
"secids": f"90.{code}",
|
||||
"fltt": "2",
|
||||
"invt": "2",
|
||||
"fields": "f12,f14,f2,f3,f4,f15,f16,f17,f18,f6,f8,f104,f105,f128,f136,f140,f124",
|
||||
},
|
||||
referer="https://quote.eastmoney.com/",
|
||||
)
|
||||
rows = list((payload.get("data") or {}).get("diff") or [])
|
||||
row = next((item for item in rows if item), None)
|
||||
if not row:
|
||||
raise AdapterError(f"Eastmoney Shenwan quote missing for {code}")
|
||||
epoch = int(finite_number(row.get("f124")) or 0)
|
||||
close = round4(finite_number(row.get("f2")))
|
||||
previous = round4(finite_number(row.get("f18")))
|
||||
if close <= 0 or previous <= 0:
|
||||
raise AdapterError(f"Eastmoney Shenwan quote empty for {code}")
|
||||
quote_time = (
|
||||
datetime.fromtimestamp(epoch).astimezone().isoformat(timespec="seconds")
|
||||
if epoch
|
||||
else ""
|
||||
)
|
||||
return {
|
||||
"ts_code": f"{code}.SI",
|
||||
"code": f"{code}.SI",
|
||||
"name": row.get("f14") or code,
|
||||
"price": close,
|
||||
"close": close,
|
||||
"pre_close": previous,
|
||||
"previous_close": previous,
|
||||
"open": round4(finite_number(row.get("f17"))),
|
||||
"high": round4(finite_number(row.get("f15"))),
|
||||
"low": round4(finite_number(row.get("f16"))),
|
||||
"change": round4(finite_number(row.get("f3"))),
|
||||
"pct_change": round4(finite_number(row.get("f3"))),
|
||||
"pct_chg": round4(finite_number(row.get("f3"))),
|
||||
"amount": round4(finite_number(row.get("f6"))),
|
||||
"leader": row.get("f128") or "--",
|
||||
"leader_code": row.get("f140") or "",
|
||||
"leading_pct": round4(finite_number(row.get("f136"))),
|
||||
"up_count": int(finite_number(row.get("f104")) or 0),
|
||||
"down_count": int(finite_number(row.get("f105")) or 0),
|
||||
"quote_time": quote_time,
|
||||
"trade_time": quote_time,
|
||||
"quote_date": datetime.fromtimestamp(epoch).astimezone().strftime("%Y%m%d") if epoch else "",
|
||||
"quote_time_epoch": epoch,
|
||||
"source": "eastmoney_sw",
|
||||
}
|
||||
|
||||
def fetch_limit_pool(self, trade_date: str = "") -> list[dict[str, Any]]:
|
||||
day = str(trade_date or "").replace("-", "")
|
||||
rows: list[dict[str, Any]] = []
|
||||
for url, limit_type in (
|
||||
("https://push2ex.eastmoney.com/getTopicZTPool", "U"),
|
||||
("https://push2ex.eastmoney.com/getTopicZBPool", "Z"),
|
||||
):
|
||||
params = {
|
||||
"ut": "7eea3edcaed734bea9cbfc24409ed989",
|
||||
"dpt": "wz.ztzt",
|
||||
"PageIndex": "0",
|
||||
"PageSize": "200",
|
||||
"sort": "fbt:asc",
|
||||
"stat": "1",
|
||||
}
|
||||
if day:
|
||||
params["date"] = day
|
||||
try:
|
||||
payload = self._get_json(url, params, referer="https://quote.eastmoney.com/")
|
||||
except AdapterError:
|
||||
continue
|
||||
pool = ((payload.get("data") or {}).get("pool") or []) if isinstance(payload.get("data"), dict) else []
|
||||
for item in pool:
|
||||
code = str(item.get("c") or item.get("code") or "")
|
||||
if not code:
|
||||
continue
|
||||
market = str(item.get("m") or item.get("market") or "")
|
||||
suffix = "SH" if market in {"1", "SH"} or code.startswith(("5", "6", "9")) else "SZ"
|
||||
first = str(item.get("fbt") or item.get("first_time") or "")
|
||||
last = str(item.get("lbt") or item.get("last_time") or "")
|
||||
rows.append(
|
||||
{
|
||||
"ts_code": f"{code}.{suffix}",
|
||||
"limit_type": limit_type,
|
||||
"first_time": first,
|
||||
"last_time": last,
|
||||
"fd_amount": item.get("fund") or item.get("fd_amount"),
|
||||
"open_times": item.get("zbc") or item.get("open_times"),
|
||||
"limit_times": item.get("lbc") or item.get("limit_times"),
|
||||
"turnover_ratio": item.get("hs") or item.get("turnover_ratio"),
|
||||
"source": "eastmoney_zt_pool",
|
||||
}
|
||||
)
|
||||
if not rows:
|
||||
raise AdapterError("Eastmoney limit pool empty")
|
||||
return rows
|
||||
|
||||
def _get_json(self, url: str, params: dict[str, str], referer: str) -> dict[str, Any]:
|
||||
request_url = f"{url}?{urllib.parse.urlencode(params)}"
|
||||
request = urllib.request.Request(
|
||||
|
||||
@@ -50,6 +50,14 @@ TUSHARE_FIELDS = {
|
||||
"ths_daily": "ts_code,trade_date,open,high,low,close,pre_close,pct_change,vol,turnover_rate",
|
||||
"dc_index": "ts_code,trade_date,name,open,high,low,close,pre_close,pct_change,vol,amount,turnover_rate",
|
||||
"sw_daily": "ts_code,trade_date,name,open,high,low,close,pct_change,vol,amount",
|
||||
"index_member_all": (
|
||||
"l1_code,l1_name,l2_code,l2_name,l3_code,l3_name,"
|
||||
"ts_code,name,in_date,out_date,is_new"
|
||||
),
|
||||
"stk_limit": "ts_code,trade_date,up_limit,down_limit",
|
||||
"suspend_d": "ts_code,suspend_date,resume_date,ann_date,suspend_reason,reason_type",
|
||||
"ths_member": "ts_code,con_code,con_name,in_date,out_date,is_new",
|
||||
"stk_mins": "ts_code,trade_time,open,close,high,low,vol,amount",
|
||||
}
|
||||
|
||||
DATASET_API = {
|
||||
@@ -254,3 +262,6 @@ class TushareAdapter(MarketAdapter):
|
||||
items = data.get("items") or []
|
||||
fields_list = data.get("fields") or (fields.split(",") if fields else [])
|
||||
return [dict(zip(fields_list, item)) for item in items]
|
||||
|
||||
def query_raw(self, api_name: str, params: dict[str, Any], fields: str = "") -> list[dict[str, Any]]:
|
||||
return self._query(api_name, params, fields or TUSHARE_FIELDS.get(api_name, ""))
|
||||
|
||||
Reference in New Issue
Block a user