Files
trade-kuns/src/server/scripts/rotation-walkforward.ts
Claude 29000a2bba feat: Rotation-Walk-Forward-CLI (gemeinsame OOS-Aggregation extrahiert)
aggregateOos() aus runWalkForward herausgezogen und exportiert; beide
Walk-Forward-Varianten (Donchian + Rotation) nutzen dieselbe OOS-Logik.
Neues Script rotation-walkforward.ts mit identischem Report-Format und
Persistenz in backtest_runs (kind='rotation-walkforward').
package.json: "rotation"-Script ergänzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 22:09:22 +00:00

110 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<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());
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<typeof computeMetrics>;
testMetrics: ReturnType<typeof computeMetrics>;
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();