chore: 重构前基线 — 9 个 sync task 全部 ok + akshare 移除 + mairui 资金流接入

状态:
- 9 个 sync task(stock_basic / kline_daily / kline_index / kline_5min /
  moneyflow / industry_sector / sector_features / share_snapshot / market_regime)
- 数据源:baostock + mairui + 雪球(pysnowball) + 新浪(4 个)
- 项目级约束:永远不用 akshare(已落实)
- kline_5min 改用 DB 快照统一全量/增量逻辑
- 零后端 Chrome 扩展 xueqiu_sync(独立项目)
This commit is contained in:
gao
2026-06-15 15:36:09 +08:00
commit 05635b76b9
55 changed files with 6307 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
"""同步任务:六大指数日 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 上证 → .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="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,
}