Files
big-qmt/py-client/strategy/trend/boot.py

188 lines
6.3 KiB
Python
Raw Normal View History

2026-08-28 18:52:27 +08:00
"""趋势策略启动器。
该模块负责组合 SDK配置状态存储和趋势策略组件 main.py 调用
"""
from __future__ import annotations
import logging
import time
from datetime import datetime
import config
from libs import init_signals, market_allow_open, trading_time
from sdk import Client
2026-08-28 22:46:04 +08:00
from libs.grid_take_profit import GridTrailingTracker
2026-08-28 18:52:27 +08:00
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:
"""打印策略启动时的账户、资金和持仓概览。
该函数对应 Go 客户端 ``logic.Overview``为便于单独测试可以
显式传入账户配置未传入时使用 ``config.account_config``
"""
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()
2026-08-30 14:46:34 +08:00
order_book = OrderBook()
2026-08-30 15:29:21 +08:00
order_book.refresh(client)
2026-08-28 18:52:27 +08:00
storeState = State.for_strategy(
config.global_config.qmt_data_dir,
config.account_config.strategy,
config.account_config.account_id,
)
2026-08-30 14:46:34 +08:00
storeState.reconcile(positions, order_book.data)
2026-08-28 18:52:27 +08:00
# 获取本策略的信号开仓数据
2026-08-29 00:44:41 +08:00
signals = init_signals(
config.global_config,
config.account_config.signal_allow,
)
2026-08-28 18:52:27 +08:00
run = Runtime(
client=client,
global_cfg=config.global_config,
account_cfg=config.account_config,
state=storeState,
2026-08-30 14:46:34 +08:00
orders=order_book,
2026-08-28 18:52:27 +08:00
open_watch=DipWatch(),
add_watch=DipWatch(),
2026-08-28 22:46:04 +08:00
profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct),
2026-08-28 18:52:27 +08:00
)
Overview(assets, positions, config.account_config)
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))
def RunOnce(run: Runtime, signals) -> None:
"""按固定步骤执行一轮趋势策略, ``RunOnce``。"""
if not trading_time(datetime.now()):
return
# 1. 取消超过有效期仍未完成的委托订单。
try:
2026-08-30 14:46:34 +08:00
run.orders.refresh(run.client)
2026-08-28 18:52:27 +08:00
except Exception:
logging.exception("取消过期订单失败")
# 2. 验证可用资金;低于资金安全线时禁止开新仓。
try:
assets = run.client.assets()
except Exception:
logging.exception("获取资产失败")
return
2026-08-28 22:46:04 +08:00
allow_open_by_cash = assets.available >= assets.total * run.account_cfg.min_cash_ratio
if not allow_open_by_cash:
2026-08-28 18:52:27 +08:00
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
2026-08-30 00:34:27 +08:00
# 每轮使用最新委托和成交恢复状态。查询或落盘失败时禁止继续开仓,
# 避免在订单结果不明确的情况下提交重复买单。
previous_state_codes = set(run.state.codes)
try:
broker_orders = run.client.trade_detail_data("order")
2026-08-30 15:29:21 +08:00
run.state.reconcile(positions, broker_orders)
2026-08-30 00:34:27 +08:00
except Exception:
logging.exception("订单状态对账失败,本轮禁止自动交易")
return
removed_codes = previous_state_codes - set(run.state.codes)
2026-08-28 22:46:04 +08:00
for code in removed_codes:
run.open_watch.forget(code)
run.add_watch.forget(code)
2026-08-30 00:34:27 +08:00
# 5. 验证有效开仓信号:排除已有持仓和未决订单。
2026-08-28 18:52:27 +08:00
position_code_set = set(position_codes)
2026-08-28 22:46:04 +08:00
allow_open = []
2026-08-30 00:34:27 +08:00
seen_codes = position_code_set | set(run.state.unresolved_codes)
2026-08-28 22:46:04 +08:00
for signal in signals:
if signal.code not in seen_codes:
allow_open.append(signal)
seen_codes.add(signal.code)
2026-08-28 18:52:27 +08:00
# 6. 获取持仓和待开仓证券的实时行情 tick。
all_codes = list(position_codes)
all_codes.extend(
signal.code for signal in allow_open if signal.code not in position_code_set
)
try:
ticks = run.client.full_tick(list(dict.fromkeys(all_codes)))
except Exception:
logging.exception("获取行情失败")
return
# 7. 执行开仓:必须同时存在有效信号且大盘允许开仓。
2026-08-28 22:46:04 +08:00
if allow_open and market_ok and allow_open_by_cash:
2026-08-28 18:52:27 +08:00
open_signal(run, ticks, allow_open)
# 8. 持仓计算。当前 Go 版本的 managePositions 为空,保留扩展入口。
manage_positions(run, ticks, positions, market_ok,assets.available)