fix: SHFE止盈止损平仓改平今并限制重复报单
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -443,7 +443,6 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
mode = get_trading_mode(get_setting)
|
mode = get_trading_mode(get_setting)
|
||||||
ctp_st = ctp_status(mode)
|
ctp_st = ctp_status(mode)
|
||||||
_sync_trade_monitors_with_ctp(conn, mode)
|
_sync_trade_monitors_with_ctp(conn, mode)
|
||||||
sync_all_sl_tp_orders(conn, mode)
|
|
||||||
rows = _build_trading_live_rows(conn)
|
rows = _build_trading_live_rows(conn)
|
||||||
pending_orders = _build_pending_orders(conn, mode)
|
pending_orders = _build_pending_orders(conn, mode)
|
||||||
capital = _capital(conn)
|
capital = _capital(conn)
|
||||||
|
|||||||
+34
-12
@@ -18,6 +18,9 @@ from vnpy_bridge import (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CHECK_INTERVAL_SEC = 20
|
CHECK_INTERVAL_SEC = 20
|
||||||
|
PLACE_COOLDOWN_SEC = 120
|
||||||
|
|
||||||
|
_last_place_attempt: dict[tuple[int, str], float] = {}
|
||||||
|
|
||||||
MONITOR_ORDER_COLUMNS = (
|
MONITOR_ORDER_COLUMNS = (
|
||||||
"ALTER TABLE trade_order_monitors ADD COLUMN sl_vt_order_id TEXT",
|
"ALTER TABLE trade_order_monitors ADD COLUMN sl_vt_order_id TEXT",
|
||||||
@@ -92,6 +95,15 @@ def _find_position(positions: list[dict], ths_code: str, direction: str) -> Opti
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _can_place_now(monitor_id: int, kind: str, *, cooldown: int = PLACE_COOLDOWN_SEC) -> bool:
|
||||||
|
last = _last_place_attempt.get((monitor_id, kind), 0.0)
|
||||||
|
return (time.time() - last) >= cooldown
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_place_attempt(monitor_id: int, kind: str) -> None:
|
||||||
|
_last_place_attempt[(monitor_id, kind)] = time.time()
|
||||||
|
|
||||||
|
|
||||||
def _order_still_active(active_orders: list[dict], vt_order_id: str) -> bool:
|
def _order_still_active(active_orders: list[dict], vt_order_id: str) -> bool:
|
||||||
if not vt_order_id:
|
if not vt_order_id:
|
||||||
return False
|
return False
|
||||||
@@ -139,6 +151,8 @@ def place_monitor_exit_orders(
|
|||||||
placed: list[str] = []
|
placed: list[str] = []
|
||||||
updates: dict[str, Optional[str]] = {}
|
updates: dict[str, Optional[str]] = {}
|
||||||
|
|
||||||
|
mid = int(mon.get("id") or 0)
|
||||||
|
|
||||||
def _maybe_place(kind: str, price: Optional[float], stored_id: str) -> None:
|
def _maybe_place(kind: str, price: Optional[float], stored_id: str) -> None:
|
||||||
if price is None or price <= 0:
|
if price is None or price <= 0:
|
||||||
return
|
return
|
||||||
@@ -150,19 +164,27 @@ def place_monitor_exit_orders(
|
|||||||
return
|
return
|
||||||
if stored_id and _order_still_active(active, stored_id) and not force:
|
if stored_id and _order_still_active(active, stored_id) and not force:
|
||||||
return
|
return
|
||||||
result = execute_order(
|
if mid > 0 and not force and not _can_place_now(mid, kind):
|
||||||
conn,
|
return
|
||||||
mode=mode,
|
try:
|
||||||
offset=offset,
|
_mark_place_attempt(mid, kind)
|
||||||
symbol=sym,
|
result = execute_order(
|
||||||
direction=direction,
|
conn,
|
||||||
lots=lots,
|
mode=mode,
|
||||||
price=price,
|
offset=offset,
|
||||||
order_type="limit",
|
symbol=sym,
|
||||||
)
|
direction=direction,
|
||||||
|
lots=lots,
|
||||||
|
price=price,
|
||||||
|
order_type="limit",
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("SL/TP place %s monitor=%s failed: %s", kind, mid, exc)
|
||||||
|
return
|
||||||
oid = str(result.get("order_id") or "")
|
oid = str(result.get("order_id") or "")
|
||||||
updates[f"{kind}_vt_order_id"] = oid
|
if oid:
|
||||||
placed.append(f"{kind}@{price}")
|
updates[f"{kind}_vt_order_id"] = oid
|
||||||
|
placed.append(f"{kind}@{price}")
|
||||||
|
|
||||||
sl_id = str(mon.get("sl_vt_order_id") or "")
|
sl_id = str(mon.get("sl_vt_order_id") or "")
|
||||||
tp_id = str(mon.get("tp_vt_order_id") or "")
|
tp_id = str(mon.get("tp_vt_order_id") or "")
|
||||||
|
|||||||
+7
-2
@@ -298,10 +298,15 @@ class CtpBridge:
|
|||||||
def _resolve_close_offset(self, sym: str, ex_name: str, hold_direction: str, lots: int) -> Any:
|
def _resolve_close_offset(self, sym: str, ex_name: str, hold_direction: str, lots: int) -> Any:
|
||||||
from vnpy.trader.constant import Offset
|
from vnpy.trader.constant import Offset
|
||||||
|
|
||||||
if ex_name not in ("CZCE", "CFFEX"):
|
ex_u = (ex_name or "").upper()
|
||||||
|
# 上期所/能源中心/郑商所/中金所须区分平今/平昨;大商所等可用通用 CLOSE
|
||||||
|
if ex_u not in ("CZCE", "CFFEX", "SHFE", "INE"):
|
||||||
return Offset.CLOSE
|
return Offset.CLOSE
|
||||||
pos = self._find_position(sym, ex_name, hold_direction)
|
pos = self._find_position(sym, ex_u, hold_direction)
|
||||||
if not pos:
|
if not pos:
|
||||||
|
# 找不到持仓明细时,日盘新开仓优先平今(避免 SHFE「平昨仓位不足」)
|
||||||
|
if ex_u in ("SHFE", "INE", "CZCE"):
|
||||||
|
return Offset.CLOSETODAY
|
||||||
return Offset.CLOSE
|
return Offset.CLOSE
|
||||||
vol = int(getattr(pos, "volume", 0) or 0)
|
vol = int(getattr(pos, "volume", 0) or 0)
|
||||||
yd = int(getattr(pos, "yd_volume", 0) or 0)
|
yd = int(getattr(pos, "yd_volume", 0) or 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user