"""趋势策略启动器。 该模块负责组合 SDK、配置、状态存储和趋势策略组件,供 main.py 调用。 """ from __future__ import annotations import logging import time from concurrent.futures import Future, ThreadPoolExecutor from datetime import datetime import config from libs.calc import trading_time from libs.market import market_allow_open from libs.signal import init_signals, SignalItem from sdk import Client from libs.grid_take_profit import GridTrailingTracker from .state import State from .order import OrderBook from .watch import DipWatch from .runtime import Runtime from .open import open_signal from .positions import manage_positions def Overview(assets, positions, account_cfg=None) -> None: """打印策略启动时的账户、资金和持仓概览。 """ account_cfg = account_cfg or config.account_config print("\n" + "=" * 80) print(f"【时间】{datetime.now():%Y-%m-%d %H:%M:%S}") if account_cfg is not None: print( "【配置】" f"account_id: {account_cfg.account_id} " f"host_key: {account_cfg.host_key} " f"buy_value: {account_cfg.buy_value:.0f}" ) if assets is not None: print( f"【资金】总资产:{assets.total:.2f}元," f"可用资金:{assets.available:.2f}元" ) else: print("【资金】查询失败") print(f"【持仓】{len(positions)}只") print("=" * 80) for position in positions: if position.volume <= 0: continue print( f"【持仓】{position.stock_code} {position.stock_name} " f"持仓={position.volume} 可用={position.can_use_volume} " f"冻结={position.frozen_volume} 在途={position.on_road_volume} " f"昨仓={position.yesterday_volume} 成本={position.open_price:.3f} " f"现价={position.last_price:.3f} 市值={position.market_value:.2f} " f"浮盈={position.float_profit:.2f} " f"盈亏比例={position.profit_rate * 100:.2f}%" ) def StartTrend() -> None: """初始化趋势策略,并以 30 秒间隔持续执行。""" client = Client( config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT, ) assets = client.assets() _, positions = client.positions() order_book = OrderBook() order_book.refresh(client) storeState = State.for_strategy( config.global_config.qmt_data_dir, config.account_config.strategy, config.account_config.account_id, ) storeState.reconcile(positions, order_book.data) # 获取本策略的信号开仓数据 signals = init_signals( config.global_config, config.account_config.signal_allow, ) run = Runtime( client=client, global_cfg=config.global_config, account_cfg=config.account_config, state=storeState, orders=order_book, open_watch=DipWatch(), add_watch=DipWatch(), profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct), executor=ThreadPoolExecutor(max_workers=2, thread_name_prefix="trend"), ) Overview(assets, positions, config.account_config) try: while True: started_at = time.monotonic() try: RunOnce(run, signals) except Exception: # 单轮错误只记录日志,下一轮仍继续运行。 logging.exception("趋势策略本轮执行失败") elapsed = time.monotonic() - started_at time.sleep(max(0.0, 30.0 - elapsed)) finally: run.executor.shutdown(wait=True, cancel_futures=True) def RunOnce(run: Runtime, signals:list[SignalItem]) -> None: """按固定步骤执行一轮趋势策略, ``RunOnce``。""" if not trading_time(datetime.now()): return # 1. 刷新订单数据,清理过期订单。 try: run.orders.refresh(run.client) except Exception: logging.exception("取消过期订单失败") return # 2. 验证可用资金;低于资金安全线时禁止开新仓。 try: assets = run.client.assets() except Exception: logging.exception("获取资产失败") return allow_open_by_cash = assets.available >= assets.total * run.account_cfg.min_cash_ratio if not allow_open_by_cash: logging.info("资金总闸:可用金额太少,禁止开新仓") # 3. 获取大盘状态,只有大盘信号允许时才执行开仓。 market_ok = market_allow_open(run.global_cfg.api_host) # 4. 获取当前持仓及持仓证券代码。 try: position_codes, positions = run.client.positions() except Exception: logging.exception("获取持仓失败") return # 5. 验证有效开仓信号:排除已有持仓和未决订单。 allow_open: list[SignalItem] = [] allow_codes: list[str] = [] for signal in signals: if signal.code not in position_codes: allow_open.append(signal) allow_codes.append(signal.code) # 6. 获取持仓和待开仓证券的实时行情 tick。 all_codes = list(dict.fromkeys(position_codes + allow_codes)) try: ticks = run.client.full_tick(all_codes) except Exception: logging.exception("获取行情失败") return # 7. 更新状态机 try: run.state.reconcile(positions, run.orders.data) except Exception: logging.exception("订单状态对账失败,本轮禁止自动交易") return # 启动线程,开始计算 # 9. 持仓计算。 futures: list[tuple[str, Future]] = [ ( "持仓计算", run.executor.submit( manage_positions, run, ticks, positions, market_ok, assets.available, ), ) ] # 10. 开仓计算:必须同时存在有效信号且大盘允许开仓。 if allow_open and market_ok and allow_open_by_cash: futures.append(("开仓计算", run.executor.submit(open_signal, run, ticks, allow_open))) # 11. 开始执行 for name, future in futures: _wait_worker(name, future) def _wait_worker(name: str, future: Future) -> None: """保留单轮继续运行的语义,分别记录工作线程异常。""" try: future.result() except Exception: logging.exception("趋势策略%s线程失败", name)