diff --git a/src/modules/trading/components/OrderForm.tsx b/src/modules/trading/components/OrderForm.tsx index fe5cb4d..5c0b82e 100644 --- a/src/modules/trading/components/OrderForm.tsx +++ b/src/modules/trading/components/OrderForm.tsx @@ -63,6 +63,37 @@ export default function OrderForm({ } } + // Validate TP/SL based on direction + const entryPriceForValidation = orderType === 'market' ? currentPrice : parseFloat(limitPrice) || currentPrice; + + if (stopLoss) { + const sl = parseFloat(stopLoss); + if (sl > 0) { + if (direction === 'long' && sl >= entryPriceForValidation) { + setError('Stop Loss must be below entry price for LONG positions'); + return; + } + if (direction === 'short' && sl <= entryPriceForValidation) { + setError('Stop Loss must be above entry price for SHORT positions'); + return; + } + } + } + + if (takeProfit) { + const tp = parseFloat(takeProfit); + if (tp > 0) { + if (direction === 'long' && tp <= entryPriceForValidation) { + setError('Take Profit must be above entry price for LONG positions'); + return; + } + if (direction === 'short' && tp >= entryPriceForValidation) { + setError('Take Profit must be below entry price for SHORT positions'); + return; + } + } + } + // Build order data const orderData: CreateOrderInput = { symbol, diff --git a/src/modules/trading/components/TradesHistory.tsx b/src/modules/trading/components/TradesHistory.tsx index a006db5..7b2014f 100644 --- a/src/modules/trading/components/TradesHistory.tsx +++ b/src/modules/trading/components/TradesHistory.tsx @@ -3,6 +3,8 @@ * Displays history of closed paper trading positions */ +import { ArrowDownTrayIcon } from '@heroicons/react/24/solid'; + interface Trade { id: string; symbol: string; @@ -21,6 +23,35 @@ interface TradesHistoryProps { isLoading: boolean; } +function exportToCSV(trades: Trade[]): void { + const headers = ['Date', 'Symbol', 'Direction', 'Entry Price', 'Exit Price', 'Quantity', 'P&L', 'P&L %', 'Reason']; + const rows = trades.map((trade) => { + const closedDate = new Date(trade.closedAt); + return [ + closedDate.toISOString(), + trade.symbol, + trade.direction.toUpperCase(), + trade.entryPrice.toFixed(8), + trade.exitPrice.toFixed(8), + trade.lotSize.toString(), + trade.realizedPnl.toFixed(2), + trade.realizedPnlPercent.toFixed(2), + trade.closeReason || '', + ]; + }); + + const csvContent = [headers.join(','), ...rows.map((row) => row.join(','))].join('\n'); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.setAttribute('href', url); + link.setAttribute('download', `trades_history_${new Date().toISOString().split('T')[0]}.csv`); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + export default function TradesHistory({ trades, isLoading, @@ -43,7 +74,17 @@ export default function TradesHistory({ } return ( -
+
+
+ +
+
{trades.map((trade) => { const isProfitable = trade.realizedPnl >= 0; const closedDate = new Date(trade.closedAt); @@ -127,6 +168,7 @@ export default function TradesHistory({
); })} +
); } diff --git a/src/modules/trading/components/TradingStatsPanel.tsx b/src/modules/trading/components/TradingStatsPanel.tsx index e554168..b4ca953 100644 --- a/src/modules/trading/components/TradingStatsPanel.tsx +++ b/src/modules/trading/components/TradingStatsPanel.tsx @@ -35,6 +35,9 @@ interface CalculatedStats { avgHoldTime: string; currentStreak: number; streakType: 'win' | 'loss' | 'none'; + sharpeRatio: number; + maxDrawdown: number; + maxDrawdownPercent: number; } export const TradingStatsPanel: React.FC = ({ compact = false }) => { @@ -61,6 +64,9 @@ export const TradingStatsPanel: React.FC = ({ compact = avgHoldTime: '0h', currentStreak: 0, streakType: 'none', + sharpeRatio: 0, + maxDrawdown: 0, + maxDrawdownPercent: 0, }; } @@ -105,6 +111,40 @@ export const TradingStatsPanel: React.FC = ({ compact = } } + // Calculate Sharpe Ratio (annualized, assuming daily returns) + let sharpeRatio = 0; + if (closedTrades.length >= 2) { + const returns = closedTrades.map((t) => t.realizedPnl || 0); + const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length; + const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length; + const stdDev = Math.sqrt(variance); + if (stdDev > 0) { + sharpeRatio = (avgReturn / stdDev) * Math.sqrt(252); + } + } + + // Calculate Max Drawdown + let maxDrawdown = 0; + let maxDrawdownPercent = 0; + if (closedTrades.length > 0) { + const chronologicalTrades = [...closedTrades].sort( + (a, b) => new Date(a.closedAt || 0).getTime() - new Date(b.closedAt || 0).getTime() + ); + let cumulativePnl = 0; + let peak = 0; + for (const trade of chronologicalTrades) { + cumulativePnl += trade.realizedPnl || 0; + if (cumulativePnl > peak) { + peak = cumulativePnl; + } + const drawdown = peak - cumulativePnl; + if (drawdown > maxDrawdown) { + maxDrawdown = drawdown; + maxDrawdownPercent = peak > 0 ? (drawdown / peak) * 100 : 0; + } + } + } + return { totalTrades: closedTrades.length, winningTrades: winningTrades.length, @@ -119,6 +159,9 @@ export const TradingStatsPanel: React.FC = ({ compact = avgHoldTime, currentStreak, streakType, + sharpeRatio, + maxDrawdown, + maxDrawdownPercent, }; }, []); @@ -345,6 +388,31 @@ export const TradingStatsPanel: React.FC = ({ compact =

+ + {/* Advanced Metrics */} +
+

Advanced Metrics

+
+
+

Sharpe Ratio

+

= 1 ? 'text-green-400' : stats.sharpeRatio >= 0 ? 'text-yellow-400' : 'text-red-400'}`}> + {stats.sharpeRatio.toFixed(2)} +

+

+ {stats.sharpeRatio >= 2 ? 'Excellent' : stats.sharpeRatio >= 1 ? 'Good' : stats.sharpeRatio >= 0 ? 'Fair' : 'Poor'} +

+
+
+

Max Drawdown

+

+ -${stats.maxDrawdown.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +

+

+ {stats.maxDrawdownPercent.toFixed(1)}% from peak +

+
+
+
)}