Add dual exit modes: fixed USDT or premium multiple.
Net PnL (after estimated close fees) drives auto close; Plan/Settings expose the choice. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from ..models.db import get_db
|
||||
from ..sim.ledger import Ledger
|
||||
from ..sim.matcher import Matcher
|
||||
from .clock import window_key
|
||||
from .exits import check_exits
|
||||
from .exits import check_exits, resolve_exit_target
|
||||
from .group import next_group_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -32,7 +32,19 @@ class StrategyEngine:
|
||||
assert row is not None
|
||||
upl = self.matcher.unrealized()
|
||||
s = get_settings()
|
||||
exit_pct = self.ledger.get_setting_float("exit_move_pct", s.exit_move_pct)
|
||||
exit_mode = self.ledger.get_setting_str("exit_mode", s.exit_mode)
|
||||
net_target = self.ledger.get_setting_float(
|
||||
"net_profit_target", s.net_profit_target
|
||||
)
|
||||
prem_mult = self.ledger.get_setting_float(
|
||||
"premium_exit_multiple", s.premium_exit_multiple
|
||||
)
|
||||
exit_amt, _ = resolve_exit_target(
|
||||
exit_mode=exit_mode,
|
||||
net_profit_target=net_target,
|
||||
premium_exit_multiple=prem_mult,
|
||||
initial_premium=float(upl.get("initial_premium") or 0),
|
||||
)
|
||||
rest_sec = self.ledger.get_setting_int("rest_seconds", s.rest_seconds)
|
||||
leverage = self.ledger.get_setting_float("leverage", s.leverage)
|
||||
min_hours = self.ledger.get_setting_float("min_option_hours", s.min_option_hours)
|
||||
@@ -55,7 +67,10 @@ class StrategyEngine:
|
||||
"rest_until_ms": rest_until,
|
||||
"rest_left_sec": rest_left,
|
||||
"rest_seconds": rest_sec,
|
||||
"exit_move_pct": exit_pct,
|
||||
"exit_mode": exit_mode,
|
||||
"net_profit_target": net_target,
|
||||
"premium_exit_multiple": prem_mult,
|
||||
"exit_target_usdt": exit_amt,
|
||||
"leverage": leverage,
|
||||
"min_option_hours": min_hours,
|
||||
"min_option_leverage": min_opt_lev,
|
||||
@@ -153,17 +168,24 @@ class StrategyEngine:
|
||||
|
||||
st = self.db.fetchone("SELECT * FROM strategy_state WHERE id=1")
|
||||
assert st is not None
|
||||
exit_pct = self.ledger.get_setting_float("exit_move_pct", s.exit_move_pct)
|
||||
exit_mode = self.ledger.get_setting_str("exit_mode", s.exit_mode)
|
||||
net_target = self.ledger.get_setting_float(
|
||||
"net_profit_target", s.net_profit_target
|
||||
)
|
||||
prem_mult = self.ledger.get_setting_float(
|
||||
"premium_exit_multiple", s.premium_exit_multiple
|
||||
)
|
||||
pos = self.matcher.current_position()
|
||||
|
||||
# 有未平仓:只盯平仓,绝不开下一组
|
||||
if pos.get("status") == "open":
|
||||
upl = self.matcher.unrealized()
|
||||
decision = check_exits(
|
||||
perp_upl=float(upl["perp_upl"]),
|
||||
initial_premium=float(upl["initial_premium"] or 0),
|
||||
move_pct=float(upl.get("move_pct") or 0),
|
||||
exit_move_pct=exit_pct,
|
||||
net_pnl=float(upl.get("net_pnl") or 0),
|
||||
exit_mode=exit_mode,
|
||||
net_profit_target=net_target,
|
||||
premium_exit_multiple=prem_mult,
|
||||
initial_premium=float(upl.get("initial_premium") or 0),
|
||||
)
|
||||
pending_close = st["phase"] in ("liquidity_wait", "closing")
|
||||
if decision.should_close or pending_close:
|
||||
|
||||
@@ -2,22 +2,48 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
EXIT_MODE_FIXED = "fixed_usdt"
|
||||
EXIT_MODE_PREMIUM = "premium_multiple"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ExitDecision:
|
||||
should_close: bool
|
||||
reason: str = ""
|
||||
target: float = 0.0
|
||||
|
||||
|
||||
def resolve_exit_target(
|
||||
*,
|
||||
exit_mode: str,
|
||||
net_profit_target: float,
|
||||
premium_exit_multiple: float,
|
||||
initial_premium: float,
|
||||
) -> tuple[float, str]:
|
||||
"""返回 (出场目标金额 USDT, 模式标记)。"""
|
||||
mode = (exit_mode or EXIT_MODE_FIXED).strip().lower()
|
||||
if mode == EXIT_MODE_PREMIUM:
|
||||
mult = max(0.0, float(premium_exit_multiple))
|
||||
return float(initial_premium) * mult, EXIT_MODE_PREMIUM
|
||||
return float(net_profit_target), EXIT_MODE_FIXED
|
||||
|
||||
|
||||
def check_exits(
|
||||
*,
|
||||
perp_upl: float,
|
||||
net_pnl: float,
|
||||
exit_mode: str,
|
||||
net_profit_target: float,
|
||||
premium_exit_multiple: float,
|
||||
initial_premium: float,
|
||||
move_pct: float,
|
||||
exit_move_pct: float,
|
||||
) -> ExitDecision:
|
||||
if initial_premium > 0 and perp_upl + 1e-9 >= initial_premium:
|
||||
return ExitDecision(True, "premium_cover")
|
||||
if exit_move_pct > 0 and move_pct + 1e-9 >= exit_move_pct:
|
||||
return ExitDecision(True, "move_pct")
|
||||
return ExitDecision(False, "")
|
||||
"""净盈利(预估全平后)≥ 所选模式目标则全平。"""
|
||||
target, mode = resolve_exit_target(
|
||||
exit_mode=exit_mode,
|
||||
net_profit_target=net_profit_target,
|
||||
premium_exit_multiple=premium_exit_multiple,
|
||||
initial_premium=initial_premium,
|
||||
)
|
||||
if target > 0 and net_pnl + 1e-9 >= target:
|
||||
reason = "premium_multiple" if mode == EXIT_MODE_PREMIUM else "fixed_usdt"
|
||||
return ExitDecision(True, reason, target)
|
||||
return ExitDecision(False, "", target)
|
||||
|
||||
Reference in New Issue
Block a user