Improve options order estimates and chain filter defaults.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
const state = {
|
const state = {
|
||||||
underlying: root.dataset.defaultUnderly || "ETH",
|
underlying: root.dataset.defaultUnderly || "ETH",
|
||||||
optType: "C",
|
optType: "C",
|
||||||
moneyFilter: "itm",
|
moneyFilter: "all",
|
||||||
chain: panelCache.chain || null,
|
chain: panelCache.chain || null,
|
||||||
selectedInst: null,
|
selectedInst: null,
|
||||||
orderQuote: null,
|
orderQuote: null,
|
||||||
@@ -137,12 +137,37 @@
|
|||||||
|
|
||||||
function matchesMoneyFilter(moneyness) {
|
function matchesMoneyFilter(moneyness) {
|
||||||
const m = (moneyness || "").toLowerCase();
|
const m = (moneyness || "").toLowerCase();
|
||||||
|
if (state.moneyFilter === "all") return true;
|
||||||
if (state.moneyFilter === "otm") return m === "otm";
|
if (state.moneyFilter === "otm") return m === "otm";
|
||||||
return m === "itm" || m === "atm";
|
return m === "itm" || m === "atm";
|
||||||
}
|
}
|
||||||
|
|
||||||
function moneyFilterLabel() {
|
function moneyFilterLabel() {
|
||||||
return state.moneyFilter === "otm" ? "虚值" : "实值";
|
if (state.moneyFilter === "otm") return "虚值";
|
||||||
|
if (state.moneyFilter === "itm") return "实值";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function countContractsForType(contracts) {
|
||||||
|
return (contracts || []).filter(function (c) {
|
||||||
|
return c.opt_type === state.optType;
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncMoneyFilterButtons() {
|
||||||
|
document.querySelectorAll(".opt-money-btn").forEach(function (b) {
|
||||||
|
b.classList.toggle("active", (b.getAttribute("data-money") || "") === state.moneyFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetMoneyFilterToAll() {
|
||||||
|
state.moneyFilter = "all";
|
||||||
|
syncMoneyFilterButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUnderlyingLabel() {
|
||||||
|
const el = document.getElementById("opt-order-eth-label");
|
||||||
|
if (el) el.textContent = state.underlying + " 数量";
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterChainContracts(contracts) {
|
function filterChainContracts(contracts) {
|
||||||
@@ -173,24 +198,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderExpiries() {
|
function renderIndexLine() {
|
||||||
const sel = document.getElementById("opt-exp-select");
|
|
||||||
sel.innerHTML = '<option value="">选择到期日</option>';
|
|
||||||
const exps = (state.chain && state.chain.expiries) || [];
|
|
||||||
exps.forEach(function (e) {
|
|
||||||
const o = document.createElement("option");
|
|
||||||
o.value = String(e.exp_time);
|
|
||||||
o.textContent = expLabel(e.exp_time) + " (" + filterChainContracts(e.contracts).length + ")";
|
|
||||||
sel.appendChild(o);
|
|
||||||
});
|
|
||||||
const idx = state.chain && state.chain.index_px;
|
const idx = state.chain && state.chain.index_px;
|
||||||
const dte = state.chain && state.chain.chain_max_dte_days;
|
const dte = state.chain && state.chain.chain_max_dte_days;
|
||||||
if (dte != null) {
|
if (dte != null) {
|
||||||
const el = document.getElementById("opt-chain-dte");
|
const el = document.getElementById("opt-chain-dte");
|
||||||
if (el) el.textContent = String(Math.round(dte));
|
if (el) el.textContent = String(Math.round(dte));
|
||||||
}
|
}
|
||||||
document.getElementById("opt-index-line").textContent =
|
const line = document.getElementById("opt-index-line");
|
||||||
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 实值含平值 · 虚值=价外";
|
if (line) {
|
||||||
|
line.textContent =
|
||||||
|
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 默认显示全部 · 实值含平值 · 虚值=价外";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderExpiryOptions(preserveSelection) {
|
||||||
|
const sel = document.getElementById("opt-exp-select");
|
||||||
|
if (!sel) return;
|
||||||
|
const prev = preserveSelection !== false ? sel.value : "";
|
||||||
|
sel.innerHTML = '<option value="">选择到期日</option>';
|
||||||
|
const exps = (state.chain && state.chain.expiries) || [];
|
||||||
|
exps.forEach(function (e) {
|
||||||
|
const o = document.createElement("option");
|
||||||
|
o.value = String(e.exp_time);
|
||||||
|
o.textContent = expLabel(e.exp_time) + " (" + countContractsForType(e.contracts) + ")";
|
||||||
|
sel.appendChild(o);
|
||||||
|
});
|
||||||
|
if (prev && exps.some(function (e) { return String(e.exp_time) === String(prev); })) {
|
||||||
|
sel.value = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderExpiries() {
|
||||||
|
renderExpiryOptions(true);
|
||||||
|
renderIndexLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtPxSz(px, sz, estimated) {
|
function fmtPxSz(px, sz, estimated) {
|
||||||
@@ -259,21 +300,28 @@
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function estimateExpiryProfit(optType, strike, targetIdx, entryPx, ethAmount) {
|
function expiryIntrinsicPerUnit(optType, strike, targetIdx) {
|
||||||
if (strike == null || targetIdx == null || entryPx == null || ethAmount == null) return null;
|
|
||||||
const amt = Number(ethAmount);
|
|
||||||
const entry = Number(entryPx);
|
|
||||||
const tgt = Number(targetIdx);
|
const tgt = Number(targetIdx);
|
||||||
const k = Number(strike);
|
const k = Number(strike);
|
||||||
if (!Number.isFinite(amt) || !Number.isFinite(entry) || !Number.isFinite(tgt) || !Number.isFinite(k) || amt <= 0) {
|
if (!Number.isFinite(tgt) || !Number.isFinite(k)) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const o = (optType || "").toUpperCase();
|
const o = (optType || "").toUpperCase();
|
||||||
let intrinsic = 0;
|
if (o === "C") return Math.max(0, tgt - k);
|
||||||
if (o === "C") intrinsic = Math.max(0, tgt - k);
|
if (o === "P") return Math.max(0, k - tgt);
|
||||||
else if (o === "P") intrinsic = Math.max(0, k - tgt);
|
return null;
|
||||||
else return null;
|
}
|
||||||
return Math.round((intrinsic - entry) * amt * 10000) / 10000;
|
|
||||||
|
function estimateExpiryValue(optType, strike, targetIdx, ethAmount) {
|
||||||
|
const amt = Number(ethAmount);
|
||||||
|
const intrinsic = expiryIntrinsicPerUnit(optType, strike, targetIdx);
|
||||||
|
if (intrinsic == null || !Number.isFinite(amt) || amt <= 0) return null;
|
||||||
|
return Math.round(intrinsic * amt * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function estimateExpiryProfit(optType, strike, targetIdx, ethAmount, totalPremium) {
|
||||||
|
const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount);
|
||||||
|
const prem = Number(totalPremium);
|
||||||
|
if (value == null || !Number.isFinite(prem)) return null;
|
||||||
|
return Math.round((value - prem) * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcContractLeverage(indexPx, ethAmount, totalPremium) {
|
function calcContractLeverage(indexPx, ethAmount, totalPremium) {
|
||||||
@@ -292,14 +340,23 @@
|
|||||||
return "约 " + Number(v).toFixed(1) + "×";
|
return "约 " + Number(v).toFixed(1) + "×";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtUsdcSigned(v) {
|
||||||
|
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||||
|
const n = Number(v);
|
||||||
|
const sign = n > 0 ? "+" : "";
|
||||||
|
return sign + fmtUsdc(n) + " USDC";
|
||||||
|
}
|
||||||
|
|
||||||
function updateOrderEstimates() {
|
function updateOrderEstimates() {
|
||||||
const levEl = document.getElementById("opt-order-leverage");
|
const levEl = document.getElementById("opt-order-leverage");
|
||||||
|
const valueEl = document.getElementById("opt-est-value");
|
||||||
const profitEl = document.getElementById("opt-est-profit");
|
const profitEl = document.getElementById("opt-est-profit");
|
||||||
const targetLevEl = document.getElementById("opt-est-leverage");
|
const targetLevEl = document.getElementById("opt-est-leverage");
|
||||||
const targetEl = document.getElementById("opt-target-idx");
|
const targetEl = document.getElementById("opt-target-idx");
|
||||||
const q = state.orderQuote;
|
const q = state.orderQuote;
|
||||||
if (!q || !q.ok) {
|
if (!q || !q.ok) {
|
||||||
if (levEl) levEl.textContent = "—";
|
if (levEl) levEl.textContent = "—";
|
||||||
|
if (valueEl) valueEl.textContent = "—";
|
||||||
if (profitEl) {
|
if (profitEl) {
|
||||||
profitEl.textContent = "—";
|
profitEl.textContent = "—";
|
||||||
profitEl.className = "v";
|
profitEl.className = "v";
|
||||||
@@ -313,20 +370,26 @@
|
|||||||
const lev = calcContractLeverage(q.index_px, ethAmount, premium);
|
const lev = calcContractLeverage(q.index_px, ethAmount, premium);
|
||||||
if (levEl) levEl.textContent = fmtLeverage(lev);
|
if (levEl) levEl.textContent = fmtLeverage(lev);
|
||||||
|
|
||||||
if (profitEl && targetEl) {
|
if (valueEl && profitEl && targetEl) {
|
||||||
const targetRaw = targetEl.value;
|
const targetRaw = targetEl.value;
|
||||||
if (targetRaw === "" || targetRaw == null) {
|
if (targetRaw === "" || targetRaw == null) {
|
||||||
|
valueEl.textContent = "—";
|
||||||
profitEl.textContent = "—";
|
profitEl.textContent = "—";
|
||||||
profitEl.className = "v";
|
profitEl.className = "v";
|
||||||
if (targetLevEl) targetLevEl.textContent = "—";
|
if (targetLevEl) targetLevEl.textContent = "—";
|
||||||
} else {
|
} else {
|
||||||
const profit = estimateExpiryProfit(q.opt_type, q.strike, Number(targetRaw), q.ask, ethAmount);
|
const value = estimateExpiryValue(q.opt_type, q.strike, Number(targetRaw), ethAmount);
|
||||||
|
const profit = estimateExpiryProfit(q.opt_type, q.strike, Number(targetRaw), ethAmount, premium);
|
||||||
|
if (value == null || Number.isNaN(value)) {
|
||||||
|
valueEl.textContent = "—";
|
||||||
|
} else {
|
||||||
|
valueEl.textContent = fmtUsdc(value) + " USDC";
|
||||||
|
}
|
||||||
if (profit == null || Number.isNaN(profit)) {
|
if (profit == null || Number.isNaN(profit)) {
|
||||||
profitEl.textContent = "—";
|
profitEl.textContent = "—";
|
||||||
profitEl.className = "v";
|
profitEl.className = "v";
|
||||||
} else {
|
} else {
|
||||||
const sign = profit > 0 ? "+" : "";
|
profitEl.textContent = fmtUsdcSigned(profit);
|
||||||
profitEl.textContent = sign + profit.toFixed(4) + " USDC";
|
|
||||||
profitEl.className = "v " + pnlCls(profit);
|
profitEl.className = "v " + pnlCls(profit);
|
||||||
}
|
}
|
||||||
const targetLev = calcContractLeverage(Number(targetRaw), ethAmount, premium);
|
const targetLev = calcContractLeverage(Number(targetRaw), ethAmount, premium);
|
||||||
@@ -358,9 +421,9 @@
|
|||||||
const tbody = document.getElementById("opt-strike-tbody");
|
const tbody = document.getElementById("opt-strike-tbody");
|
||||||
const expMs = document.getElementById("opt-exp-select").value;
|
const expMs = document.getElementById("opt-exp-select").value;
|
||||||
const prevSelected = state.selectedInst;
|
const prevSelected = state.selectedInst;
|
||||||
parkOrderPanel();
|
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
if (!expMs || !state.chain) {
|
if (!expMs || !state.chain) {
|
||||||
|
parkOrderPanel();
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">请选择到期日</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="8" class="muted">请选择到期日</td></tr>';
|
||||||
state.selectedInst = null;
|
state.selectedInst = null;
|
||||||
return;
|
return;
|
||||||
@@ -368,14 +431,23 @@
|
|||||||
const exp = (state.chain.expiries || []).find(function (e) {
|
const exp = (state.chain.expiries || []).find(function (e) {
|
||||||
return String(e.exp_time) === String(expMs);
|
return String(e.exp_time) === String(expMs);
|
||||||
});
|
});
|
||||||
if (!exp) return;
|
if (!exp) {
|
||||||
const list = filterChainContracts(exp.contracts);
|
parkOrderPanel();
|
||||||
if (!list.length) {
|
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">该到期日暂无' + moneyFilterLabel() + "合约</td></tr>";
|
|
||||||
state.selectedInst = null;
|
state.selectedInst = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let stillVisible = false;
|
const list = filterChainContracts(exp.contracts);
|
||||||
|
if (!list.length) {
|
||||||
|
parkOrderPanel();
|
||||||
|
const label = moneyFilterLabel();
|
||||||
|
const suffix = label ? label : optTypeLabel(state.optType);
|
||||||
|
tbody.innerHTML = '<tr><td colspan="8" class="muted">该到期日暂无' + suffix + "合约</td></tr>";
|
||||||
|
state.selectedInst = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stillVisible = !!prevSelected && list.some(function (c) { return c.inst_id === prevSelected; });
|
||||||
|
if (!stillVisible) parkOrderPanel();
|
||||||
|
let matchedSelected = false;
|
||||||
list.forEach(function (c) {
|
list.forEach(function (c) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.className = "opt-strike-row";
|
tr.className = "opt-strike-row";
|
||||||
@@ -393,14 +465,14 @@
|
|||||||
'<button type="button" class="btn-secondary opt-pick-btn" data-inst="' + c.inst_id + '">选择</button>' +
|
'<button type="button" class="btn-secondary opt-pick-btn" data-inst="' + c.inst_id + '">选择</button>' +
|
||||||
"</td>";
|
"</td>";
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
if (c.inst_id === prevSelected) stillVisible = true;
|
if (c.inst_id === prevSelected) matchedSelected = true;
|
||||||
});
|
});
|
||||||
tbody.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
|
tbody.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
|
||||||
btn.addEventListener("click", function () {
|
btn.addEventListener("click", function () {
|
||||||
selectContract(btn.getAttribute("data-inst"), btn);
|
selectContract(btn.getAttribute("data-inst"), btn);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (stillVisible && prevSelected) {
|
if (matchedSelected && prevSelected) {
|
||||||
selectContract(prevSelected, null, true);
|
selectContract(prevSelected, null, true);
|
||||||
} else {
|
} else {
|
||||||
state.selectedInst = null;
|
state.selectedInst = null;
|
||||||
@@ -416,7 +488,8 @@
|
|||||||
if (bidEl) bidEl.textContent = fmtPxSz(d.bid, d.bid_sz);
|
if (bidEl) bidEl.textContent = fmtPxSz(d.bid, d.bid_sz);
|
||||||
document.getElementById("opt-order-sheets").textContent = sz.sheets != null ? sz.sheets : "—";
|
document.getElementById("opt-order-sheets").textContent = sz.sheets != null ? sz.sheets : "—";
|
||||||
document.getElementById("opt-order-eth").textContent = sz.eth_amount != null ? sz.eth_amount : "—";
|
document.getElementById("opt-order-eth").textContent = sz.eth_amount != null ? sz.eth_amount : "—";
|
||||||
document.getElementById("opt-order-premium").textContent = sz.total_premium != null ? fmt(sz.total_premium, 4) + " USDC" : "—";
|
updateUnderlyingLabel();
|
||||||
|
document.getElementById("opt-order-premium").textContent = sz.total_premium != null ? fmtUsdc(sz.total_premium) + " USDC" : "—";
|
||||||
const beEl = document.getElementById("opt-order-expiry-be");
|
const beEl = document.getElementById("opt-order-expiry-be");
|
||||||
const distEl = document.getElementById("opt-order-dist-be");
|
const distEl = document.getElementById("opt-order-dist-be");
|
||||||
if (beEl) {
|
if (beEl) {
|
||||||
@@ -473,7 +546,9 @@
|
|||||||
panelCache.underlying = state.underlying;
|
panelCache.underlying = state.underlying;
|
||||||
panelCache.optType = state.optType;
|
panelCache.optType = state.optType;
|
||||||
state.selectedInst = null;
|
state.selectedInst = null;
|
||||||
|
resetMoneyFilterToAll();
|
||||||
parkOrderPanel();
|
parkOrderPanel();
|
||||||
|
updateUnderlyingLabel();
|
||||||
renderExpiries();
|
renderExpiries();
|
||||||
renderStrikes();
|
renderStrikes();
|
||||||
}
|
}
|
||||||
@@ -973,8 +1048,15 @@
|
|||||||
}, 120);
|
}, 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onExpiryChange() {
|
||||||
|
resetMoneyFilterToAll();
|
||||||
|
renderStrikes();
|
||||||
|
}
|
||||||
|
|
||||||
function bootOptionsPanel() {
|
function bootOptionsPanel() {
|
||||||
updateSizeInputs();
|
updateSizeInputs();
|
||||||
|
syncMoneyFilterButtons();
|
||||||
|
updateUnderlyingLabel();
|
||||||
const hasCache =
|
const hasCache =
|
||||||
panelCache.chain &&
|
panelCache.chain &&
|
||||||
panelCache.underlying === state.underlying &&
|
panelCache.underlying === state.underlying &&
|
||||||
@@ -1006,22 +1088,21 @@
|
|||||||
document.querySelectorAll(".opt-type-btn").forEach(function (b) { b.classList.remove("active"); });
|
document.querySelectorAll(".opt-type-btn").forEach(function (b) { b.classList.remove("active"); });
|
||||||
btn.classList.add("active");
|
btn.classList.add("active");
|
||||||
state.optType = btn.getAttribute("data-type");
|
state.optType = btn.getAttribute("data-type");
|
||||||
renderExpiries();
|
resetMoneyFilterToAll();
|
||||||
|
renderExpiryOptions(true);
|
||||||
renderStrikes();
|
renderStrikes();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll(".opt-money-btn").forEach(function (btn) {
|
document.querySelectorAll(".opt-money-btn").forEach(function (btn) {
|
||||||
btn.addEventListener("click", function () {
|
btn.addEventListener("click", function () {
|
||||||
document.querySelectorAll(".opt-money-btn").forEach(function (b) { b.classList.remove("active"); });
|
state.moneyFilter = btn.getAttribute("data-money") || "all";
|
||||||
btn.classList.add("active");
|
syncMoneyFilterButtons();
|
||||||
state.moneyFilter = btn.getAttribute("data-money") || "itm";
|
|
||||||
renderExpiries();
|
|
||||||
renderStrikes();
|
renderStrikes();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("opt-exp-select").addEventListener("change", renderStrikes);
|
document.getElementById("opt-exp-select").addEventListener("change", onExpiryChange);
|
||||||
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
|
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
|
||||||
document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
|
document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
|
||||||
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
|
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
|
||||||
|
|||||||
@@ -298,16 +298,15 @@ def format_options_breakeven_line(
|
|||||||
return " ".join(parts)
|
return " ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def estimate_expiry_profit_at_index(
|
def estimate_expiry_value_at_index(
|
||||||
*,
|
*,
|
||||||
opt_type: str,
|
opt_type: str,
|
||||||
strike: float | None,
|
strike: float | None,
|
||||||
target_idx: float | None,
|
target_idx: float | None,
|
||||||
entry_px: float | None,
|
|
||||||
eth_amount: float | None,
|
eth_amount: float | None,
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
"""到期测算:目标指数价下按内在价值减权利金(每 1 币报价 × 币量)."""
|
"""到期测算:目标指数价下期权内在价值总额(不含已付权利金)."""
|
||||||
if strike is None or target_idx is None or entry_px is None or eth_amount is None:
|
if strike is None or target_idx is None or eth_amount is None:
|
||||||
return None
|
return None
|
||||||
if eth_amount <= 0:
|
if eth_amount <= 0:
|
||||||
return None
|
return None
|
||||||
@@ -318,7 +317,33 @@ def estimate_expiry_profit_at_index(
|
|||||||
intrinsic = max(0.0, float(strike) - float(target_idx))
|
intrinsic = max(0.0, float(strike) - float(target_idx))
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
return round((intrinsic - float(entry_px)) * float(eth_amount), 4)
|
return round(intrinsic * float(eth_amount), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_expiry_profit_at_index(
|
||||||
|
*,
|
||||||
|
opt_type: str,
|
||||||
|
strike: float | None,
|
||||||
|
target_idx: float | None,
|
||||||
|
entry_px: float | None,
|
||||||
|
eth_amount: float | None,
|
||||||
|
total_premium: float | None = None,
|
||||||
|
) -> float | None:
|
||||||
|
"""到期测算:目标指数价下净盈利 = 预计价值 − 权利金."""
|
||||||
|
value = estimate_expiry_value_at_index(
|
||||||
|
opt_type=opt_type,
|
||||||
|
strike=strike,
|
||||||
|
target_idx=target_idx,
|
||||||
|
eth_amount=eth_amount,
|
||||||
|
)
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
prem = total_premium
|
||||||
|
if prem is None and entry_px is not None and eth_amount is not None:
|
||||||
|
prem = float(entry_px) * float(eth_amount)
|
||||||
|
if prem is None:
|
||||||
|
return None
|
||||||
|
return round(float(value) - float(prem), 2)
|
||||||
|
|
||||||
|
|
||||||
def equivalent_contract_leverage(
|
def equivalent_contract_leverage(
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
<select id="opt-exp-select"><option value="">选择到期日</option></select>
|
<select id="opt-exp-select"><option value="">选择到期日</option></select>
|
||||||
<button type="button" class="btn-secondary opt-type-btn active" data-type="C">看涨 Call</button>
|
<button type="button" class="btn-secondary opt-type-btn active" data-type="C">看涨 Call</button>
|
||||||
<button type="button" class="btn-secondary opt-type-btn" data-type="P">看跌 Put</button>
|
<button type="button" class="btn-secondary opt-type-btn" data-type="P">看跌 Put</button>
|
||||||
<button type="button" class="btn-secondary opt-money-btn active" data-money="itm">实值</button>
|
<button type="button" class="btn-secondary opt-money-btn active" data-money="all">全部</button>
|
||||||
|
<button type="button" class="btn-secondary opt-money-btn" data-money="itm">实值</button>
|
||||||
<button type="button" class="btn-secondary opt-money-btn" data-money="otm">虚值</button>
|
<button type="button" class="btn-secondary opt-money-btn" data-money="otm">虚值</button>
|
||||||
<button type="button" class="btn-secondary" id="opt-load-chain">刷新链</button>
|
<button type="button" class="btn-secondary" id="opt-load-chain">刷新链</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
<div><span class="k">卖一/张</span><span id="opt-order-ask" class="v">—</span></div>
|
<div><span class="k">卖一/张</span><span id="opt-order-ask" class="v">—</span></div>
|
||||||
<div><span class="k">买一/张</span><span id="opt-order-bid" class="v">—</span></div>
|
<div><span class="k">买一/张</span><span id="opt-order-bid" class="v">—</span></div>
|
||||||
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
||||||
<div><span class="k">ETH/BTC 数量</span><span id="opt-order-eth" class="v">—</span></div>
|
<div><span class="k" id="opt-order-eth-label">ETH 数量</span><span id="opt-order-eth" class="v">—</span></div>
|
||||||
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v">—</span></div>
|
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v">—</span></div>
|
||||||
<div><span class="k">合约杠杆</span><span id="opt-order-leverage" class="v" title="名义价值÷权利金,测算用">—</span></div>
|
<div><span class="k">合约杠杆</span><span id="opt-order-leverage" class="v" title="名义价值÷权利金,测算用">—</span></div>
|
||||||
<div><span class="k">到期平衡</span><span id="opt-order-expiry-be" class="v">—</span></div>
|
<div><span class="k">到期平衡</span><span id="opt-order-expiry-be" class="v">—</span></div>
|
||||||
@@ -55,7 +56,9 @@
|
|||||||
<div class="options-estimate-row">
|
<div class="options-estimate-row">
|
||||||
<label class="opt-est-label" for="opt-target-idx">目标位(指数)</label>
|
<label class="opt-est-label" for="opt-target-idx">目标位(指数)</label>
|
||||||
<input type="number" id="opt-target-idx" class="opt-target-idx" step="0.1" min="0" placeholder="到期时指数价">
|
<input type="number" id="opt-target-idx" class="opt-target-idx" step="0.1" min="0" placeholder="到期时指数价">
|
||||||
<span class="k">预计盈利</span>
|
<span class="k">预计价值</span>
|
||||||
|
<span id="opt-est-value" class="v">—</span>
|
||||||
|
<span class="k">盈利</span>
|
||||||
<span id="opt-est-profit" class="v">—</span>
|
<span id="opt-est-profit" class="v">—</span>
|
||||||
<span class="k">目标杠杆</span>
|
<span class="k">目标杠杆</span>
|
||||||
<span id="opt-est-leverage" class="v" title="目标位名义价值÷权利金">—</span>
|
<span id="opt-est-leverage" class="v" title="目标位名义价值÷权利金">—</span>
|
||||||
@@ -212,4 +215,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=26"></script>
|
<script src="/static/options_panel.js?v=27"></script>
|
||||||
|
|||||||
@@ -75,19 +75,51 @@ def test_equivalent_contract_leverage():
|
|||||||
assert lev == 144.9
|
assert lev == 144.9
|
||||||
|
|
||||||
|
|
||||||
def test_estimate_expiry_profit_at_index():
|
def test_estimate_expiry_value_and_profit_at_index():
|
||||||
from lib.options.options_pricing_lib import estimate_expiry_profit_at_index
|
from lib.options.options_pricing_lib import (
|
||||||
|
estimate_expiry_profit_at_index,
|
||||||
# Call 1780, ask 12.2, 0.01 ETH, target 1793 -> (13-12.2)*0.01 = 0.008
|
estimate_expiry_value_at_index,
|
||||||
p = estimate_expiry_profit_at_index(
|
|
||||||
opt_type="C", strike=1780, target_idx=1793, entry_px=12.2, eth_amount=0.01
|
|
||||||
)
|
)
|
||||||
assert p == 0.008
|
|
||||||
|
value = estimate_expiry_value_at_index(
|
||||||
|
opt_type="C", strike=1800, target_idx=2000, eth_amount=1.0
|
||||||
|
)
|
||||||
|
assert value == 200.0
|
||||||
|
|
||||||
|
profit = estimate_expiry_profit_at_index(
|
||||||
|
opt_type="C",
|
||||||
|
strike=1800,
|
||||||
|
target_idx=2000,
|
||||||
|
entry_px=0.148,
|
||||||
|
eth_amount=1.0,
|
||||||
|
total_premium=14.8,
|
||||||
|
)
|
||||||
|
assert profit == 185.2
|
||||||
|
|
||||||
|
# Call 1780, ask 12.2, 0.01 ETH, target 1793 -> value 0.13, profit 0.01
|
||||||
|
v = estimate_expiry_value_at_index(
|
||||||
|
opt_type="C", strike=1780, target_idx=1793, eth_amount=0.01
|
||||||
|
)
|
||||||
|
assert v == 0.13
|
||||||
|
p = estimate_expiry_profit_at_index(
|
||||||
|
opt_type="C",
|
||||||
|
strike=1780,
|
||||||
|
target_idx=1793,
|
||||||
|
entry_px=12.2,
|
||||||
|
eth_amount=0.01,
|
||||||
|
total_premium=0.122,
|
||||||
|
)
|
||||||
|
assert p == 0.01
|
||||||
# OTM call loses premium
|
# OTM call loses premium
|
||||||
p2 = estimate_expiry_profit_at_index(
|
p2 = estimate_expiry_profit_at_index(
|
||||||
opt_type="C", strike=1780, target_idx=1770, entry_px=12.2, eth_amount=0.01
|
opt_type="C",
|
||||||
|
strike=1780,
|
||||||
|
target_idx=1770,
|
||||||
|
entry_px=12.2,
|
||||||
|
eth_amount=0.01,
|
||||||
|
total_premium=0.122,
|
||||||
)
|
)
|
||||||
assert p2 == round(-12.2 * 0.01, 4)
|
assert p2 == -0.12
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_chain_quote_otm_no_quote():
|
def test_resolve_chain_quote_otm_no_quote():
|
||||||
|
|||||||
Reference in New Issue
Block a user