optz print.
This commit is contained in:
@@ -88,16 +88,17 @@ def manage_positions(
|
||||
strTag = "↑"
|
||||
elif pnl_rate< LOSS_TIERS[0]:
|
||||
strTag = "↓"
|
||||
|
||||
log.info(
|
||||
"[Position %s ] %s %s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||
strTag,
|
||||
code,
|
||||
position.stock_name,
|
||||
pnl_rate,
|
||||
profit_action,
|
||||
loss_add_action,
|
||||
)
|
||||
|
||||
if strTag != "-":
|
||||
log.info(
|
||||
"[Position %s ] %s %s,盈亏=%.2f%%,止盈=%s,补仓=%s",
|
||||
strTag,
|
||||
code,
|
||||
position.stock_name,
|
||||
pnl_rate,
|
||||
profit_action,
|
||||
loss_add_action,
|
||||
)
|
||||
except Exception:
|
||||
log.exception(
|
||||
"[Position] 持仓处理异常,代码=%s,继续处理后续持仓",
|
||||
@@ -121,15 +122,15 @@ def handle_profit(
|
||||
if observation.state == GridState.ARMED:
|
||||
return TradeDecision(
|
||||
False,
|
||||
f"首次达到 {pnl_rate:.2f}%,峰值网格={observation.current_grid}",
|
||||
f"首次, PNL:{pnl_rate:.2f}%,网格={observation.current_grid}",
|
||||
)
|
||||
if observation.state == GridState.RAISED:
|
||||
return TradeDecision(
|
||||
False,
|
||||
f"上涨至 {pnl_rate:.2f}%,峰值网格={observation.current_grid}",
|
||||
f"突破, PNL:{pnl_rate:.2f}%,网格={observation.current_grid}",
|
||||
)
|
||||
if observation.state == GridState.STEADY:
|
||||
return TradeDecision(False)
|
||||
return TradeDecision(False,f"持平, PNL:{pnl_rate:.2f}%,网格={observation.current_grid}",)
|
||||
if runtime.orders.busy(position.stock_code, "SELL"):
|
||||
return TradeDecision(False, "卖出委托处理中")
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ from libs.overview import Overview
|
||||
from libs.order import BUSY_STATUSES, OrderBook
|
||||
from libs.watch import DipWatch
|
||||
from libs.runtime import Runtime
|
||||
from .state import TState, SOLD
|
||||
from .open import open_signal
|
||||
from .positions import manage_positions
|
||||
|
||||
@@ -32,10 +31,10 @@ def StartZT() -> None:
|
||||
config.global_config.qmt_token,
|
||||
config.HTTP_TIMEOUT,
|
||||
) as client:
|
||||
state = TState(
|
||||
Path(config.global_config.qmt_data_dir)
|
||||
/ f"zt_{config.account_config.account_id}_state.db"
|
||||
)
|
||||
# state = TState(
|
||||
# Path(config.global_config.qmt_data_dir)
|
||||
# / f"zt_{config.account_config.account_id}_state.db"
|
||||
# )
|
||||
executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="zt")
|
||||
run = Runtime(
|
||||
client=client,
|
||||
|
||||
@@ -8,10 +8,9 @@ from libs.calc import calc_buy_volume
|
||||
from sdk import OP_BUY
|
||||
from libs.runtime import Runtime
|
||||
from libs.order import PlaceOrderRequest
|
||||
from .state import TState
|
||||
|
||||
|
||||
def open_signal(run: Runtime, state: TState, ticks, signals, available: float) -> float:
|
||||
def open_signal(run: Runtime, ticks, signals, available: float) -> float:
|
||||
"""逐个验证开仓信号并提交买入委托,返回本轮剩余资金。"""
|
||||
for item in signals:
|
||||
try:
|
||||
@@ -20,9 +19,6 @@ def open_signal(run: Runtime, state: TState, ticks, signals, available: float) -
|
||||
break
|
||||
if item.code in run.account_cfg.excluded_codes:
|
||||
continue
|
||||
item_state = state.items.get(item.code)
|
||||
if item_state is not None and item_state.base_qty > 0:
|
||||
continue
|
||||
# 由委托簿检查活动委托,防止重复下单。
|
||||
if (
|
||||
run.orders.busy(item.code, "BUY")
|
||||
|
||||
@@ -7,12 +7,10 @@ from libs.grid_take_profit import GridState
|
||||
from sdk import OP_BUY, OP_SELL, PositionItem
|
||||
from libs.order import PlaceOrderRequest
|
||||
from libs.runtime import Runtime
|
||||
from .state import READY, SOLD, TState
|
||||
|
||||
|
||||
def manage_positions(
|
||||
run: Runtime,
|
||||
state_store: TState,
|
||||
ticks,
|
||||
positions: list[PositionItem],
|
||||
available: float,
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
"""做 T 策略的持仓状态和逐笔实际成交记录。"""
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from sdk import DealItem, PositionItem
|
||||
from libs.orderbook import OrderBook
|
||||
|
||||
READY, SOLD, DONE = "READY", "SOLD", "DONE"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TStateItem:
|
||||
code: str
|
||||
base_qty: int = 0
|
||||
base_cost: float = 0.0
|
||||
trade_date: str = ""
|
||||
phase: str = READY
|
||||
sell_qty: int = 0
|
||||
sell_price: float = 0.0
|
||||
buy_qty: int = 0
|
||||
buy_cost: float = 0.0
|
||||
id: int = 0
|
||||
base_order_id: str = ''
|
||||
added_order_id: str = ''
|
||||
added_num: int = 0
|
||||
added_qty: int = 0
|
||||
added_cost: float = 0.0
|
||||
|
||||
|
||||
class TState:
|
||||
"""Apply actual executions immediately, atomically with their position changes."""
|
||||
|
||||
def __init__(self, path: str | Path) -> None:
|
||||
self._store = OrderBook(path)
|
||||
self.path = self._store.path
|
||||
self._load()
|
||||
|
||||
@staticmethod
|
||||
def _is_zt_deal(deal: DealItem) -> bool:
|
||||
return (
|
||||
deal.side == 'BUY' and deal.local_order_id.startswith(('zt-base-', 'zt-t-buy-'))
|
||||
) or (
|
||||
deal.side == 'SELL' and deal.local_order_id.startswith('zt-t-sell-')
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _reset(item: TStateItem, date: str) -> bool:
|
||||
if item.phase == DONE and item.trade_date != date:
|
||||
item.phase, item.trade_date = READY, ''
|
||||
item.sell_qty = item.buy_qty = 0
|
||||
item.sell_price = item.buy_cost = 0.0
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _apply_t_deal(cls, item: TStateItem, deal: dict) -> None:
|
||||
"""实时入账与重启恢复共用同一套做 T 轮次计算。"""
|
||||
cls._reset(item, deal['trade_date'])
|
||||
qty, amount = deal['volume'], deal['trade_amount']
|
||||
if str(deal['offset_flag']) in ('24', '49'):
|
||||
total = item.sell_qty + qty
|
||||
item.sell_price = (item.sell_qty * item.sell_price + amount) / total
|
||||
item.sell_qty = total
|
||||
item.phase = SOLD
|
||||
else:
|
||||
total = item.buy_qty + qty
|
||||
item.buy_cost = (item.buy_qty * item.buy_cost + amount) / total
|
||||
item.buy_qty = total
|
||||
item.phase = DONE if total >= item.sell_qty else SOLD
|
||||
item.trade_date = deal['trade_date']
|
||||
|
||||
def reconcile(
|
||||
self, positions: list[PositionItem], deals: list[DealItem]
|
||||
) -> None:
|
||||
"""Deduplicate each fill; partial fills do not wait for order completion."""
|
||||
today = datetime.now().date().isoformat()
|
||||
seen = {row['order_sys_id'] for row in self.deals}
|
||||
rows = []
|
||||
for deal in deals:
|
||||
if not self._is_zt_deal(deal) or deal.order_sys_id in seen:
|
||||
continue
|
||||
try:
|
||||
row = self._store.deal_record(deal)
|
||||
except ValueError:
|
||||
continue
|
||||
rows.append(row)
|
||||
seen.add(deal.order_sys_id)
|
||||
rows.sort(key=lambda r: (r['trade_date'], r['trade_time']))
|
||||
modified = False
|
||||
try:
|
||||
# Snapshot includes these fills: subtract their net quantity before replay.
|
||||
net = {}
|
||||
for row in rows:
|
||||
net[row['stock_code']] = net.get(row['stock_code'], 0) + (
|
||||
row['volume'] if str(row['offset_flag']) in ('23', '48') else -row['volume']
|
||||
)
|
||||
for position in positions:
|
||||
code = position.stock_code
|
||||
if code in self.items or position.volume <= 0:
|
||||
continue
|
||||
if not math.isfinite(position.open_price) or position.open_price <= 0:
|
||||
continue
|
||||
qty = max(0, position.volume - net.get(code, 0))
|
||||
self.items[code] = TStateItem(code, qty, position.open_price if qty else 0.0)
|
||||
modified = True
|
||||
|
||||
# 全部卖出时快照可能已无该证券,按净卖出数量恢复待买回的底仓数量。
|
||||
for code, delta in net.items():
|
||||
if code not in self.items and delta < 0:
|
||||
self.items[code] = TStateItem(code, -delta)
|
||||
|
||||
for row in rows:
|
||||
code = row['stock_code']
|
||||
item = self.items.get(code)
|
||||
if item is None:
|
||||
item = self.items[code] = TStateItem(code)
|
||||
self._reset(item, row['trade_date'])
|
||||
qty, amount = row['volume'], row['trade_amount']
|
||||
if row['order_local_id'].startswith('zt-base-'):
|
||||
total = item.base_qty + qty
|
||||
item.base_cost = (item.base_qty * item.base_cost + amount) / total
|
||||
item.base_qty = total
|
||||
item.base_order_id = row['order_local_id']
|
||||
else:
|
||||
self._apply_t_deal(item, row)
|
||||
self.deals.append(row)
|
||||
modified = True
|
||||
|
||||
for item in self.items.values():
|
||||
modified = self._reset(item, today) or modified
|
||||
if modified:
|
||||
self.save()
|
||||
except Exception:
|
||||
self._load()
|
||||
raise
|
||||
|
||||
def save(self) -> None:
|
||||
try:
|
||||
self._store.save(
|
||||
{
|
||||
code: {
|
||||
'stock_code': item.code,
|
||||
'volume': item.base_qty,
|
||||
'open_price': item.base_cost,
|
||||
'open_cost': item.base_qty * item.base_cost,
|
||||
}
|
||||
for code, item in self.items.items()
|
||||
},
|
||||
self.deals,
|
||||
)
|
||||
except Exception:
|
||||
self._load()
|
||||
raise
|
||||
|
||||
def _load(self) -> None:
|
||||
self._store.load()
|
||||
self.items = {}
|
||||
for code, position in self._store.positions.items():
|
||||
self.items[code] = TStateItem(code, position['volume'], position['open_price'], id=position['id'])
|
||||
self.deals = [
|
||||
{key: value for key, value in deal.items() if key != 'id'}
|
||||
for deal in self._store.deals.values()
|
||||
]
|
||||
# 轮次明细不占用持仓表字段,从已保存的逐笔成交重建。
|
||||
for deal in self.deals:
|
||||
item = self.items.get(deal['stock_code'])
|
||||
if item is None:
|
||||
continue
|
||||
local_order_id = deal['order_local_id']
|
||||
if local_order_id.startswith('zt-base-'):
|
||||
item.base_order_id = local_order_id
|
||||
else:
|
||||
self._apply_t_deal(item, deal)
|
||||
today = datetime.now().date().isoformat()
|
||||
for item in self.items.values():
|
||||
self._reset(item, today)
|
||||
Reference in New Issue
Block a user