"""日内做 T 策略启动器。""" from __future__ import annotations import logging import time from concurrent.futures import Future, ThreadPoolExecutor from datetime import datetime, time as clock_time import config from libs.calc import trading_time from libs.grid_take_profit import GridTrailingTracker from libs.market import market_allow_open from libs.signal import init_signals from sdk import Client from strategy.trend.order import OrderBook from strategy.trend.watch import DipWatch from .open import open_signal from .positions import manage_positions from .runtime import Runtime from .state import TState def StartZT() -> None: client = Client(config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT) orders = OrderBook() orders.refresh(client) _, positions = client.positions() state = TState.for_strategy(config.global_config.qmt_data_dir, config.account_config.strategy, config.account_config.account_id) state.reconcile(positions, orders.data, datetime.now().date().isoformat()) run = Runtime(client, config.global_config, config.account_config, state, orders, DipWatch(), GridTrailingTracker(config.account_config.grid_step_pct)) while True: started = time.monotonic() try: RunOnce(run) except Exception: logging.exception("ZT 策略本轮失败") time.sleep(max(0.0, 30.0 - (time.monotonic() - started))) def RunOnce(run: Runtime) -> None: if not trading_time(datetime.now()): return try: run.orders.refresh(run.client) assets = run.client.assets() position_codes, positions = run.client.positions() except Exception: logging.exception("[ZT] 刷新账户或订单失败") return today = datetime.now().date().isoformat() try: signals = init_signals(run.global_cfg, run.account_cfg.signal_allow) except Exception: logging.exception("[ZT] 获取 dcm 信号失败") return candidate_codes = [item.code for item in signals if item.code not in position_codes] codes = list(dict.fromkeys(position_codes + candidate_codes)) try: ticks = run.client.full_tick(codes) except Exception: logging.exception("[ZT] 获取行情失败") return market_ok = market_allow_open(run.global_cfg.api_host) can_open = market_ok and assets.available >= assets.total * run.account_cfg.min_cash_ratio force_buy_back = datetime.now().time() >= clock_time(14, 50) # 状态对账与开仓判断并行。持仓线程在自己的线程中等待对账完成, # 以保证它读取到最新的底仓和做 T 轮次状态,避免并发写 State。 with ThreadPoolExecutor(max_workers=3, thread_name_prefix="zt") as executor: state_future = executor.submit(run.state.reconcile, positions, run.orders.data, today) open_future = executor.submit(_run_open_signal, state_future, run, ticks, signals, can_open) positions_future = executor.submit( _run_manage_positions, state_future, run, ticks, positions, assets.available, today, force_buy_back, ) _wait_worker("状态对账", state_future) _wait_worker("开仓", open_future) _wait_worker("持仓管理", positions_future) def _run_open_signal(state_future: Future, run: Runtime, ticks, signals, can_open: bool) -> None: state_future.result() if can_open: open_signal(run, ticks, signals) def _run_manage_positions( state_future: Future, run: Runtime, ticks, positions, available: float, today: str, force_buy_back: bool, ) -> None: state_future.result() manage_positions(run, ticks, positions, available, today, force_buy_back) def _wait_worker(name: str, future: Future) -> None: try: future.result() except Exception: logging.exception("[ZT] %s线程失败", name)