Files
crypto_monitor/lib/common/static/options_position_cards.js
T
dekun 843d87422d Show hub fullscreen options positions as cards matching instance data.
Enrich hub options snapshot with close preview and render read-only option position cards in exchange fullscreen view.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 09:40:35 +08:00

136 lines
6.1 KiB
JavaScript

(function (global) {
"use strict";
function fmt(v, d) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
return Number(v).toFixed(d == null ? 2 : d);
}
function fmtDisplay(v, fallback) {
if (v !== null && v !== undefined && String(v).trim() !== "") return String(v);
if (fallback !== undefined) return fmtDisplay(fallback);
return "—";
}
function fmtOptionPx(v, tickSz) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
const n = Number(v);
const tick = Number(tickSz);
if (!tickSz || Number.isNaN(tick) || tick <= 0) return String(n);
let decimals = 0;
if (tick < 1) decimals = Math.max(0, -Math.round(Math.log10(tick)));
else if (String(tick).indexOf(".") >= 0) decimals = String(tick).split(".")[1].length;
return n.toFixed(decimals).replace(/\.?0+$/, "") || "0";
}
function fmtUsdc(v) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
return Number(v).toFixed(2);
}
function optTypeLabel(t) {
return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
}
function pnlCls(upl, hub) {
if (upl > 0) return hub ? "pnl-pos" : "pos-pnl-profit";
if (upl < 0) return hub ? "pnl-neg" : "pos-pnl-loss";
return "";
}
function fmtCloseLevels(preview, tickSz) {
const levels = ((preview && preview.levels) || []).slice(0, 5);
if (!levels.length) return "—";
return levels.map(function (x, idx) {
const levelNo = x.level != null ? x.level : idx + 1;
return "买" + levelNo + " " + fmtOptionPx(x.px, tickSz);
}).join(" · ");
}
function fmtClosePreview(preview, premiumPaid, hub) {
if (!preview || preview.total_received == null) return "—";
const recvTxt = fmtUsdc(preview.total_received);
let cls = "";
const prem = Number(premiumPaid);
const recv = Number(preview.total_received);
if (!Number.isNaN(prem) && !Number.isNaN(recv)) {
if (recv > prem) cls = " " + pnlCls(1, hub);
else if (recv < prem) cls = " " + pnlCls(-1, hub);
}
return '<span class="opt-close-value' + cls + '">' + recvTxt + " USDC</span>";
}
function expiryCdHtml(expMs) {
const ms = expMs != null && expMs !== "" ? String(expMs) : "";
if (!ms) return "—";
return '<span class="opt-expiry-cd" data-opt-exp-ms="' + ms + '">—</span>';
}
function renderCardInner(p, opts) {
opts = opts || {};
const hub = !!opts.hub;
const readOnly = !!opts.readOnly;
const upl = p.upl;
const uplCls = pnlCls(upl, hub);
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 expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
const closePreview = p.close_preview || {};
const tickSz = p.tick_sz;
const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null);
const avgTxt = fmtDisplay(p.avg_px_fmt, fmtOptionPx(p.avg_px, tickSz));
const markTxt = fmtDisplay(p.mark_px_fmt, fmtOptionPx(p.mark_px, tickSz));
let headActions = "";
if (!readOnly) {
const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos;
headActions =
'<div class="pos-head-actions">' +
'<button type="button" class="btn-primary opt-close-btn" data-inst="' + (p.inst_id || "") + '" data-sheets="' + closeSheets + '">多档平仓</button>' +
"</div>";
}
return (
'<div class="pos-card-head">' +
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + "</strong>" +
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span></div>" +
headActions +
"</div>" +
'<div class="pos-meta">' +
'<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
(expAttr
? '<span class="pos-meta-item">到期倒计时: ' + expiryCdHtml(expAttr) + "</span>"
: "") +
"</div>" +
'<div class="pos-grid">' +
'<div class="pos-cell"><span class="pos-label">权利金</span><span class="pos-value">' + premTxt + " USDC</span></div>" +
'<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + avgTxt + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + markTxt + "</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.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 + '">' +
(p.upl_ratio_pct != null ? fmt(p.upl_ratio_pct, 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--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' + fmtClosePreview(closePreview, p.premium_paid, hub) + "</span></div>" +
"</div>"
);
}
function renderCard(p, opts) {
opts = opts || {};
const hub = !!opts.hub;
const extraCls = hub ? " hub-pos-card hub-opt-pos-card" : " opt-pos-card";
return (
'<div class="pos-card' + extraCls + '" data-inst="' + (p.inst_id || "") + '">' +
renderCardInner(p, opts) +
"</div>"
);
}
global.OptionsPositionCards = {
renderCardInner: renderCardInner,
renderCard: renderCard,
};
})(typeof window !== "undefined" ? window : globalThis);