This commit is contained in:
aaron 2026-04-27 15:04:33 +08:00
parent 13f2845201
commit ceaceb29c6
4 changed files with 134 additions and 24 deletions

View File

@ -63,12 +63,25 @@ def _safe_float(value: Any, default: float = 0.0) -> float:
return default return default
def _normalize_contract_symbol(symbol: Any) -> str:
text = str(symbol or "").strip().upper()
if not text:
return "-"
if text.endswith("USDTUSDT"):
return text[:-4]
if text.endswith("/USDT:USDT"):
return f"{text.split('/')[0]}USDT"
if text.endswith("USDT"):
return text
if "/" in text:
return f"{text.split('/')[0]}USDT"
return f"{text}USDT"
def _normalize_platform_position(platform: str, position: Dict[str, Any]) -> Dict[str, Any]: def _normalize_platform_position(platform: str, position: Dict[str, Any]) -> Dict[str, Any]:
side_raw = str(position.get("side") or "").lower() side_raw = str(position.get("side") or "").lower()
side = "long" if side_raw in {"buy", "long"} else "short" side = "long" if side_raw in {"buy", "long"} else "short"
symbol = position.get("symbol") or position.get("coin") or "-" symbol = _normalize_contract_symbol(position.get("symbol") or position.get("coin") or "-")
if isinstance(symbol, str) and symbol.endswith("USDTUSDT"):
symbol = symbol.replace("USDTUSDT", "USDT")
entry_price = _safe_float(position.get("entry_price") or position.get("filled_price")) entry_price = _safe_float(position.get("entry_price") or position.get("filled_price"))
mark_price = _safe_float(position.get("mark_price") or position.get("current_price")) mark_price = _safe_float(position.get("mark_price") or position.get("current_price"))
@ -105,9 +118,7 @@ def _normalize_platform_position(platform: str, position: Dict[str, Any]) -> Dic
def _normalize_platform_order(platform: str, order: Dict[str, Any]) -> Dict[str, Any]: def _normalize_platform_order(platform: str, order: Dict[str, Any]) -> Dict[str, Any]:
side_raw = str(order.get("side") or "").lower() side_raw = str(order.get("side") or "").lower()
side = "long" if side_raw in {"buy", "long", "b"} else "short" side = "long" if side_raw in {"buy", "long", "b"} else "short"
symbol = order.get("symbol") or order.get("coin") or "-" symbol = _normalize_contract_symbol(order.get("symbol") or order.get("coin") or "-")
if isinstance(symbol, str) and symbol.endswith("USDTUSDT"):
symbol = symbol.replace("USDTUSDT", "USDT")
price = _safe_float(order.get("price") or order.get("entry_price")) price = _safe_float(order.get("price") or order.get("entry_price"))
size = abs(_safe_float(order.get("size") or order.get("quantity"))) size = abs(_safe_float(order.get("size") or order.get("quantity")))

View File

@ -3580,7 +3580,7 @@ class CryptoAgent:
for order in all_orders: for order in all_orders:
pending_orders.append({ pending_orders.append({
'order_id': order.get('order_id'), 'order_id': order.get('order_id'),
'symbol': f"{order['symbol']}USDT", 'symbol': order.get('symbol'),
'side': order.get('side', ''), 'side': order.get('side', ''),
'entry_price': order.get('price'), 'entry_price': order.get('price'),
'quantity': order.get('size'), 'quantity': order.get('size'),

View File

@ -544,7 +544,7 @@ class BitgetLiveTradingService:
result.append({ result.append({
"order_id": str(order.get('id', '')), "order_id": str(order.get('id', '')),
"symbol": coin, "symbol": f"{coin}USDT",
"side": order.get('side', ''), "side": order.get('side', ''),
"size": size_in_coins, "size": size_in_coins,
"price": display_price, "price": display_price,

View File

@ -283,6 +283,55 @@
line-height: 1.6; line-height: 1.6;
} }
.toolbar-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.filter-chip {
appearance: none;
border: 1px solid rgba(126, 200, 255, 0.16);
background: rgba(255,255,255,0.04);
color: var(--muted);
border-radius: 999px;
padding: 8px 14px;
cursor: pointer;
font-family: "IBM Plex Mono", monospace;
font-size: 12px;
transition: all 0.18s ease;
}
.filter-chip.active {
color: var(--text);
border-color: rgba(126, 200, 255, 0.34);
background: rgba(126, 200, 255, 0.14);
box-shadow: inset 0 0 0 1px rgba(126, 200, 255, 0.08);
}
.position-card.platform-paper {
border-color: rgba(126, 200, 255, 0.22);
box-shadow: inset 0 0 0 1px rgba(126, 200, 255, 0.06);
}
.position-card.platform-bitget {
border-color: rgba(156, 255, 87, 0.18);
box-shadow: inset 0 0 0 1px rgba(156, 255, 87, 0.06);
}
.platform-pill.paper {
border-color: rgba(126, 200, 255, 0.32);
color: var(--cold);
background: rgba(126, 200, 255, 0.10);
}
.platform-pill.bitget {
border-color: rgba(156, 255, 87, 0.28);
color: var(--accent);
background: rgba(156, 255, 87, 0.10);
}
.hero { .hero {
display: grid; display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(360px, 0.9fr); grid-template-columns: minmax(0, 1.4fr) minmax(360px, 0.9fr);
@ -2769,6 +2818,8 @@
let cachedConsoleData = null; let cachedConsoleData = null;
let revealSensitiveData = false; let revealSensitiveData = false;
let consoleAuthenticated = false; let consoleAuthenticated = false;
let positionsPlatformFilter = 'all';
let ordersPlatformFilter = 'all';
const SENSITIVE_VISIBILITY_KEY = 'console_sensitive_visible_v2'; const SENSITIVE_VISIBILITY_KEY = 'console_sensitive_visible_v2';
const HUB_PREFERENCE_KEY = 'console_hub_active_v1'; const HUB_PREFERENCE_KEY = 'console_hub_active_v1';
@ -2847,6 +2898,17 @@
return Object.values(platformHalts || {}).filter((item) => item && item.halted).length; return Object.values(platformHalts || {}).filter((item) => item && item.halted).length;
} }
function normalizePlatformKey(value) {
return String(value || '').trim().toLowerCase();
}
function platformDisplayName(value) {
const key = normalizePlatformKey(value);
if (key === 'paper') return 'PAPER';
if (key === 'bitget') return 'BITGET';
return String(value || '-').toUpperCase();
}
function setFeedback(message, isError = false) { function setFeedback(message, isError = false) {
const el = document.getElementById('feedback'); const el = document.getElementById('feedback');
if (!message) { if (!message) {
@ -3968,21 +4030,39 @@
const toolbar = document.getElementById('positionsToolbar'); const toolbar = document.getElementById('positionsToolbar');
const container = document.getElementById('positionsTable'); const container = document.getElementById('positionsTable');
const total = positions || []; const total = positions || [];
const platformCounts = ['paper', 'bitget'].map((platform) => { const paperCount = total.filter((item) => normalizePlatformKey(item.platform) === 'paper').length;
const count = total.filter((item) => item.platform === platform).length; const bitgetCount = total.filter((item) => normalizePlatformKey(item.platform) === 'bitget').length;
return `<span class="toolbar-chip">${platform}: ${count}</span>`; const filtered = total.filter((item) => positionsPlatformFilter === 'all' || normalizePlatformKey(item.platform) === positionsPlatformFilter);
}).join('');
toolbar.innerHTML = `${platformCounts}<span class="toolbar-chip">total: ${total.length}</span>`;
if (!total.length) { toolbar.innerHTML = `
<div class="toolbar-group">
<span class="toolbar-chip">paper: ${paperCount}</span>
<span class="toolbar-chip">bitget: ${bitgetCount}</span>
<span class="toolbar-chip">total: ${total.length}</span>
</div>
<div class="toolbar-group">
<button class="filter-chip ${positionsPlatformFilter === 'all' ? 'active' : ''}" data-position-filter="all">全部</button>
<button class="filter-chip ${positionsPlatformFilter === 'paper' ? 'active' : ''}" data-position-filter="paper">模拟盘</button>
<button class="filter-chip ${positionsPlatformFilter === 'bitget' ? 'active' : ''}" data-position-filter="bitget">Bitget</button>
</div>
`;
toolbar.querySelectorAll('[data-position-filter]').forEach((button) => {
button.addEventListener('click', () => {
positionsPlatformFilter = button.getAttribute('data-position-filter') || 'all';
renderUnifiedPositions(total);
});
});
if (!filtered.length) {
container.innerHTML = compactEmpty('当前没有跨平台持仓', '没有活动持仓时,这里会保持紧凑,不再占用大块留白。'); container.innerHTML = compactEmpty('当前没有跨平台持仓', '没有活动持仓时,这里会保持紧凑,不再占用大块留白。');
return; return;
} }
container.innerHTML = ` container.innerHTML = `
<div class="position-card-grid"> <div class="position-card-grid">
${total.map((item) => ` ${filtered.map((item) => `
<article class="position-card ${item.side === 'long' ? 'long' : 'short'}"> <article class="position-card platform-${normalizePlatformKey(item.platform)} ${item.side === 'long' ? 'long' : 'short'}">
<div class="position-card-head"> <div class="position-card-head">
<div> <div>
<div class="position-card-symbol">${item.symbol || '-'}</div> <div class="position-card-symbol">${item.symbol || '-'}</div>
@ -3994,7 +4074,7 @@
</div> </div>
</div> </div>
<div class="position-card-tags"> <div class="position-card-tags">
<span class="platform-pill">${item.platform}</span> <span class="platform-pill ${normalizePlatformKey(item.platform)}">${platformDisplayName(item.platform)}</span>
<span class="side-pill ${item.side === 'long' ? 'long' : 'short'}">${item.side === 'long' ? 'long' : 'short'}</span> <span class="side-pill ${item.side === 'long' ? 'long' : 'short'}">${item.side === 'long' ? 'long' : 'short'}</span>
${item.setup_type ? `<span class="event-inline-badge">${item.setup_type}</span>` : ''} ${item.setup_type ? `<span class="event-inline-badge">${item.setup_type}</span>` : ''}
</div> </div>
@ -4028,21 +4108,40 @@
const total = orders || []; const total = orders || [];
const entryCount = total.filter((item) => item.category === 'entry').length; const entryCount = total.filter((item) => item.category === 'entry').length;
const protectionCount = total.filter((item) => item.category === 'tp_sl').length; const protectionCount = total.filter((item) => item.category === 'tp_sl').length;
const paperCount = total.filter((item) => normalizePlatformKey(item.platform) === 'paper').length;
const bitgetCount = total.filter((item) => normalizePlatformKey(item.platform) === 'bitget').length;
const filtered = total.filter((item) => ordersPlatformFilter === 'all' || normalizePlatformKey(item.platform) === ordersPlatformFilter);
toolbar.innerHTML = ` toolbar.innerHTML = `
<span class="toolbar-chip">entry: ${entryCount}</span> <div class="toolbar-group">
<span class="toolbar-chip">tp/sl: ${protectionCount}</span> <span class="toolbar-chip">entry: ${entryCount}</span>
<span class="toolbar-chip">total: ${total.length}</span> <span class="toolbar-chip">tp/sl: ${protectionCount}</span>
<span class="toolbar-chip">paper: ${paperCount}</span>
<span class="toolbar-chip">bitget: ${bitgetCount}</span>
<span class="toolbar-chip">total: ${total.length}</span>
</div>
<div class="toolbar-group">
<button class="filter-chip ${ordersPlatformFilter === 'all' ? 'active' : ''}" data-order-filter="all">全部</button>
<button class="filter-chip ${ordersPlatformFilter === 'paper' ? 'active' : ''}" data-order-filter="paper">模拟盘</button>
<button class="filter-chip ${ordersPlatformFilter === 'bitget' ? 'active' : ''}" data-order-filter="bitget">Bitget</button>
</div>
`; `;
if (!total.length) { toolbar.querySelectorAll('[data-order-filter]').forEach((button) => {
button.addEventListener('click', () => {
ordersPlatformFilter = button.getAttribute('data-order-filter') || 'all';
renderUnifiedOrders(total);
});
});
if (!filtered.length) {
container.innerHTML = compactEmpty('当前没有跨平台挂单', '没有入场单或保护单时,这里会保持紧凑展示。'); container.innerHTML = compactEmpty('当前没有跨平台挂单', '没有入场单或保护单时,这里会保持紧凑展示。');
return; return;
} }
container.innerHTML = ` container.innerHTML = `
<div class="position-card-grid"> <div class="position-card-grid">
${total.map((item) => ` ${filtered.map((item) => `
<article class="position-card ${item.side === 'long' ? 'long' : 'short'}"> <article class="position-card platform-${normalizePlatformKey(item.platform)} ${item.side === 'long' ? 'long' : 'short'}">
<div class="position-card-head"> <div class="position-card-head">
<div> <div>
<div class="position-card-symbol">${item.symbol || '-'}</div> <div class="position-card-symbol">${item.symbol || '-'}</div>
@ -4054,7 +4153,7 @@
</div> </div>
</div> </div>
<div class="position-card-tags"> <div class="position-card-tags">
<span class="platform-pill">${item.platform}</span> <span class="platform-pill ${normalizePlatformKey(item.platform)}">${platformDisplayName(item.platform)}</span>
<span class="side-pill ${item.side === 'long' ? 'long' : 'short'}">${item.side === 'long' ? 'long' : 'short'}</span> <span class="side-pill ${item.side === 'long' ? 'long' : 'short'}">${item.side === 'long' ? 'long' : 'short'}</span>
${item.signal_grade ? `<span class="event-inline-badge">${item.signal_grade}</span>` : ''} ${item.signal_grade ? `<span class="event-inline-badge">${item.signal_grade}</span>` : ''}
${item.signal_type ? `<span class="event-inline-badge">${item.signal_type}</span>` : ''} ${item.signal_type ? `<span class="event-inline-badge">${item.signal_type}</span>` : ''}