"""同步任务:六大指数日 K 线。 数据流:新浪指数日K接口 → 写 kline_index + indices 表。""" from __future__ import annotations from datetime import datetime from typing import Any from app.core.db import ops as db_ops from app.core.datasource.base import registry as ds_registry from app.core.datasource.registry import is_source_ready from app.core.sync.base import SyncTask, _is_market_closed from app.core.sync.registry import mark_sync_blocked from app.core.utils.logging import get_logger logger = get_logger("sync.kline_index") MAJOR_INDICES = { "000001": {"name": "上证指数", "market": "CN", "category": "broad_market"}, "399001": {"name": "深证成指", "market": "CN", "category": "broad_market"}, "399006": {"name": "创业板指", "market": "CN", "category": "broad_market"}, "000300": {"name": "沪深300", "market": "CN", "category": "broad_market"}, "000905": {"name": "中证500", "market": "CN", "category": "broad_market"}, "000852": {"name": "中证1000", "market": "CN", "category": "broad_market"}, } class SyncKlineIndex(SyncTask): dataset_id = "kline_index" def _run(self, *, trigger_source: str = "manual", **kwargs) -> dict[str, Any]: # 优先 mairui(指数无单日空窗问题),sina 作为 fallback primary = ds_registry.get("datasource_mairui") use_mairui = primary is not None and is_source_ready(primary.key)[0] if not use_mairui: primary = ds_registry.get("datasource_xinlang") if primary is None: return {"status": "error", "message": "指数数据源均不可用(mairui + 新浪)"} ok, reason = is_source_ready(primary.key) if not ok: mark_sync_blocked(self.dataset_id, message=f"{primary.key} 未就绪: {reason}") return {"status": "blocked", "message": f"{primary.key} 未就绪: {reason}"} self._progress(message=f"开始同步 {len(MAJOR_INDICES)} 个指数...") total = len(MAJOR_INDICES) ok_cnt = fail_cnt = 0 results = {} for i, (code, info) in enumerate(MAJOR_INDICES.items(), 1): try: # mairui 需带交易所后缀(如 000300.SH),sina 用纯6位 if use_mairui: # 000xxx 上证 → .SH;399xxx 深证 → .SZ if code.startswith("399"): suffix = ".SZ" else: suffix = ".SH" fetch_code = f"{code}{suffix}" else: fetch_code = code df = primary.fetch_index_daily(fetch_code, start="1990-01-01", end="2099-12-31") if df is None or df.empty: raise RuntimeError("返回空数据") # 盘中未收盘则去掉当天 if not _is_market_closed(): today = datetime.now().strftime("%Y-%m-%d") df = df[df["trade_date"].dt.strftime("%Y-%m-%d") < today] rows = [ { "index_code": code, "trade_date": r["trade_date"].strftime("%Y-%m-%d"), "open": float(r["open"]), "high": float(r["high"]), "low": float(r["low"]), "close": float(r["close"]), "volume": float(r["volume"]), } for _, r in df.iterrows() ] db_ops.upsert_kline_index(rows) db_ops.upsert_index( index_code=code, index_name=info["name"], market=info["market"], category=info["category"], source="mairui_index_daily" if use_mairui else "sina_index_daily", enabled=True, ) ok_cnt += 1 results[code] = { "rows": len(rows), "date_min": rows[0]["trade_date"] if rows else "", "date_max": rows[-1]["trade_date"] if rows else "", } except Exception as e: fail_cnt += 1 results[code] = {"error": str(e)} logger.warning(f"[index {code}] 失败: {e}") self._progress( message=f"指数 {i}/{total} 完成 ({ok_cnt}成 {fail_cnt}败)", current=i, total=total, current_step=code, ) msg = f"六大指数 {ok_cnt}成 {fail_cnt}败" return { "status": "ok" if fail_cnt == 0 else "warning", "message": msg, "ok": ok_cnt, "fail": fail_cnt, "details": results, }