Files
big-qmt/py-client/strategy/zt/positions.py
2026-09-10 12:50:40 +08:00

102 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""先卖后买做 T持仓存在 State未买回数量从实际成交计算。"""
import logging as log
import math
from libs.grid_take_profit import GridState
from libs.order import PlaceOrderRequest
from libs.runtime import Runtime
from libs.state import State
from sdk import OP_BUY, OP_SELL, PositionItem
def t_rounds(store: State) -> dict[str, dict]:
"""每只证券保留最近一轮 T跨日未买回的数量继续保留。"""
rounds = {}
for deal in sorted(store.deals.values(), key=lambda d: (
d['trade_date'], d['trade_time'].replace(':', ''), d['id']
)):
order = deal['order_local_id']
code = deal['stock_code']
if order.startswith('zt-t-sell-') and deal['offset_flag'] in (24, 49):
item = rounds.get(code)
if item is None or item['bought'] >= item['sold']:
item = rounds[code] = dict(sold=0, bought=0, amount=0.0, date='')
item['sold'] += deal['volume']
item['amount'] += deal['trade_amount']
item['date'] = deal['trade_date']
elif order.startswith('zt-t-buy-') and deal['offset_flag'] in (23, 48) and code in rounds:
rounds[code]['bought'] += deal['volume']
rounds[code]['date'] = deal['trade_date']
return rounds
def manage_positions(
run: Runtime, store: State, ticks, positions: list[PositionItem],
rounds: dict[str, dict], available: float, today: str, force_buy_back: bool = False,
) -> float:
"""先偿还买回欠仓;同一证券当天完成一轮后不再卖出。"""
by_code = {p.stock_code: p for p in positions}
codes = dict.fromkeys(list(rounds) + list(store.state))
for code in sorted(codes, key=lambda c: not (c in rounds and rounds[c]['sold'] > rounds[c]['bought'])):
try:
if code in run.account_cfg.excluded_codes:
continue
if run.orders.busy(code, 'BUY') or run.orders.busy(code, 'SELL'):
continue
tick = ticks.get(code)
price = tick.last_price if tick else 0.0
if not math.isfinite(price) or price <= 0:
continue
item = rounds.get(code)
row = store.state.get(code, {})
position = by_code.get(code)
recorded = row.get('base_qty', 0) + row.get('added_qty', 0)
if recorded != (position.volume if position else 0):
log.warning('[ZT] %s 持仓快照与成交未对齐,等待下一轮', code)
continue
if item and item['sold'] > item['bought']:
volume = item['sold'] - item['bought']
# 部分成交后的零股欠仓不能按普通买入申报,不扩大买回数量。
minimum = 200 if code.startswith('688') else 100
if not code.startswith('688'):
volume = volume // 100 * 100
if volume < minimum:
log.warning('[ZT] %s 剩余买回 %d 股不满足申报数量,保留欠仓', code, item['sold'] - item['bought'])
continue
target = item['amount'] / item['sold'] * (1 - run.account_cfg.zt_buy_fall_pct / 100)
if not force_buy_back and price > target:
continue
amount = price * volume * 1.01
if amount > available:
log.warning('[ZT] %s 买回资金不足,需要 %.2f,可用 %.2f', code, amount, available)
continue
if not force_buy_back and not run.add_watch.triggered('ZT 买回', code, price):
continue
available -= amount
request = PlaceOrderRequest(OP_BUY, code, volume, run.orders.new_order_id('t-buy'), 'zt')
if run.orders.place(run.client, request):
run.add_watch.forget(code)
log.info('[ZT 买回] %s %d%s', code, volume, ',尾盘买回' if force_buy_back else '')
continue
if force_buy_back or (item and item['date'] >= today) or not position or recorded <= 0:
continue
if price > run.account_cfg.zt_max_price:
continue
cost = (row['base_qty'] * row['base_price'] + row['added_qty'] * row['added_price']) / recorded
if cost <= 0:
continue
pnl = (price / cost - 1) * 100
observation = run.profit_tracker.observe(f'{run.account_cfg.account_id}:{code}:{today}', pnl)
if observation.state != GridState.RETREAT or pnl < run.account_cfg.min_profit_pct:
continue
volume = int(min(position.can_use_volume, recorded * run.account_cfg.zt_sell_ratio)) // 100 * 100
if volume < (200 if code.startswith('688') else 100):
continue
request = PlaceOrderRequest(OP_SELL, code, volume, run.orders.new_order_id('t-sell'), 'zt')
if run.orders.place(run.client, request):
log.info('[ZT 卖出] %s %d 股,按实际成交买回', code, volume)
except Exception:
log.exception('[ZT 持仓] %s 处理失败', code)
return available