划转默认折叠,修复币本位到期平衡计算
币本位权利金为币报价,到期平衡按 OKX 结算公式 K/(1±p) 计算;链/持仓/跨式平衡带同步修正。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -878,12 +878,21 @@
|
|||||||
return Math.round((c + p) * 10000) / 10000;
|
return Math.round((c + p) * 10000) / 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatStraddleBand(strike, combinedAsk) {
|
function formatStraddleBand(strike, combinedAsk, callAsk, putAsk) {
|
||||||
const per = combinedAsk;
|
|
||||||
if (strike == null || per == null) return "—";
|
|
||||||
const k = Number(strike);
|
const k = Number(strike);
|
||||||
|
if (!Number.isFinite(k)) return "—";
|
||||||
|
if (isCoinMarginMode()) {
|
||||||
|
const pc = Number(callAsk);
|
||||||
|
const pp = Number(putAsk);
|
||||||
|
if (!Number.isFinite(pc) || !Number.isFinite(pp) || pc <= 0 || pp <= 0 || pc >= 1) return "—";
|
||||||
|
const lo = Math.round((k / (1 + pp)) * 10) / 10;
|
||||||
|
const hi = Math.round((k / (1 - pc)) * 10) / 10;
|
||||||
|
return lo.toFixed(0) + " ~ " + hi.toFixed(0);
|
||||||
|
}
|
||||||
|
const per = combinedAsk;
|
||||||
|
if (per == null) return "—";
|
||||||
const d = Number(per);
|
const d = Number(per);
|
||||||
if (!Number.isFinite(k) || !Number.isFinite(d)) return "—";
|
if (!Number.isFinite(d)) return "—";
|
||||||
const lo = Math.round((k - d) * 10) / 10;
|
const lo = Math.round((k - d) * 10) / 10;
|
||||||
const hi = Math.round((k + d) * 10) / 10;
|
const hi = Math.round((k + d) * 10) / 10;
|
||||||
return lo.toFixed(0) + " ~ " + hi.toFixed(0);
|
return lo.toFixed(0) + " ~ " + hi.toFixed(0);
|
||||||
@@ -1577,7 +1586,7 @@
|
|||||||
'<td class="opt-t-call opt-row-actions">' + pickBtnHtml(call && call.inst_id) + "</td>" +
|
'<td class="opt-t-call opt-row-actions">' + pickBtnHtml(call && call.inst_id) + "</td>" +
|
||||||
'<td class="opt-t-mid opt-t-strike"><strong>' + row.strike + "</strong></td>" +
|
'<td class="opt-t-mid opt-t-strike"><strong>' + row.strike + "</strong></td>" +
|
||||||
'<td class="opt-t-mid opt-t-straddle-prem">' + formatStraddlePremiumCell(call && call.ask, put && put.ask) + "</td>" +
|
'<td class="opt-t-mid opt-t-straddle-prem">' + formatStraddlePremiumCell(call && call.ask, put && put.ask) + "</td>" +
|
||||||
'<td class="opt-t-mid opt-t-straddle-band">' + formatStraddleBand(row.strike, combined) + "</td>" +
|
'<td class="opt-t-mid opt-t-straddle-band">' + formatStraddleBand(row.strike, combined, call && call.ask, put && put.ask) + "</td>" +
|
||||||
'<td class="opt-t-put">' + (put ? moneynessBadge(put) : "—") + "</td>" +
|
'<td class="opt-t-put">' + (put ? moneynessBadge(put) : "—") + "</td>" +
|
||||||
'<td class="opt-t-put opt-px-sz">' + (put ? fmtPxSz(put.ask, put.ask_sz, put.ask_estimated) : "—") + "</td>" +
|
'<td class="opt-t-put opt-px-sz">' + (put ? fmtPxSz(put.ask, put.ask_sz, put.ask_estimated) : "—") + "</td>" +
|
||||||
'<td class="opt-t-put opt-row-actions">' + pickBtnHtml(put && put.inst_id) + "</td>";
|
'<td class="opt-t-put opt-row-actions">' + pickBtnHtml(put && put.inst_id) + "</td>";
|
||||||
|
|||||||
@@ -816,6 +816,7 @@ def build_option_chain(
|
|||||||
family = f"{u}-USD_UM"
|
family = f"{u}-USD_UM"
|
||||||
uly = f"{u}-USD"
|
uly = f"{u}-USD"
|
||||||
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
||||||
|
chain_margin = "usdc" if "_UM" in family.upper() else "coin"
|
||||||
now_ms = time.time() * 1000
|
now_ms = time.time() * 1000
|
||||||
max_ms = now_ms + max_dte_days * 86400 * 1000
|
max_ms = now_ms + max_dte_days * 86400 * 1000
|
||||||
instruments_err = ""
|
instruments_err = ""
|
||||||
@@ -884,6 +885,8 @@ def build_option_chain(
|
|||||||
strike=strike,
|
strike=strike,
|
||||||
ask_px=ask,
|
ask_px=ask,
|
||||||
mark_px=mark,
|
mark_px=mark,
|
||||||
|
inst_id=inst_id,
|
||||||
|
margin_mode=chain_margin,
|
||||||
)
|
)
|
||||||
mny = option_moneyness(opt_type=opt_type, strike=strike, index_px=idx)
|
mny = option_moneyness(opt_type=opt_type, strike=strike, index_px=idx)
|
||||||
exp_key = str(exp_ms)
|
exp_key = str(exp_ms)
|
||||||
@@ -1028,6 +1031,7 @@ def quote_option_contract(ex: ccxt.okx, inst_id: str) -> dict[str, Any]:
|
|||||||
strike=strike,
|
strike=strike,
|
||||||
ask_px=book_ask if can_open else None,
|
ask_px=book_ask if can_open else None,
|
||||||
mark_px=mark,
|
mark_px=mark,
|
||||||
|
inst_id=inst_id,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
@@ -1815,6 +1819,8 @@ def format_position_row(
|
|||||||
strike=strike,
|
strike=strike,
|
||||||
avg_px=avg,
|
avg_px=avg,
|
||||||
be_px_api=_safe_float(pos.get("bePx")),
|
be_px_api=_safe_float(pos.get("bePx")),
|
||||||
|
inst_id=inst_id,
|
||||||
|
margin_mode=row_mode,
|
||||||
)
|
)
|
||||||
close_be = close_breakeven_idx(
|
close_be = close_breakeven_idx(
|
||||||
opt_type=str(opt_type or ""),
|
opt_type=str(opt_type or ""),
|
||||||
|
|||||||
@@ -438,10 +438,20 @@ def expiry_breakeven_from_ask(
|
|||||||
strike: float | None,
|
strike: float | None,
|
||||||
ask_px: float | None,
|
ask_px: float | None,
|
||||||
mark_px: float | None = None,
|
mark_px: float | None = None,
|
||||||
|
quote_in_coin: bool | None = None,
|
||||||
|
inst_id: str | None = None,
|
||||||
|
margin_mode: str | None = None,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
"""买入前预估到期平衡:权利金按卖一;无卖一时回退标记价."""
|
"""买入前预估到期平衡:权利金按卖一;无卖一时回退标记价."""
|
||||||
prem = ask_px if ask_px is not None and ask_px > 0 else mark_px
|
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)
|
return expiry_breakeven_px(
|
||||||
|
opt_type=opt_type,
|
||||||
|
strike=strike,
|
||||||
|
avg_px=prem,
|
||||||
|
quote_in_coin=quote_in_coin,
|
||||||
|
inst_id=inst_id,
|
||||||
|
margin_mode=margin_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def expiry_breakeven_px(
|
def expiry_breakeven_px(
|
||||||
@@ -450,17 +460,39 @@ def expiry_breakeven_px(
|
|||||||
strike: float | None,
|
strike: float | None,
|
||||||
avg_px: float | None,
|
avg_px: float | None,
|
||||||
be_px_api: float | None = None,
|
be_px_api: float | None = None,
|
||||||
|
quote_in_coin: bool | None = None,
|
||||||
|
inst_id: str | None = None,
|
||||||
|
margin_mode: str | None = None,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
"""到期平衡点:持有至到期时标的指数盈亏为 0 的价格.优先 OKX bePx."""
|
"""到期平衡点:持有至到期时标的指数盈亏为 0 的价格.优先 OKX bePx."""
|
||||||
if be_px_api is not None and be_px_api > 0:
|
if be_px_api is not None and be_px_api > 0:
|
||||||
return round(float(be_px_api), 2)
|
return round(float(be_px_api), 2)
|
||||||
if strike is None or avg_px is None:
|
if strike is None or avg_px is None:
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
|
k = float(strike)
|
||||||
|
p = float(avg_px)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if p <= 0:
|
||||||
|
return None
|
||||||
o = (opt_type or "").upper()
|
o = (opt_type or "").upper()
|
||||||
|
coin = _quote_in_coin_from_context(
|
||||||
|
quote_in_coin=quote_in_coin, inst_id=inst_id, margin_mode=margin_mode
|
||||||
|
)
|
||||||
|
if coin:
|
||||||
|
# 币本位:权利金为币报价;到期结算 payoff 亦为币 → K/(1±p)
|
||||||
|
if o == "C":
|
||||||
|
if p >= 1:
|
||||||
|
return None
|
||||||
|
return round(k / (1 - p), 2)
|
||||||
|
if o == "P":
|
||||||
|
return round(k / (1 + p), 2)
|
||||||
|
return None
|
||||||
if o == "C":
|
if o == "C":
|
||||||
return round(strike + avg_px, 2)
|
return round(k + p, 2)
|
||||||
if o == "P":
|
if o == "P":
|
||||||
return round(strike - avg_px, 2)
|
return round(k - p, 2)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -617,12 +649,24 @@ def straddle_premium_total(
|
|||||||
|
|
||||||
def straddle_breakeven_band(
|
def straddle_breakeven_band(
|
||||||
strike: float | None,
|
strike: float | None,
|
||||||
combined_ask_per_unit: float | None,
|
combined_ask_per_unit: float | None = None,
|
||||||
|
*,
|
||||||
|
call_ask: float | None = None,
|
||||||
|
put_ask: float | None = None,
|
||||||
|
quote_in_coin: bool = False,
|
||||||
) -> tuple[float | None, float | None]:
|
) -> tuple[float | None, float | None]:
|
||||||
"""跨式到期平衡带:下平衡 ~ 上平衡(按双卖一报价和)."""
|
"""跨式到期平衡带:下平衡 ~ 上平衡."""
|
||||||
if strike is None or combined_ask_per_unit is None:
|
if strike is None:
|
||||||
return None, None
|
return None, None
|
||||||
k = float(strike)
|
k = float(strike)
|
||||||
|
if quote_in_coin:
|
||||||
|
pc = _safe_px(call_ask)
|
||||||
|
pp = _safe_px(put_ask)
|
||||||
|
if pc is None or pp is None or pc <= 0 or pp <= 0 or pc >= 1:
|
||||||
|
return None, None
|
||||||
|
return round(k / (1 + pp), 2), round(k / (1 - pc), 2)
|
||||||
|
if combined_ask_per_unit is None:
|
||||||
|
return None, None
|
||||||
d = float(combined_ask_per_unit)
|
d = float(combined_ask_per_unit)
|
||||||
return round(k - d, 2), round(k + d, 2)
|
return round(k - d, 2), round(k + d, 2)
|
||||||
|
|
||||||
|
|||||||
@@ -345,7 +345,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<details class="opt-pos-transfer" id="opt-pos-transfer" open>
|
<details class="opt-pos-transfer" id="opt-pos-transfer">
|
||||||
<summary class="opt-pos-transfer-head">
|
<summary class="opt-pos-transfer-head">
|
||||||
<span class="opt-pos-transfer-title">划转</span>
|
<span class="opt-pos-transfer-title">划转</span>
|
||||||
<span class="opt-pos-transfer-open-hint muted">收起</span>
|
<span class="opt-pos-transfer-open-hint muted">收起</span>
|
||||||
@@ -380,4 +380,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/static/options_panel.js?v=68"></script>
|
<script src="/static/options_panel.js?v=69"></script>
|
||||||
|
|||||||
@@ -331,6 +331,37 @@ def test_expiry_breakeven_call_put():
|
|||||||
assert expiry_breakeven_px(opt_type="P", strike=3500, avg_px=15.6) == 3484.4
|
assert expiry_breakeven_px(opt_type="P", strike=3500, avg_px=15.6) == 3484.4
|
||||||
|
|
||||||
|
|
||||||
|
def test_expiry_breakeven_coin_margin():
|
||||||
|
from lib.options.options_pricing_lib import expiry_breakeven_from_ask, expiry_breakeven_px
|
||||||
|
|
||||||
|
# ETH-USD 币本位:卖一 0.0165 → 到期平衡 K/(1-p),非 K+p
|
||||||
|
assert expiry_breakeven_px(
|
||||||
|
opt_type="C", strike=2390, avg_px=0.0165, margin_mode="coin"
|
||||||
|
) == round(2390 / (1 - 0.0165), 2)
|
||||||
|
assert expiry_breakeven_px(
|
||||||
|
opt_type="P", strike=2450, avg_px=0.0161, margin_mode="coin"
|
||||||
|
) == round(2450 / (1 + 0.0161), 2)
|
||||||
|
assert expiry_breakeven_from_ask(
|
||||||
|
opt_type="C",
|
||||||
|
strike=2425,
|
||||||
|
ask_px=0.0187,
|
||||||
|
inst_id="ETH-USD-260823-2425-C",
|
||||||
|
) == round(2425 / (1 - 0.0187), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_straddle_breakeven_band_coin():
|
||||||
|
from lib.options.options_pricing_lib import straddle_breakeven_band
|
||||||
|
|
||||||
|
lo, hi = straddle_breakeven_band(
|
||||||
|
2425,
|
||||||
|
quote_in_coin=True,
|
||||||
|
call_ask=0.0187,
|
||||||
|
put_ask=0.0253,
|
||||||
|
)
|
||||||
|
assert lo == round(2425 / (1 + 0.0253), 2)
|
||||||
|
assert hi == round(2425 / (1 - 0.0187), 2)
|
||||||
|
|
||||||
|
|
||||||
def test_close_breakeven_at_mark_equals_avg():
|
def test_close_breakeven_at_mark_equals_avg():
|
||||||
from lib.options.options_pricing_lib import close_breakeven_idx
|
from lib.options.options_pricing_lib import close_breakeven_idx
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user