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 (
-
-
-
-
- {/* Static sidebar for desktop */}
-
-
-
-
-
- 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 && (
-
- )}
- {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 ? (
-
- ) : backtests.length === 0 ? (
-
-
No backtests found
-
-
- ) : (
-
-
-
-
- | ID |
- Strategy |
- Symbols |
- Period |
- Status |
- Created |
- Actions |
-
-
-
- {backtests.map((backtest) => (
-
- |
- {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 (
-
-
-
-
- | Symbol |
- Quantity |
- Avg Price |
- Current |
- P&L |
- Unrealized |
-
-
-
- {positions.map((position) => {
- const totalPnl = position.realizedPnl + position.unrealizedPnl;
- return (
-
- | {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 (
-
-
-
-
- | Time |
- Symbol |
- Action |
- Qty |
- Price |
- Value |
- Position |
- Comm. |
- {showPnLColumn && (
- P&L |
- )}
-
-
-
- {sortedTrades.map((trade) => {
- const tradeValue = trade.quantity * trade.price;
- const actionType = getActionType(trade);
- return (
-
- |
- {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)}
- |
- {showPnLColumn && (
- = 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';