69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
"""Tushare 数据源(主)。日线 + 前复权。
|
||
|
||
token 从 settings.tushare_token 读取(.env)。免费版 pro.daily 与 ts.pro_bar 实测可用。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from ..config import settings
|
||
from ..domain import Bar
|
||
from .symbols import to_ts_code
|
||
|
||
|
||
def _parse(date_str: str) -> datetime:
|
||
return datetime.strptime(str(date_str), "%Y%m%d")
|
||
|
||
|
||
def get_pro():
|
||
"""创建 pro 客户端(统一入口)。
|
||
|
||
15000 积分档 token 只认 quicksync 镜像(直连 api.tushare.pro 会 40101 token 不对),
|
||
官方接入方式是改写 SDK 的私有类属性 DataApi.__http_url——类属性是所有实例
|
||
(含 pro_bar 内部自建的 DataApi)共用的请求地址,打一次补丁全局生效。
|
||
"""
|
||
import tushare as ts
|
||
import tushare.pro.client as client
|
||
|
||
if not settings.tushare_token:
|
||
raise RuntimeError("未配置 TUSHARE_TOKEN")
|
||
if settings.tushare_api_url:
|
||
client.DataApi._DataApi__http_url = settings.tushare_api_url
|
||
ts.set_token(settings.tushare_token)
|
||
return ts.pro_api()
|
||
|
||
|
||
def fetch_daily(code: str, start: str = "20200101", end: str | None = None,
|
||
adjust: str = "qfq") -> list[Bar]:
|
||
import tushare as ts # 延迟导入:未装或无 token 时该数据源不可用
|
||
|
||
pro = get_pro() # 含镜像补丁(quicksync)+ token 检查
|
||
ts_code = to_ts_code(code)
|
||
end = end or datetime.now().strftime("%Y%m%d")
|
||
|
||
# 优先 pro_bar(含复权);积分不足则退化为 pro.daily(不复权)
|
||
df = None
|
||
try:
|
||
df = ts.pro_bar(ts_code=ts_code, adj=adjust, start_date=start, end_date=end, freq="D")
|
||
except Exception:
|
||
df = None
|
||
if df is None or df.empty:
|
||
df = pro.daily(ts_code=ts_code, start_date=start, end_date=end)
|
||
if df is None or df.empty:
|
||
raise RuntimeError(f"Tushare 无数据: {ts_code}")
|
||
|
||
df = df.sort_values("trade_date")
|
||
bars: list[Bar] = []
|
||
for _, r in df.iterrows():
|
||
amt = r.get("amount")
|
||
bars.append(
|
||
Bar(
|
||
ts=_parse(r["trade_date"]),
|
||
open=float(r["open"]), high=float(r["high"]),
|
||
low=float(r["low"]), close=float(r["close"]),
|
||
volume=float(r["vol"]) * 100.0, # Tushare vol 单位为手 -> 股
|
||
amount=float(amt) * 1000.0 if amt is not None and amt == amt else None, # 千元 -> 元
|
||
)
|
||
)
|
||
return bars
|