import { PAIRS, type Candle, type Pair } from '../types'; import { getCandles, getCoverage } from '../market/candle-store'; import { buildWindows, aggregateOos } from '../backtest/walkforward'; import { runRotationBacktest, type RotationConfig } from '../backtest/rotation'; import { computeMetrics } from '../backtest/metrics'; import { DEFAULT_EXEC } from '../engine/portfolio'; import { db, sql } from '../db/client'; import { backtestRuns } from '../db/schema'; // --- Feste A-priori-Parameter (kein Grid, kein Overfitting möglich) --- const LOOKBACK_BARS = 180; // 30 Tage × 6 Bars/Tag auf 4h-TF const START_CAPITAL = 1000; const EXEC = DEFAULT_EXEC; 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()); dataTo = Math.min(dataTo, cov.to.getTime()); console.log(`${pair}: ${cov.count} Candles (${cov.from.toISOString()} → ${cov.to.toISOString()})`); } console.log(`\nMomentum-Rotation (fix: lookback 30d, weekly, top-1, long-only)`); console.log(`Walk-Forward über ${((dataTo - dataFrom) / 86400000).toFixed(0)} Tage…\n`); const windows = buildWindows(dataFrom, dataTo); type WindowResult = { window: { trainFrom: number; trainTo: number; testFrom: number; testTo: number }; trainMetrics: ReturnType; testMetrics: ReturnType; testTrades: import('../engine/portfolio').ClosedTrade[]; testEquityCurve: import('../backtest/metrics').EquityPoint[]; }; const results: WindowResult[] = []; for (const [wi, w] of windows.entries()) { const trainCfg: RotationConfig = { startCapital: START_CAPITAL, exec: EXEC, lookbackBars: LOOKBACK_BARS, tradeFrom: w.trainFrom, tradeTo: w.trainTo, }; const testCfg: RotationConfig = { startCapital: START_CAPITAL, exec: EXEC, lookbackBars: LOOKBACK_BARS, tradeFrom: w.testFrom, tradeTo: w.testTo, }; const trainResult = runRotationBacktest(candles15ByPair, trainCfg); const trainMetrics = computeMetrics(trainResult.trades, trainResult.equityCurve, START_CAPITAL); const testResult = runRotationBacktest(candles15ByPair, testCfg); const testMetrics = computeMetrics(testResult.trades, testResult.equityCurve, START_CAPITAL); results.push({ window: w, trainMetrics, testMetrics, testTrades: testResult.trades, testEquityCurve: testResult.equityCurve, }); console.log( `Fenster ${wi + 1}/${windows.length}: Train-PF ${trainMetrics.profitFactor.toFixed(2)} ` + `→ Test-PF ${testMetrics.profitFactor.toFixed(2)} bei ${testMetrics.trades} Trades`, ); } // OOS-Aggregat (gemeinsame Logik aus walkforward.ts) const { oosMetrics, oosEquityCurve, gate } = aggregateOos(results, START_CAPITAL); console.log('\n========== OOS-GESAMTERGEBNIS =========='); const m = 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 gate.checks) { console.log(`${c.pass ? '✅' : '❌'} ${c.name}: ${Number.isFinite(c.value) ? c.value.toFixed(2) : c.value}`); } console.log(`\n→ GATE ${gate.pass ? 'BESTANDEN' : 'NICHT BESTANDEN'}`); // Persistenz await db.insert(backtestRuns).values({ kind: 'rotation-walkforward', config: { startCapital: START_CAPITAL, exec: EXEC, lookbackBars: LOOKBACK_BARS } as any, result: { gate, oosMetrics, oosEquityCurve, windows: results.map((r) => ({ window: r.window, trainMetrics: r.trainMetrics, testMetrics: r.testMetrics, })), } as any, }); console.log('Run in backtest_runs gespeichert.'); await sql.end();