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:
@@ -59,3 +59,9 @@ class Ledger:
|
||||
|
||||
def get_setting_int(self, key: str, default: int) -> int:
|
||||
return int(self.get_setting_float(key, float(default)))
|
||||
|
||||
def get_setting_str(self, key: str, default: str) -> str:
|
||||
v = self.db.get_setting(key)
|
||||
if v is None or v == "":
|
||||
return default
|
||||
return str(v)
|
||||
|
||||
+56
-20
@@ -412,6 +412,8 @@ class Matcher:
|
||||
"has_position": False,
|
||||
"perp_upl": 0.0,
|
||||
"option_upl": 0.0,
|
||||
"net_pnl": 0.0,
|
||||
"est_close_fees": 0.0,
|
||||
"index_px": None,
|
||||
"move_points": 0.0,
|
||||
"move_pct": 0.0,
|
||||
@@ -420,41 +422,73 @@ class Matcher:
|
||||
sess = get_session()
|
||||
snap = sess.snapshot()
|
||||
s = get_settings()
|
||||
fee_rate = self._fee_rate()
|
||||
index_px = snap.index_px
|
||||
if index_px is None and snap.perp:
|
||||
index_px = snap.perp.mark_px
|
||||
perp_side = str(pos["perp_side"])
|
||||
perp_entry = float(pos["perp_entry_px"])
|
||||
perp_qty = float(pos["perp_qty_eth"])
|
||||
mark = None
|
||||
if snap.perp:
|
||||
# 浮盈用对手方可平价粗估
|
||||
if perp_side == "long":
|
||||
mark = snap.perp.bid
|
||||
else:
|
||||
mark = snap.perp.ask
|
||||
mark = mark or snap.perp.mark_px
|
||||
opt_qty = float(pos["option_qty_eth"] or 0)
|
||||
opt_entry = float(pos["option_entry_px"] or 0)
|
||||
|
||||
# 与平仓一致:用对手价估算可平盈亏 + 手续费
|
||||
perp_upl = 0.0
|
||||
if mark is not None:
|
||||
est_perp_close_fee = 0.0
|
||||
mark = None
|
||||
if snap.perp and snap.perp.bid is not None and snap.perp.ask is not None:
|
||||
pf = perp_fill(
|
||||
side=perp_side,
|
||||
action="close",
|
||||
bid=float(snap.perp.bid),
|
||||
ask=float(snap.perp.ask),
|
||||
qty_eth=perp_qty,
|
||||
fee_rate=fee_rate,
|
||||
)
|
||||
if perp_side == "long":
|
||||
perp_upl = (float(mark) - perp_entry) * perp_qty
|
||||
perp_upl = (pf.fill_px - perp_entry) * perp_qty
|
||||
else:
|
||||
perp_upl = (perp_entry - float(mark)) * perp_qty
|
||||
perp_upl = (perp_entry - pf.fill_px) * perp_qty
|
||||
est_perp_close_fee = pf.fee
|
||||
mark = pf.fill_px
|
||||
elif snap.perp:
|
||||
if perp_side == "long":
|
||||
mark = snap.perp.bid or snap.perp.mark_px
|
||||
else:
|
||||
mark = snap.perp.ask or snap.perp.mark_px
|
||||
if mark is not None:
|
||||
if perp_side == "long":
|
||||
perp_upl = (float(mark) - perp_entry) * perp_qty
|
||||
else:
|
||||
perp_upl = (perp_entry - float(mark)) * perp_qty
|
||||
|
||||
option_side = str(pos["option_side"])
|
||||
# 优先用持仓合约盘口,避免 ATM 切换后盯错合约
|
||||
opt_inst = str(pos.get("option_inst_id") or "")
|
||||
oq = get_exchange().quote(opt_inst) if opt_inst else None
|
||||
if oq is None:
|
||||
oq = snap.call if option_side == "call" else snap.put
|
||||
opt_mark = None
|
||||
if oq:
|
||||
opt_mark = oq.bid or oq.mark_px
|
||||
option_upl = 0.0
|
||||
if opt_mark is not None:
|
||||
option_upl = (float(opt_mark) - float(pos["option_entry_px"])) * float(
|
||||
pos["option_qty_eth"]
|
||||
est_opt_close_fee = 0.0
|
||||
opt_mark = None
|
||||
if oq and oq.bid is not None:
|
||||
of = option_fill(
|
||||
action="close",
|
||||
bid=float(oq.bid),
|
||||
ask=float(oq.ask or oq.bid),
|
||||
qty_eth=opt_qty,
|
||||
fee_rate=fee_rate,
|
||||
)
|
||||
option_upl = (of.fill_px - opt_entry) * opt_qty
|
||||
est_opt_close_fee = of.fee
|
||||
opt_mark = of.fill_px
|
||||
elif oq:
|
||||
opt_mark = oq.bid or oq.mark_px
|
||||
if opt_mark is not None:
|
||||
option_upl = (float(opt_mark) - opt_entry) * opt_qty
|
||||
|
||||
est_close_fees = est_perp_close_fee + est_opt_close_fee
|
||||
# 净盈利口径与平仓结算一致:双腿盈亏 − 预估平仓手续费
|
||||
net_pnl = perp_upl + option_upl - est_close_fees
|
||||
|
||||
entry_idx = float(pos["entry_index_px"] or 0)
|
||||
move = abs(float(index_px) - entry_idx) if index_px is not None and entry_idx else 0.0
|
||||
@@ -492,14 +526,16 @@ class Matcher:
|
||||
"perp_margin": margin,
|
||||
"leverage": leverage,
|
||||
"option_inst_id": pos.get("option_inst_id"),
|
||||
"option_entry_px": float(pos["option_entry_px"] or 0),
|
||||
"option_qty_eth": float(pos["option_qty_eth"] or 0),
|
||||
"option_entry_px": opt_entry,
|
||||
"option_qty_eth": opt_qty,
|
||||
"option_qty_contracts": float(pos["option_qty_contracts"] or 0),
|
||||
"option_mark_px": float(opt_mark) if opt_mark is not None else None,
|
||||
"strike": strike,
|
||||
"expiry_ymd": expiry_ymd,
|
||||
"perp_upl": perp_upl,
|
||||
"option_upl": option_upl,
|
||||
"est_close_fees": est_close_fees,
|
||||
"net_pnl": net_pnl,
|
||||
"index_px": index_px,
|
||||
"entry_index_px": entry_idx,
|
||||
"move_points": move,
|
||||
|
||||
Reference in New Issue
Block a user