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>
This commit is contained in:
@@ -0,0 +1,135 @@
|
|||||||
|
(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);
|
||||||
@@ -24,10 +24,12 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
|||||||
if not ok:
|
if not ok:
|
||||||
return {"ok": False, "enabled": True, "msg": reason or "期权 API 未配置"}
|
return {"ok": False, "enabled": True, "msg": reason or "期权 API 未配置"}
|
||||||
try:
|
try:
|
||||||
|
from lib.options.options_positions_lib import build_display_option_positions
|
||||||
|
|
||||||
raw = cfg["fetch_option_positions"](ex)
|
raw = cfg["fetch_option_positions"](ex)
|
||||||
if raw is None:
|
if raw is None:
|
||||||
return {"ok": False, "enabled": True, "msg": "获取期权持仓失败"}
|
return {"ok": False, "enabled": True, "msg": "获取期权持仓失败"}
|
||||||
positions = [cfg["format_position_row"](p) for p in raw]
|
positions = build_display_option_positions(cfg, ex, raw)
|
||||||
upl_total = 0.0
|
upl_total = 0.0
|
||||||
has_upl = False
|
has_upl = False
|
||||||
for p in positions:
|
for p in positions:
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""期权持仓展示(实例页 / 中控快照共用)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from lib.options.options_db import init_options_tables
|
||||||
|
from lib.options.options_history_lib import enrich_position_row_display
|
||||||
|
from lib.options.options_pricing_lib import estimate_close_by_bids
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_float(v: Any) -> float | None:
|
||||||
|
if v is None or v == "":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def attach_close_preview(
|
||||||
|
cfg: dict[str, Any],
|
||||||
|
ex: Any,
|
||||||
|
row: dict[str, Any],
|
||||||
|
*,
|
||||||
|
sheets: int | None = None,
|
||||||
|
premium_paid: float | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
inst_id = str(row.get("inst_id") or row.get("instId") or "").strip()
|
||||||
|
if not inst_id:
|
||||||
|
return row
|
||||||
|
ct_mult = float(row.get("ct_mult") or 0.01)
|
||||||
|
target_sheets = int(sheets) if sheets is not None else int(abs(_safe_float(row.get("pos")) or 0))
|
||||||
|
paid = premium_paid if premium_paid is not None else _safe_float(row.get("premium_paid"))
|
||||||
|
book = cfg["fetch_option_book_depth"](ex, inst_id, 5)
|
||||||
|
row["bid_depth"] = book.get("bids") or []
|
||||||
|
row["ask_depth"] = book.get("asks") or []
|
||||||
|
row["close_preview"] = estimate_close_by_bids(
|
||||||
|
row["bid_depth"],
|
||||||
|
target_sheets,
|
||||||
|
ct_mult=ct_mult,
|
||||||
|
premium_paid=paid,
|
||||||
|
)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def build_display_option_positions(
|
||||||
|
cfg: dict[str, Any],
|
||||||
|
ex: Any,
|
||||||
|
raw_positions: list[dict[str, Any]],
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""与实例 /api/options/positions 相同 enrichment + close_preview."""
|
||||||
|
meta_cache: dict[str, dict[str, Any] | None] = {}
|
||||||
|
rows: list[dict[str, Any]] = []
|
||||||
|
conn = cfg["get_db"]()
|
||||||
|
try:
|
||||||
|
init_options_tables(conn)
|
||||||
|
for p in raw_positions:
|
||||||
|
inst = str(p.get("instId") or "").strip()
|
||||||
|
premium_override = None
|
||||||
|
if inst:
|
||||||
|
rec = conn.execute(
|
||||||
|
"""
|
||||||
|
SELECT premium_paid FROM options_trades
|
||||||
|
WHERE inst_id = ? AND status = 'open'
|
||||||
|
ORDER BY id DESC LIMIT 1
|
||||||
|
""",
|
||||||
|
(inst,),
|
||||||
|
).fetchone()
|
||||||
|
if rec and rec["premium_paid"] is not None:
|
||||||
|
premium_override = float(rec["premium_paid"])
|
||||||
|
row = enrich_position_row_display(
|
||||||
|
cfg,
|
||||||
|
ex,
|
||||||
|
p,
|
||||||
|
meta_cache=meta_cache,
|
||||||
|
premium_override=premium_override,
|
||||||
|
)
|
||||||
|
attach_close_preview(cfg, ex, row, premium_paid=_safe_float(row.get("premium_paid")))
|
||||||
|
rows.append(row)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
return rows
|
||||||
@@ -216,22 +216,15 @@ def _attach_close_preview(
|
|||||||
sheets: int | None = None,
|
sheets: int | None = None,
|
||||||
premium_paid: float | None = None,
|
premium_paid: float | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
inst_id = str(row.get("inst_id") or row.get("instId") or "").strip()
|
from lib.options.options_positions_lib import attach_close_preview
|
||||||
if not inst_id:
|
|
||||||
return row
|
return attach_close_preview(
|
||||||
ct_mult = float(row.get("ct_mult") or 0.01)
|
cfg,
|
||||||
target_sheets = int(sheets) if sheets is not None else int(abs(_safe_float(row.get("pos")) or 0))
|
ex,
|
||||||
paid = premium_paid if premium_paid is not None else _safe_float(row.get("premium_paid"))
|
row,
|
||||||
book = cfg["fetch_option_book_depth"](ex, inst_id, 5)
|
sheets=sheets,
|
||||||
row["bid_depth"] = book.get("bids") or []
|
premium_paid=premium_paid,
|
||||||
row["ask_depth"] = book.get("asks") or []
|
|
||||||
row["close_preview"] = estimate_close_by_bids(
|
|
||||||
row["bid_depth"],
|
|
||||||
target_sheets,
|
|
||||||
ct_mult=ct_mult,
|
|
||||||
premium_paid=paid,
|
|
||||||
)
|
)
|
||||||
return row
|
|
||||||
|
|
||||||
|
|
||||||
_OPTIONS_SYNC_LOCK = threading.Lock()
|
_OPTIONS_SYNC_LOCK = threading.Lock()
|
||||||
|
|||||||
@@ -773,6 +773,7 @@ _TRADE_STATS_CALENDAR_JS = _REPO_STATIC / "trade_stats_calendar.js"
|
|||||||
_ACCOUNT_RISK_BADGE_CSS = _REPO_STATIC / "account_risk_badge.css"
|
_ACCOUNT_RISK_BADGE_CSS = _REPO_STATIC / "account_risk_badge.css"
|
||||||
_ACCOUNT_RISK_BADGE_JS = _REPO_STATIC / "account_risk_badge.js"
|
_ACCOUNT_RISK_BADGE_JS = _REPO_STATIC / "account_risk_badge.js"
|
||||||
_OPTIONS_EXPIRY_COUNTDOWN_JS = _REPO_STATIC / "options_expiry_countdown.js"
|
_OPTIONS_EXPIRY_COUNTDOWN_JS = _REPO_STATIC / "options_expiry_countdown.js"
|
||||||
|
_OPTIONS_POSITION_CARDS_JS = _REPO_STATIC / "options_position_cards.js"
|
||||||
|
|
||||||
|
|
||||||
@app.get("/assets/account_risk_badge.css")
|
@app.get("/assets/account_risk_badge.css")
|
||||||
@@ -807,6 +808,16 @@ def hub_options_expiry_countdown_js():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/assets/options_position_cards.js")
|
||||||
|
def hub_options_position_cards_js():
|
||||||
|
if not _OPTIONS_POSITION_CARDS_JS.is_file():
|
||||||
|
raise HTTPException(status_code=404, detail="options_position_cards.js not found")
|
||||||
|
return FileResponse(
|
||||||
|
str(_OPTIONS_POSITION_CARDS_JS),
|
||||||
|
media_type="application/javascript; charset=utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/assets/ai_review_render.js")
|
@app.get("/assets/ai_review_render.js")
|
||||||
def hub_ai_review_render_js():
|
def hub_ai_review_render_js():
|
||||||
"""与三所实例共用仓库根 static/ai_review_render.js(须在 /assets mount 之前注册)."""
|
"""与三所实例共用仓库根 static/ai_review_render.js(须在 /assets mount 之前注册)."""
|
||||||
|
|||||||
@@ -1970,6 +1970,35 @@ body.market-chart-fs-open {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exchange-fullscreen .hub-opt-pos-list {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-opt-pos-card .pos-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-opt-pos-card .opt-pos-cell--depth {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-opt-pos-card .opt-bid-plain {
|
||||||
|
color: #dbe6ff;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
line-height: 1.35;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-opt-pos-card .opt-close-value {
|
||||||
|
font-weight: 700;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-opt-pos-card .pos-card-symbol strong {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
.hub-pos-card .pos-cell {
|
.hub-pos-card .pos-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -3569,7 +3569,23 @@
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOptionsSectionBody(row) {
|
function renderOptionsPositionsCards(pos) {
|
||||||
|
if (!pos.length) return '<div class="empty-hint">暂无期权持仓</div>';
|
||||||
|
if (!globalThis.OptionsPositionCards || !OptionsPositionCards.renderCard) {
|
||||||
|
return renderOptionsPositionsTable(pos);
|
||||||
|
}
|
||||||
|
const cls = hubPosListCountClass(pos.length);
|
||||||
|
let html = `<div class="hub-pos-list hub-opt-pos-list ${cls}" data-pos-count="${pos.length}">`;
|
||||||
|
pos.forEach((p) => {
|
||||||
|
html += OptionsPositionCards.renderCard(p, { readOnly: true, hub: true });
|
||||||
|
});
|
||||||
|
html += "</div>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOptionsSectionBody(row, opts) {
|
||||||
|
const options = opts || {};
|
||||||
|
const layout = options.layout || "table";
|
||||||
const opt = row.options || {};
|
const opt = row.options || {};
|
||||||
let html = "";
|
let html = "";
|
||||||
if (opt.enabled === false) {
|
if (opt.enabled === false) {
|
||||||
@@ -3585,7 +3601,7 @@
|
|||||||
const bal = optionsBalanceFields(opt);
|
const bal = optionsBalanceFields(opt);
|
||||||
html += renderStatRow(bal.funding, bal.trading, bal.upl);
|
html += renderStatRow(bal.funding, bal.trading, bal.upl);
|
||||||
html += `<div class="section-title hub-options-title">期权持仓 · ${pos.length} 仓</div>`;
|
html += `<div class="section-title hub-options-title">期权持仓 · ${pos.length} 仓</div>`;
|
||||||
html += renderOptionsPositionsTable(pos);
|
html += layout === "cards" ? renderOptionsPositionsCards(pos) : renderOptionsPositionsTable(pos);
|
||||||
if (row.flask_url_browser || row.flask_url) {
|
if (row.flask_url_browser || row.flask_url) {
|
||||||
html += `<div class="hub-options-actions"><a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/options">打开期权页</a></div>`;
|
html += `<div class="hub-options-actions"><a class="btn-link btn-open-instance" href="#" data-ex-id="${esc(row.id)}" data-next="/options">打开期权页</a></div>`;
|
||||||
}
|
}
|
||||||
@@ -3602,11 +3618,11 @@
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOptionsMonitorSection(row) {
|
function renderOptionsMonitorSection(row, opts) {
|
||||||
if (!rowHasOptionsLayout(row)) return "";
|
if (!rowHasOptionsLayout(row)) return "";
|
||||||
let html = '<div class="hub-monitor-block hub-monitor-options">';
|
let html = '<div class="hub-monitor-block hub-monitor-options">';
|
||||||
html += '<div class="hub-monitor-block-label">期权账户</div>';
|
html += '<div class="hub-monitor-block-label">期权账户</div>';
|
||||||
html += renderOptionsSectionBody(row);
|
html += renderOptionsSectionBody(row, opts);
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -3723,7 +3739,7 @@
|
|||||||
if (rowHasOptionsLayout(row)) {
|
if (rowHasOptionsLayout(row)) {
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
}
|
}
|
||||||
html += renderOptionsMonitorSection(row);
|
html += renderOptionsMonitorSection(row, { layout: "cards" });
|
||||||
html += '<div class="hub-fs-sections-grid">';
|
html += '<div class="hub-fs-sections-grid">';
|
||||||
if ((row.capabilities || []).includes("key")) {
|
if ((row.capabilities || []).includes("key")) {
|
||||||
if (!flaskOk) {
|
if (!flaskOk) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||||
<link rel="stylesheet" href="/assets/app.css?v=20260710-logs-cal" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260711-hub-opt-cards" />
|
||||||
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
@@ -1237,7 +1237,8 @@
|
|||||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||||
<script src="/assets/time_close_ui.js?v=3"></script>
|
<script src="/assets/time_close_ui.js?v=3"></script>
|
||||||
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
||||||
|
<script src="/assets/options_position_cards.js?v=1"></script>
|
||||||
<script src="/assets/backup.js?v=1"></script>
|
<script src="/assets/backup.js?v=1"></script>
|
||||||
<script src="/assets/app.js?v=20260708-hub-ai-save"></script>
|
<script src="/assets/app.js?v=20260711-hub-opt-cards"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user