Files
trade-kuns/docs/specs/2026-06-10-live-paper-engine-design.md

6.0 KiB
Raw Permalink Blame History

trade-kuns — Live-Paper-Engine (Phase 3, Design)

Datum: 2026-06-10 Status: Umsetzung (User-Entscheidung: bewusster Paper-Probelauf) Basis: Spec 2026-06-09-trade-kuns-design.md §4.34.6, §6, §8


1. Kontext & Entscheidung

Das Walk-Forward-Gate wurde von keiner der 7 Varianten bestanden (siehe docs/walkforward-ergebnisse-2026-06-09.md). Beste Variante: long-only mit fixen Spec-Default-Parametern (Donchian 20 / ATR×3 / EMA 200 / ADX 20) — OOS-PF 1.21, 249 Trades, MaxDD 16 %, Overfitting-Ratio 1.51; einziger Fail: 11/32 Fenster mit PF < 0.5.

Entscheidung (User, 2026-06-10): Live-Paper-Engine bauen und diese Variante im Paper-Probelauf validieren. Das Gate wird nicht aufgeweicht — der Paper-Lauf ist die nächste Validierungsstufe. Kein echtes Geld, keine Order-Ausführung, keine Schreib-API-Keys (Spec §10 unverändert).

2. Abweichungen von der Ursprungs-Spec

Spec Jetzt Grund
Subdomain trade.kuns.dev trading.kuns.dev User-Vorgabe
Hono Bun.serve 6 GET-Routen, kein Framework nötig
Vue 3 + Vite Dashboard statische Single-Page (Vanilla JS, Canvas-Chart) kein Build-Step, kein Over-Engineering
Zitadel-JWT-Auth keine Auth API ist read-only (Paper-Daten, nichts Sensibles); Schreib-Endpoints (toggle/reset/config) entfallen in v1
Port 8080 8080 unverändert

3. Architektur

Ein Bun-Prozess (src/server/index.ts): HTTP-Server + 5-min-Loop.

src/server/
  live/
    engine.ts        LiveEngine: Zyklus-Orchestrierung, Recovery, Persistenz
    process-cycle.ts pure Funktion: (candles, state) → actions (Entries/Exits/Decisions/Equity)
  api/
    server.ts        Bun.serve: /health, /api/*, statisches Dashboard
  db/schema.ts       + positions, paper_trades, decision_logs, bot_state, equity_snapshots
public/index.html    Dashboard

Ein Code-Pfad-Prinzip bleibt: process-cycle.ts nutzt exakt dieselben puren Funktionen wie der Backtest-Runner (computeIndicators, evaluateAt, updateChandelier, sizePosition, Portfolio) mit identischer Semantik: 4h-Entries (Close > Donchian-High(20) ∧ Close > EMA-200 ∧ ADX ≥ 20), 15m-Stop-Checks (Low ≤ Stop → Exit, Gap → Open als schlechterer Fill), Chandelier-Update pro abgeschlossener 4h-Bar, Fees 0.1 % + Slippage 5 bps.

4. Zyklus (alle 5 Minuten)

  1. Fetch: je Pair neueste 15m-Candles von Crypto.com (Lücke seit Cursor, end_ts-Paginierung wie Backfill, max 300/Request), nur abgeschlossene (ts + 15m ≤ now) → candles (Dedup via PK).
  2. Load: je Pair letzte ~6500 15m-Candles (≈ 400 4h-Bars — Warmup für EMA-200 + Donchian) aus DB.
  3. Process (pure, deterministisch): alle 15m-Candles mit ts > cursor chronologisch gemergt (Tie-Break PAIRS-Reihenfolge wie Runner):
    • neue abgeschlossene 4h-Bars des Pairs: Chandelier-Update → Entry-Evaluation (jede Evaluation → DecisionLog, inkl. Blockierungsgrund/Sizing-Block)
    • 15m-Stop-Check der offenen Position
    • Equity-Punkt einmal pro 4h-Bucket
  4. Persist (eine Transaktion): Positions-Upsert/Delete, Trades, DecisionLogs, Equity-Snapshots, bot_state (cash, cursor).
  5. Outcome-Backfill: decision_logs mit NULL-Outcomes füllen, sobald Candles 4h/24h/72h später vorliegen (Edge-Frühindikator, Spec §5.5).

Initialisierung (erster Start): bot_state mit 1000 USDT Cash, Cursor = neueste abgeschlossene 15m-Candle. Keine historische Replay — der erste mögliche Entry ist der nächste frische 4h-Close.

Restart-Recovery (Spec §6): Positionen + Cash + Cursor aus DB; verpasste Candles werden nachgeholt und Stops rückwirkend geprüft — identisch zum Normalzyklus, da der Prozess-Schritt nur vom Cursor abhängt.

Fehler: API-Fehler eines Pairs → Pair in diesem Zyklus überspringen, andere laufen weiter; Fetch-Retry (3×, Backoff) existiert im Client. DB-Fehler → Zyklus abbrechen, Status rot. Überlappende Zyklen durch running-Flag verhindert. Letzter Zyklus-Status sichtbar in /api/portfolio.

5. Datenbank (neu)

  • positions — pair (PK), side, qty, entry_ts, entry_price, entry_cost, initial_stop, stop, trail_extreme, risk_amount
  • paper_trades — id, pair, side, entry/exit (ts+price), qty, pnl, r, exit_reason
  • decision_logs — id, pair, bar_ts (4h), signal, blocked_by, close, atr, adx, donchian_high, trend_ema, price_after_4h/24h/72h (nullable), unique(pair, bar_ts)
  • bot_state — id=1, cash, start_capital, cursor_ts, updated_at
  • equity_snapshots — ts (PK, 4h-Bucket), equity, cash

6. API & Dashboard

Route Inhalt
GET /health {ok, lastCycle, cycleError} — Coolify-Healthcheck
GET /api/portfolio Equity, Cash, offene Positionen (inkl. Stop, unrealized PnL), Zyklus-Status
GET /api/trades?limit abgeschlossene Trades, neueste zuerst
GET /api/decisions?pair&limit DecisionLog inkl. Outcomes
GET /api/stats PF, WinRate, MaxDD, avgR, Trade-Anzahl, Equity-Kurve
`GET /api/candles?pair&tf=15m 4h&limit`

Dashboard: eine statische Seite, 30-s-Polling. KPI-Leiste (Equity, PnL, PF, WinRate, MaxDD), Equity-Kurve (Canvas), Tabellen: offene Positionen, Trades, letzte Decisions je Pair.

7. Tests

  • process-cycle: Determinismus; Entry auf 4h-Close; Stop-Check inkl. Gap-Fill; Cursor-Idempotenz (zweiter Lauf ohne neue Candles = no-op); Restart-Äquivalenz (ein Lauf über N Candles ≡ zwei Läufe mit Cut dazwischen).
  • Engine-Persistenz gegen Test-DB wird nicht automatisiert (kein CI mit DB); manuelle Verifikation beim Deploy.

8. Deployment

  • Dockerfile oven/bun:1.3, Start: Migrationen → Server, Port 8080, Healthcheck /health.
  • Coolify-App trade-kuns, Domain https://trading.kuns.dev, Netz coolify.
  • DATABASE_URL=postgres://mika:…@l8kogcggsc80sgcgk8kswww4:5432/tradekuns (shared-postgres über Coolify-Docker-Netz; vom Host aus weiterhin localhost:54320).
  • Backfill läuft weiter vom Host (bun run backfill) oder implizit über den Lücken-Fetch des Loops.