This commit is contained in:
2026-09-17 00:56:05 +08:00
parent 33693adb66
commit c7d39938a2
14 changed files with 1005 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
"""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)