"""做 T 策略启动器。 该模块负责组合 SDK、配置、状态存储和做 T 策略组件,供 main.py 调用。 """ from __future__ import annotations import logging as log import time from datetime import datetime, time as clock_time import config from libs.calc import trading_time from libs.market import market_allow_open from libs.signal import init_signals from libs.collector import collector_push from libs.grid_take_profit import GridTrailingTracker from sdk import Client from libs.order import OrderBook from libs.watch import DipWatch from libs.runtime import Runtime from .state import TState, SOLD from .open import open_signal from .positions import manage_positions def StartZT() -> None: """初始化做 T 策略,并以 30 秒间隔持续执行。""" with Client( config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT, ) as client: state = TState.for_strategy( config.global_config.qmt_data_dir, "zt", config.account_config.account_id ) run = Runtime( client=client, global_cfg=config.global_config, account_cfg=config.account_config, orders=OrderBook("zt"), open_watch=DipWatch(), add_watch=DipWatch(), profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct), ) log.info( "[ZT 启动] 账户=%s,底仓信号=dcm,状态文件=%s", run.account_cfg.account_id, state.path, ) while True: now = datetime.now() if now.time() >= clock_time(15): # 收盘前最后一次只读对账,不发新单;未完成买回继续持久保存。 try: portfolio = client.portfolio() state.reconcile( list(portfolio.positions.values()), portfolio.orders, now.date().isoformat(), ) except Exception: log.exception("[ZT] 收盘对账失败,保留本地待确认记录") for item in state.items.values(): if item.phase == SOLD or state.busy(item.code): log.warning("[ZT] 收盘仍有待完成轮次:%s", item.code) return # 单轮失败不能杀死唯一的交易定时线程。 try: RunOnce(run, state) except Exception: log.exception("[ZT] 本 tick 执行失败,下一个 tick 继续") # 计算距离下一个目标时间点(0秒或30秒)的等待时间。 time.sleep(30 - datetime.now().second % 30) def RunOnce(run: Runtime, state: TState) -> None: """账户快照 → 成交对账 → 做 T 管理 → dcm 建仓,共用一份资金预算。""" now = datetime.now() if not trading_time(now) or now.time() >= clock_time(15): return today = now.date().isoformat() started_at = time.monotonic() # 1. 一次获取资产、持仓和订单,并清理过期订单。 portfolio = run.client.portfolio() positions = list(portfolio.positions.values()) run.orders.refresh(run.client, portfolio.orders) # 对账使用完整原始订单列表,不能丢弃撤单和废单的部分成交。 state.reconcile(positions, portfolio.orders, today) # 2. 获取本策略的信号开仓数据;信号失败不阻断已有做 T 买回。 try: signals = init_signals(run.global_cfg, ["dcm"]) except Exception: log.exception("[ZT] 获取 dcm 信号失败,本轮只管理已有底仓") signals = [] position_codes = { position.stock_code for position in positions if position.volume > 0 } candidates = [ signal for signal in signals if signal.signal_key == "dcm" and signal.code not in position_codes ] # 3. 获取持仓和待开仓证券的实时行情 tick,零持仓的待买回证券也包含在内。 codes = list( dict.fromkeys( list(position_codes) + list(state.items) + [signal.code for signal in candidates] ) ) ticks = run.client.full_tick(codes) if codes else {} now = datetime.now() # 网络请求可能跨过尾盘边界,提交前重新判断。 if not trading_time(now) or now.time() >= clock_time(15): return # 4. 先完成买回,避免开底仓抢占资金;交易逻辑串行,状态无需多线程写入。 available = max(0.0, portfolio.assets.available) # 未确认买单可能尚未反映在资金快照中,保守预留,宁可少买也不重复使用。 for pending in state.pending.values(): if pending.kind != "sell": tick = ticks.get(pending.code) if tick is None or tick.last_price <= 0: available = 0.0 break available = max(0.0, available - pending.qty * tick.last_price * 1.01) force = now.time() >= clock_time(14, 50) available = manage_positions(run, state, ticks, positions, available, today, force) # 5. 验证可用资金;低于资金安全线时禁止开新仓,尾盘只完成做 T 买回。 reserve = max(0.0, portfolio.assets.total * run.account_cfg.min_cash_ratio) # 未完成的卖出/买回可能继续占用资金,不再额外开底仓。 outstanding = bool(state.pending) or any( item.phase == SOLD for item in state.items.values() ) if not force and not outstanding and market_allow_open() and available > reserve: open_signal(run, state, ticks, candidates, available - reserve) # 6. 数据采集不与交易逻辑争用状态;采集函数自身隔离传输异常。 collector_push(run.account_cfg.account_id, portfolio.assets, positions) log.info( "[ZT] 本轮完成,底仓=%d,待确认=%d,耗时=%d毫秒", len(state.items), len(state.pending), int((time.monotonic() - started_at) * 1000), )