feat: Dashboard-Tab Trump (Events, Positionen, Trades)
This commit is contained in:
@@ -53,13 +53,14 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>trade-kuns <small>Paper · Trend (BTC ETH SOL XRP) + Grid (XRP)</small></h1>
|
<h1>trade-kuns <small>Paper · Trend (BTC ETH SOL XRP) + Grid (XRP) + Trump</small></h1>
|
||||||
<div id="status"><span class="dot" style="background:var(--muted)"></span>lade…</div>
|
<div id="status"><span class="dot" style="background:var(--muted)"></span>lade…</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<button class="tab active" data-tab="trading">Trading</button>
|
<button class="tab active" data-tab="trading">Trading</button>
|
||||||
<button class="tab" data-tab="grid">GridBot</button>
|
<button class="tab" data-tab="grid">GridBot</button>
|
||||||
|
<button class="tab" data-tab="trump">Trump</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<section id="tab-trading">
|
<section id="tab-trading">
|
||||||
@@ -106,6 +107,16 @@
|
|||||||
<div class="panel"><h2>Grid-Trades</h2><div id="grid-trades"></div></div>
|
<div class="panel"><h2>Grid-Trades</h2><div id="grid-trades"></div></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="tab-trump" hidden>
|
||||||
|
<div class="kpis" id="trump-cards"></div>
|
||||||
|
|
||||||
|
<div class="panel"><h2>Offene Positionen</h2><div id="trump-positions"></div></div>
|
||||||
|
|
||||||
|
<div class="panel"><h2>Events</h2><div id="trump-events"></div></div>
|
||||||
|
|
||||||
|
<div class="panel"><h2>Trades</h2><div id="trump-trades"></div></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const fmt = (n, d = 2) => n == null || Number.isNaN(n) ? '–' : n.toLocaleString('de-DE', { minimumFractionDigits: d, maximumFractionDigits: d });
|
const fmt = (n, d = 2) => n == null || Number.isNaN(n) ? '–' : n.toLocaleString('de-DE', { minimumFractionDigits: d, maximumFractionDigits: d });
|
||||||
const fmtTs = (ts) => ts == null ? '–' : new Date(ts).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
|
const fmtTs = (ts) => ts == null ? '–' : new Date(ts).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
|
||||||
@@ -348,11 +359,13 @@ function tradeMarkers(trades, { withR = false, label = '' } = {}) {
|
|||||||
const charts = {
|
const charts = {
|
||||||
trading: [], // wird unten befüllt
|
trading: [], // wird unten befüllt
|
||||||
grid: [],
|
grid: [],
|
||||||
|
trump: [],
|
||||||
};
|
};
|
||||||
function showTab(name) {
|
function showTab(name) {
|
||||||
document.querySelectorAll('.tab').forEach(b => b.classList.toggle('active', b.dataset.tab === name));
|
document.querySelectorAll('.tab').forEach(b => b.classList.toggle('active', b.dataset.tab === name));
|
||||||
document.getElementById('tab-trading').hidden = name !== 'trading';
|
document.getElementById('tab-trading').hidden = name !== 'trading';
|
||||||
document.getElementById('tab-grid').hidden = name !== 'grid';
|
document.getElementById('tab-grid').hidden = name !== 'grid';
|
||||||
|
document.getElementById('tab-trump').hidden = name !== 'trump';
|
||||||
history.replaceState(null, '', '#' + name);
|
history.replaceState(null, '', '#' + name);
|
||||||
requestAnimationFrame(() => (charts[name] || []).forEach(c => c.redraw()));
|
requestAnimationFrame(() => (charts[name] || []).forEach(c => c.redraw()));
|
||||||
}
|
}
|
||||||
@@ -399,13 +412,15 @@ async function renderTrendPrice() {
|
|||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
try {
|
try {
|
||||||
const [pf, stats, trades, decisions, grid, gridTradesAll] = await Promise.all([
|
const [pf, stats, trades, decisions, grid, gridTradesAll, trump, trumpTradesAll] = await Promise.all([
|
||||||
fetch('/api/portfolio').then(r => r.json()),
|
fetch('/api/portfolio').then(r => r.json()),
|
||||||
fetch('/api/stats').then(r => r.json()),
|
fetch('/api/stats').then(r => r.json()),
|
||||||
fetch('/api/trades?limit=200').then(r => r.json()),
|
fetch('/api/trades?limit=200').then(r => r.json()),
|
||||||
fetch('/api/decisions?limit=12').then(r => r.json()),
|
fetch('/api/decisions?limit=12').then(r => r.json()),
|
||||||
fetch('/api/grid').then(r => r.json()),
|
fetch('/api/grid').then(r => r.json()),
|
||||||
fetch('/api/trades?bot=grid&limit=200').then(r => r.json()),
|
fetch('/api/trades?bot=grid&limit=200').then(r => r.json()),
|
||||||
|
fetch('/api/trump').then(r => r.json()),
|
||||||
|
fetch('/api/trades?bot=trump&limit=200').then(r => r.json()),
|
||||||
]);
|
]);
|
||||||
lastTrades = trades;
|
lastTrades = trades;
|
||||||
lastPositions = pf.positions;
|
lastPositions = pf.positions;
|
||||||
@@ -494,6 +509,42 @@ async function refresh() {
|
|||||||
grid.trades.map(t => `<tr><td>${fmtTs(new Date(t.entryTs).getTime())}</td><td>${fmtTs(new Date(t.exitTs).getTime())}</td><td>${fmt(t.entryPrice, 4)}</td><td>${fmt(t.exitPrice, 4)}</td><td class="${cls(t.pnl)}">${sign(t.pnl)}</td><td>${t.exitReason}</td></tr>`),
|
grid.trades.map(t => `<tr><td>${fmtTs(new Date(t.entryTs).getTime())}</td><td>${fmtTs(new Date(t.exitTs).getTime())}</td><td>${fmt(t.entryPrice, 4)}</td><td>${fmt(t.exitPrice, 4)}</td><td class="${cls(t.pnl)}">${sign(t.pnl)}</td><td>${t.exitReason}</td></tr>`),
|
||||||
'Noch keine Grid-Trades.');
|
'Noch keine Grid-Trades.');
|
||||||
|
|
||||||
|
// ── Trump-Tab ──
|
||||||
|
// Equity ≈ cash + Σ qty×entryPrice (Näherung; kein Marktpreis verfügbar im API)
|
||||||
|
const trumpPositions = trump.positions || [];
|
||||||
|
const trumpEvents = trump.events || [];
|
||||||
|
const trumpEquityApprox = (trump.cash || 0) + trumpPositions.reduce((s, p) => s + p.qty * p.entryPrice, 0);
|
||||||
|
const trumpPnl = trumpEquityApprox - (trump.startCapital || 0);
|
||||||
|
const evCount = trumpEvents.length;
|
||||||
|
document.getElementById('trump-cards').innerHTML =
|
||||||
|
kpi('Equity (ca.)', fmt(trumpEquityApprox) + ' $') +
|
||||||
|
kpi('PnL', sign(trumpPnl) + ' $', cls(trumpPnl)) +
|
||||||
|
kpi('Cash', fmt(trump.cash) + ' $') +
|
||||||
|
kpi('Offene Pos.', trumpPositions.length) +
|
||||||
|
kpi('Events', evCount >= 50 ? '50+' : evCount);
|
||||||
|
|
||||||
|
document.getElementById('trump-positions').innerHTML = table(
|
||||||
|
['Pair', 'Entry-Zeit', 'Entry-Preis', 'Qty', 'Exit fällig'],
|
||||||
|
trumpPositions.map(p => `<tr><td><span class="tag long">${p.pair}</span></td><td>${fmtTs(p.entryTs)}</td><td>${fmt(p.entryPrice, priceDec(p.entryPrice))}</td><td>${fmt(p.qty, 4)}</td><td>${fmtTs(p.exitDueTs)}</td></tr>`),
|
||||||
|
'Keine offenen Positionen.');
|
||||||
|
|
||||||
|
document.getElementById('trump-events').innerHTML = table(
|
||||||
|
['Zeit', 'Quelle', 'Coin', 'Instrument', 'Notional $', 'Ref'],
|
||||||
|
trumpEvents.map(e => {
|
||||||
|
const ref = e.ref
|
||||||
|
? e.source === 'onchain'
|
||||||
|
? `<a href="https://etherscan.io/tx/${e.ref}" target="_blank" style="color:var(--accent)">${e.ref.slice(0, 12)}…</a>`
|
||||||
|
: `<a href="${e.ref}" target="_blank" style="color:var(--accent)">${e.ref.slice(0, 24)}${e.ref.length > 24 ? '…' : ''}</a>`
|
||||||
|
: '–';
|
||||||
|
return `<tr><td>${fmtTs(e.eventTs)}</td><td>${e.source ?? '–'}</td><td>${e.token ?? '–'}</td><td>${e.instrument ?? '–'}</td><td>${e.notionalUsd != null ? fmt(e.notionalUsd) + ' $' : '–'}</td><td style="text-align:left">${ref}</td></tr>`;
|
||||||
|
}),
|
||||||
|
'Keine Events vorhanden.');
|
||||||
|
|
||||||
|
document.getElementById('trump-trades').innerHTML = table(
|
||||||
|
['Entry', 'Exit', 'Entry-Preis', 'Exit-Preis', 'PnL $', 'Grund'],
|
||||||
|
trumpTradesAll.map(t => `<tr><td>${fmtTs(new Date(t.entryTs).getTime())}</td><td>${fmtTs(new Date(t.exitTs).getTime())}</td><td>${fmt(t.entryPrice, 4)}</td><td>${fmt(t.exitPrice, 4)}</td><td class="${cls(t.pnl)}">${sign(t.pnl)}</td><td>${t.exitReason}</td></tr>`),
|
||||||
|
'Noch keine Trump-Trades.');
|
||||||
|
|
||||||
const eng = pf.engine || {};
|
const eng = pf.engine || {};
|
||||||
const ok = eng.lastCycleOk !== false;
|
const ok = eng.lastCycleOk !== false;
|
||||||
document.getElementById('status').innerHTML =
|
document.getElementById('status').innerHTML =
|
||||||
@@ -505,6 +556,7 @@ async function refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (location.hash === '#grid') showTab('grid');
|
if (location.hash === '#grid') showTab('grid');
|
||||||
|
else if (location.hash === '#trump') showTab('trump');
|
||||||
refresh();
|
refresh();
|
||||||
setInterval(refresh, 30000);
|
setInterval(refresh, 30000);
|
||||||
let resizeT;
|
let resizeT;
|
||||||
|
|||||||
Reference in New Issue
Block a user