Files
market_sync/app/tasks/task_kline_index.py
gao daef609e8b feat: QMT Bridge 设为主数据源,新增指数日K支持及数据源文档
- QmtBridgeSource: provides 增加 index_daily,新增 fetch_index_daily 方法
- task_kline_index: 优先级改为 qmt_bridge → mairui → sina 三级降级
- task_kline_5min: 优先用 qmt_bridge,不可用时降级 mairui
- task_kline_daily: 优先级加入 qmt_bridge(首位)
- config: 新增 qmt_bridge_url 配置项
- registry: qmt_bridge 注册信息同步更新
- docs: 新增 DATASETS_AND_SOURCES.md,完整说明数据集与数据源依赖关系
- AGENTS.md: 同步更新指数数据源描述
2026-07-23 23:39:59 +08:00

124 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""同步任务:六大指数日 K 线。
数据流:QMT Bridge(主)→ 麦蕊 → 新浪 逐级 fallback → 写 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"},
}
# 数据源优先级:qmt_bridge(免限速免凭证)→ mairui → sina
SOURCE_PRIORITY = [
("datasource_qmt_bridge", True, "qmt_bridge_index_daily"),
("datasource_mairui", True, "mairui_index_daily"),
("datasource_xinlang", False, "sina_index_daily"),
]
class SyncKlineIndex(SyncTask):
dataset_id = "kline_index"
def _run(self, *, trigger_source: str = "manual", **kwargs) -> dict[str, Any]:
# 按优先级选择第一个就绪的数据源
primary = None
use_suffix = True
source_label = ""
for key, suffix, label in SOURCE_PRIORITY:
src = ds_registry.get(key)
if src is not None and is_source_ready(src.key)[0]:
primary = src
use_suffix = suffix
source_label = label
break
if primary is None:
return {"status": "error", "message": "指数数据源均不可用(qmt_bridge + 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)} 个指数,数据源: {source_label}...")
total = len(MAJOR_INDICES)
ok_cnt = fail_cnt = 0
results = {}
for i, (code, info) in enumerate(MAJOR_INDICES.items(), 1):
try:
# qmt_bridge / mairui 需带交易所后缀(如 000300.SH),sina 用纯6位
if use_suffix:
# 000xxx 上证 → .SH399xxx 深证 → .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=source_label, 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}败({source_label}"
return {
"status": "ok" if fail_cnt == 0 else "warning",
"message": msg,
"ok": ok_cnt, "fail": fail_cnt,
"details": results,
}