Add ratio-to-move-points mode for hub perpetual-options calculator.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""中控永期对冲计算器:永续 1 币 + 按目标盈利反推期权仓位(纯函数)."""
|
||||
"""中控永期对冲计算器:永续 1 币 + 按目标盈利反推期权仓位/波动点数(纯函数)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional, Tuple
|
||||
@@ -18,6 +18,63 @@ def _f(v: Any) -> Optional[float]:
|
||||
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) -> float:
|
||||
"""净利 = move − premium − fee(move) = target → 解 move.
|
||||
|
||||
fee = (2*spot + move) * fee_rate
|
||||
move*(1-fee_rate) = target + premium + 2*spot*fee_rate
|
||||
"""
|
||||
denom = 1.0 - float(fee_rate)
|
||||
if denom <= 0:
|
||||
return 0.0
|
||||
return (float(target) + float(premium) + 2.0 * float(spot) * float(fee_rate)) / denom
|
||||
|
||||
|
||||
def calc_perp_options_hedge(
|
||||
*,
|
||||
base: str = "ETH",
|
||||
@@ -30,60 +87,51 @@ def calc_perp_options_hedge(
|
||||
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()
|
||||
if b not in ("ETH", "BTC"):
|
||||
return None, "币种仅支持 BTC / ETH"
|
||||
|
||||
s = _f(spot)
|
||||
capital = _f(capital_usdt)
|
||||
target = _f(target_profit_u)
|
||||
move = _f(move_value)
|
||||
p_lev = _f(perp_leverage)
|
||||
o_lev = _f(option_leverage)
|
||||
ct = _f(ct_mult)
|
||||
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 s is None or capital is None or target is None or move is None or p_lev is None or o_lev is None:
|
||||
if move is None:
|
||||
return None, "参数格式错误"
|
||||
if ct is None or ct <= 0:
|
||||
ct = DEFAULT_CT_MULT
|
||||
if s <= 0 or capital <= 0 or move <= 0 or p_lev <= 0 or o_lev <= 0:
|
||||
if move <= 0:
|
||||
return None, "现价、资金、波动、杠杆须大于 0"
|
||||
if target < 0:
|
||||
return None, "目标盈利不能为负"
|
||||
|
||||
# 波动对应的绝对点数(价格变动)
|
||||
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 # 永续方向对:按上涨测算 1 币多头
|
||||
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,
|
||||
)
|
||||
fee_rate = taker_fee_rate()
|
||||
|
||||
prem_per_coin = s / o_lev
|
||||
if prem_per_coin <= 0:
|
||||
return None, "单币权利金无效"
|
||||
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:
|
||||
@@ -91,21 +139,16 @@ def calc_perp_options_hedge(
|
||||
|
||||
opt_coins = premium_budget / prem_per_coin
|
||||
opt_sheets = opt_coins / ct
|
||||
premium_total = opt_coins * prem_per_coin # == premium_budget
|
||||
premium_total = opt_coins * prem_per_coin
|
||||
|
||||
margin = (s * PERP_COINS) / p_lev
|
||||
capital_ok = capital >= margin
|
||||
|
||||
# A: 永续方向对,期权权利金全亏
|
||||
case_a_net = perp_gross - premium_total - fee
|
||||
|
||||
# B: 期权方向对,永续 1 币反向亏掉同等波动
|
||||
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),
|
||||
@@ -127,7 +170,7 @@ def calc_perp_options_hedge(
|
||||
"opt_sheets": round(opt_sheets, 8),
|
||||
"premium_total_u": round(premium_total, 8),
|
||||
"perp_margin_u": round(margin, 8),
|
||||
"capital_ok": bool(capital_ok),
|
||||
"capital_ok": bool(capital >= margin),
|
||||
"case_a": {
|
||||
"label": "永续方向对",
|
||||
"perp_pnl_u": round(perp_gross, 8),
|
||||
@@ -144,3 +187,155 @@ def calc_perp_options_hedge(
|
||||
"portfolio_net_u": round(portfolio_net, 8),
|
||||
},
|
||||
}, 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 归一后的 1 币侧(perp_coins = PERP_COINS).
|
||||
期权币数 = PERP_COINS * (ratio_opt / ratio_perp),例 1:2 → 2 币.
|
||||
|
||||
A 永续方向对: move − premium − fee(move) = 目标盈利
|
||||
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"]
|
||||
margin = common["margin"]
|
||||
fee_rate = common["fee_rate"]
|
||||
b = (base or "ETH").strip().upper()
|
||||
|
||||
opt_coins = PERP_COINS * (ro / rp)
|
||||
premium_total = opt_coins * prem_per_coin
|
||||
opt_sheets = opt_coins / ct
|
||||
|
||||
move_a = _move_for_perp_correct(spot=s, target=target, premium=premium_total, fee_rate=fee_rate)
|
||||
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": PERP_COINS,
|
||||
"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),
|
||||
},
|
||||
}, 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)
|
||||
|
||||
Reference in New Issue
Block a user