feat: Walk-Forward-CLI mit Gate-Report und Run-Persistenz
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
52
src/server/scripts/walkforward.ts
Normal file
52
src/server/scripts/walkforward.ts
Normal file
@@ -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<Pair, Candle[]>();
|
||||||
|
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();
|
||||||
Reference in New Issue
Block a user