Files
big-qmt/py-client/strategy/zt/boot.py
2026-08-31 15:33:39 +08:00

113 lines
3.9 KiB
Python

"""日内做 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)