feat(web): admin-only "Test against history" on /plans cards (Stage A)
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>
This commit is contained in:
parent
35efa786bd
commit
1a6c0352e3
@ -7,6 +7,7 @@ import type {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import { parseSymbolsInput, toDateInputValue } from '../utils';
|
import { parseSymbolsInput, toDateInputValue } from '../utils';
|
||||||
import { Button, Input, Select } from '../../components/ui/Primitives';
|
import { Button, Input, Select } from '../../components/ui/Primitives';
|
||||||
|
import { HistoricalPresetPicker } from './HistoricalPresetPicker';
|
||||||
|
|
||||||
type SourceType = 'csv' | 'json' | 'replay' | 'kraken';
|
type SourceType = 'csv' | 'json' | 'replay' | 'kraken';
|
||||||
|
|
||||||
@ -35,6 +36,21 @@ const defaultTo = toDateInputValue(now);
|
|||||||
const [timeframe, setTimeframe] = useState<BacktestTimeframe>('15m');
|
const [timeframe, setTimeframe] = useState<BacktestTimeframe>('15m');
|
||||||
const [fromDate, setFromDate] = useState(defaultFrom);
|
const [fromDate, setFromDate] = useState(defaultFrom);
|
||||||
const [toDate, setToDate] = useState(defaultTo);
|
const [toDate, setToDate] = useState(defaultTo);
|
||||||
|
const [activePresetId, setActivePresetId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handlePresetSelect = (presetFrom: string, presetTo: string, presetId: string) => {
|
||||||
|
setFromDate(presetFrom);
|
||||||
|
setToDate(presetTo);
|
||||||
|
setActivePresetId(presetId);
|
||||||
|
};
|
||||||
|
const handleFromDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFromDate(event.target.value);
|
||||||
|
setActivePresetId(null);
|
||||||
|
};
|
||||||
|
const handleToDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setToDate(event.target.value);
|
||||||
|
setActivePresetId(null);
|
||||||
|
};
|
||||||
const [sourceType, setSourceType] = useState<SourceType>('csv');
|
const [sourceType, setSourceType] = useState<SourceType>('csv');
|
||||||
const [sourcePayload, setSourcePayload] = useState<any>(null);
|
const [sourcePayload, setSourcePayload] = useState<any>(null);
|
||||||
const [sourceName, setSourceName] = useState('');
|
const [sourceName, setSourceName] = useState('');
|
||||||
@ -163,7 +179,7 @@ const defaultTo = toDateInputValue(now);
|
|||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={fromDate}
|
value={fromDate}
|
||||||
onChange={(event) => setFromDate(event.target.value)}
|
onChange={handleFromDateChange}
|
||||||
controlSize="sm"
|
controlSize="sm"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@ -172,11 +188,15 @@ const defaultTo = toDateInputValue(now);
|
|||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={toDate}
|
value={toDate}
|
||||||
onChange={(event) => setToDate(event.target.value)}
|
onChange={handleToDateChange}
|
||||||
controlSize="sm"
|
controlSize="sm"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<HistoricalPresetPicker
|
||||||
|
selectedId={activePresetId}
|
||||||
|
onSelect={handlePresetSelect}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<label className="space-y-1">
|
<label className="space-y-1">
|
||||||
|
|||||||
82
web/src/backtest/components/BacktestPlanModal.tsx
Normal file
82
web/src/backtest/components/BacktestPlanModal.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import { BacktestRunnerPanel } from './BacktestRunnerPanel';
|
||||||
|
import type { TradeProfilePayload } from '../../lib/profileApi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin-only modal that wraps BacktestRunnerPanel with the strategy_config
|
||||||
|
* + symbols + initial capital pre-loaded from a saved trade plan's profile.
|
||||||
|
*
|
||||||
|
* Stage A of docs/backtest/ENGINE_READINESS.md §4. Surfaces existing
|
||||||
|
* backtest infrastructure to admin users without changing the customer-
|
||||||
|
* facing experience or the production feature flag.
|
||||||
|
*/
|
||||||
|
export interface BacktestPlanModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
/** The trade profile whose strategy_config + symbols + capital seed the run. */
|
||||||
|
profile: TradeProfilePayload | null;
|
||||||
|
/** The symbol from the saved-setup card (overrides profile.symbols if set). */
|
||||||
|
symbolOverride?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BacktestPlanModal: React.FC<BacktestPlanModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
profile,
|
||||||
|
symbolOverride,
|
||||||
|
}) => {
|
||||||
|
if (!open || !profile) return null;
|
||||||
|
|
||||||
|
const profileSymbols = String(profile.symbols || '')
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const symbols = symbolOverride
|
||||||
|
? [symbolOverride.trim().toUpperCase()]
|
||||||
|
: profileSymbols;
|
||||||
|
|
||||||
|
// Use the trade profile's allocated_capital as the starting capital so the
|
||||||
|
// backtest reflects what the customer actually configured. Falls back to
|
||||||
|
// 10k for unconfigured profiles.
|
||||||
|
const initialCapitalUsd = Number(profile.allocated_capital) > 0
|
||||||
|
? Number(profile.allocated_capital)
|
||||||
|
: 10000;
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div className="backtest-plan-modal-backdrop" role="dialog" aria-modal="true" onClick={onClose}>
|
||||||
|
<div className="backtest-plan-modal-panel" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="backtest-plan-modal-header">
|
||||||
|
<div>
|
||||||
|
<div className="backtest-plan-modal-eyebrow">Test plan against history (admin)</div>
|
||||||
|
<h2 className="backtest-plan-modal-title">{profile.name || 'Trade plan'}</h2>
|
||||||
|
<div className="backtest-plan-modal-sub">
|
||||||
|
Symbols: <span className="font-mono">{symbols.join(', ') || '—'}</span>
|
||||||
|
{' · '}
|
||||||
|
Capital: ${initialCapitalUsd.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="backtest-plan-modal-close"
|
||||||
|
aria-label="Close backtest modal"
|
||||||
|
>
|
||||||
|
<X size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="backtest-plan-modal-body">
|
||||||
|
<BacktestRunnerPanel
|
||||||
|
profileId={profile.id}
|
||||||
|
strategyConfig={profile.strategy_config}
|
||||||
|
symbols={symbols}
|
||||||
|
initialCapitalUsd={initialCapitalUsd}
|
||||||
|
title="Pick a date range or historical preset"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
};
|
||||||
93
web/src/backtest/components/HistoricalPresetPicker.tsx
Normal file
93
web/src/backtest/components/HistoricalPresetPicker.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preset historical event 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.
|
||||||
|
*/
|
||||||
|
export interface HistoricalEventPreset {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
fromDate: string; // YYYY-MM-DD
|
||||||
|
toDate: string; // YYYY-MM-DD
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'covid-recovery',
|
||||||
|
label: 'COVID recovery',
|
||||||
|
description: 'Apr-Dec 2020 — fastest bull market in modern history.',
|
||||||
|
fromDate: '2020-04-01',
|
||||||
|
toDate: '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',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bear-2022',
|
||||||
|
label: '2022 bear market',
|
||||||
|
description: 'Full year — sustained drawdown across crypto + tech equities.',
|
||||||
|
fromDate: '2022-01-01',
|
||||||
|
toDate: '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',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface HistoricalPresetPickerProps {
|
||||||
|
/** Currently-selected preset id (or null when user is editing custom dates). */
|
||||||
|
selectedId: string | null;
|
||||||
|
/** 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[];
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HistoricalPresetPicker: React.FC<HistoricalPresetPickerProps> = ({
|
||||||
|
selectedId,
|
||||||
|
onSelect,
|
||||||
|
presets = DEFAULT_HISTORICAL_PRESETS,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`historical-preset-picker ${className || ''}`}>
|
||||||
|
<div className="historical-preset-picker-label">Historical events</div>
|
||||||
|
<div className="historical-preset-picker-row">
|
||||||
|
{presets.map((preset) => {
|
||||||
|
const active = selectedId === preset.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={preset.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onSelect(preset.fromDate, preset.toDate, preset.id)}
|
||||||
|
className={`historical-preset-chip ${active ? 'is-active' : ''}`}
|
||||||
|
title={`${preset.description} (${preset.fromDate} → ${preset.toDate})`}
|
||||||
|
>
|
||||||
|
{preset.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -494,3 +494,145 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Section 25 — Historical event preset picker (Stage A backtest POC)
|
||||||
|
Used inside BacktestConfigurator. Compact chip row that lets the user
|
||||||
|
pick a pre-validated date range (COVID, war periods) instead of typing
|
||||||
|
dates manually.
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
.historical-preset-picker {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.historical-preset-picker-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--bl-text-quiet);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
.historical-preset-picker-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.historical-preset-chip {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
font: inherit;
|
||||||
|
border: 1px solid var(--bl-border-subtle);
|
||||||
|
background: var(--bl-surface-muted);
|
||||||
|
color: var(--bl-text-secondary);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
|
||||||
|
}
|
||||||
|
.historical-preset-chip:hover {
|
||||||
|
background: var(--bl-surface-highlight);
|
||||||
|
border-color: var(--bl-border);
|
||||||
|
color: var(--bl-text-primary);
|
||||||
|
}
|
||||||
|
.historical-preset-chip.is-active {
|
||||||
|
background: var(--bl-accent-muted);
|
||||||
|
border-color: var(--bl-accent);
|
||||||
|
color: var(--bl-accent);
|
||||||
|
}
|
||||||
|
.historical-preset-chip:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px var(--bl-bg-canvas, #0b0f17),
|
||||||
|
0 0 0 4px var(--bl-focus-ring, var(--bl-accent, #5A8CFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
Section 26 — Admin-only "Test against history" backtest modal (Stage A)
|
||||||
|
Lives in BacktestPlanModal.tsx. Portaled to <body> so it escapes any
|
||||||
|
parent overflow:hidden in the trade-plans card grid.
|
||||||
|
--------------------------------------------------------------------------- */
|
||||||
|
.backtest-plan-modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background: color-mix(in oklab, var(--bl-bg-canvas, #0b0f17) 70%, transparent);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 5vh 16px 8vh;
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-panel {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 960px;
|
||||||
|
background: var(--card-elevated);
|
||||||
|
border: 1px solid var(--bl-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px 24px 16px;
|
||||||
|
border-bottom: 1px solid var(--bl-border-subtle);
|
||||||
|
background: var(--bl-surface-overlay);
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-eyebrow {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--bl-warning);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--foreground);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-sub {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-close {
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--bl-border-subtle);
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--bl-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-close:hover {
|
||||||
|
background: var(--bl-surface-highlight);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
.backtest-plan-modal-body {
|
||||||
|
padding: 20px 24px 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(90vh - 120px);
|
||||||
|
}
|
||||||
|
.saved-setup-action.is-admin-only {
|
||||||
|
border-color: color-mix(in oklab, var(--bl-warning) 30%, var(--bl-border));
|
||||||
|
color: var(--bl-warning);
|
||||||
|
}
|
||||||
|
.saved-setup-action.is-admin-only:hover {
|
||||||
|
background: color-mix(in oklab, var(--bl-warning) 10%, transparent);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
import { useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
||||||
import type { ChangeEvent, FormEvent } from 'react';
|
import type { ChangeEvent, FormEvent } from 'react';
|
||||||
import { Pencil, RefreshCw, Trash2 } from 'lucide-react';
|
import { History, Pencil, RefreshCw, Trash2 } from 'lucide-react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useAppContext } from '../context/AppContext';
|
import { useAppContext } from '../context/AppContext';
|
||||||
import { fetchChartBars, fetchResearchProfile } from '../lib/marketApi';
|
import { fetchChartBars, fetchResearchProfile } from '../lib/marketApi';
|
||||||
@ -25,6 +25,8 @@ import {
|
|||||||
} from './tradePlansState';
|
} from './tradePlansState';
|
||||||
import { useTradePlansNavigationState } from './useTradePlansNavigationState';
|
import { useTradePlansNavigationState } from './useTradePlansNavigationState';
|
||||||
import { CardButton } from '@bytelyst/ui';
|
import { CardButton } from '@bytelyst/ui';
|
||||||
|
import { useAuth } from '../components/AuthContext';
|
||||||
|
import { BacktestPlanModal } from '../backtest/components/BacktestPlanModal';
|
||||||
|
|
||||||
type SimpleHolding = {
|
type SimpleHolding = {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
@ -592,8 +594,11 @@ function describeSavedSetup(entry: ManualEntryPayload): string {
|
|||||||
|
|
||||||
export function SimpleView() {
|
export function SimpleView() {
|
||||||
const { botState } = useAppContext();
|
const { botState } = useAppContext();
|
||||||
|
const { profile: authProfile } = useAuth();
|
||||||
|
const isAdmin = authProfile?.role === 'admin';
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [profiles, setProfiles] = useState<TradeProfilePayload[]>([]);
|
const [profiles, setProfiles] = useState<TradeProfilePayload[]>([]);
|
||||||
|
const [backtestModalSetupId, setBacktestModalSetupId] = useState<string | null>(null);
|
||||||
const [savedSetups, setSavedSetups] = useState<ManualEntryPayload[]>([]);
|
const [savedSetups, setSavedSetups] = useState<ManualEntryPayload[]>([]);
|
||||||
const [uiState, dispatch] = useReducer(reduceTradePlansUiState, DEFAULT_TRADE_PLANS_UI_STATE);
|
const [uiState, dispatch] = useReducer(reduceTradePlansUiState, DEFAULT_TRADE_PLANS_UI_STATE);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@ -1454,6 +1459,21 @@ export function SimpleView() {
|
|||||||
Resume exit management
|
Resume exit management
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
{isAdmin ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setBacktestModalSetupId(entryId)}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="saved-setup-action is-admin-only"
|
||||||
|
title="Test this plan against historical market data (admin only)"
|
||||||
|
>
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<History size={14} />
|
||||||
|
Test history
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleEdit(entry)}
|
onClick={() => handleEdit(entry)}
|
||||||
@ -1627,6 +1647,21 @@ export function SimpleView() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
<BacktestPlanModal
|
||||||
|
open={Boolean(backtestModalSetupId)}
|
||||||
|
onClose={() => 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;
|
||||||
|
})()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user