This commit is contained in:
2026-09-07 13:34:26 +08:00
parent ad9245abdd
commit 359f9ae2e4
23 changed files with 2260 additions and 513 deletions

View File

@@ -7,6 +7,7 @@
from __future__ import annotations
import asyncio
from datetime import datetime
from sqlalchemy import func, select
from sqlalchemy.dialects.postgresql import insert as pg_insert
@@ -16,6 +17,7 @@ from ..config import settings
from ..domain import Bar
from ..models import Candle
from . import akshare_provider, tushare_provider
from .symbols import is_etf_symbol, to_ts_code
DEFAULT_START = settings.data_default_start or "20200101"
@@ -74,19 +76,19 @@ async def sync_symbol(
# 增量:从缓存最后一根当天开始(重叠一天重新拉取,容忍数据源漏行/盘后修订)
start = last_ts.strftime("%Y%m%d")
start = start or DEFAULT_START
errors: list[str] = []
bars: list[Bar] = []
used = None
for name, fn in _providers(source):
# ETF 走 Tushare fund_dailyquicksync 镜像可用;与股票同源同控频),
# 按ts_code 增量拉取,未收盘当日数据未生成时自然返回空。
if is_etf_symbol(code):
errors: list[str] = []
try:
# tushare/akshare 是同步网络 IO丢到线程池避免阻塞事件循环
# adjust=None -> 不复权(复权在读取时按 adj_factor 换算)
bars = await asyncio.to_thread(fn, code, start, end, None)
used = name
break
bars = await asyncio.to_thread(_fetch_etf_daily, code, start, end)
used = "tushare"
except Exception as e: # noqa: BLE001
errors.append(f"{name}: {e}")
errors.append(f"tushare: {e}")
bars = []
else:
bars, used, errors = await _fetch_stock(code, start, end, source)
if not bars:
if last_ts is not None:
@@ -112,3 +114,47 @@ async def sync_symbol(
await session.execute(stmt)
await session.commit()
return {"symbol": code, "bars": len(bars), "source": used}
def _fetch_etf_daily(code: str, start: str | None, end: str | None) -> list[Bar]:
"""Tushare fund_daily 按 ts_code 拉 ETF 日线(同步网络 IOto_thread 调用)。
单位沿用 Tusharevol 手、amount 千元,此处换算为 股/元。"""
from .tushare_provider import get_pro
pro = get_pro()
df = pro.fund_daily(ts_code=to_ts_code(code), start_date=start, end_date=end)
if df is None or df.empty:
raise RuntimeError(f"Tushare 无数据: {to_ts_code(code)}")
df = df.sort_values("trade_date")
bars: list[Bar] = []
for _, r in df.iterrows():
amt = r.get("amount")
bars.append(
Bar(
ts=datetime.strptime(str(r["trade_date"]), "%Y%m%d"),
open=float(r["open"]), high=float(r["high"]),
low=float(r["low"]), close=float(r["close"]),
volume=float(r["vol"]) * 100.0, # 手 -> 份
amount=float(amt) * 1000.0 if amt is not None and amt == amt else None, # 千元 -> 元
)
)
return bars
async def _fetch_stock(code: str, start: str, end: str | None, source: str):
"""Tushare 主 -> AKShare 兜底拉股票日线(不复权)。"""
errors: list[str] = []
bars: list[Bar] = []
used = None
for name, fn in _providers(source):
try:
# tushare/akshare 是同步网络 IO丢到线程池避免阻塞事件循环
# adjust=None -> 不复权(复权在读取时按 adj_factor 换算)
bars = await asyncio.to_thread(fn, code, start, end, None)
used = name
break
except Exception as e: # noqa: BLE001
errors.append(f"{name}: {e}")
return bars, used, errors