From 1a6c0352e3c7249f3fdf239f4c1d031d2d0e8063 Mon Sep 17 00:00:00 2001 From: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 10:50:59 +0000 Subject: [PATCH] feat(web): admin-only "Test against history" on /plans cards (Stage A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "Test history" action button on each saved-setup card on /plans, visible only when the authenticated profile has role=admin. Clicking opens a portaled modal containing the existing BacktestRunnerPanel pre-loaded with: - the plan's TradeProfile.strategy_config (the actual rules + risk limits the customer configured) - the saved-setup's symbol (overrides profile.symbols when scoped) - the profile's allocated_capital as initialCapitalUsd Also adds a "Historical events" preset row to BacktestConfigurator with 5 pre-validated date ranges (COVID crash, COVID recovery, Russia/Ukraine 2022, 2022 bear market, SVB banking shock). Selecting a preset fills in the from/to date inputs; manually editing dates clears the active preset highlight. This is a customer-experience-neutral change: - No production feature flag is flipped (customer non-admins see no new UI; existing useBacktestFeatureGate still gates the actual backtest API call) - No backend changes — reuses existing /api/backtest/run - No new strategy code paths Files: + web/src/backtest/components/HistoricalPresetPicker.tsx (new) + web/src/backtest/components/BacktestPlanModal.tsx (new, portaled) ~ web/src/backtest/components/BacktestConfigurator.tsx (preset wiring) ~ web/src/views/SimpleView.tsx (admin button + modal mount) ~ web/src/layout-fixes.css (§25 preset chips, §26 modal styles) Stage A of docs/backtest/ENGINE_READINESS.md §4. Lets admins dogfood the backtest UX and surface bugs before any customer-facing rollout (stages B/C/D/F). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../components/BacktestConfigurator.tsx | 24 ++- .../backtest/components/BacktestPlanModal.tsx | 82 ++++++++++ .../components/HistoricalPresetPicker.tsx | 93 ++++++++++++ web/src/layout-fixes.css | 142 ++++++++++++++++++ web/src/views/SimpleView.tsx | 37 ++++- 5 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 web/src/backtest/components/BacktestPlanModal.tsx create mode 100644 web/src/backtest/components/HistoricalPresetPicker.tsx diff --git a/web/src/backtest/components/BacktestConfigurator.tsx b/web/src/backtest/components/BacktestConfigurator.tsx index a3657b2..db6a8ab 100644 --- a/web/src/backtest/components/BacktestConfigurator.tsx +++ b/web/src/backtest/components/BacktestConfigurator.tsx @@ -7,6 +7,7 @@ import type { } from '../types'; import { parseSymbolsInput, toDateInputValue } from '../utils'; import { Button, Input, Select } from '../../components/ui/Primitives'; +import { HistoricalPresetPicker } from './HistoricalPresetPicker'; type SourceType = 'csv' | 'json' | 'replay' | 'kraken'; @@ -35,6 +36,21 @@ const defaultTo = toDateInputValue(now); const [timeframe, setTimeframe] = useState('15m'); const [fromDate, setFromDate] = useState(defaultFrom); const [toDate, setToDate] = useState(defaultTo); + const [activePresetId, setActivePresetId] = useState(null); + + const handlePresetSelect = (presetFrom: string, presetTo: string, presetId: string) => { + setFromDate(presetFrom); + setToDate(presetTo); + setActivePresetId(presetId); + }; + const handleFromDateChange = (event: React.ChangeEvent) => { + setFromDate(event.target.value); + setActivePresetId(null); + }; + const handleToDateChange = (event: React.ChangeEvent) => { + setToDate(event.target.value); + setActivePresetId(null); + }; const [sourceType, setSourceType] = useState('csv'); const [sourcePayload, setSourcePayload] = useState(null); const [sourceName, setSourceName] = useState(''); @@ -163,7 +179,7 @@ const defaultTo = toDateInputValue(now); setFromDate(event.target.value)} + onChange={handleFromDateChange} controlSize="sm" /> @@ -172,11 +188,15 @@ const defaultTo = toDateInputValue(now); setToDate(event.target.value)} + onChange={handleToDateChange} controlSize="sm" /> +
+ setBacktestModalSetupId(null)} + profile={(() => { + if (!backtestModalSetupId) return null; + const setup = savedSetups.find((s) => String(s.stock_instance_id || '') === backtestModalSetupId) || null; + if (!setup) return null; + return profiles.find((p) => p.id === String(setup.profile_id || '')) || null; + })()} + symbolOverride={(() => { + if (!backtestModalSetupId) return undefined; + const setup = savedSetups.find((s) => String(s.stock_instance_id || '') === backtestModalSetupId); + return setup?.symbol ? String(setup.symbol) : undefined; + })()} + /> ); }