Split live positions from hedge plans; show option+perp net PnL after fees.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -862,7 +862,8 @@
|
||||
}
|
||||
|
||||
function setHpPosTab(tab) {
|
||||
const next = tab === "pending" || tab === "history" || tab === "stats" ? tab : "live";
|
||||
const allowed = { live: 1, plans: 1, pending: 1, history: 1, stats: 1 };
|
||||
const next = allowed[tab] ? tab : "live";
|
||||
state.posTab = next;
|
||||
document.querySelectorAll(".hp-pos-tab").forEach(function (btn) {
|
||||
const on = btn.getAttribute("data-hp-pos-tab") === next;
|
||||
@@ -875,17 +876,146 @@
|
||||
if (on) pane.removeAttribute("hidden");
|
||||
else pane.setAttribute("hidden", "");
|
||||
});
|
||||
if (next === "live" || next === "pending") void loadActivePlans();
|
||||
if (next === "live") void loadLivePositions();
|
||||
else if (next === "plans" || next === "pending") void loadActivePlans();
|
||||
else if (next === "history") void loadHistory();
|
||||
else if (next === "stats") void loadStats();
|
||||
}
|
||||
|
||||
function refreshHpPosPanel() {
|
||||
void loadLivePositions();
|
||||
void loadActivePlans();
|
||||
void loadHistory();
|
||||
if (state.posTab === "stats") void loadStats();
|
||||
}
|
||||
|
||||
function pnlClsNum(v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "";
|
||||
const n = Number(v);
|
||||
if (n > 0) return "hp-pnl-pos";
|
||||
if (n < 0) return "hp-pnl-neg";
|
||||
return "";
|
||||
}
|
||||
|
||||
function fmtSigned(v, d) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
const s = fmt(n, d == null ? 2 : d);
|
||||
return n > 0 ? "+" + s : s;
|
||||
}
|
||||
|
||||
function renderLivePosCard(card) {
|
||||
const opt = card.option || {};
|
||||
const perp = card.perp || {};
|
||||
const combo = card.combo || {};
|
||||
const optType = String(opt.opt_type || "").toUpperCase() === "P" ? "Put" : "Call";
|
||||
const perpSide = String(perp.side || "").toLowerCase() === "short" ? "空" : "多";
|
||||
const comboCls = pnlClsNum(combo.net_usdt);
|
||||
return (
|
||||
'<article class="hp-live-card" data-plan-id="' +
|
||||
(card.plan_id || "") +
|
||||
'">' +
|
||||
'<div class="hp-live-card-head">' +
|
||||
"<strong>#" +
|
||||
(card.plan_id || "—") +
|
||||
" · " +
|
||||
(card.underlying || "") +
|
||||
'</strong><span class="muted">' +
|
||||
(card.opened_at || "") +
|
||||
"</span></div>" +
|
||||
'<div class="hp-live-legs">' +
|
||||
'<div class="hp-live-leg">' +
|
||||
'<div class="hp-live-leg-title">期权 · ' +
|
||||
optType +
|
||||
"</div>" +
|
||||
"<code>" +
|
||||
(opt.inst_id || "—") +
|
||||
"</code>" +
|
||||
'<div class="hp-live-leg-grid">' +
|
||||
"<span>张数 <strong>" +
|
||||
fmt(opt.sheets, 0) +
|
||||
"</strong></span>" +
|
||||
"<span>开仓 <strong>" +
|
||||
fmt(opt.avg_open, 4) +
|
||||
"</strong></span>" +
|
||||
"<span>标记 <strong>" +
|
||||
fmt(opt.mark_px, 4) +
|
||||
"</strong></span>" +
|
||||
'<span>净利 <strong class="' +
|
||||
pnlClsNum(opt.net_usdt) +
|
||||
'">' +
|
||||
fmtSigned(opt.net_usdt, 2) +
|
||||
" U</strong></span>" +
|
||||
"</div></div>" +
|
||||
'<div class="hp-live-leg">' +
|
||||
'<div class="hp-live-leg-title">永续 · ' +
|
||||
perpSide +
|
||||
"</div>" +
|
||||
"<code>" +
|
||||
(perp.symbol || "—") +
|
||||
"</code>" +
|
||||
'<div class="hp-live-leg-grid">' +
|
||||
"<span>张数 <strong>" +
|
||||
fmt(perp.contracts, 4) +
|
||||
"</strong></span>" +
|
||||
"<span>开仓 <strong>" +
|
||||
fmt(perp.avg_open, 2) +
|
||||
"</strong></span>" +
|
||||
"<span>标记 <strong>" +
|
||||
fmt(perp.mark_px, 2) +
|
||||
"</strong></span>" +
|
||||
'<span>净利 <strong class="' +
|
||||
pnlClsNum(perp.net_usdt) +
|
||||
'">' +
|
||||
fmtSigned(perp.net_usdt, 2) +
|
||||
" U</strong></span>" +
|
||||
"</div></div>" +
|
||||
"</div>" +
|
||||
'<div class="hp-live-combo">' +
|
||||
'<span class="muted">组合净盈亏(扣开平手续费)</span>' +
|
||||
'<strong class="' +
|
||||
comboCls +
|
||||
'">' +
|
||||
fmtSigned(combo.net_usdt, 2) +
|
||||
" U</strong>" +
|
||||
"</div>" +
|
||||
'<div class="hp-live-actions">' +
|
||||
'<button type="button" class="btn-secondary hp-btn-detail" data-id="' +
|
||||
(card.plan_id || "") +
|
||||
'">成交细节</button> ' +
|
||||
'<button type="button" class="btn-secondary hp-btn-end" data-id="' +
|
||||
(card.plan_id || "") +
|
||||
'">结束计划</button>' +
|
||||
"</div></article>"
|
||||
);
|
||||
}
|
||||
|
||||
async function loadLivePositions() {
|
||||
const box = $("hp-live-pos-list");
|
||||
if (!box) return;
|
||||
try {
|
||||
const d = await apiJson("/api/hedge-plan/live-positions");
|
||||
const cards = d.cards || [];
|
||||
if (!cards.length) {
|
||||
box.innerHTML = '<p class="muted">暂无期权/永续对冲持仓</p>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = cards.map(renderLivePosCard).join("");
|
||||
box.querySelectorAll(".hp-btn-detail").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
void showPlanDetail(Number(btn.getAttribute("data-id")));
|
||||
});
|
||||
});
|
||||
box.querySelectorAll(".hp-btn-end").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
void endActivePlan(Number(btn.getAttribute("data-id")));
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
box.innerHTML = '<p class="err">' + (e.message || e) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
/** 手动选约:不自动代选;仅在已选失效(换 Call/Put 或筛掉)时清空. */
|
||||
function autoMatchPoOption(force) {
|
||||
if (!isOptionPrimary()) return;
|
||||
@@ -3379,8 +3509,8 @@
|
||||
if (state.tab === "perp_options" && isOptionPrimary()) void refreshPoStrategyStatus();
|
||||
}, 15000);
|
||||
setInterval(function () {
|
||||
if (state.tab === "perp_options" && (state.posTab === "live" || state.posTab === "pending")) {
|
||||
void loadActivePlans();
|
||||
}
|
||||
}, 20000);
|
||||
if (state.tab !== "perp_options") return;
|
||||
if (state.posTab === "live") void loadLivePositions();
|
||||
else if (state.posTab === "plans" || state.posTab === "pending") void loadActivePlans();
|
||||
}, 8000);
|
||||
})();
|
||||
|
||||
@@ -4118,6 +4118,83 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
.hedge-plan-page-wrap .hp-po-pos-card .options-pos-pane {
|
||||
overflow: auto;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-pos-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-card {
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-legs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
@media (min-width: 720px) {
|
||||
.hedge-plan-page-wrap .hp-live-legs {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-leg {
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-leg-title {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
color: #9ec0ff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-leg code {
|
||||
display: block;
|
||||
font-size: 0.78rem;
|
||||
word-break: break-all;
|
||||
margin-bottom: 8px;
|
||||
color: #c5d0ee;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-leg-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px 10px;
|
||||
font-size: 0.76rem;
|
||||
color: #9aa4b2;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-leg-grid strong {
|
||||
color: #e8ecf4;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-combo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-combo strong {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-po-order-dialog .hp-po-section-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 0.85rem;
|
||||
|
||||
@@ -1931,7 +1931,6 @@
|
||||
'<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>" +
|
||||
sourceBadgeHtml(p) +
|
||||
"</div>" +
|
||||
'<div class="pos-head-actions">' +
|
||||
'<button type="button" class="btn-primary opt-close-btn" data-inst="' + p.inst_id + '" data-sheets="' + closeSheets + '">买一平仓</button>' +
|
||||
@@ -2192,7 +2191,6 @@
|
||||
'<span class="opt-pos-bar-id-group">' +
|
||||
'<strong class="opt-pos-bar-title" title="' + inst + '">' + inst + "</strong>" +
|
||||
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span>" +
|
||||
sourceBadgeHtml(p) +
|
||||
"</span>" +
|
||||
'<span class="opt-pos-bar-meta">行权 ' + fmt(p.strike, 0) + " · " + fmt(p.pos, 0) + "张</span>" +
|
||||
"</span>" +
|
||||
|
||||
@@ -249,7 +249,6 @@
|
||||
'<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>" +
|
||||
sourceBadgeHtml(p) +
|
||||
"</div>" +
|
||||
headActions +
|
||||
"</div>" +
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
"""永期对冲 · 当前持仓:期权+永续实时卡(组合净利扣开平双边手续费)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from lib.hedge_plan.hedge_plan_option_primary_lib import (
|
||||
estimate_combo_net_pnl,
|
||||
fee_rate,
|
||||
opt_type_for_view,
|
||||
perp_direction_for_view,
|
||||
)
|
||||
|
||||
|
||||
def _sf(v: Any) -> Optional[float]:
|
||||
try:
|
||||
if v is None or v == "":
|
||||
return None
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _pick_option_row(rows: list[dict[str, Any]] | None, inst_id: str) -> Optional[dict[str, Any]]:
|
||||
want = str(inst_id or "").strip()
|
||||
if not want or not rows:
|
||||
return None
|
||||
for r in rows:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
rid = str(r.get("instId") or r.get("inst_id") or "").strip()
|
||||
if rid == want:
|
||||
return r
|
||||
return None
|
||||
|
||||
|
||||
def _option_bid(ex: Any, inst_id: str, fallback: Optional[float]) -> Optional[float]:
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import fetch_option_book_depth
|
||||
|
||||
book = fetch_option_book_depth(ex, inst_id) or {}
|
||||
bids = book.get("bids") or []
|
||||
if bids and isinstance(bids[0], dict):
|
||||
bid = _sf(bids[0].get("px") or bids[0].get("price"))
|
||||
if bid is not None and bid > 0:
|
||||
return bid
|
||||
except Exception:
|
||||
pass
|
||||
return fallback
|
||||
|
||||
|
||||
def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any]]:
|
||||
"""进行中的永期计划 → 期权/永续腿实时卡 + 扣费组合净利."""
|
||||
from lib.hedge_plan.hedge_plan_db import attach_legs_to_plans, list_plans
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
for status in ("opening", "active", "partial"):
|
||||
rows.extend(list_plans(conn, status=status, limit=40))
|
||||
rows = [r for r in rows if str(r.get("plan_type") or "") == "perp_options"]
|
||||
rows.sort(key=lambda row: int(row.get("id") or 0), reverse=True)
|
||||
if not rows:
|
||||
return []
|
||||
plans = attach_legs_to_plans(conn, rows)
|
||||
|
||||
fetch_opt = cfg.get("fetch_option_positions")
|
||||
ex_opt = None
|
||||
try:
|
||||
get_ex = cfg.get("exchange_options")
|
||||
ex_opt = get_ex() if callable(get_ex) else get_ex
|
||||
except Exception:
|
||||
ex_opt = None
|
||||
opt_rows: list[dict[str, Any]] = []
|
||||
if callable(fetch_opt) and ex_opt is not None:
|
||||
try:
|
||||
opt_rows = fetch_opt(ex_opt) or []
|
||||
except Exception:
|
||||
opt_rows = []
|
||||
|
||||
get_perp_metrics = cfg.get("get_live_position_exchange_metrics")
|
||||
get_cs = cfg.get("get_contract_size")
|
||||
fetch_idx = cfg.get("fetch_index_price")
|
||||
fr = fee_rate()
|
||||
out: list[dict[str, Any]] = []
|
||||
|
||||
for plan in plans:
|
||||
legs = plan.get("legs") or []
|
||||
opt_leg = next((x for x in legs if str(x.get("leg_role") or "") in ("option_hedge", "option_a")), None)
|
||||
perp_leg = next((x for x in legs if str(x.get("leg_role") or "") == "perp"), None)
|
||||
if not opt_leg and not perp_leg:
|
||||
continue
|
||||
|
||||
view = str(plan.get("direction") or "long").lower()
|
||||
uly = str(plan.get("underlying") or "ETH").upper()
|
||||
inst_id = str((opt_leg or {}).get("inst_id") or "")
|
||||
opt_raw = _pick_option_row(opt_rows, inst_id) if inst_id else None
|
||||
|
||||
sheets = _sf((opt_leg or {}).get("size")) or _sf((opt_raw or {}).get("pos")) or 0.0
|
||||
ct = _sf((opt_leg or {}).get("ct_mult")) or 0.01
|
||||
avg_open = _sf((opt_leg or {}).get("avg_open")) or _sf((opt_raw or {}).get("avgPx")) or 0.0
|
||||
mark_opt = _sf((opt_raw or {}).get("markPx")) or _sf((opt_raw or {}).get("last")) or avg_open
|
||||
bid = _option_bid(ex_opt, inst_id, mark_opt) if inst_id else mark_opt
|
||||
strike = _sf((opt_leg or {}).get("strike")) or 0.0
|
||||
opt_type = str((opt_leg or {}).get("opt_type") or opt_type_for_view(view)).upper()
|
||||
|
||||
sym = str((perp_leg or {}).get("symbol") or plan.get("exchange_symbol") or "")
|
||||
perp_dir = str((perp_leg or {}).get("side") or plan.get("perp_direction") or perp_direction_for_view(view)).lower()
|
||||
contracts = _sf((perp_leg or {}).get("size")) or _sf(plan.get("perp_size")) or 0.0
|
||||
perp_entry = _sf((perp_leg or {}).get("avg_open")) or _sf(plan.get("entry_mark")) or 0.0
|
||||
cs = 0.01
|
||||
if callable(get_cs) and sym:
|
||||
try:
|
||||
cs = float(get_cs(sym) or 0.01)
|
||||
except Exception:
|
||||
cs = 0.01
|
||||
|
||||
perp_mark = None
|
||||
perp_upl = None
|
||||
live_contracts = None
|
||||
if callable(get_perp_metrics) and sym:
|
||||
try:
|
||||
m = get_perp_metrics(sym, perp_dir, order_leverage=plan.get("leverage"))
|
||||
if isinstance(m, dict):
|
||||
perp_mark = _sf(m.get("mark_price") or m.get("mark"))
|
||||
perp_upl = _sf(m.get("unrealized_pnl"))
|
||||
live_contracts = _sf(m.get("contracts") or m.get("size"))
|
||||
except Exception:
|
||||
pass
|
||||
if perp_mark is None or perp_mark <= 0:
|
||||
perp_mark = perp_entry
|
||||
if live_contracts and live_contracts > 0:
|
||||
contracts = live_contracts
|
||||
|
||||
idx = None
|
||||
if callable(fetch_idx):
|
||||
try:
|
||||
idx = _sf(fetch_idx(uly))
|
||||
except Exception:
|
||||
idx = None
|
||||
if idx is None or idx <= 0:
|
||||
idx = perp_mark or strike or 0.0
|
||||
|
||||
net = estimate_combo_net_pnl(
|
||||
view_side=view,
|
||||
strike=float(strike or 0),
|
||||
index_px=float(idx or 0),
|
||||
ask_open=float(avg_open or 0),
|
||||
bid=float(bid or 0),
|
||||
sheets=float(sheets or 0),
|
||||
ct_mult=float(ct or 0.01),
|
||||
perp_direction=perp_dir,
|
||||
perp_entry=float(perp_entry or 0),
|
||||
perp_mark=float(perp_mark or 0),
|
||||
contracts=float(contracts or 0),
|
||||
contract_size=float(cs or 0.01),
|
||||
fee=fr,
|
||||
)
|
||||
# 币本位期权净利(币) → USDT;永续净利已是 U
|
||||
opt_net_coin = float(net.get("opt_net") or 0)
|
||||
opt_net_usdt = round(opt_net_coin * float(idx or 0), 4) if idx else None
|
||||
perp_net = float(net.get("perp_net") or 0)
|
||||
combo_usdt = round((opt_net_usdt or 0) + perp_net, 4) if opt_net_usdt is not None else round(perp_net, 4)
|
||||
|
||||
out.append(
|
||||
{
|
||||
"plan_id": plan.get("id"),
|
||||
"status": plan.get("status"),
|
||||
"underlying": uly,
|
||||
"direction": view,
|
||||
"opened_at": plan.get("opened_at"),
|
||||
"index_px": idx,
|
||||
"fee_rate": fr,
|
||||
"option": {
|
||||
"inst_id": inst_id or "—",
|
||||
"opt_type": opt_type,
|
||||
"strike": strike,
|
||||
"sheets": sheets,
|
||||
"ct_mult": ct,
|
||||
"avg_open": avg_open,
|
||||
"mark_px": mark_opt,
|
||||
"bid": bid,
|
||||
"net_coin": opt_net_coin,
|
||||
"net_usdt": opt_net_usdt,
|
||||
"live": bool(opt_raw),
|
||||
},
|
||||
"perp": {
|
||||
"symbol": sym or "—",
|
||||
"side": perp_dir,
|
||||
"contracts": contracts,
|
||||
"contract_size": cs,
|
||||
"avg_open": perp_entry,
|
||||
"mark_px": perp_mark,
|
||||
"upl": perp_upl,
|
||||
"net_usdt": perp_net,
|
||||
"live": perp_upl is not None or (perp_mark is not None and perp_mark > 0),
|
||||
},
|
||||
"combo": {
|
||||
"net_usdt": combo_usdt,
|
||||
"opt_net_usdt": opt_net_usdt,
|
||||
"perp_net_usdt": perp_net,
|
||||
"note": "扣开平双边手续费后估",
|
||||
},
|
||||
}
|
||||
)
|
||||
return out
|
||||
@@ -50,6 +50,7 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
from lib.exchange.okx_options_lib import (
|
||||
build_option_chain,
|
||||
fetch_index_price,
|
||||
fetch_option_positions,
|
||||
options_header_balances,
|
||||
place_option_limit_order,
|
||||
quote_option_contract,
|
||||
@@ -76,6 +77,10 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
"place_exchange_order": getattr(app_module, "place_exchange_order", None),
|
||||
"close_exchange_order": getattr(app_module, "close_exchange_order", None),
|
||||
"get_live_position_contracts": getattr(app_module, "get_live_position_contracts", None),
|
||||
"get_live_position_exchange_metrics": getattr(
|
||||
app_module, "get_live_position_exchange_metrics", None
|
||||
),
|
||||
"fetch_option_positions": fetch_option_positions,
|
||||
"amount_to_precision": _amount_to_precision,
|
||||
"build_option_chain": build_option_chain,
|
||||
"options_header_balances": options_header_balances,
|
||||
@@ -1117,6 +1122,22 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
conn.close()
|
||||
return jsonify({"ok": True, "plans": plans})
|
||||
|
||||
@app.route("/api/hedge-plan/live-positions")
|
||||
@lr
|
||||
def api_hedge_live_positions():
|
||||
"""当前持仓:期权+永续实时卡,组合净利已扣开平双边手续费."""
|
||||
from lib.hedge_plan.hedge_plan_db import init_hedge_plan_tables
|
||||
from lib.hedge_plan.hedge_plan_live_positions_lib import build_live_hedge_cards
|
||||
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_hedge_plan_tables(conn)
|
||||
cards = build_live_hedge_cards(cfg, conn)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify({"ok": True, "cards": cards})
|
||||
|
||||
@app.route("/api/hedge-plan/stats")
|
||||
@lr
|
||||
def api_hedge_stats():
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
<button type="button" class="btn-secondary" id="hp-refresh" title="刷新永续行情与期权链">刷新行情</button>
|
||||
<span class="muted hp-po-gate-inline" id="hp-po-gate-inline" aria-live="polite"></span>
|
||||
</div>
|
||||
<div class="hp-tabs" role="tablist" aria-label="对冲模式" hidden>
|
||||
{# 模式由导航/env 决定,页内不再显示「永期对冲」Tab #}
|
||||
<div class="hp-tabs" role="tablist" aria-label="对冲模式" hidden style="display:none!important">
|
||||
{% if hedge_plan_show_perp_options %}
|
||||
<button type="button" class="hp-tab" role="tab" aria-selected="false" data-tab="perp_options">永期对冲</button>
|
||||
{% endif %}
|
||||
@@ -213,13 +214,18 @@
|
||||
</div>
|
||||
<div class="options-pos-tabs" role="tablist" aria-label="永期持仓面板">
|
||||
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab active" data-hp-pos-tab="live" role="tab" aria-selected="true">当前持仓</button>
|
||||
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab" data-hp-pos-tab="plans" role="tab" aria-selected="false">对冲计划</button>
|
||||
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab" data-hp-pos-tab="pending" role="tab" aria-selected="false">当前委托</button>
|
||||
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab" data-hp-pos-tab="history" role="tab" aria-selected="false">历史记录</button>
|
||||
<button type="button" class="btn-secondary opt-pos-tab hp-pos-tab" data-hp-pos-tab="stats" role="tab" aria-selected="false">统计分析</button>
|
||||
</div>
|
||||
<div class="options-pos-tab-body">
|
||||
<div class="options-pos-pane is-active" data-hp-pos-pane="live" role="tabpanel">
|
||||
<p class="muted" style="margin:0 0 8px;font-size:.82rem">进行中的对冲计划(持仓中 / 半腿待补)</p>
|
||||
<div id="hp-live-pos-list" class="hp-live-pos-list">
|
||||
<p class="muted">加载中…</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="options-pos-pane" data-hp-pos-pane="plans" role="tabpanel" hidden>
|
||||
<div class="options-strike-table-wrap">
|
||||
<table class="options-strike-table hp-history-table">
|
||||
<thead>
|
||||
@@ -426,4 +432,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/hedge_plan.js?v=53"></script>
|
||||
<script src="/static/hedge_plan.js?v=54"></script>
|
||||
|
||||
Reference in New Issue
Block a user