feat: wavelets simulation

This commit is contained in:
OpenSquared
2026-07-19 19:01:49 +02:00
parent e61bc50106
commit 32d30c01f2
2 changed files with 37 additions and 1 deletions

View File

@@ -362,6 +362,16 @@ export type WaveletOptimizationResult = {
winRate: number
tradeCount: number
analysisMethod?: 'cwt' | 'ssq'
// Per-trade distribution — a strong totalReturnPct can hide one outlier trade
// carrying the whole result while every other trade is flat or losing; these expose
// that shape instead of just the aggregate. null when there are no wins/losses to
// measure (e.g. tradeCount === 0, or every trade was a win).
avgGainPct: number | null
maxGainPct: number | null
minGainPct: number | null
avgLossPct: number | null
maxLossPct: number | null // most negative — the single worst trade
minLossPct: number | null // least negative — the smallest losing trade
}
// Trend-confirmation filter: require a SECONDARY band's slope to align with the
@@ -407,6 +417,22 @@ export function buildSymmetricTradeConfig(
}
}
function tradeDistributionStats(trades: WaveletTrade[]): {
avgGainPct: number | null; maxGainPct: number | null; minGainPct: number | null
avgLossPct: number | null; maxLossPct: number | null; minLossPct: number | null
} {
const gains = trades.map(t => t.returnPct).filter(r => r > 0)
const losses = trades.map(t => t.returnPct).filter(r => r <= 0)
return {
avgGainPct: gains.length ? gains.reduce((a, b) => a + b, 0) / gains.length : null,
maxGainPct: gains.length ? Math.max(...gains) : null,
minGainPct: gains.length ? Math.min(...gains) : null,
avgLossPct: losses.length ? losses.reduce((a, b) => a + b, 0) / losses.length : null,
maxLossPct: losses.length ? Math.min(...losses) : null, // most negative = the single worst trade
minLossPct: losses.length ? Math.max(...losses) : null, // least negative = the smallest losing trade
}
}
export function runOptimizationGrid(
symbol: string, lookback: number, wavelet: string, rolling: any,
bandsToTest: number[], kindsToTest: WaveletTriggerKind[], modesToTest: WaveletPositionMode[],
@@ -433,6 +459,7 @@ export function runOptimizationGrid(
for (const variant of exitVariants) {
const config = buildSymmetricTradeConfig(curveId, kind, mode, variant, filterCurveId)
const sim = runWaveletTradeSimulation(rolling.dates, rolling.original, curves, config)
const dist = tradeDistributionStats(sim.trades)
results.push({
symbol, lookback, wavelet, mode,
triggerKind: kind,
@@ -440,6 +467,7 @@ export function runOptimizationGrid(
exitStyle: variant.exitStyle, takeProfitPct: variant.takeProfitPct, stopLossPct: variant.stopLossPct,
totalReturnPct: sim.totalReturnPct, winRate: sim.winRate, tradeCount: sim.trades.length,
analysisMethod: rolling.method ?? 'cwt',
...dist,
})
}
}