diff --git a/apps/stock/web-app/src/App.tsx.old b/apps/stock/web-app/src/App.tsx.old deleted file mode 100644 index c8205c1..0000000 --- a/apps/stock/web-app/src/App.tsx.old +++ /dev/null @@ -1,232 +0,0 @@ -import { useState } from 'react'; -import { Dialog } from '@headlessui/react'; -import { - Bars3Icon, - XMarkIcon, - HomeIcon, - ChartBarIcon, - CogIcon, - DocumentTextIcon, - CurrencyDollarIcon, -} from '@heroicons/react/24/outline'; - -const navigation = [ - { name: 'Dashboard', href: '#', icon: HomeIcon, current: true }, - { name: 'Portfolio', href: '#', icon: CurrencyDollarIcon, current: false }, - { name: 'Analytics', href: '#', icon: ChartBarIcon, current: false }, - { name: 'Reports', href: '#', icon: DocumentTextIcon, current: false }, - { name: 'Settings', href: '#', icon: CogIcon, current: false }, -]; - -function classNames(...classes: string[]) { - return classes.filter(Boolean).join(' '); -} - -export default function App() { - const [sidebarOpen, setSidebarOpen] = useState(false); - - return ( -
-
- -
- -
- -
- -
- -
-
-

Stock Bot

-
- -
-
-
-
- - {/* Static sidebar for desktop */} -
-
-
-

Stock Bot

-
- -
-
- -
- -
- Dashboard -
-
- -
-
-

- Welcome to Stock Bot Dashboard -

-

- Monitor your trading performance, manage portfolios, and analyze market data. -

- -
-
-
-
- -
-
-

Portfolio Value

-

$0.00

-

Total assets

-
-
-
- -
-
-
- -
-
-

Total Return

-

+0.00%

-

Since inception

-
-
-
- -
-
-
- -
-
-

- Active Strategies -

-

0

-

Running algorithms

-
-
-
-
- -
-
-

- Recent Activity -

-
-
- No recent activity - -- -
-
-
- -
-

- Market Overview -

-
-
- - Market data loading... - - -- -
-
-
-
-
-
-
-
- ); -} diff --git a/apps/stock/web-app/src/features/backtest/BacktestDetailPage.tsx b/apps/stock/web-app/src/features/backtest/BacktestDetailPage.tsx deleted file mode 100644 index 6ba6a59..0000000 --- a/apps/stock/web-app/src/features/backtest/BacktestDetailPage.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { BacktestConfiguration } from './components/BacktestConfiguration'; -import { BacktestMetrics } from './components/BacktestMetrics'; -import { BacktestChart } from './components/BacktestChart'; -import { BacktestTrades } from './components/BacktestTrades'; -import { useBacktest } from './hooks/useBacktest'; -import type { BacktestConfig, BacktestResult as LocalBacktestResult } from './types/backtest.types'; -import { ArrowLeftIcon } from '@heroicons/react/24/outline'; - -const tabs = [ - { id: 'settings', name: 'Settings' }, - { id: 'metrics', name: 'Performance Metrics' }, - { id: 'chart', name: 'Chart' }, - { id: 'trades', name: 'Trades' }, -]; - -export function BacktestDetailPage() { - const { id } = useParams<{ id: string }>(); - const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState('settings'); - const { - backtest, - results, - isLoading, - isPolling, - error, - loadBacktest, - createBacktest, - updateBacktest, - cancelBacktest, - } = useBacktest(); - - // Local state to bridge between the API format and the existing UI components - const [config, setConfig] = useState(null); - const [adaptedResults, setAdaptedResults] = useState(null); - - // Load the specific backtest on mount - useEffect(() => { - if (id) { - loadBacktest(id); - } - }, [id, loadBacktest]); - - // Adapt the backtest data to config format when loaded - useEffect(() => { - if (backtest && !config) { - const backtestConfig: BacktestConfig = { - name: backtest.config?.name || '', - startDate: new Date(backtest.startDate), - endDate: new Date(backtest.endDate), - initialCapital: backtest.initialCapital, - symbols: backtest.symbols, - strategy: backtest.strategy, - speedMultiplier: backtest.config?.speedMultiplier || 1, - commission: backtest.config?.commission || 0.001, - slippage: backtest.config?.slippage || 0.0001, - }; - setConfig(backtestConfig); - } - }, [backtest, config]); - - // Adapt the backtest status from API format to local format - const status = backtest ? - (backtest.status === 'pending' ? 'configured' : - backtest.status === 'running' ? 'running' : - backtest.status === 'completed' ? 'completed' : - backtest.status === 'failed' ? 'error' : - backtest.status === 'cancelled' ? 'stopped' : 'idle') : 'idle'; - - // Current time is not available in the new API, so we'll estimate it based on progress - const currentTime = null; - - // No adaptation needed - results are already in the correct format - useEffect(() => { - setAdaptedResults(results); - }, [results]); - - const handleRunBacktest = useCallback(async () => { - if (!config) return; - - const backtestRequest = { - strategy: config.strategy, - symbols: config.symbols, - startDate: config.startDate.toISOString().split('T')[0], - endDate: config.endDate.toISOString().split('T')[0], - initialCapital: config.initialCapital, - config: { - name: config.name, - commission: config.commission, - slippage: config.slippage, - speedMultiplier: config.speedMultiplier, - useTypeScriptImplementation: true, - }, - }; - - await createBacktest(backtestRequest); - }, [config, createBacktest]); - - const handleConfigSubmit = useCallback(async (newConfig: BacktestConfig) => { - setConfig(newConfig); - setAdaptedResults(null); - - const backtestRequest = { - strategy: newConfig.strategy, - symbols: newConfig.symbols, - startDate: newConfig.startDate.toISOString().split('T')[0], - endDate: newConfig.endDate.toISOString().split('T')[0], - initialCapital: newConfig.initialCapital, - config: { - name: newConfig.name, - commission: newConfig.commission, - slippage: newConfig.slippage, - speedMultiplier: newConfig.speedMultiplier, - useTypeScriptImplementation: true, // Enable TypeScript strategy execution - }, - }; - - // If we have an existing backtest ID, update it, otherwise create new - if (id) { - await updateBacktest(id, backtestRequest); - } else { - await createBacktest(backtestRequest); - } - }, [id, createBacktest, updateBacktest]); - - const handleStop = useCallback(async () => { - await cancelBacktest(); - }, [cancelBacktest]); - - const renderTabContent = () => { - switch (activeTab) { - case 'settings': - return ( -
- -
- ); - case 'metrics': - return ( -
- -
- ); - case 'chart': - return ( - - ); - case 'trades': - return ( -
- -
- ); - default: - return null; - } - }; - - return ( -
- {/* Header with Tabs */} -
-
-
- -
-

- {backtest?.config?.name || config?.name || 'Backtest Detail'} - {id && ( - - ID: {id} - - )} -

-

- {backtest?.strategy || config?.strategy || 'No strategy selected'} -

-
- - {/* Tabs */} - -
- - {/* Run/Stop button */} -
- {status === 'running' ? ( - - ) : status !== 'running' && config && config.symbols.length > 0 ? ( - - ) : null} -
-
-
- - {/* Tab Content */} -
- {error && ( -
-

{error}

-
- )} - {renderTabContent()} -
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/BacktestListPage.tsx b/apps/stock/web-app/src/features/backtest/BacktestListPage.tsx deleted file mode 100644 index 8c1be67..0000000 --- a/apps/stock/web-app/src/features/backtest/BacktestListPage.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useBacktestList } from './hooks/useBacktest'; -import { Link, useNavigate } from 'react-router-dom'; -import { - PlusIcon, - PlayIcon, - CheckCircleIcon, - XCircleIcon, - ClockIcon, - ExclamationTriangleIcon -} from '@heroicons/react/24/solid'; - -export function BacktestListPage() { - const { backtests, isLoading, error, loadBacktests } = useBacktestList(); - const navigate = useNavigate(); - const [refreshInterval, setRefreshInterval] = useState(null); - - useEffect(() => { - loadBacktests(); - - // Refresh every 5 seconds if there are running backtests - const interval = setInterval(() => { - if (backtests.some(b => b.status === 'running' || b.status === 'pending')) { - loadBacktests(); - } - }, 5000); - - setRefreshInterval(interval); - - return () => { - if (refreshInterval) { - clearInterval(refreshInterval); - } - }; - }, [loadBacktests]); - - const getStatusIcon = (status: string) => { - switch (status) { - case 'completed': - return ; - case 'running': - return ; - case 'pending': - return ; - case 'failed': - return ; - case 'cancelled': - return ; - default: - return ; - } - }; - - const getStatusColor = (status: string) => { - switch (status) { - case 'completed': - return 'text-success'; - case 'running': - return 'text-primary-400'; - case 'failed': - return 'text-error'; - case 'cancelled': - return 'text-text-muted'; - default: - return 'text-text-secondary'; - } - }; - - const formatDate = (dateString: string) => { - return new Date(dateString).toLocaleString(); - }; - - return ( -
-
-
-

Backtest History

-

- View and manage your backtest runs -

-
- -
- - {error && ( -
- {error} -
- )} - - {isLoading ? ( -
-
Loading backtests...
-
- ) : backtests.length === 0 ? ( -
-

No backtests found

- -
- ) : ( -
- - - - - - - - - - - - - - {backtests.map((backtest) => ( - - - - - - - - - - ))} - -
IDStrategySymbolsPeriodStatusCreatedActions
- {backtest.id.slice(0, 8)}... - - {backtest.strategy} - - {backtest.symbols.join(', ')} - - {new Date(backtest.startDate).toLocaleDateString()} - {new Date(backtest.endDate).toLocaleDateString()} - -
- {getStatusIcon(backtest.status)} - - {backtest.status} - -
-
- {formatDate(backtest.createdAt)} - - - View - -
-
- )} -
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/BacktestPage.tsx b/apps/stock/web-app/src/features/backtest/BacktestPage.tsx deleted file mode 100644 index 3e9ade3..0000000 --- a/apps/stock/web-app/src/features/backtest/BacktestPage.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { BacktestConfiguration } from './components/BacktestConfiguration'; -import { BacktestControls } from './components/BacktestControls'; -import { BacktestResults } from './components/BacktestResults'; -import { useBacktest } from './hooks/useBacktest'; -import type { BacktestConfig, BacktestResult as LocalBacktestResult } from './types/backtest.types'; - -export function BacktestPage() { - const { - backtest, - results, - isLoading, - isPolling, - error, - createBacktest, - cancelBacktest, - } = useBacktest(); - - // Local state to bridge between the API format and the existing UI components - const [config, setConfig] = useState(null); - const [adaptedResults, setAdaptedResults] = useState(null); - - // Adapt the backtest status from API format to local format - const status = backtest ? - (backtest.status === 'pending' ? 'configured' : - backtest.status === 'running' ? 'running' : - backtest.status === 'completed' ? 'completed' : - backtest.status === 'failed' ? 'error' : - backtest.status === 'cancelled' ? 'stopped' : 'idle') : 'idle'; - - // Current time is not available in the new API, so we'll estimate it based on progress - const currentTime = null; - - // No adaptation needed - results are already in the correct format - useEffect(() => { - setAdaptedResults(results); - }, [results]); - - const handleConfigSubmit = useCallback(async (newConfig: BacktestConfig) => { - setConfig(newConfig); - setAdaptedResults(null); - - // Convert local config to API format - await createBacktest({ - strategy: newConfig.strategy, - symbols: newConfig.symbols, - startDate: newConfig.startDate.toISOString().split('T')[0], - endDate: newConfig.endDate.toISOString().split('T')[0], - initialCapital: newConfig.initialCapital, - config: { - name: newConfig.name, - commission: newConfig.commission, - slippage: newConfig.slippage, - speedMultiplier: newConfig.speedMultiplier, - useTypeScriptImplementation: true, // Enable TypeScript strategy execution - }, - }); - }, [createBacktest]); - - const handleStart = useCallback(() => { - // Backtest starts automatically after creation in the new API - // Nothing to do here - }, []); - - const handlePause = useCallback(() => { - // Pause not supported in current API - console.warn('Pause not supported in current API'); - }, []); - - const handleResume = useCallback(() => { - // Resume not supported in current API - console.warn('Resume not supported in current API'); - }, []); - - const handleStop = useCallback(async () => { - await cancelBacktest(); - }, [cancelBacktest]); - - const handleStep = useCallback(() => { - // Step not supported in current API - console.warn('Step not supported in current API'); - }, []); - - return ( -
-
-

Backtest Strategy

-

- Test your trading strategies against historical data to evaluate performance and risk. -

-
- - {error && ( -
- {error} -
- )} - -
-
- - - {config && ( - - )} -
- -
- -
-
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx index 03f7cf9..5c6c22a 100644 --- a/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx +++ b/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx @@ -46,15 +46,32 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }: // Find trades for this symbol const symbolTrades = result.trades?.filter(t => t.symbol === symbol) || []; + console.log('Symbol trades for', symbol, ':', symbolTrades); + const tradeMarkers = symbolTrades - .filter(trade => trade.entryPrice != null && trade.entryDate != null) - .map(trade => ({ - time: new Date(trade.entryDate).getTime() / 1000, - position: 'belowBar' as const, - color: trade.side === 'buy' ? '#10b981' : '#ef4444', - shape: trade.side === 'buy' ? 'arrowUp' : 'arrowDown' as const, - text: `${trade.side === 'buy' ? 'Buy' : 'Sell'} @ $${trade.entryPrice.toFixed(2)}` - })); + .filter(trade => { + // Check multiple possible field names + const hasPrice = trade.price != null || trade.entryPrice != null; + const hasTime = trade.timestamp != null || trade.entryDate != null || trade.date != null; + return hasPrice && hasTime; + }) + .map(trade => { + // Use whatever field names are present + const price = trade.price || trade.entryPrice; + const timestamp = trade.timestamp || trade.entryDate || trade.date; + const side = trade.side || (trade.quantity > 0 ? 'buy' : 'sell'); + + return { + time: new Date(timestamp).getTime() / 1000, + position: 'belowBar' as const, + color: side === 'buy' ? '#10b981' : '#ef4444', + shape: (side === 'buy' ? 'arrowUp' : 'arrowDown') as const, + text: `${side === 'buy' ? 'Buy' : 'Sell'} @ $${price.toFixed(2)}`, + price: price + }; + }); + + console.log('Trade markers:', tradeMarkers); const processedOhlcData = ohlcData .filter(d => d && d.timestamp != null && d.open != null && d.high != null && d.low != null && d.close != null) diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestControls.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestControls.tsx deleted file mode 100644 index 8b41b9b..0000000 --- a/apps/stock/web-app/src/features/backtest/components/BacktestControls.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { - ArrowPathIcon, - ForwardIcon, - PauseIcon, - PlayIcon, - StopIcon, -} from '@heroicons/react/24/solid'; -import type { BacktestStatus } from '../types'; - -interface BacktestControlsProps { - status: BacktestStatus; - onStart: () => void; - onPause: () => void; - onResume: () => void; - onStop: () => void; - onStep: () => void; - currentTime: number | null; - startTime: number; - endTime: number; -} - -export function BacktestControls({ - status, - onStart, - onPause, - onResume, - onStop, - onStep, - currentTime, - startTime, - endTime, -}: BacktestControlsProps) { - const progress = currentTime - ? ((currentTime - startTime) / (endTime - startTime)) * 100 - : 0; - - const formatTime = (timestamp: number) => { - return new Date(timestamp).toLocaleString(); - }; - - return ( -
-

Controls

- -
-
- {status === 'configured' || status === 'stopped' ? ( - - ) : status === 'running' ? ( - - ) : status === 'paused' ? ( - - ) : null} - - {(status === 'running' || status === 'paused') && ( - - )} - - {status === 'paused' && ( - - )} -
- - {status !== 'idle' && status !== 'configured' && ( - <> -
-
- Progress - {progress.toFixed(1)}% -
-
-
-
-
- -
-
- Status - - {status.charAt(0).toUpperCase() + status.slice(1)} - -
- - {currentTime && ( -
- Current Time - - {formatTime(currentTime)} - -
- )} -
- - )} - - {status === 'completed' && ( - - )} -
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx deleted file mode 100644 index dbcb77e..0000000 --- a/apps/stock/web-app/src/features/backtest/components/BacktestResults.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import type { BacktestStatus } from '../types'; -import type { BacktestResult } from '../services/backtestApi'; -import { MetricsCard } from './MetricsCard'; -import { PositionsTable } from './PositionsTable'; -import { TradeLog } from './TradeLog'; -import { Chart } from '../../../components/charts'; -import { useState, useMemo } from 'react'; - -interface BacktestResultsProps { - status: BacktestStatus; - results: BacktestResult | null; - currentTime: number | null; -} - -export function BacktestResults({ status, results, currentTime }: BacktestResultsProps) { - const [selectedSymbol, setSelectedSymbol] = useState(''); - - if (status === 'idle') { - return ( -
-
-

- Configure Your Backtest -

-

- Set up your strategy parameters and click "Configure Backtest" to begin. -

-
-
- ); - } - - if (status === 'configured') { - return ( -
-
-

- Ready to Start -

-

- Click the "Start" button to begin backtesting your strategy. -

-
-
- ); - } - - if (status === 'running' && !results) { - return ( -
-
-
-

- Running Backtest... -

-

- Processing historical data and executing trades. -

-
-
- ); - } - - if (!results) { - return ( -
-
-

- No Results Yet -

-

- Results will appear here once the backtest is complete. -

-
-
- ); - } - - return ( -
- {/* Metrics Overview */} -
- = 0 ? '+' : ''}${(results.metrics.totalReturn || 0).toFixed(2)}%`} - trend={(results.metrics.totalReturn || 0) >= 0 ? 'up' : 'down'} - /> - = 1 ? 'up' : 'down'} - /> - - = 50 ? 'up' : 'down'} - /> - - {results.metrics.profitFactor !== null && results.metrics.profitFactor !== undefined && ( - = 1 ? 'up' : 'down'} - /> - )} -
- - {/* Performance Chart */} -
-
-

- Portfolio Performance -

- {results.ohlcData && Object.keys(results.ohlcData).length > 1 && ( - - )} -
- {(() => { - const hasOhlcData = results.ohlcData && Object.keys(results.ohlcData).length > 0; - const hasEquityData = results.equity && results.equity.length > 0; - - if (hasOhlcData) { - const activeSymbol = selectedSymbol || Object.keys(results.ohlcData)[0]; - const ohlcData = results.ohlcData[activeSymbol]; - - // Create trade markers for the selected symbol (individual fills) - const tradeMarkers = results.trades - .filter(trade => trade.symbol === activeSymbol) - .map(trade => { - // Buy = green up arrow, Sell = red down arrow - const isBuy = trade.side === 'buy'; - const pnlText = trade.pnl !== undefined ? ` (${trade.pnl >= 0 ? '+' : ''}${trade.pnl.toFixed(2)})` : ''; - const positionText = ` → ${trade.positionAfter > 0 ? '+' : ''}${trade.positionAfter}`; - - return { - time: Math.floor(new Date(trade.timestamp).getTime() / 1000), - position: isBuy ? 'belowBar' as const : 'aboveBar' as const, - color: isBuy ? '#10b981' : '#ef4444', - shape: isBuy ? 'arrowUp' as const : 'arrowDown' as const, - text: `${trade.side.toUpperCase()} ${trade.quantity}@${trade.price.toFixed(2)}${positionText}${pnlText}`, - id: trade.id, - price: trade.price - }; - }); - - // Convert OHLC data timestamps - const chartData = ohlcData.map((bar: any) => ({ - ...bar, - time: bar.timestamp || bar.time - })); - - return ( - ({ - time: Math.floor(new Date(point.date).getTime() / 1000), - value: point.value - })), - color: '#10b981', - lineWidth: 3 - } - ] : []} - tradeMarkers={tradeMarkers} - className="rounded" - /> - ); - } else if (hasEquityData) { - return ( - ({ - time: Math.floor(new Date(point.date).getTime() / 1000), - value: point.value - }))} - height={400} - type="area" - showVolume={false} - theme="dark" - className="rounded" - /> - ); - } else { - return ( -
-

- No data available -

-
- ); - } - })()} -
- - {/* Trade Log */} - {results.trades && results.trades.length > 0 && ( -
-

- Trade Log ({results.trades.length} fills) -

- -
- )} -
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestTrades.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestTrades.tsx index 1ab6d2f..f6e47cc 100644 --- a/apps/stock/web-app/src/features/backtest/components/BacktestTrades.tsx +++ b/apps/stock/web-app/src/features/backtest/components/BacktestTrades.tsx @@ -3,7 +3,7 @@ import { DataTable } from '@/components/ui/DataTable'; import type { ColumnDef } from '@tanstack/react-table'; interface BacktestTradesProps { - result: BacktestResult | null; + result: any | null; isLoading: boolean; } @@ -28,7 +28,10 @@ export function BacktestTrades({ result, isLoading }: BacktestTradesProps) { ); } - const columns: ColumnDef[] = [ + // Add debug logging + console.log('Trades data:', result.trades); + + const columns: ColumnDef[] = [ { accessorKey: 'symbol', header: 'Symbol', @@ -38,11 +41,12 @@ export function BacktestTrades({ result, isLoading }: BacktestTradesProps) { ), }, { - accessorKey: 'side', + id: 'side', header: 'Side', size: 80, - cell: ({ getValue }) => { - const side = getValue() as string || 'unknown'; + cell: ({ row }) => { + const trade = row.original; + const side = trade.side || (trade.quantity > 0 ? 'buy' : 'sell'); return ( { - const date = getValue() as string; + cell: ({ row }) => { + const trade = row.original; + const date = trade.timestamp || trade.entryDate || trade.date; return ( {date ? new Date(date).toLocaleString() : '-'} @@ -68,37 +73,12 @@ export function BacktestTrades({ result, isLoading }: BacktestTradesProps) { }, }, { - accessorKey: 'entryPrice', - header: 'Entry Price', + id: 'price', + header: 'Price', size: 100, - cell: ({ getValue }) => { - const price = getValue() as number; - return ( - - ${price != null ? price.toFixed(2) : '0.00'} - - ); - }, - }, - { - accessorKey: 'exitDate', - header: 'Exit Date', - size: 180, - cell: ({ getValue }) => { - const date = getValue() as string | null; - return ( - - {date ? new Date(date).toLocaleString() : '-'} - - ); - }, - }, - { - accessorKey: 'exitPrice', - header: 'Exit Price', - size: 100, - cell: ({ getValue }) => { - const price = getValue() as number; + cell: ({ row }) => { + const trade = row.original; + const price = trade.price || trade.entryPrice; return ( ${price != null ? price.toFixed(2) : '0.00'} @@ -110,49 +90,83 @@ export function BacktestTrades({ result, isLoading }: BacktestTradesProps) { accessorKey: 'quantity', header: 'Quantity', size: 80, - cell: ({ getValue }) => ( - {getValue() as number || 0} - ), + cell: ({ getValue }) => { + const qty = Math.abs(getValue() as number || 0); + return {qty}; + }, }, { - accessorKey: 'pnl', - header: 'P&L', + accessorKey: 'commission', + header: 'Commission', size: 100, cell: ({ getValue }) => { - const pnl = getValue() as number || 0; + const commission = getValue() as number || 0; return ( - = 0 ? 'text-success' : 'text-error' - }`}> - ${pnl != null ? pnl.toFixed(2) : '0.00'} + + ${commission.toFixed(2)} ); }, }, { - accessorKey: 'pnlPercent', - header: 'P&L %', + id: 'pnl', + header: 'P&L', size: 100, - cell: ({ getValue }) => { - const pnlPercent = getValue() as number || 0; + cell: ({ row }) => { + const trade = row.original; + const pnl = trade.pnl || trade.realizedPnl || 0; return ( = 0 ? 'text-success' : 'text-error' + pnl >= 0 ? 'text-success' : 'text-error' }`}> - {pnlPercent != null ? pnlPercent.toFixed(2) : '0.00'}% + ${pnl.toFixed(2)} ); }, }, ]; + // Calculate trade statistics + const tradeStats = result.trades.reduce((stats: any, trade: any) => { + const pnl = trade.pnl || trade.realizedPnl || 0; + if (pnl > 0) { + stats.wins++; + stats.totalWin += pnl; + } else if (pnl < 0) { + stats.losses++; + stats.totalLoss += Math.abs(pnl); + } + stats.totalPnL += pnl; + return stats; + }, { wins: 0, losses: 0, totalWin: 0, totalLoss: 0, totalPnL: 0 }); + + const winRate = result.trades.length > 0 ? (tradeStats.wins / result.trades.length) * 100 : 0; + return (
-
-

Trade History

-

- Total: {result.trades.length} trades -

+
+
+

Total Trades

+

{result.trades.length}

+
+
+

Win Rate

+

{winRate.toFixed(1)}%

+
+
+

Wins

+

{tradeStats.wins}

+
+
+

Losses

+

{tradeStats.losses}

+
+
+

Total P&L

+

= 0 ? 'text-success' : 'text-error'}`}> + ${tradeStats.totalPnL.toFixed(2)} +

+
void; -} - -export function CompactPositionsTable({ positions, onExpand }: CompactPositionsTableProps) { - if (positions.length === 0) { - return ( -
-

Open Positions

-

No open positions

-
- ); - } - - const totalPnL = positions.reduce((sum, p) => { - const quantity = p.quantity || 0; - const avgPrice = p.averagePrice || p.avgPrice || 0; - const currentPrice = p.currentPrice || p.lastPrice || avgPrice; - return sum + ((currentPrice - avgPrice) * quantity); - }, 0); - - return ( -
-
-

- Open Positions ({positions.length}) -

- = 0 ? 'text-success' : 'text-error'}`}> - P&L: ${totalPnL.toFixed(2)} - -
- -
- {positions.slice(0, 8).map((position, index) => { - const quantity = position.quantity || 0; - const avgPrice = position.averagePrice || position.avgPrice || 0; - const currentPrice = position.currentPrice || position.lastPrice || avgPrice; - const side = quantity > 0 ? 'long' : 'short'; - const absQuantity = Math.abs(quantity); - const pnl = (currentPrice - avgPrice) * quantity; - const pnlPercent = avgPrice > 0 ? (pnl / (avgPrice * absQuantity)) * 100 : 0; - - return ( -
-
- {position.symbol} - - {side.toUpperCase()} - - - {absQuantity} @ ${avgPrice.toFixed(2)} - -
-
- - ${currentPrice.toFixed(2)} - - = 0 ? 'text-success' : 'text-error'}`}> - {pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(1)}% - -
-
- ); - })} - - {positions.length > 8 && ( - - )} -
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/PositionsTable.tsx b/apps/stock/web-app/src/features/backtest/components/PositionsTable.tsx deleted file mode 100644 index 43671ed..0000000 --- a/apps/stock/web-app/src/features/backtest/components/PositionsTable.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import type { Position } from '../types'; - -interface PositionsTableProps { - positions: Position[]; -} - -export function PositionsTable({ positions }: PositionsTableProps) { - const formatCurrency = (value: number) => { - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }).format(value); - }; - - const formatPnl = (value: number) => { - const formatted = formatCurrency(Math.abs(value)); - return value >= 0 ? `+${formatted}` : `-${formatted}`; - }; - - return ( -
- - - - - - - - - - - - - {positions.map((position) => { - const totalPnl = position.realizedPnl + position.unrealizedPnl; - return ( - - - - - - - - - ); - })} - -
SymbolQuantityAvg PriceCurrentP&LUnrealized
{position.symbol} - {position.quantity.toLocaleString()} - - {formatCurrency(position.averagePrice)} - - {formatCurrency(position.currentPrice)} - = 0 ? 'text-success' : 'text-error' - }`}> - {formatPnl(totalPnl)} - = 0 ? 'text-success' : 'text-error' - }`}> - {formatPnl(position.unrealizedPnl)} -
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/RunsList.tsx b/apps/stock/web-app/src/features/backtest/components/RunsList.tsx deleted file mode 100644 index a634610..0000000 --- a/apps/stock/web-app/src/features/backtest/components/RunsList.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { DataTable } from '@/components/ui/DataTable'; -import type { ColumnDef } from '@tanstack/react-table'; -import type { Run } from '../services/backtestApiV2'; -import { useNavigate, useParams } from 'react-router-dom'; -import { - CheckCircleIcon, - XCircleIcon, - PauseIcon, - PlayIcon, - ClockIcon, -} from '@heroicons/react/24/outline'; - -interface RunsListProps { - runs: Run[]; - currentRunId?: string; - onSelectRun: (runId: string) => void; -} - -export function RunsList({ runs, currentRunId, onSelectRun }: RunsListProps) { - const navigate = useNavigate(); - const { id: backtestId } = useParams<{ id: string }>(); - - const getStatusIcon = (status: Run['status']) => { - switch (status) { - case 'completed': - return ; - case 'failed': - return ; - case 'cancelled': - return ; - case 'running': - return ; - case 'paused': - return ; - case 'pending': - return ; - } - }; - - const getStatusLabel = (status: Run['status']) => { - return status.charAt(0).toUpperCase() + status.slice(1); - }; - - const formatDuration = (startedAt?: string, completedAt?: string) => { - if (!startedAt) return '-'; - const start = new Date(startedAt).getTime(); - const end = completedAt ? new Date(completedAt).getTime() : Date.now(); - const duration = end - start; - - const seconds = Math.floor(duration / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - - if (hours > 0) { - return `${hours}h ${minutes % 60}m`; - } else if (minutes > 0) { - return `${minutes}m ${seconds % 60}s`; - } else { - return `${seconds}s`; - } - }; - - const columns: ColumnDef[] = [ - { - accessorKey: 'runNumber', - header: 'Run #', - size: 80, - cell: ({ getValue, row }) => ( - - #{getValue() as number} - - ), - }, - { - accessorKey: 'status', - header: 'Status', - size: 120, - cell: ({ getValue }) => { - const status = getValue() as Run['status']; - return ( -
- {getStatusIcon(status)} - {getStatusLabel(status)} -
- ); - }, - }, - { - accessorKey: 'progress', - header: 'Progress', - size: 150, - cell: ({ row }) => { - const progress = row.original.progress; - const status = row.original.status; - - if (status === 'pending') return -; - - return ( -
-
-
-
- {progress.toFixed(0)}% -
- ); - }, - }, - { - accessorKey: 'speedMultiplier', - header: 'Speed', - size: 80, - cell: ({ getValue }) => ( - {getValue() as number}x - ), - }, - { - accessorKey: 'startedAt', - header: 'Started', - size: 180, - cell: ({ getValue }) => { - const date = getValue() as string | undefined; - return ( - - {date ? new Date(date).toLocaleString() : '-'} - - ); - }, - }, - { - id: 'duration', - header: 'Duration', - size: 100, - cell: ({ row }) => ( - - {formatDuration(row.original.startedAt, row.original.completedAt)} - - ), - }, - { - accessorKey: 'error', - header: 'Error', - size: 200, - cell: ({ getValue }) => { - const error = getValue() as string | undefined; - return error ? ( - {error} - ) : ( - - - ); - }, - }, - ]; - - if (runs.length === 0) { - return ( -
-

No runs yet. Create a new run to get started.

-
- ); - } - - return ( - { - navigate(`/backtests/${backtestId}/run/${run.id}`); - onSelectRun(run.id); - }} - className="bg-surface-secondary rounded-lg border border-border" - height={400} - /> - ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/TradeLog.tsx b/apps/stock/web-app/src/features/backtest/components/TradeLog.tsx deleted file mode 100644 index eb7c682..0000000 --- a/apps/stock/web-app/src/features/backtest/components/TradeLog.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import type { Trade } from '../types'; - -interface TradeLogProps { - trades: Trade[]; -} - -export function TradeLog({ trades }: TradeLogProps) { - const formatCurrency = (value: number) => { - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }).format(value); - }; - - const formatTime = (timestamp: string) => { - return new Date(timestamp).toLocaleString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); - }; - - // Show latest trades first - const sortedTrades = [...trades].reverse(); - - // Check if any trades have P&L - const showPnLColumn = trades.some(t => t.pnl !== undefined); - - // Determine the action type based on side and position change - const getActionType = (trade: Trade): string => { - const positionBefore = trade.positionAfter + (trade.side === 'buy' ? -trade.quantity : trade.quantity); - - if (trade.side === 'buy') { - // If we had a negative position (short) and buying reduces it, it's a COVER - if (positionBefore < 0 && trade.positionAfter > positionBefore) { - return 'COVER'; - } - // Otherwise it's a BUY (opening or adding to long) - return 'BUY'; - } else { - // If we had a positive position (long) and selling reduces it, it's a SELL - if (positionBefore > 0 && trade.positionAfter < positionBefore) { - return 'SELL'; - } - // Otherwise it's a SHORT (opening or adding to short) - return 'SHORT'; - } - }; - - // Get color for action type - const getActionColor = (action: string): string => { - switch (action) { - case 'BUY': - return 'bg-success/10 text-success'; - case 'SELL': - return 'bg-error/10 text-error'; - case 'SHORT': - return 'bg-warning/10 text-warning'; - case 'COVER': - return 'bg-primary/10 text-primary'; - default: - return 'bg-surface-tertiary text-text-secondary'; - } - }; - - return ( -
- - - - - - - - - - - - {showPnLColumn && ( - - )} - - - - {sortedTrades.map((trade) => { - const tradeValue = trade.quantity * trade.price; - const actionType = getActionType(trade); - return ( - - - - - - - - - - {showPnLColumn && ( - - )} - - ); - })} - -
TimeSymbolActionQtyPriceValuePositionComm.P&L
- {formatTime(trade.timestamp)} - {trade.symbol} - - {actionType} - - - {trade.quantity.toLocaleString()} - - {formatCurrency(trade.price)} - - {formatCurrency(tradeValue)} - 0 ? 'text-success' : - trade.positionAfter < 0 ? 'text-error' : - 'text-text-muted' - }`}> - {trade.positionAfter > 0 ? '+' : ''}{trade.positionAfter.toLocaleString()} - - {formatCurrency(trade.commission)} - = 0 ? 'text-success' : 'text-error') : 'text-text-muted' - }`}> - {trade.pnl !== undefined ? ( - <>{trade.pnl >= 0 ? '+' : ''}{formatCurrency(trade.pnl)} - ) : ( - '-' - )} -
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/components/index.ts b/apps/stock/web-app/src/features/backtest/components/index.ts index a46456a..06a90b8 100644 --- a/apps/stock/web-app/src/features/backtest/components/index.ts +++ b/apps/stock/web-app/src/features/backtest/components/index.ts @@ -1,12 +1,11 @@ export { BacktestConfiguration } from './BacktestConfiguration'; -export { BacktestControls } from './BacktestControls'; -export { BacktestResults } from './BacktestResults'; export { MetricsCard } from './MetricsCard'; -export { PositionsTable } from './PositionsTable'; -export { TradeLog } from './TradeLog'; -export { RunsList } from './RunsList'; export { RunsListWithMetrics } from './RunsListWithMetrics'; export { CompactPerformanceMetrics } from './CompactPerformanceMetrics'; -export { CompactPositionsTable } from './CompactPositionsTable'; export { PositionsSummary } from './PositionsSummary'; -export { ChartContainer } from './ChartContainer'; \ No newline at end of file +export { ChartContainer } from './ChartContainer'; +export { BacktestChart } from './BacktestChart'; +export { BacktestMetrics } from './BacktestMetrics'; +export { BacktestPlayback } from './BacktestPlayback'; +export { BacktestTrades } from './BacktestTrades'; +export { RunControlsCompact } from './RunControlsCompact'; \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/hooks/index.ts b/apps/stock/web-app/src/features/backtest/hooks/index.ts deleted file mode 100644 index f0a2e7c..0000000 --- a/apps/stock/web-app/src/features/backtest/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useBacktest } from './useBacktest'; \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/hooks/useBacktest.ts b/apps/stock/web-app/src/features/backtest/hooks/useBacktest.ts deleted file mode 100644 index 63715af..0000000 --- a/apps/stock/web-app/src/features/backtest/hooks/useBacktest.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import type { BacktestJob, BacktestRequest, BacktestResult } from '../services/backtestApi'; -import { backtestApi, } from '../services/backtestApi'; - -interface UseBacktestReturn { - // State - backtest: BacktestJob | null; - results: BacktestResult | null; - isLoading: boolean; - isPolling: boolean; - error: string | null; - - // Actions - loadBacktest: (id: string) => Promise; - createBacktest: (request: BacktestRequest) => Promise; - updateBacktest: (id: string, request: BacktestRequest) => Promise; - cancelBacktest: () => Promise; - reset: () => void; -} - -export function useBacktest(): UseBacktestReturn { - const [backtest, setBacktest] = useState(null); - const [results, setResults] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [isPolling, setIsPolling] = useState(false); - const [error, setError] = useState(null); - - const pollingIntervalRef = useRef(null); - - // Poll for status updates - const pollStatus = useCallback(async (backtestId: string) => { - try { - const updatedBacktest = await backtestApi.getBacktest(backtestId); - setBacktest(updatedBacktest); - - if (updatedBacktest.status === 'completed') { - // Fetch results - const backtestResults = await backtestApi.getBacktestResults(backtestId); - setResults(backtestResults); - setIsPolling(false); - - // Clear polling interval - if (pollingIntervalRef.current) { - clearInterval(pollingIntervalRef.current); - pollingIntervalRef.current = null; - } - } else if (updatedBacktest.status === 'failed' || updatedBacktest.status === 'cancelled') { - setIsPolling(false); - - // Clear polling interval - if (pollingIntervalRef.current) { - clearInterval(pollingIntervalRef.current); - pollingIntervalRef.current = null; - } - - if (updatedBacktest.status === 'failed' && updatedBacktest.error) { - setError(updatedBacktest.error); - } - } - } catch (err) { - console.error('Error polling backtest status:', err); - // Don't stop polling on transient errors - } - }, []); - - // Load a specific backtest by ID - const loadBacktest = useCallback(async (id: string) => { - setIsLoading(true); - setError(null); - - try { - const loadedBacktest = await backtestApi.getBacktest(id); - setBacktest(loadedBacktest); - - // If completed, also load results - if (loadedBacktest.status === 'completed') { - const backtestResults = await backtestApi.getBacktestResults(id); - setResults(backtestResults); - } - - // If running, start polling - if (loadedBacktest.status === 'running') { - setIsPolling(true); - pollingIntervalRef.current = setInterval(() => { - pollStatus(id); - }, 2000); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load backtest'); - } finally { - setIsLoading(false); - } - }, [pollStatus]); - - // Create a new backtest - const createBacktest = useCallback(async (request: BacktestRequest) => { - setIsLoading(true); - setError(null); - setResults(null); - - try { - const newBacktest = await backtestApi.createBacktest(request); - setBacktest(newBacktest); - - // Start polling for updates - setIsPolling(true); - pollingIntervalRef.current = setInterval(() => { - pollStatus(newBacktest.id); - }, 2000); // Poll every 2 seconds - - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to create backtest'); - } finally { - setIsLoading(false); - } - }, [pollStatus]); - - // Update an existing backtest - const updateBacktest = useCallback(async (id: string, request: BacktestRequest) => { - setIsLoading(true); - setError(null); - - try { - // For now, we'll delete and recreate since update isn't implemented in the API - await backtestApi.deleteBacktest(id); - const newBacktest = await backtestApi.createBacktest(request); - setBacktest(newBacktest); - - // Start polling for updates - setIsPolling(true); - pollingIntervalRef.current = setInterval(() => { - pollStatus(newBacktest.id); - }, 2000); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to update backtest'); - } finally { - setIsLoading(false); - } - }, [pollStatus]); - - // Cancel running backtest - const cancelBacktest = useCallback(async () => { - if (!backtest || backtest.status !== 'running') return; - - try { - await backtestApi.cancelBacktest(backtest.id); - setBacktest({ ...backtest, status: 'cancelled' }); - setIsPolling(false); - - // Clear polling interval - if (pollingIntervalRef.current) { - clearInterval(pollingIntervalRef.current); - pollingIntervalRef.current = null; - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to cancel backtest'); - } - }, [backtest]); - - // Reset state - const reset = useCallback(() => { - setBacktest(null); - setResults(null); - setError(null); - setIsLoading(false); - setIsPolling(false); - - // Clear polling interval - if (pollingIntervalRef.current) { - clearInterval(pollingIntervalRef.current); - pollingIntervalRef.current = null; - } - }, []); - - // Cleanup on unmount - useEffect(() => { - return () => { - if (pollingIntervalRef.current) { - clearInterval(pollingIntervalRef.current); - } - }; - }, []); - - return { - backtest, - results, - isLoading, - isPolling, - error, - loadBacktest, - createBacktest, - updateBacktest, - cancelBacktest, - reset, - }; -} - -// Separate hook for listing backtests -interface UseBacktestListReturn { - backtests: BacktestJob[]; - isLoading: boolean; - error: string | null; - loadBacktests: (limit?: number, offset?: number) => Promise; -} - -export function useBacktestList(): UseBacktestListReturn { - const [backtests, setBacktests] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const loadBacktests = useCallback(async (limit = 50, offset = 0) => { - setIsLoading(true); - setError(null); - - try { - const list = await backtestApi.listBacktests(limit, offset); - setBacktests(list); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load backtests'); - } finally { - setIsLoading(false); - } - }, []); - - return { - backtests, - isLoading, - error, - loadBacktests, - }; -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/index.ts b/apps/stock/web-app/src/features/backtest/index.ts index cd88c81..2d58ad0 100644 --- a/apps/stock/web-app/src/features/backtest/index.ts +++ b/apps/stock/web-app/src/features/backtest/index.ts @@ -1,6 +1,3 @@ -export { BacktestPage } from './BacktestPage'; -export { BacktestListPage } from './BacktestListPage'; -export { BacktestDetailPage } from './BacktestDetailPage'; export { BacktestListPageV2 } from './BacktestListPageV2'; export { BacktestDetailPageV2 } from './BacktestDetailPageV2'; export * from './types'; \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/services/backtestApi.ts b/apps/stock/web-app/src/features/backtest/services/backtestApi.ts deleted file mode 100644 index 6386351..0000000 --- a/apps/stock/web-app/src/features/backtest/services/backtestApi.ts +++ /dev/null @@ -1,171 +0,0 @@ -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:2003'; - -export interface BacktestRequest { - strategy: string; - symbols: string[]; - startDate: string; - endDate: string; - initialCapital: number; - config?: Record; -} - -export interface BacktestJob { - id: string; - status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'; - strategy: string; - symbols: string[]; - startDate: string; - endDate: string; - initialCapital: number; - config: Record; - createdAt: string; - updatedAt: string; - error?: string; -} - -export interface BacktestResult { - // Identification - backtestId: string; - status: 'completed' | 'failed' | 'cancelled'; - completedAt: string; - - // Configuration - config: { - name: string; - strategy: string; - symbols: string[]; - startDate: string; - endDate: string; - initialCapital: number; - commission: number; - slippage: number; - dataFrequency: string; - }; - - // Performance metrics - metrics: { - totalReturn: number; - sharpeRatio: number; - maxDrawdown: number; - winRate: number; - totalTrades: number; - profitFactor: number; - profitableTrades: number; - avgWin: number; - avgLoss: number; - expectancy: number; - calmarRatio: number; - sortinoRatio: number; - }; - - // Chart data - equity: Array<{ date: string; value: number }>; - ohlcData: Record>; - - // Trade history - trades: Array<{ - id: string; - symbol: string; - entryDate: string; - exitDate: string | null; - entryPrice: number; - exitPrice: number; - quantity: number; - side: string; - pnl: number; - pnlPercent: number; - commission: number; - duration: number; - }>; - - // Positions - positions: Array<{ - symbol: string; - quantity: number; - averagePrice: number; - currentPrice: number; - unrealizedPnl: number; - realizedPnl: number; - }>; - - // Analytics - analytics: { - drawdownSeries: Array<{ timestamp: number; value: number }>; - dailyReturns: number[]; - monthlyReturns: Record; - exposureTime: number; - riskMetrics: Record; - }; -} - -export const backtestApi = { - // Create a new backtest - async createBacktest(request: BacktestRequest): Promise { - const response = await fetch(`${API_BASE_URL}/api/backtests`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(request), - }); - - if (!response.ok) { - throw new Error(`Failed to create backtest: ${response.statusText}`); - } - - return response.json(); - }, - - // Get backtest status - async getBacktest(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/api/backtests/${id}`); - - if (!response.ok) { - throw new Error(`Failed to get backtest: ${response.statusText}`); - } - - return response.json(); - }, - - // Get backtest results - async getBacktestResults(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/api/backtests/${id}/results`); - - if (!response.ok) { - throw new Error(`Failed to get results: ${response.statusText}`); - } - - return response.json(); - }, - - // List all backtests - async listBacktests(limit = 50, offset = 0): Promise { - const response = await fetch( - `${API_BASE_URL}/api/backtests?limit=${limit}&offset=${offset}` - ); - - if (!response.ok) { - throw new Error(`Failed to list backtests: ${response.statusText}`); - } - - return response.json(); - }, - - // Cancel a running backtest - async cancelBacktest(id: string): Promise { - const response = await fetch(`${API_BASE_URL}/api/backtests/${id}/cancel`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error(`Failed to cancel backtest: ${response.statusText}`); - } - }, -}; \ No newline at end of file diff --git a/apps/stock/web-app/src/features/backtest/services/backtestService.ts b/apps/stock/web-app/src/features/backtest/services/backtestService.ts deleted file mode 100644 index b320512..0000000 --- a/apps/stock/web-app/src/features/backtest/services/backtestService.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { BacktestConfig, BacktestResult, BacktestStatus } from '../types'; - -const API_BASE = '/api/backtest'; - -export class BacktestService { - static async createBacktest(config: BacktestConfig): Promise<{ id: string }> { - const response = await fetch(`${API_BASE}/create`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - ...config, - startDate: config.startDate.toISOString(), - endDate: config.endDate.toISOString(), - }), - }); - - if (!response.ok) { - throw new Error('Failed to create backtest'); - } - - return response.json(); - } - - static async startBacktest(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}/start`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error('Failed to start backtest'); - } - } - - static async pauseBacktest(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}/pause`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error('Failed to pause backtest'); - } - } - - static async resumeBacktest(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}/resume`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error('Failed to resume backtest'); - } - } - - static async stopBacktest(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}/stop`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error('Failed to stop backtest'); - } - } - - static async stepBacktest(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}/step`, { - method: 'POST', - }); - - if (!response.ok) { - throw new Error('Failed to step backtest'); - } - } - - static async getBacktestStatus(id: string): Promise<{ - status: BacktestStatus; - currentTime?: number; - progress?: number; - }> { - const response = await fetch(`${API_BASE}/${id}/status`); - - if (!response.ok) { - throw new Error('Failed to get backtest status'); - } - - return response.json(); - } - - static async getBacktestResults(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}/results`); - - if (!response.ok) { - throw new Error('Failed to get backtest results'); - } - - const data = await response.json(); - - // Convert date strings back to Date objects - return { - ...data, - config: { - ...data.config, - startDate: new Date(data.config.startDate), - endDate: new Date(data.config.endDate), - }, - }; - } - - static async listBacktests(): Promise { - const response = await fetch(`${API_BASE}/list`); - - if (!response.ok) { - throw new Error('Failed to list backtests'); - } - - const data = await response.json(); - - // Convert date strings back to Date objects - return data.map((backtest: any) => ({ - ...backtest, - config: { - ...backtest.config, - startDate: new Date(backtest.config.startDate), - endDate: new Date(backtest.config.endDate), - }, - })); - } - - static async deleteBacktest(id: string): Promise { - const response = await fetch(`${API_BASE}/${id}`, { - method: 'DELETE', - }); - - if (!response.ok) { - throw new Error('Failed to delete backtest'); - } - } - - // Helper method to poll for updates - static async pollBacktestUpdates( - id: string, - onUpdate: (status: BacktestStatus, progress?: number, currentTime?: number) => void, - interval: number = 200 - ): Promise<() => void> { - let isPolling = true; - - const poll = async () => { - while (isPolling) { - try { - const { status, progress, currentTime } = await this.getBacktestStatus(id); - onUpdate(status, progress, currentTime); - - if (status === 'completed' || status === 'error' || status === 'stopped') { - isPolling = false; - break; - } - } catch (error) { - console.error('Polling error:', error); - } - - await new Promise(resolve => setTimeout(resolve, interval)); - } - }; - - poll(); - - // Return cleanup function - return () => { - isPolling = false; - }; - } -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/charts/components/ChartErrorBoundary.tsx b/apps/stock/web-app/src/features/charts/components/ChartErrorBoundary.tsx deleted file mode 100644 index c4df966..0000000 --- a/apps/stock/web-app/src/features/charts/components/ChartErrorBoundary.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, ReactNode } from 'react'; - -interface Props { - children: ReactNode; -} - -interface State { - hasError: boolean; - error: Error | null; -} - -export class ChartErrorBoundary extends Component { - constructor(props: Props) { - super(props); - this.state = { hasError: false, error: null }; - } - - static getDerivedStateFromError(error: Error): State { - return { hasError: true, error }; - } - - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - console.error('Chart error:', error, errorInfo); - } - - render() { - if (this.state.hasError) { - return ( -
-
-
⚠️
-

- Chart Loading Error -

-

- {this.state.error?.message || 'Unable to load the chart'} -

- -
-
- ); - } - - return this.props.children; - } -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/charts/components/MarketOverview.tsx b/apps/stock/web-app/src/features/charts/components/MarketOverview.tsx deleted file mode 100644 index c0ebb18..0000000 --- a/apps/stock/web-app/src/features/charts/components/MarketOverview.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { ArrowUpIcon, ArrowDownIcon } from '@heroicons/react/24/solid'; -import type { MarketData } from '../types'; - -interface MarketOverviewProps { - data: MarketData | null; - loading?: boolean; -} - -export function MarketOverview({ data, loading }: MarketOverviewProps) { - if (loading || !data) { - return ( -
-
-
-
-
-
- ); - } - - const isPositive = data.change >= 0; - - return ( -
-
-
-
-

{data.symbol}

- {data.name} -
-
- - ${data.price.toFixed(2)} - -
- {isPositive ? ( - - ) : ( - - )} - - ${Math.abs(data.change).toFixed(2)} - - - ({isPositive ? '+' : ''}{data.changePercent.toFixed(2)}%) - -
-
-
- -
-
- Open -

${data.open.toFixed(2)}

-
-
- High -

${data.high.toFixed(2)}

-
-
- Low -

${data.low.toFixed(2)}

-
-
- Prev Close -

${data.previousClose.toFixed(2)}

-
-
- Volume -

- {(data.volume / 1000000).toFixed(2)}M -

-
-
- Time -

- {new Date(data.timestamp).toLocaleTimeString()} -

-
-
-
-
- ); -} \ No newline at end of file diff --git a/apps/stock/web-app/src/features/charts/components/index.ts b/apps/stock/web-app/src/features/charts/components/index.ts index 09e81a0..3c9ed01 100644 --- a/apps/stock/web-app/src/features/charts/components/index.ts +++ b/apps/stock/web-app/src/features/charts/components/index.ts @@ -1,7 +1,5 @@ export { SymbolChart } from './SymbolChart'; export { ChartToolbar } from './ChartToolbar'; -export { MarketOverview } from './MarketOverview'; -export { ChartErrorBoundary } from './ChartErrorBoundary'; export { IndicatorSelector } from './IndicatorSelector'; export { IndicatorList } from './IndicatorList'; export { IndicatorSettings } from './IndicatorSettings';