feat: wavelets simulation
This commit is contained in:
@@ -362,6 +362,16 @@ export type WaveletOptimizationResult = {
|
|||||||
winRate: number
|
winRate: number
|
||||||
tradeCount: number
|
tradeCount: number
|
||||||
analysisMethod?: 'cwt' | 'ssq'
|
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
|
// 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(
|
export function runOptimizationGrid(
|
||||||
symbol: string, lookback: number, wavelet: string, rolling: any,
|
symbol: string, lookback: number, wavelet: string, rolling: any,
|
||||||
bandsToTest: number[], kindsToTest: WaveletTriggerKind[], modesToTest: WaveletPositionMode[],
|
bandsToTest: number[], kindsToTest: WaveletTriggerKind[], modesToTest: WaveletPositionMode[],
|
||||||
@@ -433,6 +459,7 @@ export function runOptimizationGrid(
|
|||||||
for (const variant of exitVariants) {
|
for (const variant of exitVariants) {
|
||||||
const config = buildSymmetricTradeConfig(curveId, kind, mode, variant, filterCurveId)
|
const config = buildSymmetricTradeConfig(curveId, kind, mode, variant, filterCurveId)
|
||||||
const sim = runWaveletTradeSimulation(rolling.dates, rolling.original, curves, config)
|
const sim = runWaveletTradeSimulation(rolling.dates, rolling.original, curves, config)
|
||||||
|
const dist = tradeDistributionStats(sim.trades)
|
||||||
results.push({
|
results.push({
|
||||||
symbol, lookback, wavelet, mode,
|
symbol, lookback, wavelet, mode,
|
||||||
triggerKind: kind,
|
triggerKind: kind,
|
||||||
@@ -440,6 +467,7 @@ export function runOptimizationGrid(
|
|||||||
exitStyle: variant.exitStyle, takeProfitPct: variant.takeProfitPct, stopLossPct: variant.stopLossPct,
|
exitStyle: variant.exitStyle, takeProfitPct: variant.takeProfitPct, stopLossPct: variant.stopLossPct,
|
||||||
totalReturnPct: sim.totalReturnPct, winRate: sim.winRate, tradeCount: sim.trades.length,
|
totalReturnPct: sim.totalReturnPct, winRate: sim.winRate, tradeCount: sim.trades.length,
|
||||||
analysisMethod: rolling.method ?? 'cwt',
|
analysisMethod: rolling.method ?? 'cwt',
|
||||||
|
...dist,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,6 +396,8 @@ export default function WaveletsSimulation() {
|
|||||||
<th className="pr-3 text-right">Retour</th>
|
<th className="pr-3 text-right">Retour</th>
|
||||||
<th className="pr-3 text-right">Win rate</th>
|
<th className="pr-3 text-right">Win rate</th>
|
||||||
<th className="pr-3 text-right">Trades</th>
|
<th className="pr-3 text-right">Trades</th>
|
||||||
|
<th className="pr-3 text-right" title="Gain moyen / max / min parmi les trades gagnants — un gain max qui écrase les autres révèle un résultat porté par un seul trade, pas une vraie récurrence">Gains (moy/max/min)</th>
|
||||||
|
<th className="pr-3 text-right" title="Perte moyenne / la pire / la plus petite parmi les trades perdants">Pertes (moy/max/min)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -414,10 +416,16 @@ export default function WaveletsSimulation() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="pr-3 text-right font-mono text-slate-300">{(r.winRate * 100).toFixed(0)}%</td>
|
<td className="pr-3 text-right font-mono text-slate-300">{(r.winRate * 100).toFixed(0)}%</td>
|
||||||
<td className="pr-3 text-right font-mono text-slate-300">{r.tradeCount}</td>
|
<td className="pr-3 text-right font-mono text-slate-300">{r.tradeCount}</td>
|
||||||
|
<td className="pr-3 text-right font-mono text-emerald-400/90 whitespace-nowrap">
|
||||||
|
{r.avgGainPct != null ? `${r.avgGainPct.toFixed(1)} / ${r.maxGainPct!.toFixed(1)} / ${r.minGainPct!.toFixed(1)}` : '—'}
|
||||||
|
</td>
|
||||||
|
<td className="pr-3 text-right font-mono text-red-400/90 whitespace-nowrap">
|
||||||
|
{r.avgLossPct != null ? `${r.avgLossPct.toFixed(1)} / ${r.maxLossPct!.toFixed(1)} / ${r.minLossPct!.toFixed(1)}` : '—'}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{expandedRow === i && (
|
{expandedRow === i && (
|
||||||
<tr key={`${i}-detail`} className="bg-dark-900/40">
|
<tr key={`${i}-detail`} className="bg-dark-900/40">
|
||||||
<td colSpan={9} className="p-2">
|
<td colSpan={11} className="p-2">
|
||||||
<div className="text-[10px] text-slate-500 mb-1">Détail par instrument (même config)</div>
|
<div className="text-[10px] text-slate-500 mb-1">Détail par instrument (même config)</div>
|
||||||
<div className="grid grid-cols-4 gap-1.5">
|
<div className="grid grid-cols-4 gap-1.5">
|
||||||
{perSymbolBreakdown(r).map((d, j) => (
|
{perSymbolBreakdown(r).map((d, j) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user