68 lines
3.2 KiB
Python
68 lines
3.2 KiB
Python
"""ETF 策略入口:每 30 秒运行,日线指标当天缓存,失败标的单独重试。"""
|
||
|
||
from datetime import datetime, timedelta
|
||
import hashlib
|
||
import logging as log
|
||
from pathlib import Path
|
||
import time
|
||
import httpx
|
||
|
||
import config
|
||
from libs.calc import trading_time
|
||
from libs.snapshot import cache_portfolio
|
||
from sdk import Client
|
||
|
||
from .config import load
|
||
from .data import daily_bars
|
||
from .engine import Engine
|
||
from .indicators import calculate
|
||
from .state import Store
|
||
|
||
|
||
def StartETF() -> None:
|
||
cfg = load(config.account_config.etf_config_path or None)
|
||
account = str(config.account_config.account_id).strip()
|
||
if not account:
|
||
raise ValueError('ETF 策略缺少账户编号')
|
||
key = hashlib.sha256(account.encode('utf-8')).hexdigest()
|
||
store = Store(Path(config.global_config.qmt_data_dir) / 'etf' / key / 'state.json', account)
|
||
# 独立 HTTP 连接池读取外部日线,不向外部接口发送 QMT 认证信息。
|
||
with Client(config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT) as client, \
|
||
httpx.Client(timeout=config.HTTP_TIMEOUT) as history_client:
|
||
engine = Engine(client, cfg, store, config.account_config.min_cash_ratio,
|
||
config.account_config.excluded_codes)
|
||
log.info('[ETF启动] 标的=%s 每次=%d手 每只上限=%d手 状态=%s',
|
||
cfg.codes, cfg.buy_hands, cfg.max_hands, store.path)
|
||
log.info('[ETF启动] 管理配置白名单内已有持仓,卖出以券商可用份额为限')
|
||
indicators, retry_at = {}, {}
|
||
cached_day = None
|
||
while True:
|
||
now = datetime.now()
|
||
if now.hour >= 15:
|
||
log.info('[ETF结束] 已到 15:00')
|
||
return
|
||
if trading_time(now):
|
||
try:
|
||
if cached_day != now.date():
|
||
indicators, retry_at, cached_day = {}, {}, now.date()
|
||
for code in cfg.codes:
|
||
if code in indicators or now < retry_at.get(code, datetime.min):
|
||
continue
|
||
try:
|
||
rows = daily_bars(history_client, code, now.date())
|
||
indicators[code] = calculate(rows, now.date(), cfg)
|
||
log.info('[ETF指标] %s %s', code, indicators[code])
|
||
except Exception:
|
||
retry_at[code] = now + timedelta(minutes=5)
|
||
log.exception('[ETF日线] %s 获取或计算失败,5分钟后重试', code)
|
||
portfolio = client.portfolio()
|
||
ticks = client.full_tick(list(cfg.codes))
|
||
engine.run(portfolio, ticks, indicators, datetime.now())
|
||
try:
|
||
cache_portfolio(account, portfolio.assets, list(portfolio.positions.values()), client.deals())
|
||
except Exception:
|
||
log.exception('[ETF采集] 成交快照读取失败')
|
||
except Exception:
|
||
log.exception('[ETF异常] 本轮失败,下一轮继续')
|
||
time.sleep(30 - datetime.now().second % 30)
|