Polish dashboard tables: hub-style orders, options source/expiry, hedge status.
Show 进行中 in green for active hedges; options columns include source/target/expiry; merge live mark/contracts/pnl from price snapshot. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,6 +37,52 @@
|
||||
return '<span class="' + cls + '">' + sign + n.toFixed(2) + "U</span>";
|
||||
}
|
||||
|
||||
function fmtPnlPlain(v) {
|
||||
if (v == null || v === "") return "—";
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return "—";
|
||||
const cls = n > 0 ? "pos-pnl-profit" : n < 0 ? "pos-pnl-loss" : "";
|
||||
return '<span class="' + cls + '">' + n.toFixed(2) + "</span>";
|
||||
}
|
||||
|
||||
function dirCell(it) {
|
||||
const d = String(it.direction || "").toLowerCase();
|
||||
const label = it.direction_label || (d === "short" ? "做空" : d === "long" ? "做多" : "-");
|
||||
const cls = d === "short" ? "inst-dash-dir-short" : d === "long" ? "inst-dash-dir-long" : "";
|
||||
return '<td class="' + cls + '">' + escapeHtml(label) + "</td>";
|
||||
}
|
||||
|
||||
function fmtExpiry(ms) {
|
||||
const n = Number(ms);
|
||||
if (!Number.isFinite(n) || n <= 0) return "—";
|
||||
let fallback = "—";
|
||||
try {
|
||||
const d = new Date(n);
|
||||
if (!Number.isNaN(d.getTime())) {
|
||||
const pad = function (x) {
|
||||
return String(x).padStart(2, "0");
|
||||
};
|
||||
fallback =
|
||||
d.getFullYear() +
|
||||
"-" +
|
||||
pad(d.getMonth() + 1) +
|
||||
"-" +
|
||||
pad(d.getDate()) +
|
||||
" " +
|
||||
pad(d.getHours()) +
|
||||
":" +
|
||||
pad(d.getMinutes());
|
||||
}
|
||||
} catch (_) {}
|
||||
return (
|
||||
'<span class="opt-expiry-cd" data-opt-exp-ms="' +
|
||||
escapeHtml(String(Math.floor(n))) +
|
||||
'">' +
|
||||
escapeHtml(fallback) +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
|
||||
function goTab(tab) {
|
||||
if (!tab) return;
|
||||
if (global.InstanceEmbed && typeof global.InstanceEmbed.loadTab === "function") {
|
||||
@@ -59,9 +105,11 @@
|
||||
'<div class="inst-dash-table-wrap">' +
|
||||
'<table class="inst-dash-table">' +
|
||||
"<thead><tr>" +
|
||||
headers.map(function (h) {
|
||||
return "<th>" + escapeHtml(h) + "</th>";
|
||||
}).join("") +
|
||||
headers
|
||||
.map(function (h) {
|
||||
return "<th>" + escapeHtml(h) + "</th>";
|
||||
})
|
||||
.join("") +
|
||||
"</tr></thead>" +
|
||||
"<tbody>" +
|
||||
rowsHtml +
|
||||
@@ -73,36 +121,84 @@
|
||||
return ' class="inst-dash-row" data-dash-tab="' + escapeHtml(tab || "") + '" role="link" tabindex="0"';
|
||||
}
|
||||
|
||||
function mergeOrderLive(items, orderPrices) {
|
||||
const map = {};
|
||||
(orderPrices || []).forEach(function (p) {
|
||||
if (p && p.id != null) map[String(p.id)] = p;
|
||||
});
|
||||
return (items || []).map(function (it) {
|
||||
const live = map[String(it.id)] || {};
|
||||
const mark =
|
||||
live.exchange_mark_price != null
|
||||
? live.exchange_mark_price
|
||||
: live.price != null
|
||||
? live.price
|
||||
: it.mark_price;
|
||||
const contracts =
|
||||
live.contracts != null
|
||||
? live.contracts
|
||||
: live.order_amount != null
|
||||
? live.order_amount
|
||||
: it.contracts;
|
||||
const entry =
|
||||
live.avg_entry_price != null
|
||||
? live.avg_entry_price
|
||||
: it.entry;
|
||||
return Object.assign({}, it, {
|
||||
entry: entry,
|
||||
mark_price: mark,
|
||||
mark_display: live.price_display || null,
|
||||
contracts: contracts,
|
||||
tp_profit: live.reward_at_tp_usdt != null ? live.reward_at_tp_usdt : it.tp_profit,
|
||||
float_pnl: live.float_pnl != null ? live.float_pnl : it.float_pnl,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderOrdersTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
const sym = it.symbol || "-";
|
||||
const mark =
|
||||
it.mark_display != null && it.mark_display !== ""
|
||||
? escapeHtml(it.mark_display)
|
||||
: fmtNum(it.mark_price);
|
||||
const tpProfit =
|
||||
it.tp_profit != null && Number.isFinite(Number(it.tp_profit))
|
||||
? '<span class="pos-tp-profit">' + Number(it.tp_profit).toFixed(2) + "U</span>"
|
||||
: "—";
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "trade") +
|
||||
">" +
|
||||
"<td>" +
|
||||
escapeHtml(it.symbol || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.direction_label || it.direction || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.subtitle || "—") +
|
||||
"</td>" +
|
||||
'<td class="td-symbol"><span class="inst-dash-sym-link">' +
|
||||
escapeHtml(sym) +
|
||||
"</span></td>" +
|
||||
dirCell(it) +
|
||||
"<td>" +
|
||||
fmtNum(it.entry) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.stop_loss) +
|
||||
mark +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.take_profit) +
|
||||
fmtNum(it.contracts) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
tpProfit +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtPnlPlain(it.float_pnl) +
|
||||
"</td>" +
|
||||
"<td>—</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["合约", "方向", "类型", "入场", "止损", "止盈"], rows);
|
||||
return tableWrap(
|
||||
["合约", "方向", "开仓价", "标记价", "张数", "盈利金额", "浮盈", "操作"],
|
||||
rows
|
||||
);
|
||||
}
|
||||
|
||||
function renderKeysTable(items) {
|
||||
@@ -115,9 +211,7 @@
|
||||
"<td>" +
|
||||
escapeHtml(it.symbol || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.direction_label || it.direction || "-") +
|
||||
"</td>" +
|
||||
dirCell(it) +
|
||||
"<td>" +
|
||||
escapeHtml(it.subtitle || "—") +
|
||||
"</td>" +
|
||||
@@ -148,9 +242,7 @@
|
||||
"<td>" +
|
||||
escapeHtml(it.symbol || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.direction_label || it.direction || "-") +
|
||||
"</td>" +
|
||||
dirCell(it) +
|
||||
"<td>" +
|
||||
escapeHtml(it.status || "—") +
|
||||
"</td>" +
|
||||
@@ -167,12 +259,12 @@
|
||||
function renderOptionsTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
const opt =
|
||||
String(it.opt_type || "").toUpperCase() === "C"
|
||||
const opt = it.opt_type_label ||
|
||||
(String(it.opt_type || "").toUpperCase() === "C"
|
||||
? "Call"
|
||||
: String(it.opt_type || "").toUpperCase() === "P"
|
||||
? "Put"
|
||||
: it.opt_type || "—";
|
||||
: it.opt_type || "—");
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "options") +
|
||||
@@ -181,24 +273,35 @@
|
||||
escapeHtml(it.inst_id || it.title || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.source_label || "纯期权") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(opt) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.pos) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtExpiry(it.exp_time_ms) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.target_monitor || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtPnl(it.pnl) +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["合约", "类型", "张数", "盈亏"], rows);
|
||||
return tableWrap(["合约", "来源", "类型", "张数", "到期时间", "目标监控", "盈亏"], rows);
|
||||
}
|
||||
|
||||
function renderHedgeTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
const stCls = it.status_active ? "inst-dash-status-active" : "";
|
||||
const stText = it.status_label || (it.status_active ? "进行中" : it.status || "—");
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "hedge_plan") +
|
||||
@@ -210,13 +313,15 @@
|
||||
escapeHtml(it.underlying || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.plan_type || "—") +
|
||||
escapeHtml(it.plan_type_label || it.plan_type || "—") +
|
||||
"</td>" +
|
||||
'<td class="' +
|
||||
stCls +
|
||||
'">' +
|
||||
escapeHtml(stText) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.status || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.subtitle || "—") +
|
||||
escapeHtml(it.contracts_summary || it.subtitle || "—") +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
@@ -290,12 +395,28 @@
|
||||
loading = true;
|
||||
if (status && !(opts && opts.silent)) status.textContent = "加载中…";
|
||||
try {
|
||||
const res = await fetch("/api/instance/dashboard", { credentials: "same-origin" });
|
||||
const data = await res.json().catch(function () {
|
||||
const [dashRes, priceRes] = await Promise.all([
|
||||
fetch("/api/instance/dashboard", { credentials: "same-origin" }),
|
||||
fetch("/api/price_snapshot", { credentials: "same-origin" }).catch(function () {
|
||||
return null;
|
||||
}),
|
||||
]);
|
||||
const data = await dashRes.json().catch(function () {
|
||||
return {};
|
||||
});
|
||||
if (!res.ok || !data.ok) {
|
||||
throw new Error(data.msg || res.statusText || "加载失败");
|
||||
if (!dashRes.ok || !data.ok) {
|
||||
throw new Error(data.msg || dashRes.statusText || "加载失败");
|
||||
}
|
||||
let orderPrices = [];
|
||||
if (priceRes && priceRes.ok) {
|
||||
try {
|
||||
const snap = await priceRes.json();
|
||||
orderPrices = snap.order_prices || [];
|
||||
} catch (_) {}
|
||||
}
|
||||
if (data.orders && Array.isArray(data.orders.items)) {
|
||||
data.orders.items = mergeOrderLive(data.orders.items, orderPrices);
|
||||
data.orders.count = data.orders.items.length;
|
||||
}
|
||||
if (updated) updated.textContent = "更新 " + (data.updated_at || "—");
|
||||
if (sections) {
|
||||
@@ -305,6 +426,14 @@
|
||||
sections.innerHTML =
|
||||
html || '<p class="inst-dash-empty muted">当前无活跃监控与持仓</p>';
|
||||
bindClicks(sections);
|
||||
if (global.OptionsExpiryCountdown) {
|
||||
if (typeof global.OptionsExpiryCountdown.tick === "function") {
|
||||
global.OptionsExpiryCountdown.tick(sections);
|
||||
}
|
||||
if (typeof global.OptionsExpiryCountdown.ensureTimer === "function") {
|
||||
global.OptionsExpiryCountdown.ensureTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status) status.textContent = "";
|
||||
} catch (e) {
|
||||
|
||||
@@ -287,3 +287,8 @@
|
||||
.inst-dash-table tbody tr:last-child td{border-bottom:none}
|
||||
.inst-dash-table tbody tr.inst-dash-row{cursor:pointer}
|
||||
.inst-dash-table tbody tr.inst-dash-row:hover{background:#1e2740}
|
||||
.inst-dash-sym-link{color:#8fc8ff;text-decoration:underline}
|
||||
.inst-dash-dir-long{color:#4cd97f;font-weight:600}
|
||||
.inst-dash-dir-short{color:#ff6666;font-weight:600}
|
||||
.inst-dash-status-active{color:#4cd97f;font-weight:600}
|
||||
.inst-dash-table .pos-tp-profit{color:#cfd3ef}
|
||||
|
||||
@@ -73,6 +73,7 @@ def install_instance_theme_static(app) -> None:
|
||||
"instance_live.js": "application/javascript; charset=utf-8",
|
||||
"instance_settings_prefs.js": "application/javascript; charset=utf-8",
|
||||
"instance_dashboard.js": "application/javascript; charset=utf-8",
|
||||
"options_expiry_countdown.js": "application/javascript; charset=utf-8",
|
||||
"options_panel.js": "application/javascript; charset=utf-8",
|
||||
"order_entry_model.js": "application/javascript; charset=utf-8",
|
||||
"focus_chart_page.js": "application/javascript; charset=utf-8",
|
||||
|
||||
@@ -67,12 +67,137 @@ def _format_order_item(od: dict[str, Any]) -> dict[str, Any]:
|
||||
"direction": direction,
|
||||
"direction_label": _dir_label(direction),
|
||||
"entry": entry,
|
||||
"mark_price": None,
|
||||
"contracts": _safe_float(od.get("order_amount")),
|
||||
"tp_profit": None,
|
||||
"float_pnl": None,
|
||||
"stop_loss": sl,
|
||||
"take_profit": tp,
|
||||
"status": od.get("status") or "active",
|
||||
}
|
||||
|
||||
|
||||
OPTIONS_SOURCE_LABELS = {
|
||||
"option": "纯期权",
|
||||
"perp_options": "永期对冲",
|
||||
"options_options": "期期对冲",
|
||||
}
|
||||
|
||||
HEDGE_ACTIVE_STATUSES = frozenset({"opening", "active", "partial"})
|
||||
|
||||
|
||||
def _resolve_options_source(conn, inst_id: str) -> tuple[str, str]:
|
||||
"""根据进行中对冲计划腿判定来源;默认纯期权."""
|
||||
if not inst_id or not _table_exists(conn, "hedge_plans") or not _table_exists(conn, "hedge_plan_legs"):
|
||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
||||
try:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT p.plan_type
|
||||
FROM hedge_plans p
|
||||
JOIN hedge_plan_legs l ON l.plan_id = p.id
|
||||
WHERE p.status IN ('opening', 'active', 'partial')
|
||||
AND l.status = 'open'
|
||||
AND l.inst_id = ?
|
||||
ORDER BY p.id DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(inst_id,),
|
||||
).fetchone()
|
||||
except Exception:
|
||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
||||
if not row:
|
||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
||||
pt = str((_row_dict(row).get("plan_type") if isinstance(row, dict) else row[0]) or "").strip()
|
||||
if pt in OPTIONS_SOURCE_LABELS:
|
||||
return pt, OPTIONS_SOURCE_LABELS[pt]
|
||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
||||
|
||||
|
||||
def _format_options_target(p: dict[str, Any]) -> str:
|
||||
hedge = p.get("hedge_plan_target") if isinstance(p.get("hedge_plan_target"), dict) else None
|
||||
opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
|
||||
if hedge:
|
||||
ot = str(hedge.get("opt_type") or opt_type).upper()
|
||||
side = "Put ≤" if ot == "P" else "Call ≥"
|
||||
tgt = _safe_float(hedge.get("target_index"))
|
||||
pid = hedge.get("plan_id")
|
||||
if tgt is not None:
|
||||
return f"对冲#{pid} {side} {tgt:g}" if pid is not None else f"{side} {tgt:g}"
|
||||
tgt = _safe_float(p.get("target_index"))
|
||||
if tgt is not None and tgt > 0:
|
||||
side = "Put ≤" if opt_type == "P" else "Call ≥"
|
||||
return f"{side} {tgt:g}"
|
||||
return "—"
|
||||
|
||||
|
||||
def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]:
|
||||
inst = str(p.get("inst_id") or p.get("instId") or "-").strip() or "-"
|
||||
opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
|
||||
label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else (opt_type or "OPT")
|
||||
upl = _safe_float(p.get("upl"))
|
||||
net = None
|
||||
try:
|
||||
from lib.options.options_positions_lib import net_pnl_from_display_row
|
||||
|
||||
net = net_pnl_from_display_row(p)
|
||||
except Exception:
|
||||
net = None
|
||||
pnl = net if net is not None else upl
|
||||
pos = _safe_float(p.get("pos"))
|
||||
exp_ms = p.get("exp_time_ms")
|
||||
if exp_ms is None:
|
||||
exp_ms = p.get("exp_time")
|
||||
try:
|
||||
exp_ms = int(float(exp_ms)) if exp_ms not in (None, "") else None
|
||||
except (TypeError, ValueError):
|
||||
exp_ms = None
|
||||
source_key, source_label = (
|
||||
_resolve_options_source(conn, inst) if conn is not None else ("option", OPTIONS_SOURCE_LABELS["option"])
|
||||
)
|
||||
return {
|
||||
"id": inst,
|
||||
"kind": "options",
|
||||
"tab": "options",
|
||||
"title": f"{inst} {label}",
|
||||
"subtitle": f"张数 {pos if pos is not None else '-'}",
|
||||
"inst_id": inst,
|
||||
"opt_type": opt_type,
|
||||
"opt_type_label": label,
|
||||
"source": source_key,
|
||||
"source_label": source_label,
|
||||
"pos": pos,
|
||||
"exp_time_ms": exp_ms,
|
||||
"target_monitor": _format_options_target(p),
|
||||
"pnl": round(pnl, 4) if pnl is not None else None,
|
||||
}
|
||||
|
||||
|
||||
def _format_hedge_item(plan: dict[str, Any]) -> dict[str, Any]:
|
||||
pid = plan.get("id")
|
||||
underlying = plan.get("underlying") or "-"
|
||||
plan_type = plan.get("plan_type") or ""
|
||||
status = str(plan.get("status") or "")
|
||||
summary = plan.get("contracts_summary") or ""
|
||||
plan_type_label = OPTIONS_SOURCE_LABELS.get(plan_type, plan_type)
|
||||
active = status in HEDGE_ACTIVE_STATUSES
|
||||
status_label = "进行中" if active else (status or "—")
|
||||
return {
|
||||
"id": pid,
|
||||
"kind": "hedge_plan",
|
||||
"tab": "hedge_plan",
|
||||
"title": f"对冲 #{pid} {underlying}",
|
||||
"subtitle": " · ".join(x for x in (plan_type_label, status_label, summary) if x),
|
||||
"underlying": underlying,
|
||||
"plan_type": plan_type,
|
||||
"plan_type_label": plan_type_label,
|
||||
"status": status,
|
||||
"status_label": status_label,
|
||||
"status_active": active,
|
||||
"contracts_summary": summary,
|
||||
}
|
||||
|
||||
|
||||
def _format_key_item(kd: dict[str, Any]) -> dict[str, Any]:
|
||||
sym = kd.get("exchange_symbol") or kd.get("symbol") or "-"
|
||||
direction = str(kd.get("direction") or "long").lower()
|
||||
@@ -137,51 +262,6 @@ def _format_roll_item(rd: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _format_options_item(p: dict[str, Any]) -> dict[str, Any]:
|
||||
inst = p.get("inst_id") or p.get("instId") or "-"
|
||||
opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
|
||||
label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else (opt_type or "OPT")
|
||||
upl = _safe_float(p.get("upl"))
|
||||
net = None
|
||||
try:
|
||||
from lib.options.options_positions_lib import net_pnl_from_display_row
|
||||
|
||||
net = net_pnl_from_display_row(p)
|
||||
except Exception:
|
||||
net = None
|
||||
pnl = net if net is not None else upl
|
||||
pos = _safe_float(p.get("pos"))
|
||||
return {
|
||||
"id": inst,
|
||||
"kind": "options",
|
||||
"tab": "options",
|
||||
"title": f"{inst} {label}",
|
||||
"subtitle": f"张数 {pos if pos is not None else '-'}",
|
||||
"inst_id": inst,
|
||||
"opt_type": opt_type,
|
||||
"pnl": round(pnl, 4) if pnl is not None else None,
|
||||
"pos": pos,
|
||||
}
|
||||
|
||||
|
||||
def _format_hedge_item(plan: dict[str, Any]) -> dict[str, Any]:
|
||||
pid = plan.get("id")
|
||||
underlying = plan.get("underlying") or "-"
|
||||
plan_type = plan.get("plan_type") or ""
|
||||
status = plan.get("status") or ""
|
||||
summary = plan.get("contracts_summary") or ""
|
||||
return {
|
||||
"id": pid,
|
||||
"kind": "hedge_plan",
|
||||
"tab": "hedge_plan",
|
||||
"title": f"对冲 #{pid} {underlying}",
|
||||
"subtitle": " · ".join(x for x in (plan_type, status, summary) if x),
|
||||
"underlying": underlying,
|
||||
"plan_type": plan_type,
|
||||
"status": status,
|
||||
}
|
||||
|
||||
|
||||
def _table_exists(conn, name: str) -> bool:
|
||||
try:
|
||||
row = conn.execute(
|
||||
@@ -253,6 +333,8 @@ def collect_hedge_plans(conn) -> list[dict[str, Any]]:
|
||||
|
||||
def collect_options_items(
|
||||
fetch_options_positions: Optional[Callable[[], list[dict[str, Any]]]] = None,
|
||||
*,
|
||||
conn=None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if not callable(fetch_options_positions):
|
||||
return []
|
||||
@@ -264,7 +346,7 @@ def collect_options_items(
|
||||
for p in raw:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
out.append(_format_options_item(p))
|
||||
out.append(_format_options_item(p, conn=conn))
|
||||
return out
|
||||
|
||||
|
||||
@@ -279,7 +361,7 @@ def build_instance_dashboard_payload(
|
||||
trends = collect_trends(conn)
|
||||
rolls = collect_rolls(conn)
|
||||
strategy_items = trends + rolls
|
||||
options_items = collect_options_items(fetch_options_positions)
|
||||
options_items = collect_options_items(fetch_options_positions, conn=conn)
|
||||
hedge_items = collect_hedge_plans(conn) if hedge_enabled else []
|
||||
now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S")
|
||||
return {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script src="/static/instance_theme.js?v=50"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=8">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=9">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=95">
|
||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
@@ -110,7 +110,8 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
||||
<script src="/static/instance_stats.js?v=4"></script>
|
||||
{% include 'embed_boot_scripts.html' %}
|
||||
<script src="/static/records_review_page.js?v=2"></script>
|
||||
<script src="/static/instance_dashboard.js?v=2"></script>
|
||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||
<script src="/static/instance_dashboard.js?v=3"></script>
|
||||
<script>
|
||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||
<title>{{ pwa_app_name }}</title>
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=8">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=9">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=95">
|
||||
|
||||
</head>
|
||||
@@ -1992,7 +1992,8 @@ tickOrderHoldDurations();
|
||||
setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }});
|
||||
</script>
|
||||
<script src="/static/records_review_page.js?v=2"></script>
|
||||
<script src="/static/instance_dashboard.js?v=2"></script>
|
||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||
<script src="/static/instance_dashboard.js?v=3"></script>
|
||||
<script>
|
||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||
{% if page == 'dashboard' %}
|
||||
|
||||
@@ -103,9 +103,70 @@ class TestInstanceDashboardLib(unittest.TestCase):
|
||||
self.assertEqual(payload["strategy"]["count"], 2)
|
||||
self.assertTrue(payload["options"]["visible"])
|
||||
self.assertEqual(payload["options"]["count"], 1)
|
||||
self.assertEqual(payload["options"]["items"][0]["source_label"], "纯期权")
|
||||
self.assertFalse(payload["hedge_plan"]["visible"])
|
||||
conn.close()
|
||||
|
||||
def test_hedge_status_label_active(self):
|
||||
conn = _mem_conn()
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE hedge_plans (
|
||||
id INTEGER PRIMARY KEY,
|
||||
underlying TEXT,
|
||||
plan_type TEXT,
|
||||
status TEXT
|
||||
);
|
||||
CREATE TABLE hedge_plan_legs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
plan_id INTEGER,
|
||||
leg_role TEXT,
|
||||
symbol TEXT,
|
||||
inst_id TEXT,
|
||||
opt_type TEXT,
|
||||
status TEXT
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO hedge_plans (id, underlying, plan_type, status) "
|
||||
"VALUES (2, 'ETH', 'options_options', 'active')"
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO hedge_plan_legs (plan_id, leg_role, inst_id, opt_type, status) "
|
||||
"VALUES (2, 'option', 'ETH-USD-260719-1850-P', 'P', 'open')"
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def fetch_opts():
|
||||
return [
|
||||
{
|
||||
"inst_id": "ETH-USD-260719-1850-P",
|
||||
"opt_type": "P",
|
||||
"pos": 40,
|
||||
"upl": 1.2,
|
||||
"exp_time_ms": 1784505600000,
|
||||
"hedge_plan_target": {
|
||||
"plan_id": 2,
|
||||
"opt_type": "P",
|
||||
"target_index": 1800,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
payload = build_instance_dashboard_payload(
|
||||
conn,
|
||||
fetch_options_positions=fetch_opts,
|
||||
hedge_enabled=True,
|
||||
)
|
||||
self.assertTrue(payload["hedge_plan"]["visible"])
|
||||
self.assertEqual(payload["hedge_plan"]["items"][0]["status_label"], "进行中")
|
||||
self.assertTrue(payload["hedge_plan"]["items"][0]["status_active"])
|
||||
opt = payload["options"]["items"][0]
|
||||
self.assertEqual(opt["source_label"], "期期对冲")
|
||||
self.assertIn("对冲#2", opt["target_monitor"])
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user