永期手动下单:修币本位杠杆显示,张数定仓与双出场模式.
右侧以张数+比例算永续,杠杆取 env(BTC10/ETH5);开仓买币+5%滑点;出场可选盈利金额(扣双边费)或目标位;平仓后卖光币回 U. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -172,6 +172,52 @@ def _notify_end_reload(cfg: dict[str, Any], conn: Any, plan_id: int) -> None:
|
||||
notify_plan_end(cfg, conn, plan)
|
||||
|
||||
|
||||
def _sell_all_coin_to_usdt(cfg: dict[str, Any], underlying: str) -> dict[str, Any]:
|
||||
"""平仓后把交易账户标的币全部卖回 USDT."""
|
||||
try:
|
||||
from lib.options.options_spot_bridge_lib import spot_market_sell_coin_to_usdt
|
||||
|
||||
ex = cfg.get("exchange_options") or cfg.get("exchange")
|
||||
if ex is None:
|
||||
return {"ok": False, "msg": "无交易所实例,跳过卖币"}
|
||||
return spot_market_sell_coin_to_usdt(ex, underlying=str(underlying or "ETH").upper())
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
def _plan_exit_mode(plan: dict[str, Any]) -> str:
|
||||
raw = str(plan.get("exit_mode") or "").strip().lower()
|
||||
if raw in ("profit", "target"):
|
||||
return raw
|
||||
# 兼容旧单:preview_json
|
||||
try:
|
||||
import json
|
||||
|
||||
meta = json.loads(plan.get("preview_json") or "{}")
|
||||
m = str(meta.get("exit_mode") or "").strip().lower()
|
||||
if m in ("profit", "target"):
|
||||
return m
|
||||
except Exception:
|
||||
pass
|
||||
return "target"
|
||||
|
||||
|
||||
def _plan_profit_target(plan: dict[str, Any]) -> float:
|
||||
v = _sf(plan.get("profit_target_usdt"))
|
||||
if v is not None and v > 0:
|
||||
return float(v)
|
||||
try:
|
||||
import json
|
||||
|
||||
meta = json.loads(plan.get("preview_json") or "{}")
|
||||
v2 = _sf(meta.get("profit_target_usdt"))
|
||||
if v2 is not None and v2 > 0:
|
||||
return float(v2)
|
||||
except Exception:
|
||||
pass
|
||||
return 0.0
|
||||
|
||||
|
||||
def resolve_oo_rest_close_mode(plan: dict[str, Any]) -> str:
|
||||
"""盈利腿平后另一腿:close_all(残值平) / hold_expiry(到期平).
|
||||
|
||||
@@ -639,7 +685,7 @@ def _tick_po_option_primary_expiry(
|
||||
def _tick_po_option_primary(
|
||||
cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: list[dict[str, Any]]
|
||||
) -> Optional[dict[str, Any]]:
|
||||
"""以期权为主:触达目标位立即执行分叉平仓规则."""
|
||||
"""以期权为主:盈利金额出场或目标位出场;先平期权(须买一流动性)再平永续,再卖币回 U."""
|
||||
from lib.hedge_plan.hedge_plan_option_primary_lib import (
|
||||
estimate_combo_net_pnl,
|
||||
option_bid_liquidity_ok,
|
||||
@@ -662,6 +708,8 @@ def _tick_po_option_primary(
|
||||
strike = _sf(opt.get("strike"))
|
||||
n = _sf(plan.get("option_target_points"))
|
||||
m = _sf(plan.get("perp_target_points"))
|
||||
exit_mode = _plan_exit_mode(plan)
|
||||
profit_target = _plan_profit_target(plan)
|
||||
if strike is None or strike <= 0:
|
||||
return None
|
||||
idx = _index_px(cfg, str(plan.get("underlying") or "ETH"))
|
||||
@@ -670,7 +718,13 @@ def _tick_po_option_primary(
|
||||
|
||||
hit_opt = bool(n is not None and target_hit(view_side=view, index_px=idx, strike=strike, points=float(n)))
|
||||
hit_perp = bool(m is not None and target_hit(view_side=view, index_px=idx, strike=strike, points=float(m)))
|
||||
if not hit_opt and not hit_perp:
|
||||
# 盈利金额出场:不依赖点数,后面按净利判断
|
||||
if exit_mode == "profit":
|
||||
if profit_target <= 0:
|
||||
return None
|
||||
hit_opt = True
|
||||
hit_perp = False
|
||||
elif not hit_opt and not hit_perp:
|
||||
return None
|
||||
|
||||
symbol = str(perp.get("symbol") or "")
|
||||
@@ -710,7 +764,7 @@ def _tick_po_option_primary(
|
||||
ct = float(opt.get("ct_mult") or 0.01)
|
||||
contracts = float(perp.get("size") or plan.get("perp_size") or 0)
|
||||
|
||||
# 优先期权目标;买一不足或净利≤0 时若永续目标已触达则改走永续目标
|
||||
# 优先期权腿平仓(须买一流动性);盈利金额模式要求扣双边费后净利≥目标
|
||||
if hit_opt:
|
||||
liq_ok, liq_msg = option_bid_liquidity_ok(bid, bid_sz, need_sheets=sheets)
|
||||
net = None
|
||||
@@ -729,9 +783,14 @@ def _tick_po_option_primary(
|
||||
contracts=contracts,
|
||||
contract_size=cs,
|
||||
)
|
||||
can_opt_exit = bool(liq_ok and net is not None and float(net.get("net") or 0) > 0)
|
||||
if exit_mode == "profit":
|
||||
can_opt_exit = bool(
|
||||
liq_ok and net is not None and float(net.get("net") or 0) + 1e-9 >= profit_target
|
||||
)
|
||||
else:
|
||||
can_opt_exit = bool(liq_ok and net is not None and float(net.get("net") or 0) > 0)
|
||||
if can_opt_exit:
|
||||
reason = "opt_target_points"
|
||||
reason = "profit_target_usdt" if exit_mode == "profit" else "opt_target_points"
|
||||
close_r = _sell_option(cfg, inst_id=str(opt.get("inst_id") or ""), sheets=sheets)
|
||||
if close_r.get("already_flat"):
|
||||
opt_pnl = _option_leg_pnl_after_close(cfg, opt, fallback=net["opt_net"])
|
||||
@@ -783,8 +842,26 @@ def _tick_po_option_primary(
|
||||
stats_bucket="opt_primary",
|
||||
closed_at=_now(),
|
||||
)
|
||||
sell_r = _sell_all_coin_to_usdt(cfg, str(plan.get("underlying") or "ETH"))
|
||||
_notify_end_reload(cfg, conn, int(plan["id"]))
|
||||
return {"plan_id": plan["id"], "close_reason": reason, "total": total, "net": net}
|
||||
return {
|
||||
"plan_id": plan["id"],
|
||||
"close_reason": reason,
|
||||
"total": total,
|
||||
"net": net,
|
||||
"sell_coin": sell_r,
|
||||
}
|
||||
if exit_mode == "profit":
|
||||
return {
|
||||
"plan_id": plan["id"],
|
||||
"skip": True,
|
||||
"msg": (
|
||||
liq_msg
|
||||
if not liq_ok
|
||||
else f"净利未达目标 {profit_target:g}(当前 {float((net or {}).get('net') or 0):.2f})"
|
||||
),
|
||||
"net": net,
|
||||
}
|
||||
if not hit_perp:
|
||||
return {
|
||||
"plan_id": plan["id"],
|
||||
|
||||
Reference in New Issue
Block a user