import { PAIRS, type Candle, type Pair } from '../types'; import { getCandles, getCoverage } from '../market/candle-store'; import { buildWindows, aggregateOos } from '../backtest/walkforward'; import { runGridBacktest, DEFAULT_GRID_PARAMS, type GridConfig } from '../backtest/grid'; 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-Search; Varianten nur als bewusste // Design-Entscheidung via CLI: --spacing 1.5 --levels 4 --adx 15) --- function argNum(flag: string, def: number): number { const i = process.argv.indexOf(flag); return i >= 0 ? Number(process.argv[i + 1]) : def; } const PARAMS = { ...DEFAULT_GRID_PARAMS, // spacing 1×ATR, 4 Levels, ADX < 20, tf 4h spacingAtrMult: argNum('--spacing', DEFAULT_GRID_PARAMS.spacingAtrMult), gridLevels: argNum('--levels', DEFAULT_GRID_PARAMS.gridLevels), adxMax: argNum('--adx', DEFAULT_GRID_PARAMS.adxMax), tfMs: argNum('--tf', DEFAULT_GRID_PARAMS.tfMs / 60000) * 60000, // Minuten }; const START_CAPITAL = 1000; const EXEC = DEFAULT_EXEC; const MIN_NOTIONAL = 10; 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(`\nATR-Grid (fix: spacing ${PARAMS.spacingAtrMult}×ATR, ${PARAMS.gridLevels} Levels, ADX < ${PARAMS.adxMax}, tf ${PARAMS.tfMs / 60000}m, 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 mkCfg = (tradeFrom: number, tradeTo: number): GridConfig => ({ startCapital: START_CAPITAL, exec: EXEC, params: PARAMS, minNotionalUsdt: MIN_NOTIONAL, tradeFrom, tradeTo, }); const trainResult = runGridBacktest(candles15ByPair, mkCfg(w.trainFrom, w.trainTo)); const trainMetrics = computeMetrics(trainResult.trades, trainResult.equityCurve, START_CAPITAL); const testResult = runGridBacktest(candles15ByPair, mkCfg(w.testFrom, w.testTo)); 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`, ); } 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'}`); await db.insert(backtestRuns).values({ kind: 'grid-walkforward', config: { startCapital: START_CAPITAL, exec: EXEC, params: PARAMS, minNotionalUsdt: MIN_NOTIONAL } 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();