Add daily loss force-flatten at configurable equity limit
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+113
-10
@@ -1189,6 +1189,9 @@ class CtpBridge:
|
||||
price: float,
|
||||
tick: float,
|
||||
use_market: bool,
|
||||
urgency: str = "normal",
|
||||
equity: Optional[float] = None,
|
||||
slippage_buffer_pct: Optional[float] = None,
|
||||
) -> str:
|
||||
"""平仓:VeighNa OffsetConverter 自动拆分平今/平昨(与 CTA 引擎一致)。"""
|
||||
from vnpy.trader.constant import Offset
|
||||
@@ -1210,7 +1213,11 @@ class CtpBridge:
|
||||
|
||||
lp = float(price)
|
||||
if use_market:
|
||||
lp = self._aggressive_limit_price(ths_code, sym, ex_name, direction, tick, lp)
|
||||
lp = self._aggressive_limit_price(
|
||||
ths_code, sym, ex_name, direction, tick, lp,
|
||||
urgency=urgency, equity=equity,
|
||||
slippage_buffer_pct=slippage_buffer_pct, lots=lots,
|
||||
)
|
||||
else:
|
||||
lp = round_to_tick(lp, tick)
|
||||
if lp <= 0:
|
||||
@@ -1245,6 +1252,9 @@ class CtpBridge:
|
||||
if use_market:
|
||||
sub_price = self._aggressive_limit_price(
|
||||
ths_code, sym, ex_name, sub.direction, tick, float(price),
|
||||
urgency=urgency, equity=equity,
|
||||
slippage_buffer_pct=slippage_buffer_pct,
|
||||
lots=int(sub.volume or lots),
|
||||
)
|
||||
else:
|
||||
sub_price = round_to_tick(float(sub.price or lp), tick)
|
||||
@@ -1266,6 +1276,48 @@ class CtpBridge:
|
||||
logger.debug("offset converter order req: %s", exc)
|
||||
return last_vt
|
||||
|
||||
def _find_tick_obj(self, sym: str, ex_name: str) -> Any:
|
||||
if not self._engine:
|
||||
return None
|
||||
sym_l = sym.lower()
|
||||
ex_u = ex_name.upper()
|
||||
try:
|
||||
for tick in self._engine.get_all_ticks():
|
||||
ts = (getattr(tick, "symbol", "") or "").lower()
|
||||
te = getattr(tick, "exchange", None)
|
||||
te_s = str(te.value if hasattr(te, "value") else te or "").upper()
|
||||
if ts == sym_l and te_s == ex_u:
|
||||
return tick
|
||||
except Exception as exc:
|
||||
logger.debug("find tick: %s", exc)
|
||||
return None
|
||||
|
||||
def _opponent_price_from_tick(self, tick: Any, direction: Any) -> Optional[float]:
|
||||
from vnpy.trader.constant import Direction
|
||||
|
||||
if not tick:
|
||||
return None
|
||||
if direction == Direction.LONG:
|
||||
attrs = ("ask_price_1", "last_price", "pre_close")
|
||||
else:
|
||||
attrs = ("bid_price_1", "last_price", "pre_close")
|
||||
for attr in attrs:
|
||||
try:
|
||||
v = float(getattr(tick, attr, 0) or 0)
|
||||
except (TypeError, ValueError):
|
||||
v = 0.0
|
||||
if v > 0:
|
||||
return v
|
||||
return None
|
||||
|
||||
def _urgency_slip_ticks(self, urgency: str) -> int:
|
||||
table = {
|
||||
"normal": 5,
|
||||
"stop_loss": 12,
|
||||
"risk_flatten": 20,
|
||||
}
|
||||
return max(1, int(table.get((urgency or "normal").strip().lower(), 5)))
|
||||
|
||||
def _aggressive_limit_price(
|
||||
self,
|
||||
ths_code: str,
|
||||
@@ -1274,19 +1326,47 @@ class CtpBridge:
|
||||
direction: Any,
|
||||
tick: float,
|
||||
fallback: float,
|
||||
*,
|
||||
urgency: str = "normal",
|
||||
equity: Optional[float] = None,
|
||||
slippage_buffer_pct: Optional[float] = None,
|
||||
lots: int = 1,
|
||||
) -> float:
|
||||
from vnpy.trader.constant import Direction
|
||||
|
||||
self.subscribe_symbol(ths_code)
|
||||
lp = fallback
|
||||
detail = self.get_tick_detail(ths_code, mode=self._connected_mode or "")
|
||||
if detail.get("price"):
|
||||
lp = float(detail["price"])
|
||||
slip = max(tick, tick * 3)
|
||||
tick_obj = self._find_tick_obj(sym, ex_name)
|
||||
opp = self._opponent_price_from_tick(tick_obj, direction)
|
||||
if opp is None or opp <= 0:
|
||||
detail = self.get_tick_detail(ths_code, mode=self._connected_mode or "")
|
||||
if detail.get("price"):
|
||||
opp = float(detail["price"])
|
||||
else:
|
||||
opp = float(fallback or 0)
|
||||
if opp <= 0:
|
||||
return 0.0
|
||||
|
||||
slip_ticks = self._urgency_slip_ticks(urgency)
|
||||
slip_price = slip_ticks * max(tick, 1e-9)
|
||||
if (
|
||||
(urgency or "").strip().lower() == "risk_flatten"
|
||||
and equity
|
||||
and float(equity) > 0
|
||||
and slippage_buffer_pct is not None
|
||||
and float(slippage_buffer_pct) > 0
|
||||
and lots > 0
|
||||
):
|
||||
spec = get_contract_spec(ths_code)
|
||||
mult = float(spec.get("mult") or 10)
|
||||
max_yuan = float(equity) * float(slippage_buffer_pct) / 100.0
|
||||
denom = mult * max(1, int(lots))
|
||||
if denom > 0:
|
||||
slip_price = min(slip_price, max_yuan / denom)
|
||||
|
||||
if direction == Direction.LONG:
|
||||
lp = lp + slip
|
||||
lp = opp + slip_price
|
||||
else:
|
||||
lp = max(tick, lp - slip)
|
||||
lp = max(tick, opp - slip_price)
|
||||
return round_to_tick(lp, tick)
|
||||
|
||||
def ping(self) -> bool:
|
||||
@@ -2479,6 +2559,9 @@ class CtpBridge:
|
||||
lots: int,
|
||||
price: float,
|
||||
order_type: str = "limit",
|
||||
urgency: str = "normal",
|
||||
equity: Optional[float] = None,
|
||||
slippage_buffer_pct: Optional[float] = None,
|
||||
) -> str:
|
||||
from vnpy.trader.constant import Direction, Offset, OrderType
|
||||
from vnpy.trader.object import OrderRequest
|
||||
@@ -2502,7 +2585,11 @@ class CtpBridge:
|
||||
use_market = (order_type or "limit").lower() == "market"
|
||||
if use_market:
|
||||
ot = OrderType.FAK
|
||||
price = self._aggressive_limit_price(ths_code, sym, ex_name, d, tick, price)
|
||||
price = self._aggressive_limit_price(
|
||||
ths_code, sym, ex_name, d, tick, price,
|
||||
urgency=urgency, equity=equity,
|
||||
slippage_buffer_pct=slippage_buffer_pct, lots=lots,
|
||||
)
|
||||
else:
|
||||
ot = OrderType.LIMIT
|
||||
price = round_to_tick(float(price), tick)
|
||||
@@ -2541,7 +2628,11 @@ class CtpBridge:
|
||||
ot = OrderType.LIMIT
|
||||
price = round_to_tick(float(price), tick)
|
||||
if use_market:
|
||||
price = self._aggressive_limit_price(ths_code, sym, ex_name, d, tick, price)
|
||||
price = self._aggressive_limit_price(
|
||||
ths_code, sym, ex_name, d, tick, price,
|
||||
urgency=urgency, equity=equity,
|
||||
slippage_buffer_pct=slippage_buffer_pct, lots=lots,
|
||||
)
|
||||
if price <= 0:
|
||||
raise ValueError("委托价格无效,请检查行情或手动填写价格")
|
||||
return self._submit_close_orders(
|
||||
@@ -2556,6 +2647,9 @@ class CtpBridge:
|
||||
price=price,
|
||||
tick=tick,
|
||||
use_market=use_market,
|
||||
urgency=urgency,
|
||||
equity=equity,
|
||||
slippage_buffer_pct=slippage_buffer_pct,
|
||||
)
|
||||
raise ValueError(f"未知开平: {offset}")
|
||||
|
||||
@@ -3062,6 +3156,9 @@ def execute_order(
|
||||
price: float,
|
||||
settings: dict | None = None,
|
||||
order_type: str = "limit",
|
||||
urgency: str = "normal",
|
||||
equity: Optional[float] = None,
|
||||
slippage_buffer_pct: Optional[float] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""统一下单:simulation=SimNow,live=期货公司 CTP。"""
|
||||
if _use_ctp_worker_client():
|
||||
@@ -3074,6 +3171,9 @@ def execute_order(
|
||||
"price": price,
|
||||
"settings": settings or {},
|
||||
"order_type": order_type,
|
||||
"urgency": urgency,
|
||||
"equity": equity,
|
||||
"slippage_buffer_pct": slippage_buffer_pct,
|
||||
})
|
||||
del conn, settings
|
||||
if mode not in ("simulation", "live"):
|
||||
@@ -3092,6 +3192,9 @@ def execute_order(
|
||||
lots=lots,
|
||||
price=price,
|
||||
order_type=order_type,
|
||||
urgency=urgency,
|
||||
equity=equity,
|
||||
slippage_buffer_pct=slippage_buffer_pct,
|
||||
)
|
||||
return {
|
||||
"order_id": order_id,
|
||||
|
||||
Reference in New Issue
Block a user