From 1a1e2301a5adfbbea69af87cd5ac504dacbbc8dc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 20:58:32 +0000 Subject: [PATCH] feat: Walk-Forward-CLI mit Gate-Report und Run-Persistenz Co-Authored-By: Claude Fable 5 --- src/server/scripts/walkforward.ts | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/server/scripts/walkforward.ts diff --git a/src/server/scripts/walkforward.ts b/src/server/scripts/walkforward.ts new file mode 100644 index 0000000..83a7396 --- /dev/null +++ b/src/server/scripts/walkforward.ts @@ -0,0 +1,52 @@ +import { PAIRS, type Candle, type Pair } from '../types'; +import { getCandles, getCoverage } from '../market/candle-store'; +import { runWalkForward } from '../backtest/walkforward'; +import { DEFAULT_RISK } from '../engine/sizing'; +import { DEFAULT_EXEC } from '../engine/portfolio'; +import { db, sql } from '../db/client'; +import { backtestRuns } from '../db/schema'; + +const candles15ByPair = new Map(); +let dataFrom = 0; +let dataTo = Number.MAX_SAFE_INTEGER; + +for (const pair of PAIRS) { + const cov = await getCoverage(pair); + if (!cov.from || !cov.to) throw new Error(`Keine Candles für ${pair} — erst 'bun run backfill' ausführen.`); + candles15ByPair.set(pair, await getCandles(pair)); + dataFrom = Math.max(dataFrom, cov.from.getTime()); // gemeinsamer Zeitraum aller Pairs + dataTo = Math.min(dataTo, cov.to.getTime()); + console.log(`${pair}: ${cov.count} Candles (${cov.from.toISOString()} → ${cov.to.toISOString()})`); +} + +const baseCfg = { startCapital: 1000, risk: DEFAULT_RISK, exec: DEFAULT_EXEC, maxPositions: 4 }; +console.log(`\nWalk-Forward über ${((dataTo - dataFrom) / 86400000).toFixed(0)} Tage…\n`); + +const result = runWalkForward(candles15ByPair, baseCfg, dataFrom, dataTo, (msg) => console.log(msg)); + +console.log('\n========== OOS-GESAMTERGEBNIS =========='); +const m = result.oosMetrics; +console.log(`Trades: ${m.trades} | WinRate: ${(m.winRate * 100).toFixed(1)}% | PF: ${m.profitFactor.toFixed(2)}`); +console.log(`TotalPnl: ${m.totalPnl.toFixed(2)} USDT | MaxDD: ${(m.maxDrawdownPct * 100).toFixed(1)}% | AvgR: ${m.avgR.toFixed(2)}`); + +console.log('\n========== DEPLOY-GATE =========='); +for (const c of result.gate.checks) { + console.log(`${c.pass ? '✅' : '❌'} ${c.name}: ${Number.isFinite(c.value) ? c.value.toFixed(2) : c.value}`); +} +console.log(`\n→ GATE ${result.gate.pass ? 'BESTANDEN' : 'NICHT BESTANDEN'}`); + +// Persistenz (Equity-Kurven kompakt halten: nur Fenster-Metriken + OOS-Kurve) +await db.insert(backtestRuns).values({ + kind: 'walkforward', + config: baseCfg as any, + result: { + gate: result.gate, + oosMetrics: result.oosMetrics, + oosEquityCurve: result.oosEquityCurve, + windows: result.windows.map((w) => ({ + window: w.window, bestParams: w.bestParams, trainMetrics: w.trainMetrics, testMetrics: w.testMetrics, + })), + } as any, +}); +console.log('Run in backtest_runs gespeichert.'); +await sql.end();