feat(hub): show exchange mark price on monitor positions
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -408,6 +408,24 @@ def _position_entry_price(p: dict[str, Any]) -> float | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _position_mark_price(p: dict[str, Any]) -> float | None:
|
||||||
|
"""四所 ccxt 持仓统一解析标记价(用于强平/浮盈计算)。"""
|
||||||
|
info = p.get("info") or {}
|
||||||
|
if not isinstance(info, dict):
|
||||||
|
info = {}
|
||||||
|
for key in (
|
||||||
|
p.get("markPrice"),
|
||||||
|
p.get("mark_price"),
|
||||||
|
info.get("markPx"),
|
||||||
|
info.get("mark_price"),
|
||||||
|
info.get("markPrice"),
|
||||||
|
):
|
||||||
|
px = _finite_or_none(key)
|
||||||
|
if px is not None and px > 0:
|
||||||
|
return px
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _extract_usdt_total(balance: dict[str, Any]) -> float | None:
|
def _extract_usdt_total(balance: dict[str, Any]) -> float | None:
|
||||||
"""从 ccxt balance 结构中尽量取出 USDT 总额(与 crypto_monitor_binance 一致)。"""
|
"""从 ccxt balance 结构中尽量取出 USDT 总额(与 crypto_monitor_binance 一致)。"""
|
||||||
usdt_info = balance.get("USDT") or {}
|
usdt_info = balance.get("USDT") or {}
|
||||||
@@ -583,6 +601,10 @@ def _status_inner(x_control_token: str | None) -> Any:
|
|||||||
notional_f = None
|
notional_f = None
|
||||||
entry_f = _position_entry_price(p)
|
entry_f = _position_entry_price(p)
|
||||||
_, entry_fmt, price_tick = _position_price_fmt(ex, sym, entry_f)
|
_, entry_fmt, price_tick = _position_price_fmt(ex, sym, entry_f)
|
||||||
|
mark_f = _position_mark_price(p)
|
||||||
|
_, mark_fmt, mark_tick = _position_price_fmt(ex, sym, mark_f)
|
||||||
|
if price_tick is None and mark_tick is not None:
|
||||||
|
price_tick = mark_tick
|
||||||
positions_out.append(
|
positions_out.append(
|
||||||
{
|
{
|
||||||
"symbol": sym,
|
"symbol": sym,
|
||||||
@@ -593,6 +615,8 @@ def _status_inner(x_control_token: str | None) -> Any:
|
|||||||
"unrealized_pnl": _finite_or_none(upnl_f),
|
"unrealized_pnl": _finite_or_none(upnl_f),
|
||||||
"entry_price": entry_f,
|
"entry_price": entry_f,
|
||||||
"entry_price_fmt": entry_fmt,
|
"entry_price_fmt": entry_fmt,
|
||||||
|
"mark_price": mark_f,
|
||||||
|
"mark_price_fmt": mark_fmt,
|
||||||
"price_tick": _finite_or_none(price_tick) if price_tick is not None else None,
|
"price_tick": _finite_or_none(price_tick) if price_tick is not None else None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -261,6 +261,18 @@
|
|||||||
return fmtSymbolPrice(positionEntryPrice(pos), pos && pos.symbol, tickMap);
|
return fmtSymbolPrice(positionEntryPrice(pos), pos && pos.symbol, tickMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function positionMarkPrice(pos) {
|
||||||
|
if (!pos) return null;
|
||||||
|
const n = Number(pos.mark_price);
|
||||||
|
if (!Number.isFinite(n) || n <= 0) return null;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtMarkPrice(pos, tickMap) {
|
||||||
|
if (pos && pos.mark_price_fmt) return String(pos.mark_price_fmt);
|
||||||
|
return fmtSymbolPrice(positionMarkPrice(pos), pos && pos.symbol, tickMap);
|
||||||
|
}
|
||||||
|
|
||||||
function pnlCls(v) {
|
function pnlCls(v) {
|
||||||
const n = Number(v);
|
const n = Number(v);
|
||||||
if (!Number.isFinite(n) || n === 0) return "";
|
if (!Number.isFinite(n) || n === 0) return "";
|
||||||
@@ -1301,6 +1313,7 @@
|
|||||||
<div class="pos-meta">${meta.map((m) => `<span class="pos-meta-item">${m}</span>`).join("")}</div>
|
<div class="pos-meta">${meta.map((m) => `<span class="pos-meta-item">${m}</span>`).join("")}</div>
|
||||||
<div class="pos-grid">
|
<div class="pos-grid">
|
||||||
<div class="pos-cell"><span class="pos-label">开仓价</span><span class="pos-value">${fmtEntryPrice(pos, tickMap)}</span></div>
|
<div class="pos-cell"><span class="pos-label">开仓价</span><span class="pos-value">${fmtEntryPrice(pos, tickMap)}</span></div>
|
||||||
|
<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">${fmtMarkPrice(pos, tickMap)}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">止损</span><span class="pos-value">${sl != null && sl !== "" ? fmtSymbolPrice(sl, symbol, tickMap) : "—"}</span></div>
|
<div class="pos-cell"><span class="pos-label">止损</span><span class="pos-value">${sl != null && sl !== "" ? fmtSymbolPrice(sl, symbol, tickMap) : "—"}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value">${tpMonitored ? "程序监控" : tp != null && tp !== "" ? fmtSymbolPrice(tp, symbol, tickMap) : "—"}</span></div>
|
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value">${tpMonitored ? "程序监控" : tp != null && tp !== "" ? fmtSymbolPrice(tp, symbol, tickMap) : "—"}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${tpMonitored ? "—" : rr != null ? fmt(rr, 2) + ":1" : "-:1"}</span></div>
|
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${tpMonitored ? "—" : rr != null ? fmt(rr, 2) + ":1" : "-:1"}</span></div>
|
||||||
@@ -1396,11 +1409,12 @@
|
|||||||
const symBeBadge = beSecured ? ` ${breakevenBadgeHtml()}` : "";
|
const symBeBadge = beSecured ? ` ${breakevenBadgeHtml()}` : "";
|
||||||
return `<div class="pos-block">
|
return `<div class="pos-block">
|
||||||
<div class="table-scroll">
|
<div class="table-scroll">
|
||||||
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>开仓价</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
|
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>开仓价</th><th>标记价</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-symbol"><button type="button" class="btn-open-market sym-link" ${mktAttrs} title="打开行情区(含入场/止盈止损)">${esc(x.symbol)}</button>${symBeBadge}</td>
|
<td class="td-symbol"><button type="button" class="btn-open-market sym-link" ${mktAttrs} title="打开行情区(含入场/止盈止损)">${esc(x.symbol)}</button>${symBeBadge}</td>
|
||||||
<td class="${sideDirCls(x.side)}">${renderDirectionHtml(x.side)}</td>
|
<td class="${sideDirCls(x.side)}">${renderDirectionHtml(x.side)}</td>
|
||||||
<td class="td-entry">${fmtEntryPrice(x, tickMap)}</td>
|
<td class="td-entry">${fmtEntryPrice(x, tickMap)}</td>
|
||||||
|
<td>${fmtMarkPrice(x, tickMap)}</td>
|
||||||
<td>${fmt(x.contracts, 4)}</td>
|
<td>${fmt(x.contracts, 4)}</td>
|
||||||
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td>
|
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td>
|
||||||
<td class="td-actions">
|
<td class="td-actions">
|
||||||
|
|||||||
@@ -245,6 +245,6 @@
|
|||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||||
<script src="/assets/chart.js?v=20260528-hub-no-30m"></script>
|
<script src="/assets/chart.js?v=20260528-hub-no-30m"></script>
|
||||||
<script src="/assets/app.js?v=20260603-hub-board-sse"></script>
|
<script src="/assets/app.js?v=20260603-hub-mark-price"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""子代理持仓:四所标记价字段统一解析。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT / "manual_trading_hub"))
|
||||||
|
|
||||||
|
from agent import _position_mark_price # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class TestHubAgentMarkPrice(unittest.TestCase):
|
||||||
|
def test_binance_mark_price(self):
|
||||||
|
px = _position_mark_price({"markPrice": 65880.1, "info": {}})
|
||||||
|
self.assertAlmostEqual(px, 65880.1)
|
||||||
|
|
||||||
|
def test_okx_mark_px(self):
|
||||||
|
px = _position_mark_price({"info": {"markPx": "72.85"}})
|
||||||
|
self.assertAlmostEqual(px, 72.85)
|
||||||
|
|
||||||
|
def test_gate_info_mark(self):
|
||||||
|
px = _position_mark_price({"info": {"mark_price": "0.2241"}})
|
||||||
|
self.assertAlmostEqual(px, 0.2241)
|
||||||
|
|
||||||
|
def test_missing_returns_none(self):
|
||||||
|
self.assertIsNone(_position_mark_price({"info": {}}))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user