Files
DinQuant/backend_api_python/app/data_sources/cn_stock.py
T
Dinger 8563e4ea53 v3.0.1
Signed-off-by: Dinger <quantdinger@gmail.com>
2026-04-07 22:47:07 +08:00

100 lines
3.4 KiB
Python
Raw 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.
"""
中国A股数据源 — 多层 fallback
有 TWELVE_DATA_API_KEY:
所有周期 → Twelve Data(主) → 腾讯日/周线 → yfinance → AkShare
无 API Key:
分钟/小时 → yfinance → AkShare
日/周线 → 腾讯 fqkline → yfinance → AkShare
"""
from __future__ import annotations
from typing import Dict, List, Any, Optional
from app.data_sources.base import BaseDataSource
from app.data_sources.tencent import normalize_cn_code, fetch_quote, parse_quote_to_ticker, fetch_kline, tencent_kline_rows_to_dicts
from app.data_sources.asia_stock_kline import (
normalize_chart_timeframe,
fetch_twelvedata_klines,
fetch_yfinance_klines,
fetch_akshare_minute_klines,
fetch_akshare_weekly_klines,
)
from app.utils.logger import get_logger
logger = get_logger(__name__)
class CNStockDataSource(BaseDataSource):
"""A股数据源(TwelveData + Tencent + yfinance + AkShare"""
name = "CNStock/multi-source"
def get_ticker(self, symbol: str) -> Dict[str, Any]:
code = normalize_cn_code(symbol)
parts = fetch_quote(code)
if not parts:
return {"last": 0, "symbol": code}
t = parse_quote_to_ticker(parts)
return {
"last": t.get("last", 0),
"change": t.get("change", 0),
"changePercent": t.get("changePercent", 0),
"high": t.get("high", 0),
"low": t.get("low", 0),
"open": t.get("open", 0),
"previousClose": t.get("previousClose", 0),
"name": t.get("name", ""),
"symbol": code,
}
def get_kline(
self,
symbol: str,
timeframe: str,
limit: int,
before_time: Optional[int] = None,
) -> List[Dict[str, Any]]:
code = normalize_cn_code(symbol)
tf = normalize_chart_timeframe(timeframe)
lim = max(int(limit or 300), 1)
# Tier 1: Twelve Data (paid, most reliable)
rows = fetch_twelvedata_klines(
is_hk=False, tencent_code=code, timeframe=tf, limit=lim, before_time=before_time
)
if rows:
return self.filter_and_limit(rows, limit=lim, before_time=before_time)
# Tier 2: Tencent for daily/weekly (fast, free)
if tf in ("1D", "1W"):
tf_map = {"1D": "day", "1W": "week"}
period = tf_map.get(tf, "day")
raw_rows = fetch_kline(code, period=period, count=lim, adj="qfq")
out = tencent_kline_rows_to_dicts(raw_rows)
if out:
return self.filter_and_limit(out, limit=lim, before_time=before_time)
# Tier 3: yfinance (works when Yahoo not rate-limited)
rows = fetch_yfinance_klines(
is_hk=False, tencent_code=code, timeframe=tf, limit=lim, before_time=before_time
)
if rows:
return self.filter_and_limit(rows, limit=lim, before_time=before_time)
# Tier 4: AkShare (fragile overseas, last resort)
if tf in ("1m", "5m", "15m", "30m", "1H", "4H"):
rows = fetch_akshare_minute_klines(
is_hk=False, tencent_code=code, timeframe=tf, limit=lim, before_time=before_time
)
elif tf == "1W":
rows = fetch_akshare_weekly_klines(
is_hk=False, tencent_code=code, limit=lim, before_time=before_time
)
else:
rows = []
return self.filter_and_limit(rows, limit=lim, before_time=before_time)