From 81b71dc96ef17b4943455b6b6fd04e9b78fffaf9 Mon Sep 17 00:00:00 2001 From: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 10:53:08 +0000 Subject: [PATCH] feat(web): rolling 'Last N days' presets in backtest picker (Stage E) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "Recent" preset row to HistoricalPresetPicker with three rolling windows resolved against today at click time: - Last 30 days — fast feedback for recently saved plans - Last 90 days — captures most recent regime shifts - Last 12 months — captures multiple regime cycles These complement the fixed-date "Historical events" row (COVID etc.) already shipped in Stage A. Together they answer two distinct user questions: "how would this plan have played my last 90 days?" (Recent) vs. "how would this plan have done in the COVID crash?" (Historical). Internals: - Refactored HistoricalEventPreset to use resolveRange() instead of static fromDate/toDate strings — lets rolling windows stay accurate across days without rebuilding the preset list. - HistoricalPresetPicker now accepts both `recentPresets` and `historicalPresets` props. Either can be empty/omitted to hide that row. No backend changes — uses existing /api/backtest/run with kraken or upload sources. Stage E of docs/backtest/ENGINE_READINESS.md §4. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../components/HistoricalPresetPicker.tsx | 120 +++++++++++++----- 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/web/src/backtest/components/HistoricalPresetPicker.tsx b/web/src/backtest/components/HistoricalPresetPicker.tsx index 15ed0f9..c168ea1 100644 --- a/web/src/backtest/components/HistoricalPresetPicker.tsx +++ b/web/src/backtest/components/HistoricalPresetPicker.tsx @@ -1,55 +1,84 @@ import React from 'react'; /** - * Preset historical event windows for the backtest configurator. + * Preset windows for the backtest configurator. * - * These are pre-validated against typical Kraken/Alpaca data depth (5-7 - * years), so customers don't waste a backtest on a window that will return - * "no data" for newer symbols. See docs/backtest/ENGINE_READINESS.md §3.4. + * Two categories: + * - "Recent" — rolling windows relative to today. Most useful for + * "how would this plan have played my last 90 days?" (Stage E of + * docs/backtest/ENGINE_READINESS.md §4). + * - "Historical events" — fixed-date windows around significant market + * regimes. Pre-validated against typical Kraken/Alpaca data depth + * (5-7 years) so customers don't waste a backtest on a window with + * no data for newer symbols. */ export interface HistoricalEventPreset { id: string; label: string; description: string; - fromDate: string; // YYYY-MM-DD - toDate: string; // YYYY-MM-DD + /** YYYY-MM-DD; resolved at click time so "Last 30 days" stays accurate. */ + resolveRange: () => { fromDate: string; toDate: string }; } +const isoDate = (d: Date): string => d.toISOString().slice(0, 10); +const fixedRange = (from: string, to: string) => () => ({ fromDate: from, toDate: to }); +const trailingDays = (n: number) => () => { + const to = new Date(); + const from = new Date(to.getTime() - n * 24 * 60 * 60 * 1000); + return { fromDate: isoDate(from), toDate: isoDate(to) }; +}; + +export const RECENT_PRESETS: HistoricalEventPreset[] = [ + { + id: 'recent-30d', + label: 'Last 30 days', + description: 'Rolling 30-day window — fast feedback for recently saved plans.', + resolveRange: trailingDays(30), + }, + { + id: 'recent-90d', + label: 'Last 90 days', + description: 'Rolling quarter — captures most recent regime shifts.', + resolveRange: trailingDays(90), + }, + { + id: 'recent-365d', + label: 'Last 12 months', + description: 'Rolling year — captures multiple regime cycles.', + resolveRange: trailingDays(365), + }, +]; + export const DEFAULT_HISTORICAL_PRESETS: HistoricalEventPreset[] = [ { id: 'covid-crash', label: 'COVID crash', description: 'Feb-Apr 2020 — ~30% drawdown across most assets in 5 weeks.', - fromDate: '2020-02-15', - toDate: '2020-04-30', + resolveRange: fixedRange('2020-02-15', '2020-04-30'), }, { id: 'covid-recovery', label: 'COVID recovery', description: 'Apr-Dec 2020 — fastest bull market in modern history.', - fromDate: '2020-04-01', - toDate: '2020-12-31', + resolveRange: fixedRange('2020-04-01', '2020-12-31'), }, { id: 'russia-ukraine', label: 'Russia/Ukraine 2022', description: 'Feb-Jun 2022 — energy + commodity shocks, risk-off pivot.', - fromDate: '2022-02-01', - toDate: '2022-06-30', + resolveRange: fixedRange('2022-02-01', '2022-06-30'), }, { id: 'bear-2022', label: '2022 bear market', description: 'Full year — sustained drawdown across crypto + tech equities.', - fromDate: '2022-01-01', - toDate: '2022-12-31', + resolveRange: fixedRange('2022-01-01', '2022-12-31'), }, { id: 'svb-banking', label: 'SVB banking shock', description: 'Mar 2023 — bank failures + emergency Fed response.', - fromDate: '2023-03-01', - toDate: '2023-04-15', + resolveRange: fixedRange('2023-03-01', '2023-04-15'), }, ]; @@ -59,35 +88,56 @@ export interface HistoricalPresetPickerProps { /** Called with `(fromDate, toDate, presetId)` when a preset is clicked. */ onSelect: (fromDate: string, toDate: string, presetId: string) => void; /** Override the default preset list. */ - presets?: HistoricalEventPreset[]; + recentPresets?: HistoricalEventPreset[]; + historicalPresets?: HistoricalEventPreset[]; className?: string; } +const renderRow = ( + presets: HistoricalEventPreset[], + selectedId: string | null, + onSelect: HistoricalPresetPickerProps['onSelect'] +) => ( +