Spaces:
Running
Running
Upload hub_dashboard.html
Browse files- hub_dashboard.html +25 -39
hub_dashboard.html
CHANGED
|
@@ -1531,47 +1531,13 @@ td.rc {
|
|
| 1531 |
});
|
| 1532 |
|
| 1533 |
// ββ Trade detection ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1534 |
-
//
|
| 1535 |
-
//
|
| 1536 |
-
//
|
| 1537 |
-
//
|
| 1538 |
-
|
| 1539 |
-
let _prevMsgRx = 0;
|
| 1540 |
-
let _seenTimestamps = new Set();
|
| 1541 |
-
let _firstRefresh = true;
|
| 1542 |
|
| 1543 |
window._onTradeRefresh = function(rankings, health) {
|
| 1544 |
-
//
|
| 1545 |
-
const msgRx = (health && health.messages_rx) || 0;
|
| 1546 |
-
if (!_firstRefresh && msgRx > _prevMsgRx) {
|
| 1547 |
-
window.playTradeSound();
|
| 1548 |
-
}
|
| 1549 |
-
_prevMsgRx = msgRx;
|
| 1550 |
-
|
| 1551 |
-
// ββ Signal B: new last_updated timestamps in feed ββββββββββββββββββββ
|
| 1552 |
-
let newTrade = false;
|
| 1553 |
-
if (rankings && rankings.length) {
|
| 1554 |
-
for (const r of rankings) {
|
| 1555 |
-
if (!r.last_updated) continue;
|
| 1556 |
-
const key = r.space_name + ':' + r.last_updated;
|
| 1557 |
-
if (!_seenTimestamps.has(key)) {
|
| 1558 |
-
if (!_firstRefresh) newTrade = true;
|
| 1559 |
-
_seenTimestamps.add(key);
|
| 1560 |
-
// Prune set β keep only latest 500 keys to avoid memory growth
|
| 1561 |
-
if (_seenTimestamps.size > 500) {
|
| 1562 |
-
const oldest = [..._seenTimestamps][0];
|
| 1563 |
-
_seenTimestamps.delete(oldest);
|
| 1564 |
-
}
|
| 1565 |
-
}
|
| 1566 |
-
}
|
| 1567 |
-
}
|
| 1568 |
-
|
| 1569 |
-
// Only fire once even if both signals trigger on same refresh cycle
|
| 1570 |
-
if (!_firstRefresh && newTrade && msgRx <= _prevMsgRx) {
|
| 1571 |
-
window.playTradeSound();
|
| 1572 |
-
}
|
| 1573 |
-
|
| 1574 |
-
_firstRefresh = false;
|
| 1575 |
};
|
| 1576 |
|
| 1577 |
console.log('[TradeAudio] Engine initialised β waiting for user gesture to unlock AudioContext');
|
|
@@ -3016,6 +2982,26 @@ async function updateTerminal() {
|
|
| 3016 |
const closed = data.closed || [];
|
| 3017 |
const stats = data.stats || {};
|
| 3018 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3019 |
// ββ KPI strip ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3020 |
const totalPnl = stats.total_pnl || 0;
|
| 3021 |
const totalClosed = stats.total_closed || 0;
|
|
|
|
| 1531 |
});
|
| 1532 |
|
| 1533 |
// ββ Trade detection ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1534 |
+
// Sound fires ONLY when a genuinely new trade_id appears in the Open
|
| 1535 |
+
// Positions panel (/api/trades β data.open[]). Detection runs inside
|
| 1536 |
+
// updateTerminal() which owns the canonical open-positions data.
|
| 1537 |
+
// _onTradeRefresh is kept as a no-op stub so call-sites don't break.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1538 |
|
| 1539 |
window._onTradeRefresh = function(rankings, health) {
|
| 1540 |
+
// intentionally empty β sound is now driven by open-position diff only
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1541 |
};
|
| 1542 |
|
| 1543 |
console.log('[TradeAudio] Engine initialised β waiting for user gesture to unlock AudioContext');
|
|
|
|
| 2982 |
const closed = data.closed || [];
|
| 2983 |
const stats = data.stats || {};
|
| 2984 |
|
| 2985 |
+
// ββ Open-position trade alert βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2986 |
+
// Sound fires once per genuinely new trade_id that appears in open[].
|
| 2987 |
+
// _prevOpenTradeIds is module-scoped so it persists across polling cycles.
|
| 2988 |
+
if (typeof window._prevOpenTradeIds === 'undefined') {
|
| 2989 |
+
// First call β seed the set silently (don't alert on existing positions)
|
| 2990 |
+
window._prevOpenTradeIds = new Set(open.map(t => t.trade_id).filter(Boolean));
|
| 2991 |
+
} else {
|
| 2992 |
+
let hasNewTrade = false;
|
| 2993 |
+
const nextIds = new Set();
|
| 2994 |
+
for (const t of open) {
|
| 2995 |
+
if (!t.trade_id) continue;
|
| 2996 |
+
nextIds.add(t.trade_id);
|
| 2997 |
+
if (!window._prevOpenTradeIds.has(t.trade_id)) {
|
| 2998 |
+
hasNewTrade = true; // brand-new trade_id not seen before
|
| 2999 |
+
}
|
| 3000 |
+
}
|
| 3001 |
+
if (hasNewTrade) window.playTradeSound();
|
| 3002 |
+
window._prevOpenTradeIds = nextIds; // update to current snapshot
|
| 3003 |
+
}
|
| 3004 |
+
|
| 3005 |
// ββ KPI strip ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3006 |
const totalPnl = stats.total_pnl || 0;
|
| 3007 |
const totalClosed = stats.total_closed || 0;
|