b733e551a0
Add scripts/normalize_ambiguous_unicode.py; fix corrupted patch_instance_theme_templates.py. Preserves curly quotes in string literals; removes Git homoglyph warnings on .env.example. Co-authored-by: Cursor <cursoragent@cursor.com>
274 lines
8.8 KiB
Python
274 lines
8.8 KiB
Python
"""OKX USDⓈ 期权:张数与权利金计算."""
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import Any
|
||
|
||
|
||
def ct_mult_from_meta(meta: dict[str, Any] | None) -> float:
|
||
if not meta:
|
||
return 0.01
|
||
try:
|
||
return float(meta.get("ctMult") or 0.01)
|
||
except (TypeError, ValueError):
|
||
return 0.01
|
||
|
||
|
||
def min_sz_from_meta(meta: dict[str, Any] | None) -> int:
|
||
if not meta:
|
||
return 1
|
||
try:
|
||
return max(1, int(float(meta.get("minSz") or 1)))
|
||
except (TypeError, ValueError):
|
||
return 1
|
||
|
||
|
||
def premium_per_sheet(quote_per_unit: float, ct_mult: float = 0.01) -> float:
|
||
"""报价为每 1 ETH/BTC;每张权利金 = 报价 × ctMult."""
|
||
return float(quote_per_unit) * float(ct_mult)
|
||
|
||
|
||
def format_quote_liquidity(px: float | None, sz: float | None, *, px_decimals: int = 4) -> str | None:
|
||
"""盘口展示:价格/张数,如 17.2/150."""
|
||
if px is None:
|
||
return None
|
||
try:
|
||
price = f"{float(px):.{px_decimals}f}".rstrip("0").rstrip(".")
|
||
except (TypeError, ValueError):
|
||
return None
|
||
if sz is None:
|
||
return price
|
||
try:
|
||
s = float(sz)
|
||
size = str(int(s)) if abs(s - int(s)) < 1e-9 else str(s).rstrip("0").rstrip(".")
|
||
except (TypeError, ValueError):
|
||
return price
|
||
return f"{price}/{size}"
|
||
|
||
|
||
def total_premium(quote_per_unit: float, eth_amount: float, ct_mult: float = 0.01) -> float:
|
||
return float(quote_per_unit) * float(eth_amount)
|
||
|
||
|
||
def sheets_from_eth_amount(eth_amount: float, ct_mult: float = 0.01) -> int:
|
||
if eth_amount <= 0 or ct_mult <= 0:
|
||
return 0
|
||
return int(math.floor(eth_amount / ct_mult + 1e-12))
|
||
|
||
|
||
def eth_amount_from_sheets(sheets: int, ct_mult: float = 0.01) -> float:
|
||
return round(int(sheets) * float(ct_mult), 8)
|
||
|
||
|
||
def calc_order_size(
|
||
*,
|
||
quote_per_unit: float,
|
||
ct_mult: float,
|
||
min_sz: int,
|
||
budget_usdc: float | None = None,
|
||
budget_buffer: float = 0.95,
|
||
eth_amount: float | None = None,
|
||
sheets: int | None = None,
|
||
budget_cap: float | None = None,
|
||
) -> dict[str, Any]:
|
||
"""
|
||
返回 sheets, eth_amount, total_premium.
|
||
mode: budget_full / eth_amount / sheets.
|
||
"""
|
||
if quote_per_unit <= 0:
|
||
return {"ok": False, "msg": "卖一价无效", "sheets": 0, "eth_amount": 0.0, "total_premium": 0.0}
|
||
|
||
if sheets is not None and int(sheets) > 0:
|
||
sheets = int(sheets)
|
||
elif eth_amount is not None and eth_amount > 0:
|
||
sheets = sheets_from_eth_amount(eth_amount, ct_mult)
|
||
elif budget_usdc is not None and budget_usdc > 0:
|
||
eff = float(budget_usdc) * float(budget_buffer)
|
||
per_sheet = premium_per_sheet(quote_per_unit, ct_mult)
|
||
if per_sheet <= 0:
|
||
return {"ok": False, "msg": "无法计算单张权利金", "sheets": 0, "eth_amount": 0.0, "total_premium": 0.0}
|
||
sheets = int(math.floor(eff / per_sheet))
|
||
else:
|
||
return {"ok": False, "msg": "请指定预算,币数量或张数", "sheets": 0, "eth_amount": 0.0, "total_premium": 0.0}
|
||
|
||
if sheets < min_sz:
|
||
per = premium_per_sheet(quote_per_unit, ct_mult)
|
||
return {
|
||
"ok": False,
|
||
"msg": f"预算不足,无法买入 {min_sz} 张(单张约 {per:.4f} USDC)",
|
||
"sheets": sheets,
|
||
"eth_amount": eth_amount_from_sheets(sheets, ct_mult),
|
||
"total_premium": total_premium(quote_per_unit, eth_amount_from_sheets(sheets, ct_mult)),
|
||
}
|
||
|
||
eth = eth_amount_from_sheets(sheets, ct_mult)
|
||
prem = total_premium(quote_per_unit, eth)
|
||
if budget_cap is not None and prem > float(budget_cap) + 1e-9:
|
||
return {
|
||
"ok": False,
|
||
"msg": f"权利金 {prem:.4f} 超过单笔上限 {budget_cap} USDC",
|
||
"sheets": sheets,
|
||
"eth_amount": eth,
|
||
"total_premium": prem,
|
||
}
|
||
return {"ok": True, "msg": "", "sheets": sheets, "eth_amount": eth, "total_premium": prem}
|
||
|
||
|
||
def is_shallow_itm(
|
||
*,
|
||
opt_type: str,
|
||
strike: float,
|
||
index_px: float,
|
||
max_dist_usd: float,
|
||
) -> bool:
|
||
o = (opt_type or "").upper()
|
||
if o == "C":
|
||
if strike >= index_px:
|
||
return False
|
||
return (index_px - strike) <= max_dist_usd
|
||
if o == "P":
|
||
if strike <= index_px:
|
||
return False
|
||
return (strike - index_px) <= max_dist_usd
|
||
return False
|
||
|
||
|
||
def option_moneyness(*, opt_type: str, strike: float, index_px: float) -> str:
|
||
"""返回 itm / otm / atm."""
|
||
o = (opt_type or "").upper()
|
||
if strike is None or index_px is None or index_px <= 0:
|
||
return "unknown"
|
||
atm_band = max(index_px * 0.002, 2.0)
|
||
if abs(strike - index_px) <= atm_band:
|
||
return "atm"
|
||
if o == "C":
|
||
return "itm" if strike < index_px else "otm"
|
||
if o == "P":
|
||
return "itm" if strike > index_px else "otm"
|
||
return "unknown"
|
||
|
||
|
||
def option_moneyness_label(moneyness: str) -> str:
|
||
return {"itm": "实值", "otm": "虚值", "atm": "平值"}.get((moneyness or "").lower(), "")
|
||
|
||
|
||
def expiry_breakeven_from_ask(
|
||
*,
|
||
opt_type: str,
|
||
strike: float | None,
|
||
ask_px: float | None,
|
||
mark_px: float | None = None,
|
||
) -> float | None:
|
||
"""买入前预估到期平衡:权利金按卖一;无卖一时回退标记价."""
|
||
prem = ask_px if ask_px is not None and ask_px > 0 else mark_px
|
||
return expiry_breakeven_px(opt_type=opt_type, strike=strike, avg_px=prem)
|
||
|
||
|
||
def expiry_breakeven_px(
|
||
*,
|
||
opt_type: str,
|
||
strike: float | None,
|
||
avg_px: float | None,
|
||
be_px_api: float | None = None,
|
||
) -> float | None:
|
||
"""到期平衡点:持有至到期时标的指数盈亏为 0 的价格.优先 OKX bePx."""
|
||
if be_px_api is not None and be_px_api > 0:
|
||
return round(float(be_px_api), 2)
|
||
if strike is None or avg_px is None:
|
||
return None
|
||
o = (opt_type or "").upper()
|
||
if o == "C":
|
||
return round(strike + avg_px, 2)
|
||
if o == "P":
|
||
return round(strike - avg_px, 2)
|
||
return None
|
||
|
||
|
||
def close_breakeven_idx(
|
||
*,
|
||
opt_type: str,
|
||
idx_px: float | None,
|
||
mark_px: float | None,
|
||
avg_px: float | None,
|
||
delta_pa: float | None = None,
|
||
pos: float = 0,
|
||
ct_mult: float = 0.01,
|
||
) -> float | None:
|
||
"""
|
||
平掉回本:标的指数达到该价位时,按标记价平仓近似盈亏为 0.
|
||
优先用 deltaPA 线性外推,否则用时间价值近似(适合短期轻度实值).
|
||
"""
|
||
if idx_px is None or mark_px is None or avg_px is None:
|
||
return None
|
||
eth_amt = abs(float(pos)) * float(ct_mult)
|
||
if eth_amt > 1e-12 and delta_pa is not None and abs(float(delta_pa)) > 1e-12:
|
||
slope = float(delta_pa) / eth_amt
|
||
return round(float(idx_px) + (float(avg_px) - float(mark_px)) / slope, 2)
|
||
o = (opt_type or "").upper()
|
||
if o == "C":
|
||
return round(float(idx_px) + float(avg_px) - float(mark_px), 2)
|
||
if o == "P":
|
||
return round(float(idx_px) + float(mark_px) - float(avg_px), 2)
|
||
return None
|
||
|
||
|
||
def idx_distance_to_be(idx_px: float | None, be_px: float | None) -> float | None:
|
||
"""指数距平衡点(正=指数需上涨才到平衡点)."""
|
||
if idx_px is None or be_px is None:
|
||
return None
|
||
return round(float(be_px) - float(idx_px), 2)
|
||
|
||
|
||
def format_options_breakeven_line(
|
||
*,
|
||
expiry_be_px: float | None,
|
||
close_be_px: float | None,
|
||
idx_px: float | None = None,
|
||
) -> str:
|
||
"""持仓摘要行:到期平衡 / 平掉回本."""
|
||
parts: list[str] = []
|
||
if expiry_be_px is not None:
|
||
parts.append(f"到期平衡{expiry_be_px:.0f}")
|
||
if close_be_px is not None:
|
||
parts.append(f"平掉回本{close_be_px:.0f}")
|
||
if idx_px is not None and parts:
|
||
return " ".join(parts) + f"(指数{idx_px:.0f})"
|
||
return " ".join(parts)
|
||
|
||
|
||
def estimate_expiry_profit_at_index(
|
||
*,
|
||
opt_type: str,
|
||
strike: float | None,
|
||
target_idx: float | None,
|
||
entry_px: float | None,
|
||
eth_amount: float | None,
|
||
) -> float | None:
|
||
"""到期测算:目标指数价下按内在价值减权利金(每 1 币报价 × 币量)."""
|
||
if strike is None or target_idx is None or entry_px is None or eth_amount is None:
|
||
return None
|
||
if eth_amount <= 0:
|
||
return None
|
||
o = (opt_type or "").upper()
|
||
if o == "C":
|
||
intrinsic = max(0.0, float(target_idx) - float(strike))
|
||
elif o == "P":
|
||
intrinsic = max(0.0, float(strike) - float(target_idx))
|
||
else:
|
||
return None
|
||
return round((intrinsic - float(entry_px)) * float(eth_amount), 4)
|
||
|
||
|
||
def equivalent_contract_leverage(
|
||
*,
|
||
index_px: float | None,
|
||
eth_amount: float | None,
|
||
total_premium: float | None,
|
||
) -> float | None:
|
||
"""名义价值 / 权利金,近似相当于永续合约杠杆倍数(测算用)."""
|
||
if index_px is None or eth_amount is None or total_premium is None:
|
||
return None
|
||
if eth_amount <= 0 or total_premium <= 0:
|
||
return None
|
||
return round(float(index_px) * float(eth_amount) / float(total_premium), 1)
|