Files
big-qmt/py-client/strategy/zt/positions.py
2026-08-31 13:00:22 +08:00

72 lines
3.0 KiB
Python

"""日内先卖后买的做 T 规则。"""
from __future__ import annotations
import logging
from libs.grid_take_profit import GridState
from sdk import OP_BUY, OP_SELL, PositionItem
from strategy.trend.order import PlaceOrderRequest
from .state import BUYING, READY, SELLING, SOLD
def manage_positions(run, ticks, positions: list[PositionItem], available: float, today: str, force_buy_back: bool = False) -> None:
for position in positions:
code = position.stock_code
tick = ticks.get(code)
if not code or code in run.account_cfg.excluded_codes or tick is None:
continue
price = tick.last_price
if price <= 0 or price > run.account_cfg.zt_max_price:
continue
try:
state = run.state.get(code)
except KeyError:
continue
if state.phase == READY and not force_buy_back:
_try_sell(run, state, position, price, today)
elif state.phase == SOLD:
_try_buy_back(run, state, price, available, today, force_buy_back)
def _try_sell(run, state, position: PositionItem, price: float, today: str) -> None:
if state.base_cost <= 0 or run.orders.busy(state.code, "SELL"):
return
pnl_rate = (price - state.base_cost) / state.base_cost * 100
observation = run.sell_tracker.observe(f"{run.account_cfg.account_id}:{state.code}", pnl_rate)
if observation.state != GridState.RETREAT:
return
volume = min(position.can_use_volume, int(state.base_qty * run.account_cfg.zt_sell_ratio) // 100 * 100)
if volume <= 0:
return
order_id = run.orders.new_order_id("t-sell")
request = PlaceOrderRequest(run.client, OP_SELL, state.code, volume, order_id, run.account_cfg.strategy)
if not run.orders.place(request):
return
state.trade_date, state.phase = today, SELLING
state.sell_order_id, state.sell_qty, state.sell_price = order_id, volume, price
run.state.set(state)
run.state.save()
logging.info("[ZT 卖出] %s %d 股,网格回撤触发", state.code, volume)
def _try_buy_back(run, state, price: float, available: float, today: str, force: bool) -> None:
target = state.sell_price * (1 - run.account_cfg.zt_buy_fall_pct / 100)
if (not force and price > target) or run.orders.busy(state.code, "BUY"):
return
if state.sell_qty <= 0 or price * state.sell_qty > available:
return
if not force and not run.buy_watch.triggered("ZT 买回", state.code, price):
return
order_id = run.orders.new_order_id("t-buy")
request = PlaceOrderRequest(run.client, OP_BUY, state.code, state.sell_qty, order_id, run.account_cfg.strategy)
if not run.orders.place(request):
return
state.trade_date, state.phase, state.buy_order_id = today, BUYING, order_id
run.state.set(state)
run.state.save()
run.buy_watch.forget(state.code)
reason = "尾盘强制买回" if force else f"回撤 {run.account_cfg.zt_buy_fall_pct:.2f}% 后反弹确认"
logging.info("[ZT 买回] %s %d 股,%s", state.code, state.sell_qty, reason)