"""中控永期对冲计算器:永续 1 币 + 按目标盈利反推期权仓位/波动点数(纯函数).""" from __future__ import annotations from typing import Any, Optional, Tuple from lib.trade.trade_fee_lib import estimate_roundtrip_fee_usdt, taker_fee_rate DEFAULT_CT_MULT = 0.01 PERP_COINS = 1.0 def _f(v: Any) -> Optional[float]: if v is None or v == "": return None try: return float(v) except (TypeError, ValueError): return None def _parse_base_common( *, base: str, spot: Any, capital_usdt: Any, target_profit_u: Any, perp_leverage: Any, option_leverage: Any, ct_mult: Any, ) -> Tuple[Optional[dict[str, float]], Optional[str]]: b = (base or "ETH").strip().upper() if b not in ("ETH", "BTC"): return None, "币种仅支持 BTC / ETH" s = _f(spot) capital = _f(capital_usdt) target = _f(target_profit_u) p_lev = _f(perp_leverage) o_lev = _f(option_leverage) ct = _f(ct_mult) if s is None or capital is None or target is None or p_lev is None or o_lev is None: return None, "参数格式错误" if ct is None or ct <= 0: ct = DEFAULT_CT_MULT if s <= 0 or capital <= 0 or p_lev <= 0 or o_lev <= 0: return None, "现价、资金、杠杆须大于 0" if target < 0: return None, "目标盈利不能为负" prem_per_coin = s / o_lev if prem_per_coin <= 0: return None, "单币权利金无效" margin = (s * PERP_COINS) / p_lev return { "base_ok": 1.0, "spot": s, "capital": capital, "target": target, "p_lev": p_lev, "o_lev": o_lev, "ct": ct, "prem_per_coin": prem_per_coin, "margin": margin, "fee_rate": taker_fee_rate(), }, None def _move_for_perp_correct( *, spot: float, target: float, premium: float, fee_rate: float, perp_coins: float = 1.0, ) -> float: """净利 = qty*move − premium − fee(move,qty) = target → 解 move. fee = (2*spot + move) * qty * fee_rate qty*move*(1-fee_rate) = target + premium + 2*spot*qty*fee_rate """ qty = float(perp_coins) if qty <= 0: return 0.0 denom = qty * (1.0 - float(fee_rate)) if denom <= 0: return 0.0 return (float(target) + float(premium) + 2.0 * float(spot) * qty * float(fee_rate)) / denom def _case_sideways( *, spot: float, premium_total: float, perp_coins: float = 1.0, ) -> dict[str, Any]: """横盘/到期无方向:永续≈0,期权权利金全亏,另计永续开平同价手续费. 最大亏损(正数) = 权利金总额 + 开平手续费(exit=entry) 组合净利 = −最大亏损 """ qty = float(perp_coins) if float(perp_coins) > 0 else PERP_COINS fee_flat = estimate_roundtrip_fee_usdt(spot, spot, qty=qty, contract_size=1.0) prem = float(premium_total) max_loss = prem + float(fee_flat) return { "label": "横盘", "perp_pnl_u": 0.0, "premium_u": round(prem, 8), "fee_u": round(float(fee_flat), 8), "max_loss_u": round(max_loss, 8), "net_u": round(-max_loss, 8), } def calc_perp_options_hedge( *, base: str = "ETH", spot: float, capital_usdt: float, target_profit_u: float, move_mode: str = "points", move_value: float, perp_leverage: float, option_leverage: float, ct_mult: float = DEFAULT_CT_MULT, ) -> Tuple[Optional[dict[str, Any]], Optional[str]]: """由波动反推期权开仓币数/张数(calc_mode=size).""" common, err = _parse_base_common( base=base, spot=spot, capital_usdt=capital_usdt, target_profit_u=target_profit_u, perp_leverage=perp_leverage, option_leverage=option_leverage, ct_mult=ct_mult, ) if err or not common: return None, err s = common["spot"] capital = common["capital"] target = common["target"] p_lev = common["p_lev"] o_lev = common["o_lev"] ct = common["ct"] prem_per_coin = common["prem_per_coin"] margin = common["margin"] fee_rate = common["fee_rate"] b = (base or "ETH").strip().upper() move = _f(move_value) mode = (move_mode or "points").strip().lower() if mode not in ("points", "pct", "percent", "rate"): return None, "波动模式须为 points 或 pct" if mode in ("percent", "rate"): mode = "pct" if move is None: return None, "参数格式错误" if move <= 0: return None, "现价、资金、波动、杠杆须大于 0" if mode == "pct": move_points = s * (move / 100.0) else: move_points = move if move_points <= 0: return None, "波动对应价格变动须大于 0" exit_px = s + move_points perp_gross = move_points * PERP_COINS fee = estimate_roundtrip_fee_usdt(s, exit_px, qty=PERP_COINS, contract_size=1.0) premium_budget = perp_gross - target - fee if premium_budget <= 0: return None, "波动收益不足以覆盖目标盈利+手续费,无法开期权" opt_coins = premium_budget / prem_per_coin opt_sheets = opt_coins / ct premium_total = opt_coins * prem_per_coin case_a_net = perp_gross - premium_total - fee opt_intrinsic = opt_coins * move_points opt_net = opt_intrinsic - premium_total perp_loss = -perp_gross portfolio_net = opt_net + perp_loss return { "calc_mode": "size", "base": b, "spot": round(s, 8), "capital_usdt": round(capital, 8), "target_profit_u": round(target, 8), "move_mode": mode, "move_value": round(move, 8), "move_points": round(move_points, 8), "exit_price": round(exit_px, 8), "perp_coins": PERP_COINS, "perp_leverage": round(p_lev, 8), "option_leverage": round(o_lev, 8), "ct_mult": ct, "prem_per_coin": round(prem_per_coin, 8), "perp_gross_u": round(perp_gross, 8), "perp_fee_u": round(fee, 8), "fee_rate": fee_rate, "premium_budget_u": round(premium_budget, 8), "opt_coins": round(opt_coins, 8), "opt_sheets": round(opt_sheets, 8), "premium_total_u": round(premium_total, 8), "perp_margin_u": round(margin, 8), "capital_ok": bool(capital >= margin), "case_a": { "label": "永续方向对", "perp_pnl_u": round(perp_gross, 8), "premium_u": round(premium_total, 8), "fee_u": round(fee, 8), "net_u": round(case_a_net, 8), }, "case_b": { "label": "期权方向对", "opt_intrinsic_u": round(opt_intrinsic, 8), "premium_u": round(premium_total, 8), "opt_net_u": round(opt_net, 8), "perp_pnl_u": round(perp_loss, 8), "portfolio_net_u": round(portfolio_net, 8), }, "case_sideways": _case_sideways(spot=s, premium_total=premium_total), }, None def calc_perp_options_points( *, base: str = "ETH", spot: float, capital_usdt: float, target_profit_u: float, perp_leverage: float, option_leverage: float, ratio_perp: float = 1.0, ratio_opt: float = 2.0, ct_mult: float = DEFAULT_CT_MULT, ) -> Tuple[Optional[dict[str, Any]], Optional[str]]: """按永续/期权币数 + 目标盈利,反推两套情景所需波动点数. 永续币数 = ratio_perp, 期权币数 = ratio_opt(按绝对币数,不再归一到 1 币). 例 2:4 → 永续 2 币 + 期权 4 币;1:2 → 永续 1 币 + 期权 2 币. A 永续方向对: qty*move − premium − fee(move,qty) = 目标盈利 B 期权方向对: - 期权净利达目标: opt_coins*move − premium = 目标 - 组合净利达目标: move*(opt_coins − perp_coins) − premium = 目标 """ common, err = _parse_base_common( base=base, spot=spot, capital_usdt=capital_usdt, target_profit_u=target_profit_u, perp_leverage=perp_leverage, option_leverage=option_leverage, ct_mult=ct_mult, ) if err or not common: return None, err rp = _f(ratio_perp) ro = _f(ratio_opt) if rp is None or ro is None or rp <= 0 or ro <= 0: return None, "永续/期权币数须大于 0" s = common["spot"] capital = common["capital"] target = common["target"] p_lev = common["p_lev"] o_lev = common["o_lev"] ct = common["ct"] prem_per_coin = common["prem_per_coin"] fee_rate = common["fee_rate"] b = (base or "ETH").strip().upper() perp_coins = rp opt_coins = ro premium_total = opt_coins * prem_per_coin opt_sheets = opt_coins / ct margin = (s * perp_coins) / p_lev move_a = _move_for_perp_correct( spot=s, target=target, premium=premium_total, fee_rate=fee_rate, perp_coins=perp_coins, ) if move_a <= 0: return None, "无法解出永续方向对所需点数" fee_a = estimate_roundtrip_fee_usdt(s, s + move_a, qty=perp_coins, contract_size=1.0) net_a = move_a * perp_coins - premium_total - fee_a # 期权净利 = 目标 move_b_opt = (target + premium_total) / opt_coins opt_net_at_b_opt = opt_coins * move_b_opt - premium_total portfolio_at_b_opt = opt_net_at_b_opt - move_b_opt * perp_coins # 组合净利 = 目标 edge = opt_coins - perp_coins if edge <= 0: move_b_port = None port_err = "期权币数须大于永续币数,组合才能在方向对时赚到目标盈利" else: move_b_port = (target + premium_total) / edge port_err = None if move_b_port is not None: opt_net_at_b_port = opt_coins * move_b_port - premium_total portfolio_at_b_port = opt_net_at_b_port - move_b_port * perp_coins else: opt_net_at_b_port = None portfolio_at_b_port = None return { "calc_mode": "points", "base": b, "spot": round(s, 8), "capital_usdt": round(capital, 8), "target_profit_u": round(target, 8), "ratio_perp": round(rp, 8), "ratio_opt": round(ro, 8), "ratio_label": f"{_fmt_ratio(rp)}:{_fmt_ratio(ro)}", "perp_coins": round(perp_coins, 8), "opt_coins": round(opt_coins, 8), "opt_sheets": round(opt_sheets, 8), "perp_leverage": round(p_lev, 8), "option_leverage": round(o_lev, 8), "ct_mult": ct, "prem_per_coin": round(prem_per_coin, 8), "premium_total_u": round(premium_total, 8), "fee_rate": fee_rate, "perp_margin_u": round(margin, 8), "capital_ok": bool(capital >= margin), "case_a": { "label": "永续方向对", "move_points": round(move_a, 8), "move_pct": round(move_a / s * 100.0, 8), "perp_pnl_u": round(move_a * perp_coins, 8), "premium_u": round(premium_total, 8), "fee_u": round(fee_a, 8), "net_u": round(net_a, 8), }, "case_b": { "label": "期权方向对", "move_points_opt_net": round(move_b_opt, 8), "move_pct_opt_net": round(move_b_opt / s * 100.0, 8), "opt_net_u": round(opt_net_at_b_opt, 8), "portfolio_net_at_opt_target_u": round(portfolio_at_b_opt, 8), "move_points_portfolio": None if move_b_port is None else round(move_b_port, 8), "move_pct_portfolio": None if move_b_port is None else round(move_b_port / s * 100.0, 8), "opt_net_at_portfolio_target_u": None if opt_net_at_b_port is None else round(opt_net_at_b_port, 8), "portfolio_net_u": None if portfolio_at_b_port is None else round(portfolio_at_b_port, 8), "portfolio_error": port_err, "premium_u": round(premium_total, 8), }, "case_sideways": _case_sideways( spot=s, premium_total=premium_total, perp_coins=perp_coins ), }, None def _fmt_ratio(v: float) -> str: if abs(v - round(v)) < 1e-9: return str(int(round(v))) s = f"{v:.4f}".rstrip("0").rstrip(".") return s def calc_perp_options( *, calc_mode: str = "size", **kwargs: Any, ) -> Tuple[Optional[dict[str, Any]], Optional[str]]: """统一入口:size=由波动推仓位;points=由比例推点数.""" mode = (calc_mode or "size").strip().lower() if mode in ("points", "ratio", "move"): return calc_perp_options_points(**kwargs) # size mode: ignore ratio kwargs if present kwargs.pop("ratio_perp", None) kwargs.pop("ratio_opt", None) return calc_perp_options_hedge(**kwargs)