feat: Grid-Timeframe parametrisierbar (--tf) — kürzere TFs monoton schlechter
aggregateTf verallgemeinert aggregate4h. Walk-Forward 1h/15m: PF 0.59/0.29 (vs 0.87 auf 4h), 15m MaxDD 97% — Fee-Mathematik bestätigt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { Candle, Pair } from '../types';
|
||||
import { PAIRS } from '../types';
|
||||
import { aggregate4h, H4 } from '../market/aggregate';
|
||||
import { aggregateTf, H4 } from '../market/aggregate';
|
||||
import { atr } from '../indicators/atr';
|
||||
import { adx } from '../indicators/adx';
|
||||
import type { ClosedTrade, ExecConfig } from '../engine/portfolio';
|
||||
@@ -8,10 +8,11 @@ import type { EquityPoint } from './metrics';
|
||||
import type { BacktestResult } from './runner';
|
||||
|
||||
export interface GridParams {
|
||||
spacingAtrMult: number; // Level-Abstand = mult × ATR(14, 4h) bei Aktivierung
|
||||
spacingAtrMult: number; // Level-Abstand = mult × ATR(atrPeriod, tf) bei Aktivierung
|
||||
gridLevels: number; // Buy-Levels unterhalb des Centers
|
||||
adxMax: number; // Grid nur aktiv im Seitwärtsregime (ADX < adxMax)
|
||||
atrPeriod: number;
|
||||
tfMs: number; // Entscheidungs-Timeframe (Aktivierung/Deaktivierung, ATR/ADX-Basis)
|
||||
}
|
||||
|
||||
export const DEFAULT_GRID_PARAMS: GridParams = {
|
||||
@@ -19,6 +20,7 @@ export const DEFAULT_GRID_PARAMS: GridParams = {
|
||||
gridLevels: 4,
|
||||
adxMax: 20,
|
||||
atrPeriod: 14,
|
||||
tfMs: H4,
|
||||
};
|
||||
|
||||
export interface GridConfig {
|
||||
@@ -50,8 +52,8 @@ interface ActiveGrid {
|
||||
|
||||
/**
|
||||
* ATR-Grid mit ADX-Regime-Filter, long-only, je Pair unabhängig.
|
||||
* 4h-Close: Aktivierung/Deaktivierung; 15m: Fills (Sells vor Buys —
|
||||
* ein im selben Bar gekaufter Lot kann nicht im selben Bar verkaufen).
|
||||
* Tf-Close (default 4h): Aktivierung/Deaktivierung; 15m: Fills (Sells vor
|
||||
* Buys — ein im selben Bar gekaufter Lot kann nicht im selben Bar verkaufen).
|
||||
* Fee/Slippage-Mathematik identisch zu Portfolio (pessimistische Fills).
|
||||
*/
|
||||
export function runGridBacktest(candles15ByPair: Map<Pair, Candle[]>, cfg: GridConfig): BacktestResult {
|
||||
@@ -106,7 +108,7 @@ export function runGridBacktest(candles15ByPair: Map<Pair, Candle[]>, cfg: GridC
|
||||
// --- Kontexte + gemergte 15m-Timeline (wie runner.ts) ---
|
||||
const contexts = PAIRS.filter((pr) => candles15ByPair.has(pr)).map((pair) => {
|
||||
const c15 = candles15ByPair.get(pair)!;
|
||||
const c4h = aggregate4h(c15);
|
||||
const c4h = aggregateTf(c15, p.tfMs);
|
||||
return { pair, c4h, atr: atr(c4h, p.atrPeriod), adx: adx(c4h, p.atrPeriod), next4h: 0 };
|
||||
});
|
||||
const byPair = new Map(contexts.map((c) => [c.pair, c]));
|
||||
@@ -123,13 +125,13 @@ export function runGridBacktest(candles15ByPair: Map<Pair, Candle[]>, cfg: GridC
|
||||
|
||||
for (const { ts, pair, candle } of timeline) {
|
||||
const ctx = byPair.get(pair)!;
|
||||
const bucket = Math.floor(ts / H4) * H4;
|
||||
const bucket = Math.floor(ts / p.tfMs) * p.tfMs;
|
||||
|
||||
// 1) Neu abgeschlossene 4h-Bars: Deaktivierung / Aktivierung
|
||||
// 1) Neu abgeschlossene Tf-Bars: Deaktivierung / Aktivierung
|
||||
while (ctx.next4h < ctx.c4h.length && ctx.c4h[ctx.next4h].ts < bucket) {
|
||||
const i = ctx.next4h++;
|
||||
const bar = ctx.c4h[i];
|
||||
const barCloseTs = bar.ts + H4;
|
||||
const barCloseTs = bar.ts + p.tfMs;
|
||||
if (barCloseTs < cfg.tradeFrom || barCloseTs >= cfg.tradeTo) continue;
|
||||
|
||||
const g = grids.get(pair);
|
||||
|
||||
Reference in New Issue
Block a user