from __future__ import annotations import time from dataclasses import dataclass from typing import Any 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 lock_trade_exit_target( db: Any, *, group_id: str, initial_premium: float, ) -> float: """ 开仓成交后锁定本组成交出场目标到 groups/positions。 持仓期间盯盘与展示均用该值,不再跟随时价重算以损定仓/出场。 """ from ..config import get_settings from ..sim.ledger import Ledger s = get_settings() ledger = Ledger(db) exit_mode = ledger.get_setting_str("exit_mode", s.exit_mode) or EXIT_MODE_FIXED net_target = float( ledger.get_setting_float("net_profit_target", s.net_profit_target) or s.net_profit_target ) prem_mult = float( ledger.get_setting_float("premium_exit_multiple", s.premium_exit_multiple) or s.premium_exit_multiple ) target, _mode = resolve_exit_target( exit_mode=str(exit_mode), net_profit_target=net_target, premium_exit_multiple=prem_mult, initial_premium=float(initial_premium or 0), ) target = float(target) db.execute( "UPDATE groups SET exit_target_usdt=? WHERE group_id=?", (target, group_id), ) db.execute( "UPDATE positions SET exit_target_usdt=? WHERE id=1", (target,), ) return target def read_locked_exit_target(pos: dict[str, Any] | None) -> float | None: """持仓行上的锁定目标;无则 None(旧仓回退设置值)。""" if not pos: return None v = pos.get("exit_target_usdt") if v is None or v == "": return None try: f = float(v) except (TypeError, ValueError): return None return f if f > 0 else None def check_expiry_close( *, expiry_ms: int | None, now_ms: int | None = None, ) -> ExitDecision: """期权到期时刻(含)→ 强制全平。""" if expiry_ms is None: return ExitDecision(False, "", 0.0) now = int(time.time() * 1000) if now_ms is None else int(now_ms) if now >= int(expiry_ms): return ExitDecision(True, "expiry", 0.0) return ExitDecision(False, "", 0.0) def check_exits( *, net_pnl: float, exit_mode: str, net_profit_target: float, premium_exit_multiple: float, initial_premium: float, locked_exit_target: float | None = None, ) -> ExitDecision: """净盈利(预估全平后)≥ 所选模式目标则全平。持仓锁定目标优先。""" # 模式始终按设置解析(权利金倍数 vs 固定),勿因锁定目标就改成 fixed_usdt _resolved_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 locked_exit_target is not None and float(locked_exit_target) > 0: target = float(locked_exit_target) else: target = float(_resolved_target) 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)