Show option net P/L from bid recycle minus premium.
Rename floating P/L to net P/L, align ROI, and display bid depth as price/liquidity for only the levels needed to close. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -372,16 +372,46 @@
|
|||||||
return price + "/" + size;
|
return price + "/" + size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 买盘深度:价格/流动性;仅展示平仓所需档位(买一不够才出买二…). */
|
||||||
function fmtCloseLevels(preview, tickSz) {
|
function fmtCloseLevels(preview, tickSz) {
|
||||||
const levels = ((preview && preview.levels) || []).slice(0, 5);
|
const levels = ((preview && preview.levels) || []).slice(0, 5);
|
||||||
if (!levels.length) return "—";
|
if (!levels.length) return "—";
|
||||||
return levels.map(function (x, idx) {
|
return levels.map(function (x, idx) {
|
||||||
const levelNo = x.level != null ? x.level : idx + 1;
|
const levelNo = x.level != null ? x.level : idx + 1;
|
||||||
|
const liq = x.available_sheets != null ? x.available_sheets : x.sz;
|
||||||
const pxTxt = fmtOptionPx(x.px, tickSz);
|
const pxTxt = fmtOptionPx(x.px, tickSz);
|
||||||
return "买" + levelNo + " " + pxTxt;
|
if (liq === null || liq === undefined || liq === "" || Number.isNaN(Number(liq))) {
|
||||||
|
return "买" + levelNo + " " + pxTxt;
|
||||||
|
}
|
||||||
|
const s = Number(liq);
|
||||||
|
const size = Math.abs(s - Math.round(s)) < 1e-9 ? String(Math.round(s)) : String(s);
|
||||||
|
return "买" + levelNo + " " + pxTxt + "/" + size;
|
||||||
}).join(" · ");
|
}).join(" · ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function netPnlFromPos(p) {
|
||||||
|
const preview = (p && p.close_preview) || {};
|
||||||
|
if (preview.estimated_pnl != null && !Number.isNaN(Number(preview.estimated_pnl))) {
|
||||||
|
return Number(preview.estimated_pnl);
|
||||||
|
}
|
||||||
|
const recv = Number(preview.total_received);
|
||||||
|
const prem = Number(p && p.premium_paid);
|
||||||
|
if (preview.total_received != null && !Number.isNaN(recv) && !Number.isNaN(prem)) {
|
||||||
|
return recv - prem;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function netRoiFromPos(p, net) {
|
||||||
|
const preview = (p && p.close_preview) || {};
|
||||||
|
if (preview.estimated_pnl_ratio_pct != null && !Number.isNaN(Number(preview.estimated_pnl_ratio_pct))) {
|
||||||
|
return Number(preview.estimated_pnl_ratio_pct);
|
||||||
|
}
|
||||||
|
const prem = Number(p && p.premium_paid);
|
||||||
|
if (net == null || Number.isNaN(prem) || prem <= 0) return null;
|
||||||
|
return (net / prem) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
function fmtUsdc(v) {
|
function fmtUsdc(v) {
|
||||||
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||||
return Number(v).toFixed(2);
|
return Number(v).toFixed(2);
|
||||||
@@ -821,8 +851,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPositionCardInner(p) {
|
function renderPositionCardInner(p) {
|
||||||
const upl = p.upl;
|
const net = netPnlFromPos(p);
|
||||||
const uplCls = upl > 0 ? "pos-pnl-profit" : upl < 0 ? "pos-pnl-loss" : "";
|
const roi = netRoiFromPos(p, net);
|
||||||
|
const uplCls = pnlCls(net);
|
||||||
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
||||||
const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
|
const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
|
||||||
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
|
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
|
||||||
@@ -853,9 +884,10 @@
|
|||||||
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">到期平衡</span><span class="pos-value">' + fmt(p.expiry_be_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">到期平衡</span><span class="pos-value">' + fmt(p.expiry_be_px, 0) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">平掉回本</span><span class="pos-value">' + fmt(p.close_be_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">平掉回本</span><span class="pos-value">' + fmt(p.close_be_px, 0) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ' + uplCls + '">' + fmt(p.upl, 2) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">净盈亏</span><span class="pos-value ' + uplCls + '">' +
|
||||||
|
(net == null ? "—" : fmt(net, 2)) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
|
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
|
||||||
(p.upl_ratio_pct != null ? fmt(p.upl_ratio_pct, 2) + "%" : "—") + "</span></div>" +
|
(roi == null ? "—" : fmt(roi, 2) + "%") + "</span></div>" +
|
||||||
'<div class="pos-cell opt-pos-cell--depth"><span class="pos-label">买盘深度</span><span class="pos-value opt-bid-plain">' + fmtCloseLevels(closePreview, tickSz) + "</span></div>" +
|
'<div class="pos-cell opt-pos-cell--depth"><span class="pos-label">买盘深度</span><span class="pos-value opt-bid-plain">' + fmtCloseLevels(closePreview, tickSz) + "</span></div>" +
|
||||||
'<div class="pos-cell opt-pos-cell--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' + fmtClosePreview(closePreview, p.premium_paid) + "</span></div>" +
|
'<div class="pos-cell opt-pos-cell--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' + fmtClosePreview(closePreview, p.premium_paid) + "</span></div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
@@ -957,8 +989,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPositionAccordionItem(p, expanded) {
|
function renderPositionAccordionItem(p, expanded) {
|
||||||
const upl = p.upl;
|
const net = netPnlFromPos(p);
|
||||||
const uplCls = upl > 0 ? "pos-pnl-profit" : upl < 0 ? "pos-pnl-loss" : "";
|
const roi = netRoiFromPos(p, net);
|
||||||
|
const uplCls = pnlCls(net);
|
||||||
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
||||||
const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
|
const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
|
||||||
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
|
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
|
||||||
@@ -978,9 +1011,9 @@
|
|||||||
(expAttr
|
(expAttr
|
||||||
? '<span class="opt-pos-bar-cd">到期 <span class="opt-expiry-cd" data-opt-exp-ms="' + expAttr + '">—</span></span>'
|
? '<span class="opt-pos-bar-cd">到期 <span class="opt-expiry-cd" data-opt-exp-ms="' + expAttr + '">—</span></span>'
|
||||||
: "") +
|
: "") +
|
||||||
'<span class="opt-pos-bar-pnl ' + uplCls + '">' + fmt(p.upl, 2) + " USDC</span>" +
|
'<span class="opt-pos-bar-pnl ' + uplCls + '">' + (net == null ? "—" : fmt(net, 2) + " USDC") + "</span>" +
|
||||||
'<span class="opt-pos-bar-roi ' + uplCls + '">' +
|
'<span class="opt-pos-bar-roi ' + uplCls + '">' +
|
||||||
(p.upl_ratio_pct != null ? fmt(p.upl_ratio_pct, 2) + "%" : "—") + "</span>" +
|
(roi == null ? "—" : fmt(roi, 2) + "%") + "</span>" +
|
||||||
"</span>" +
|
"</span>" +
|
||||||
"</button>" +
|
"</button>" +
|
||||||
'<div class="opt-pos-accordion-body"' + (expanded ? "" : ' hidden') + '>' +
|
'<div class="opt-pos-accordion-body"' + (expanded ? "" : ' hidden') + '>' +
|
||||||
|
|||||||
@@ -41,15 +41,49 @@
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtPxSz(px, sz, tickSz) {
|
||||||
|
if (px === null || px === undefined || Number.isNaN(Number(px))) return "—";
|
||||||
|
let price = fmtOptionPx(px, tickSz);
|
||||||
|
if (sz === null || sz === undefined || sz === "" || Number.isNaN(Number(sz))) return price;
|
||||||
|
const s = Number(sz);
|
||||||
|
const size = Math.abs(s - Math.round(s)) < 1e-9 ? String(Math.round(s)) : String(s);
|
||||||
|
return price + "/" + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 买盘深度:价格/流动性;仅展示平仓所需档位(买一不够才出买二…). */
|
||||||
function fmtCloseLevels(preview, tickSz) {
|
function fmtCloseLevels(preview, tickSz) {
|
||||||
const levels = ((preview && preview.levels) || []).slice(0, 5);
|
const levels = ((preview && preview.levels) || []).slice(0, 5);
|
||||||
if (!levels.length) return "—";
|
if (!levels.length) return "—";
|
||||||
return levels.map(function (x, idx) {
|
return levels.map(function (x, idx) {
|
||||||
const levelNo = x.level != null ? x.level : idx + 1;
|
const levelNo = x.level != null ? x.level : idx + 1;
|
||||||
return "买" + levelNo + " " + fmtOptionPx(x.px, tickSz);
|
const liq = x.available_sheets != null ? x.available_sheets : x.sz;
|
||||||
|
return "买" + levelNo + " " + fmtPxSz(x.px, liq, tickSz);
|
||||||
}).join(" · ");
|
}).join(" · ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function netPnlFromPos(p) {
|
||||||
|
const preview = (p && p.close_preview) || {};
|
||||||
|
if (preview.estimated_pnl != null && !Number.isNaN(Number(preview.estimated_pnl))) {
|
||||||
|
return Number(preview.estimated_pnl);
|
||||||
|
}
|
||||||
|
const recv = Number(preview.total_received);
|
||||||
|
const prem = Number(p && p.premium_paid);
|
||||||
|
if (preview.total_received != null && !Number.isNaN(recv) && !Number.isNaN(prem)) {
|
||||||
|
return recv - prem;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function netRoiFromPos(p, net) {
|
||||||
|
const preview = (p && p.close_preview) || {};
|
||||||
|
if (preview.estimated_pnl_ratio_pct != null && !Number.isNaN(Number(preview.estimated_pnl_ratio_pct))) {
|
||||||
|
return Number(preview.estimated_pnl_ratio_pct);
|
||||||
|
}
|
||||||
|
const prem = Number(p && p.premium_paid);
|
||||||
|
if (net == null || Number.isNaN(prem) || prem <= 0) return null;
|
||||||
|
return (net / prem) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
function fmtClosePreview(preview, premiumPaid, hub) {
|
function fmtClosePreview(preview, premiumPaid, hub) {
|
||||||
if (!preview || preview.total_received == null) return "—";
|
if (!preview || preview.total_received == null) return "—";
|
||||||
const recvTxt = fmtUsdc(preview.total_received);
|
const recvTxt = fmtUsdc(preview.total_received);
|
||||||
@@ -73,8 +107,9 @@
|
|||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
const hub = !!opts.hub;
|
const hub = !!opts.hub;
|
||||||
const readOnly = !!opts.readOnly;
|
const readOnly = !!opts.readOnly;
|
||||||
const upl = p.upl;
|
const net = netPnlFromPos(p);
|
||||||
const uplCls = pnlCls(upl, hub);
|
const roi = netRoiFromPos(p, net);
|
||||||
|
const uplCls = pnlCls(net, hub);
|
||||||
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
||||||
const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
|
const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
|
||||||
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
|
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
|
||||||
@@ -111,9 +146,10 @@
|
|||||||
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">到期平衡</span><span class="pos-value">' + fmt(p.expiry_be_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">到期平衡</span><span class="pos-value">' + fmt(p.expiry_be_px, 0) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">平掉回本</span><span class="pos-value">' + fmt(p.close_be_px, 0) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">平掉回本</span><span class="pos-value">' + fmt(p.close_be_px, 0) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ' + uplCls + '">' + fmt(p.upl, 2) + "</span></div>" +
|
'<div class="pos-cell"><span class="pos-label">净盈亏</span><span class="pos-value ' + uplCls + '">' +
|
||||||
|
(net == null ? "—" : fmt(net, 2)) + "</span></div>" +
|
||||||
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
|
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
|
||||||
(p.upl_ratio_pct != null ? fmt(p.upl_ratio_pct, 2) + "%" : "—") + "</span></div>" +
|
(roi == null ? "—" : fmt(roi, 2) + "%") + "</span></div>" +
|
||||||
'<div class="pos-cell opt-pos-cell--depth"><span class="pos-label">买盘深度</span><span class="pos-value opt-bid-plain">' + fmtCloseLevels(closePreview, tickSz) + "</span></div>" +
|
'<div class="pos-cell opt-pos-cell--depth"><span class="pos-label">买盘深度</span><span class="pos-value opt-bid-plain">' + fmtCloseLevels(closePreview, tickSz) + "</span></div>" +
|
||||||
'<div class="pos-cell opt-pos-cell--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' + fmtClosePreview(closePreview, p.premium_paid, hub) + "</span></div>" +
|
'<div class="pos-cell opt-pos-cell--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' + fmtClosePreview(closePreview, p.premium_paid, hub) + "</span></div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
|
|||||||
@@ -51,11 +51,15 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
|||||||
upl_total = 0.0
|
upl_total = 0.0
|
||||||
has_upl = False
|
has_upl = False
|
||||||
for p in positions:
|
for p in positions:
|
||||||
upl = p.get("upl")
|
# 汇总优先用买盘净盈亏,与持仓卡「净盈亏」一致
|
||||||
if upl is None:
|
preview = p.get("close_preview") or {}
|
||||||
|
net = preview.get("estimated_pnl")
|
||||||
|
if net is None:
|
||||||
|
net = p.get("upl")
|
||||||
|
if net is None:
|
||||||
continue
|
continue
|
||||||
has_upl = True
|
has_upl = True
|
||||||
upl_total += float(upl)
|
upl_total += float(net)
|
||||||
bal = cfg["fetch_options_balances"](ex)
|
bal = cfg["fetch_options_balances"](ex)
|
||||||
stats = _compute_options_stats(ex, cfg)
|
stats = _compute_options_stats(ex, cfg)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ def estimate_close_by_bids(
|
|||||||
"total_received": 0.0,
|
"total_received": 0.0,
|
||||||
"avg_px": None,
|
"avg_px": None,
|
||||||
"estimated_pnl": None,
|
"estimated_pnl": None,
|
||||||
|
"estimated_pnl_ratio_pct": None,
|
||||||
}
|
}
|
||||||
for i, level in enumerate(bids or [], start=1):
|
for i, level in enumerate(bids or [], start=1):
|
||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
@@ -98,10 +99,14 @@ def estimate_close_by_bids(
|
|||||||
remaining -= take
|
remaining -= take
|
||||||
covered = target - remaining
|
covered = target - remaining
|
||||||
avg_px = (total_received / eth_amount_from_sheets(covered, ct_mult)) if covered > 0 else None
|
avg_px = (total_received / eth_amount_from_sheets(covered, ct_mult)) if covered > 0 else None
|
||||||
|
# 净盈亏 = 按买盘可回收 − 全部权利金(与「可落袋」口径一致;买一不够会展开更多档)
|
||||||
estimated_pnl = None
|
estimated_pnl = None
|
||||||
|
estimated_pnl_ratio_pct = None
|
||||||
if premium_paid is not None and covered > 0:
|
if premium_paid is not None and covered > 0:
|
||||||
paid_basis = float(premium_paid) * (covered / target)
|
paid = float(premium_paid)
|
||||||
estimated_pnl = round(total_received - paid_basis, 4)
|
estimated_pnl = round(total_received - paid, 4)
|
||||||
|
if paid > 0:
|
||||||
|
estimated_pnl_ratio_pct = round(estimated_pnl / paid * 100.0, 2)
|
||||||
return {
|
return {
|
||||||
"levels": levels,
|
"levels": levels,
|
||||||
"covered_sheets": covered,
|
"covered_sheets": covered,
|
||||||
@@ -109,6 +114,7 @@ def estimate_close_by_bids(
|
|||||||
"total_received": round(total_received, 4),
|
"total_received": round(total_received, 4),
|
||||||
"avg_px": round(avg_px, 4) if avg_px is not None else None,
|
"avg_px": round(avg_px, 4) if avg_px is not None else None,
|
||||||
"estimated_pnl": estimated_pnl,
|
"estimated_pnl": estimated_pnl,
|
||||||
|
"estimated_pnl_ratio_pct": estimated_pnl_ratio_pct,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3550,10 +3550,19 @@
|
|||||||
function renderOptionsPositionsTable(pos) {
|
function renderOptionsPositionsTable(pos) {
|
||||||
if (!pos.length) return '<div class="empty-hint">暂无期权持仓</div>';
|
if (!pos.length) return '<div class="empty-hint">暂无期权持仓</div>';
|
||||||
let html = '<div class="table-wrap hub-options-table-wrap"><table class="hub-options-table"><thead><tr>';
|
let html = '<div class="table-wrap hub-options-table-wrap"><table class="hub-options-table"><thead><tr>';
|
||||||
html += "<th>合约</th><th>类型</th><th>张数</th><th>到期倒计时</th><th>指数</th><th>到期平衡</th><th>平掉回本</th><th>浮盈</th><th>浮盈%</th>";
|
html += "<th>合约</th><th>类型</th><th>张数</th><th>到期倒计时</th><th>指数</th><th>到期平衡</th><th>平掉回本</th><th>净盈亏</th><th>收益率</th>";
|
||||||
html += "</tr></thead><tbody>";
|
html += "</tr></thead><tbody>";
|
||||||
pos.forEach((p) => {
|
pos.forEach((p) => {
|
||||||
const optType = (p.opt_type || "").toUpperCase() === "C" ? "Call" : (p.opt_type || "").toUpperCase() === "P" ? "Put" : (p.opt_type || "—");
|
const optType = (p.opt_type || "").toUpperCase() === "C" ? "Call" : (p.opt_type || "").toUpperCase() === "P" ? "Put" : (p.opt_type || "—");
|
||||||
|
const preview = p.close_preview || {};
|
||||||
|
let net = preview.estimated_pnl;
|
||||||
|
if (net == null && preview.total_received != null && p.premium_paid != null) {
|
||||||
|
net = Number(preview.total_received) - Number(p.premium_paid);
|
||||||
|
}
|
||||||
|
let roi = preview.estimated_pnl_ratio_pct;
|
||||||
|
if (roi == null && net != null && Number(p.premium_paid) > 0) {
|
||||||
|
roi = (Number(net) / Number(p.premium_paid)) * 100;
|
||||||
|
}
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td><code class="hub-options-inst" title="${esc(p.inst_id || "")}">${esc(shortOptionsInst(p.inst_id))}</code></td>
|
<td><code class="hub-options-inst" title="${esc(p.inst_id || "")}">${esc(shortOptionsInst(p.inst_id))}</code></td>
|
||||||
<td>${esc(optType)}</td>
|
<td>${esc(optType)}</td>
|
||||||
@@ -3562,8 +3571,8 @@
|
|||||||
<td>${p.idx_px != null ? fmt(p.idx_px, 0) : "—"}</td>
|
<td>${p.idx_px != null ? fmt(p.idx_px, 0) : "—"}</td>
|
||||||
<td>${p.expiry_be_px != null ? fmt(p.expiry_be_px, 0) : "—"}</td>
|
<td>${p.expiry_be_px != null ? fmt(p.expiry_be_px, 0) : "—"}</td>
|
||||||
<td>${p.close_be_px != null ? fmt(p.close_be_px, 0) : "—"}</td>
|
<td>${p.close_be_px != null ? fmt(p.close_be_px, 0) : "—"}</td>
|
||||||
<td class="${pnlCls(p.upl)}">${fmt(p.upl, 2)}</td>
|
<td class="${pnlCls(net)}">${net == null ? "—" : fmt(net, 2)}</td>
|
||||||
<td class="${pnlCls(p.upl)}">${p.upl_ratio_pct != null ? esc(p.upl_ratio_pct) + "%" : "—"}</td>
|
<td class="${pnlCls(net)}">${roi == null ? "—" : esc(Number(roi).toFixed(2)) + "%"}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
html += "</tbody></table></div>";
|
html += "</tbody></table></div>";
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ def test_estimate_close_by_bids_full_depth():
|
|||||||
assert out["total_received"] == 0.488
|
assert out["total_received"] == 0.488
|
||||||
assert out["avg_px"] == 12.2
|
assert out["avg_px"] == 12.2
|
||||||
assert out["estimated_pnl"] == 0.088
|
assert out["estimated_pnl"] == 0.088
|
||||||
|
assert out["estimated_pnl_ratio_pct"] == 22.0
|
||||||
assert [x["sheets"] for x in out["levels"]] == [2, 2]
|
assert [x["sheets"] for x in out["levels"]] == [2, 2]
|
||||||
|
|
||||||
|
|
||||||
@@ -206,7 +207,9 @@ def test_estimate_close_by_bids_partial_depth():
|
|||||||
assert out["covered_sheets"] == 1
|
assert out["covered_sheets"] == 1
|
||||||
assert out["uncovered_sheets"] == 2
|
assert out["uncovered_sheets"] == 2
|
||||||
assert out["total_received"] == 0.1
|
assert out["total_received"] == 0.1
|
||||||
assert out["estimated_pnl"] == -0.1
|
# 净盈亏 = 回收 − 全部权利金(不按覆盖比例摊薄)
|
||||||
|
assert out["estimated_pnl"] == -0.5
|
||||||
|
assert out["estimated_pnl_ratio_pct"] == round(-0.5 / 0.6 * 100, 2)
|
||||||
|
|
||||||
|
|
||||||
def test_estimate_close_by_bids_empty():
|
def test_estimate_close_by_bids_empty():
|
||||||
|
|||||||
Reference in New Issue
Block a user