feat: GridBot als zweite Paper-Engine — No-Stop-XRP-Grid live
processGridCycle (Paritätstest gegen runGridBacktest), GridEngine mit DB-Recovery (grid_state/grid_lots, bot_state id=2), bot-Spalte in paper_trades/equity_snapshots, /api/grid, Dashboard-Panel. Bewusster Paper-Probelauf trotz Gate-Fail (User-Entscheidung 2026-06-10). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,14 @@
|
||||
|
||||
<div class="panel"><h2>Letzte Entscheidungen (4h-Bars)</h2><div id="decisions"></div></div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>GridBot XRP <span class="tag">No-Stop · 3×ATR · 8 Levels · Paper</span></h2>
|
||||
<div class="kpis" id="grid-kpis" style="margin-bottom:12px"></div>
|
||||
<div id="grid-info" style="color:var(--muted);font-size:12.5px;margin-bottom:10px"></div>
|
||||
<h2>Offene Lots</h2><div id="grid-lots"></div>
|
||||
<h2 style="margin-top:14px">Grid-Trades</h2><div id="grid-trades"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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' });
|
||||
@@ -109,11 +117,12 @@ function drawChart(curve, startCapital) {
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const [pf, stats, trades, decisions] = await Promise.all([
|
||||
const [pf, stats, trades, decisions, grid] = await Promise.all([
|
||||
fetch('/api/portfolio').then(r => r.json()),
|
||||
fetch('/api/stats').then(r => r.json()),
|
||||
fetch('/api/trades?limit=30').then(r => r.json()),
|
||||
fetch('/api/decisions?limit=12').then(r => r.json()),
|
||||
fetch('/api/grid').then(r => r.json()),
|
||||
]);
|
||||
|
||||
const pnl = pf.equity - pf.startCapital;
|
||||
@@ -150,6 +159,29 @@ async function refresh() {
|
||||
}),
|
||||
'Noch keine Entscheidungen — die erste 4h-Bar schließt demnächst.');
|
||||
|
||||
const gPnl = grid.equity - grid.startCapital;
|
||||
document.getElementById('grid-kpis').innerHTML =
|
||||
kpi('Equity', fmt(grid.equity) + ' $') +
|
||||
kpi('PnL', sign(gPnl) + ' $', cls(gPnl)) +
|
||||
kpi('Cash', fmt(grid.cash) + ' $') +
|
||||
kpi('Trades', grid.stats.trades) +
|
||||
kpi('Win Rate', grid.stats.trades ? fmt(grid.stats.winRate * 100, 0) + ' %' : '–');
|
||||
|
||||
const gs = grid.grids[0];
|
||||
document.getElementById('grid-info').textContent = gs
|
||||
? `Aktives Grid: Center ${fmt(gs.center, 4)} · Spacing ${fmt(gs.spacing, 4)} · Range ${fmt(gs.lowerBound, 4)}–${fmt(gs.upperBound, 4)} · Budget/Level ${fmt(gs.budgetPerLevel)} $ · aktiviert ${fmtTs(gs.activatedTs)} · XRP jetzt ${fmt(gs.lastPrice, 4)}`
|
||||
: 'Kein aktives Grid — Aktivierung beim nächsten 4h-Close.';
|
||||
|
||||
document.getElementById('grid-lots').innerHTML = table(
|
||||
['Level', 'Entry', 'Entry-Preis', 'Letzter', 'Wert $', 'PnL $'],
|
||||
grid.lots.map(l => `<tr><td>L${l.levelIdx + 1}</td><td>${fmtTs(l.entryTs)}</td><td>${fmt(l.entryPrice, 4)}</td><td>${fmt(l.lastPrice, 4)}</td><td>${fmt(l.value)}</td><td class="${cls(l.unrealizedPnl)}">${sign(l.unrealizedPnl)}</td></tr>`),
|
||||
'Keine offenen Lots — warten auf den ersten Dip unter ein Level.');
|
||||
|
||||
document.getElementById('grid-trades').innerHTML = table(
|
||||
['Entry', 'Exit', 'Entry-Preis', 'Exit-Preis', 'PnL $', 'Grund'],
|
||||
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.');
|
||||
|
||||
const eng = pf.engine || {};
|
||||
const ok = eng.lastCycleOk !== false;
|
||||
document.getElementById('status').innerHTML =
|
||||
|
||||
Reference in New Issue
Block a user