56 lines
2.6 KiB
Python
56 lines
2.6 KiB
Python
"""ETF 专用历史日线适配,不依赖或修改 QMT SDK。"""
|
||
|
||
from datetime import date, datetime
|
||
import math
|
||
import re
|
||
|
||
import httpx
|
||
|
||
|
||
DAILY_URL = 'http://go.apinb.com/a/get_daily'
|
||
|
||
|
||
def daily_bars(client: httpx.Client, code: str, today: date, count: int = 120) -> list[dict]:
|
||
"""读取指定证券日线;只使用 code 参数,截取历史窗口在本地完成。"""
|
||
response = client.get(DAILY_URL, params={'code': code})
|
||
response.raise_for_status()
|
||
return parse_daily(response.json(), code, today, count)
|
||
|
||
|
||
def parse_daily(payload: dict, code: str, today: date, count: int = 120) -> list[dict]:
|
||
"""校验业务状态、证券归属和 OHLC,将 trade_date 转为指标需要的 date。"""
|
||
if type(count) is not int or count <= 0:
|
||
raise ValueError('日线数量必须为正整数')
|
||
if not isinstance(payload, dict) or type(payload.get('code')) is not int or payload['code'] != 0:
|
||
raise ValueError(f'日线接口业务失败:{payload.get("message", "状态无效") if isinstance(payload, dict) else "响应非对象"}')
|
||
details = payload.get('details')
|
||
if not isinstance(details, list) or not details:
|
||
raise ValueError(f'{code} 日线接口未返回有效 details 列表')
|
||
bars = {}
|
||
for row in details:
|
||
if not isinstance(row, dict) or row.get('ts_code') != code:
|
||
raise ValueError(f'{code} 日线证券代码不一致')
|
||
stamp = str(row.get('trade_date', ''))
|
||
if not re.fullmatch(r'[0-9]{8}', stamp):
|
||
raise ValueError(f'{code} 日线日期无效:{stamp}')
|
||
day = datetime.strptime(stamp, '%Y%m%d').date()
|
||
# 当前日及未来日线均不可用于盘中指标,先过滤再截取最近 count 根。
|
||
if day >= today:
|
||
continue
|
||
if stamp in bars:
|
||
raise ValueError(f'{code} 日线日期重复:{stamp}')
|
||
values = {}
|
||
for key in ('open', 'high', 'low', 'close'):
|
||
value = row.get(key)
|
||
if isinstance(value, bool) or not isinstance(value, (int, float, str)):
|
||
raise ValueError(f'{code} 日线 {key} 无效')
|
||
value = float(value)
|
||
if not math.isfinite(value) or value <= 0:
|
||
raise ValueError(f'{code} 日线 {key} 非有限正数')
|
||
values[key] = value
|
||
if not (values['low'] <= values['open'] <= values['high']
|
||
and values['low'] <= values['close'] <= values['high']):
|
||
raise ValueError(f'{code} 日线 OHLC 关系异常')
|
||
bars[stamp] = dict(date=stamp, **values)
|
||
return [bars[stamp] for stamp in sorted(bars)[-count:]]
|