Files
trading_bot_v4/app/analytics/page.tsx
mindesbunister 9d7932ff2f feat: Add distinction between regular SL and trailing SL
User Request: Distinguish between SL and Trailing SL in analytics overview

Changes:
1. Position Manager:
   - Updated ExitResult interface to include 'TRAILING_SL' exit reason
   - Modified trailing stop exit (line 1457) to use 'TRAILING_SL' instead of 'SL'
   - Enhanced external closure detection (line 937) to identify trailing stops
   - Updated handleManualClosure to detect trailing SL at price target

2. Database:
   - Updated UpdateTradeExitParams interface to accept 'TRAILING_SL'

3. Frontend Analytics:
   - Updated last trade display to show 'Trailing SL' with special formatting
   - Purple background/border for TRAILING_SL vs blue for regular SL
   - Runner emoji (🏃) prefix for trailing stops

Impact:
- Users can now see when trades exit via trailing stop vs regular SL
- Better understanding of runner system performance
- Trailing stops visually distinct in analytics dashboard

Files Modified:
- lib/trading/position-manager.ts (4 locations)
- lib/database/trades.ts (UpdateTradeExitParams interface)
- app/analytics/page.tsx (exit reason display)
- .github/copilot-instructions.md (Common Pitfalls #61, #62)
2025-11-24 08:40:09 +01:00

898 lines
41 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Trading Bot v4 - Analytics Dashboard
*
* Comprehensive view of trading performance and statistics
*/
'use client'
import { useState, useEffect } from 'react'
interface Stats {
period: string
realTrades: {
total: number
winning: number
losing: number
winRate: string
totalPnL: string
avgWin: string
avgLoss: string
profitFactor: string
}
testTrades: {
count: number
note: string
}
}
interface LastTrade {
id: string
symbol: string
direction: string
entryPrice: number
entryTime: string
exitPrice?: number
exitTime?: string
exitReason?: string
realizedPnL?: number
realizedPnLPercent?: number
positionSizeUSD: number
leverage: number
stopLossPrice: number
takeProfit1Price: number
takeProfit2Price: number
isTestTrade: boolean
signalQualityScore?: number
}
interface NetPosition {
symbol: string
longUSD: number
shortUSD: number
netUSD: number
netSOL: number
netDirection: 'long' | 'short' | 'flat'
tradeCount: number
}
interface PositionSummary {
summary: {
individualTrades: number
testTrades: number
totalIndividualExposure: string
netExposure: string
explanation: string
}
netPositions: NetPosition[]
}
interface VersionStats {
version: string
tradeCount: number
winRate: number
totalPnL: number
avgPnL: number
avgQualityScore: number | null
avgMFE: number | null
avgMAE: number | null
archived?: boolean
extremePositions: {
count: number
avgADX: number | null
weakADXCount: number
winRate: number
avgPnL: number
}
}
interface VersionComparison {
versions: VersionStats[]
descriptions: Record<string, string>
}
interface LivePosition {
id: string
symbol: string
direction: 'long' | 'short'
entryPrice: number
currentPrice: number
entryTime: string
positionSize: number
currentSize: number
leverage: number
stopLoss: number
takeProfit1: number
takeProfit2: number
tp1Hit: boolean
slMovedToBreakeven: boolean
realizedPnL: number
unrealizedPnL: number
peakPnL: number
profitPercent: number
accountPnL: number
priceChecks: number
ageMinutes: number
}
interface LivePositionsData {
success: boolean
monitoring: {
isActive: boolean
tradeCount: number
symbols: string[]
}
positions: LivePosition[]
}
export default function AnalyticsPage() {
const [stats, setStats] = useState<Stats | null>(null)
const [positions, setPositions] = useState<PositionSummary | null>(null)
const [lastTrade, setLastTrade] = useState<LastTrade | null>(null)
const [versionComparison, setVersionComparison] = useState<VersionComparison | null>(null)
const [livePositions, setLivePositions] = useState<LivePositionsData | null>(null)
const [loading, setLoading] = useState(true)
const [selectedDays, setSelectedDays] = useState(30)
useEffect(() => {
loadData()
}, [selectedDays])
// Auto-refresh live positions every 3 seconds
useEffect(() => {
loadLivePositions() // Initial load
const interval = setInterval(() => {
loadLivePositions()
}, 3000)
return () => clearInterval(interval)
}, [])
const loadData = async () => {
setLoading(true)
try {
const [statsRes, positionsRes, lastTradeRes, versionRes] = await Promise.all([
fetch(`/api/analytics/stats?days=${selectedDays}`),
fetch('/api/analytics/positions'),
fetch('/api/analytics/last-trade'),
fetch('/api/analytics/version-comparison'),
])
const statsData = await statsRes.json()
const positionsData = await positionsRes.json()
const lastTradeData = await lastTradeRes.json()
const versionData = await versionRes.json()
setStats(statsData.stats)
setPositions(positionsData.summary)
setLastTrade(lastTradeData.trade)
setVersionComparison(versionData.success ? versionData : null)
} catch (error) {
console.error('Failed to load analytics:', error)
}
setLoading(false)
}
const loadLivePositions = async () => {
try {
const res = await fetch('/api/trading/positions', {
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_API_SECRET_KEY || 'your-secret-key-here'}`
}
})
const data = await res.json()
setLivePositions(data)
} catch (error) {
console.error('Failed to load live positions:', error)
}
}
const clearManuallyClosed = async () => {
if (!confirm('Clear all open trades from database? Use this if you manually closed positions in Drift UI.')) {
return
}
try {
const res = await fetch('/api/trading/clear-manual-closes', {
method: 'POST',
})
if (res.ok) {
alert('✅ Manually closed trades cleared from database')
loadData() // Reload data
} else {
const error = await res.json()
alert(`❌ Failed to clear: ${error.error}`)
}
} catch (error) {
console.error('Failed to clear trades:', error)
alert('❌ Failed to clear trades')
}
}
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 flex items-center justify-center">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div>
<p className="text-gray-400">Loading analytics...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900">
{/* Header */}
<div className="bg-gray-800/50 backdrop-blur-sm border-b border-gray-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<a href="/" className="text-gray-400 hover:text-white transition">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
</a>
<div>
<h1 className="text-2xl font-bold text-white">📊 Analytics Dashboard</h1>
<p className="text-sm text-gray-400">Trading performance and statistics</p>
</div>
</div>
{/* Time Period Selector */}
<div className="flex items-center space-x-4">
<a
href="/analytics/optimization"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white font-semibold transition-colors"
>
🎯 TP/SL Optimization
</a>
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-400">Period:</span>
<select
value={selectedDays}
onChange={(e) => setSelectedDays(Number(e.target.value))}
className="bg-gray-700 text-white rounded-lg px-4 py-2 border border-gray-600 focus:border-blue-500 focus:outline-none"
>
<option value={7}>7 days</option>
<option value={30}>30 days</option>
<option value={90}>90 days</option>
<option value={365}>1 year</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Live Position Monitor */}
{livePositions && livePositions.monitoring.isActive && livePositions.positions.length > 0 && (
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
<h2 className="text-xl font-bold text-white">🔴 LIVE Position Monitor</h2>
</div>
<span className="text-xs text-gray-500">(updates every 3s)</span>
</div>
</div>
{livePositions.positions.map((pos) => {
const isProfitable = pos.profitPercent >= 0
const totalPnL = pos.realizedPnL + pos.unrealizedPnL
return (
<div key={pos.id} className="bg-gradient-to-br from-gray-800/80 to-gray-900/80 backdrop-blur-sm rounded-xl p-6 border-2 border-blue-500/50 shadow-xl mb-4">
{/* Header */}
<div className="flex items-start justify-between mb-6">
<div className="flex items-center space-x-4">
<div className="text-4xl">{pos.direction === 'long' ? '📈' : '📉'}</div>
<div>
<div className="flex items-center space-x-3">
<h3 className="text-2xl font-bold text-white">{pos.symbol}</h3>
<span className={`px-3 py-1 rounded-full text-sm font-bold ${pos.direction === 'long' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}`}>
{pos.direction.toUpperCase()}
</span>
<span className="px-3 py-1 rounded-full text-sm font-bold bg-purple-500/20 text-purple-400">
{pos.leverage}x
</span>
</div>
<div className="text-sm text-gray-400 mt-1">
Open for {pos.ageMinutes}m {pos.priceChecks} price checks
</div>
</div>
</div>
<div className="text-right">
<div className={`text-3xl font-bold ${isProfitable ? 'text-green-400' : 'text-red-400'}`}>
{isProfitable ? '+' : ''}{pos.profitPercent.toFixed(2)}%
</div>
<div className={`text-sm ${isProfitable ? 'text-green-400' : 'text-red-400'}`}>
{isProfitable ? '+' : ''}{pos.accountPnL.toFixed(2)}% account
</div>
</div>
</div>
{/* Price Info */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-xs text-gray-400 mb-1">Entry Price</div>
<div className="text-lg font-bold text-white">${pos.entryPrice.toFixed(2)}</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-xs text-gray-400 mb-1">Current Price</div>
<div className={`text-lg font-bold ${isProfitable ? 'text-green-400' : 'text-red-400'}`}>
${pos.currentPrice.toFixed(2)}
</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-xs text-gray-400 mb-1">Position Size</div>
<div className="text-lg font-bold text-white">
${pos.currentSize.toFixed(2)}
{pos.tp1Hit && pos.currentSize < pos.positionSize && (
<span className="text-xs text-yellow-400 ml-2">({((pos.currentSize/pos.positionSize)*100).toFixed(0)}%)</span>
)}
</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-xs text-gray-400 mb-1">Total P&L</div>
<div className={`text-lg font-bold ${totalPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
${totalPnL.toFixed(2)}
</div>
</div>
</div>
{/* Exit Targets */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div className={`rounded-lg p-4 border-2 ${pos.tp1Hit ? 'bg-green-900/30 border-green-500' : 'bg-gray-700/20 border-gray-600'}`}>
<div className="flex items-center justify-between mb-2">
<div className="text-xs text-gray-400">TP1 Target</div>
{pos.tp1Hit && <span className="text-xs font-bold text-green-400"> HIT</span>}
</div>
<div className="text-lg font-bold text-white">${pos.takeProfit1.toFixed(2)}</div>
</div>
<div className="bg-gray-700/20 rounded-lg p-4 border-2 border-gray-600">
<div className="text-xs text-gray-400 mb-2">TP2 / Runner</div>
<div className="text-lg font-bold text-white">${pos.takeProfit2.toFixed(2)}</div>
</div>
<div className={`rounded-lg p-4 border-2 ${pos.slMovedToBreakeven ? 'bg-blue-900/30 border-blue-500' : 'bg-gray-700/20 border-gray-600'}`}>
<div className="flex items-center justify-between mb-2">
<div className="text-xs text-gray-400">Stop Loss</div>
{pos.slMovedToBreakeven && <span className="text-xs font-bold text-blue-400">@ B/E</span>}
</div>
<div className="text-lg font-bold text-white">${pos.stopLoss.toFixed(2)}</div>
</div>
</div>
{/* P&L Breakdown */}
<div className="bg-gray-700/20 rounded-lg p-4">
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-xs text-gray-400 mb-1">Realized P&L</div>
<div className={`font-bold ${pos.realizedPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
${pos.realizedPnL.toFixed(2)}
</div>
</div>
<div>
<div className="text-xs text-gray-400 mb-1">Unrealized P&L</div>
<div className={`font-bold ${pos.unrealizedPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
${pos.unrealizedPnL.toFixed(2)}
</div>
</div>
<div>
<div className="text-xs text-gray-400 mb-1">Peak P&L</div>
<div className={`font-bold ${pos.peakPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
${pos.peakPnL.toFixed(2)}
</div>
</div>
</div>
</div>
</div>
)
})}
</div>
)}
{/* Position Summary */}
{positions && (
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-white">📍 Current Positions</h2>
<button
onClick={clearManuallyClosed}
className="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-white text-sm font-semibold transition-colors"
title="Clear open trades from database if you manually closed them in Drift UI"
>
🗑 Clear Manual Closes
</button>
</div>
<div className="grid md:grid-cols-4 gap-4">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Open Trades</div>
<div className="text-3xl font-bold text-white">{positions.summary.individualTrades}</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Test Trades</div>
<div className="text-3xl font-bold text-yellow-500">{positions.summary.testTrades}</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Total Exposure</div>
<div className="text-3xl font-bold text-blue-400">{positions.summary.totalIndividualExposure}</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Net Exposure</div>
<div className="text-3xl font-bold text-purple-400">{positions.summary.netExposure}</div>
</div>
</div>
{positions.netPositions.length > 0 && (
<div className="mt-4 bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<h3 className="text-lg font-semibold text-white mb-4">Net Positions (Drift View)</h3>
<div className="space-y-3">
{positions.netPositions.map((pos, i) => (
<div key={i} className="flex items-center justify-between p-4 bg-gray-700/30 rounded-lg">
<div className="flex items-center space-x-4">
<div className="text-2xl">🎯</div>
<div>
<div className="text-white font-medium">{pos.symbol}</div>
<div className="text-sm text-gray-400">{pos.tradeCount} trade{pos.tradeCount > 1 ? 's' : ''}</div>
</div>
</div>
<div className="text-right">
<div className={`text-lg font-bold ${pos.netDirection === 'long' ? 'text-green-400' : pos.netDirection === 'short' ? 'text-red-400' : 'text-gray-400'}`}>
{pos.netDirection.toUpperCase()}: {Math.abs(pos.netSOL).toFixed(4)} SOL
</div>
<div className="text-sm text-gray-400">${Math.abs(pos.netUSD).toFixed(2)}</div>
</div>
</div>
))}
</div>
</div>
)}
{positions.summary.individualTrades === 0 && (
<div className="mt-4 bg-gray-800/30 backdrop-blur-sm rounded-xl p-8 border border-gray-700 text-center">
<div className="text-4xl mb-2">📭</div>
<p className="text-gray-400">No open positions</p>
</div>
)}
</div>
)}
{/* Indicator Version Comparison */}
{versionComparison && versionComparison.versions.length > 0 && (
<div className="mb-8">
<h2 className="text-xl font-bold text-white mb-4">🎯 Indicator Versions (v8 Production)</h2>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<p className="text-gray-300 text-sm mb-6 leading-relaxed">
<strong className="text-blue-400">v8 Money Line</strong> is the production system with perfect quality separation (95 = 100% wins).
Archived versions shown for statistical comparison and future v9 development.
</p>
<div className="space-y-4">
{versionComparison.versions.map((version, idx) => {
const isProduction = version.version === 'v8'
const isArchived = version.archived === true
return (
<div
key={version.version}
className={`p-5 rounded-lg border transition-all ${
isProduction
? 'bg-blue-900/30 border-blue-500 shadow-lg shadow-blue-500/20'
: isArchived
? 'bg-gray-800/20 border-gray-700 opacity-60'
: 'bg-gray-700/30 border-gray-600'
}`}
>
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h3 className={`text-lg font-bold ${
isProduction
? 'text-blue-400'
: isArchived
? 'text-gray-500'
: 'text-white'
}`}>
{version.version.toUpperCase()}
{isProduction && (
<span className="ml-2 px-3 py-1 text-xs bg-blue-600 text-white rounded-full font-bold">
🚀 PRODUCTION
</span>
)}
{isArchived && (
<span className="ml-2 px-2 py-1 text-xs bg-gray-700 text-gray-400 rounded-full">
ARCHIVED
</span>
)}
</h3>
</div>
<p className={`text-sm ${
isArchived ? 'text-gray-500' : 'text-gray-400'
}`}>
{versionComparison.descriptions[version.version] || 'Unknown version'}
</p>
</div>
</div>
{/* Main Metrics Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Trades</div>
<div className="text-xl font-bold text-white">{version.tradeCount}</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Win Rate</div>
<div className={`text-xl font-bold ${version.winRate >= 50 ? 'text-green-400' : 'text-red-400'}`}>
{version.winRate}%
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Total P&L</div>
<div className={`text-xl font-bold ${version.totalPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{version.totalPnL >= 0 ? '+' : ''}${version.totalPnL.toFixed(2)}
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg P&L</div>
<div className={`text-xl font-bold ${version.avgPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{version.avgPnL >= 0 ? '+' : ''}${version.avgPnL.toFixed(2)}
</div>
</div>
</div>
{/* Advanced Metrics */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-4">
{version.avgQualityScore !== null && (
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg Quality Score</div>
<div className={`text-lg font-semibold ${version.avgQualityScore >= 75 ? 'text-green-400' : 'text-yellow-400'}`}>
{version.avgQualityScore}/100
</div>
</div>
)}
{version.avgMFE !== null && (
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg MFE</div>
<div className="text-lg font-semibold text-green-400">
+${version.avgMFE.toFixed(2)}
</div>
</div>
)}
{version.avgMAE !== null && (
<div className="bg-gray-800/50 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Avg MAE</div>
<div className="text-lg font-semibold text-red-400">
${version.avgMAE.toFixed(2)}
</div>
</div>
)}
</div>
{/* Extreme Position Stats */}
{version.extremePositions.count > 0 && (
<div className="pt-4 border-t border-gray-600/50">
<div className="text-xs text-gray-400 mb-3 flex items-center">
<span className="text-yellow-500 mr-2"></span>
Extreme Positions (&lt; 15% or &gt; 85% range)
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Count</div>
<div className="text-sm font-semibold text-white">
{version.extremePositions.count}
</div>
</div>
{version.extremePositions.avgADX !== null && (
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Avg ADX</div>
<div className={`text-sm font-semibold ${version.extremePositions.avgADX >= 18 ? 'text-green-400' : 'text-orange-400'}`}>
{version.extremePositions.avgADX.toFixed(1)}
</div>
</div>
)}
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Weak ADX</div>
<div className="text-sm font-semibold text-orange-400">
{version.extremePositions.weakADXCount}
</div>
</div>
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Win Rate</div>
<div className={`text-sm font-semibold ${version.extremePositions.winRate >= 50 ? 'text-green-400' : 'text-red-400'}`}>
{version.extremePositions.winRate}%
</div>
</div>
<div className="bg-gray-800/50 rounded p-2">
<div className="text-xs text-gray-500">Avg P&L</div>
<div className={`text-sm font-semibold ${version.extremePositions.avgPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{version.extremePositions.avgPnL >= 0 ? '+' : ''}${version.extremePositions.avgPnL.toFixed(2)}
</div>
</div>
</div>
</div>
)}
{/* Data Collection Notice for v8 */}
{isProduction && version.tradeCount < 50 && (
<div className="mt-4 p-3 bg-blue-900/20 rounded-lg border border-blue-500/30">
<div className="flex items-start space-x-2">
<span className="text-blue-400 text-sm">📊</span>
<p className="text-xs text-blue-300/80 leading-relaxed">
<strong>Data Collection Phase:</strong> {version.tradeCount}/50 trades completed.
v8 has shown perfect quality separation (95 = 100% wins, 90 = 0% wins).
Collecting more data for statistical confidence and future v9 development baseline.
</p>
</div>
</div>
)}
</div>
)
})}
</div>
{/* Legend */}
<div className="mt-6 pt-6 border-t border-gray-600/50">
<div className="text-xs text-gray-400 space-y-1">
<div><strong className="text-gray-300">MFE (Max Favorable Excursion):</strong> Best profit % reached during trade lifetime</div>
<div><strong className="text-gray-300">MAE (Max Adverse Excursion):</strong> Worst loss % reached during trade lifetime</div>
<div><strong className="text-gray-300">Extreme Positions:</strong> Trades entered at price range extremes (&lt; 15% or &gt; 85%)</div>
<div><strong className="text-gray-300">Weak ADX:</strong> Trend strength below 18 (indicates sideways/choppy market)</div>
</div>
</div>
</div>
</div>
)}
{/* Last Trade Details */}
{lastTrade && (
<div className="mb-8">
<h2 className="text-xl font-bold text-white mb-4">🔍 Last Trade</h2>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center space-x-4">
<div className="text-3xl">
{lastTrade.direction === 'long' ? '📈' : '📉'}
</div>
<div>
<div className="text-2xl font-bold text-white">{lastTrade.symbol}</div>
<div className="flex items-center space-x-2 mt-1">
<span className={`px-3 py-1 rounded-full text-sm font-semibold ${lastTrade.direction === 'long' ? 'bg-green-900/50 text-green-400' : 'bg-red-900/50 text-red-400'}`}>
{lastTrade.direction.toUpperCase()}
</span>
{lastTrade.isTestTrade && (
<span className="px-3 py-1 rounded-full text-sm font-semibold bg-yellow-900/50 text-yellow-400">
TEST
</span>
)}
</div>
</div>
</div>
{lastTrade.exitTime && lastTrade.realizedPnL !== undefined && (
<div className="text-right">
<div className={`text-3xl font-bold ${lastTrade.realizedPnL >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{lastTrade.realizedPnL >= 0 ? '+' : ''}${lastTrade.realizedPnL.toFixed(2)}
</div>
{lastTrade.realizedPnLPercent !== undefined && (
<div className={`text-sm ${lastTrade.realizedPnLPercent >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{lastTrade.realizedPnLPercent >= 0 ? '+' : ''}{lastTrade.realizedPnLPercent.toFixed(2)}%
</div>
)}
</div>
)}
{!lastTrade.exitTime && (
<div className="text-right">
<div className="text-2xl font-bold text-blue-400">OPEN</div>
<div className="text-sm text-gray-400">Currently active</div>
</div>
)}
</div>
<div className="grid md:grid-cols-3 gap-4 mb-4">
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-1">Entry</div>
<div className="text-xl font-bold text-white">${lastTrade.entryPrice.toFixed(4)}</div>
<div className="text-xs text-gray-500">
{new Date(lastTrade.entryTime).toLocaleString()}
</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-1">Position Size</div>
<div className="text-xl font-bold text-white">${lastTrade.positionSizeUSD.toFixed(2)}</div>
<div className="text-xs text-gray-500">
{lastTrade.leverage}x leverage
</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-1">Signal Quality</div>
{lastTrade.signalQualityScore !== undefined ? (
<>
<div className={`text-xl font-bold ${lastTrade.signalQualityScore >= 80 ? 'text-green-400' : lastTrade.signalQualityScore >= 70 ? 'text-yellow-400' : 'text-orange-400'}`}>
{lastTrade.signalQualityScore}/100
</div>
<div className="text-xs text-gray-500">
{lastTrade.signalQualityScore >= 80 ? 'Excellent' : lastTrade.signalQualityScore >= 70 ? 'Good' : 'Marginal'}
</div>
</>
) : (
<>
<div className="text-xl font-bold text-gray-500">N/A</div>
<div className="text-xs text-gray-500">No score available</div>
</>
)}
</div>
</div>
{lastTrade.exitTime && lastTrade.exitPrice && (
<div className="grid md:grid-cols-1 gap-4 mb-4">
<div className="bg-gray-700/30 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-1">Exit</div>
<div className="text-xl font-bold text-white">${lastTrade.exitPrice.toFixed(4)}</div>
<div className="text-xs text-gray-500">
{new Date(lastTrade.exitTime).toLocaleString()}
</div>
</div>
</div>
)}
<div className="grid md:grid-cols-3 gap-4">
<div className="bg-gray-700/30 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">Stop Loss</div>
<div className="text-lg font-semibold text-red-400">${lastTrade.stopLossPrice.toFixed(4)}</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">TP1</div>
<div className="text-lg font-semibold text-green-400">${lastTrade.takeProfit1Price.toFixed(4)}</div>
</div>
<div className="bg-gray-700/30 rounded-lg p-3">
<div className="text-xs text-gray-400 mb-1">TP2</div>
<div className="text-lg font-semibold text-green-400">${lastTrade.takeProfit2Price.toFixed(4)}</div>
</div>
</div>
{lastTrade.exitReason && (
<div className={`mt-4 p-3 rounded-lg border ${
lastTrade.exitReason === 'TRAILING_SL'
? 'bg-purple-900/20 border-purple-500/30'
: 'bg-blue-900/20 border-blue-500/30'
}`}>
<span className="text-sm text-gray-400">Exit Reason: </span>
<span className={`text-sm font-semibold ${
lastTrade.exitReason === 'TRAILING_SL'
? 'text-purple-400'
: 'text-blue-400'
}`}>
{lastTrade.exitReason === 'TRAILING_SL' ? '🏃 Trailing SL' : lastTrade.exitReason}
</span>
</div>
)}
</div>
</div>
)}
{/* Trading Statistics */}
{stats && (
<div>
<h2 className="text-xl font-bold text-white mb-4">📈 Performance ({stats.period})</h2>
{/* Main Stats Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Total Trades</div>
<div className="text-3xl font-bold text-white">{stats.realTrades.total}</div>
<div className="text-xs text-gray-500 mt-2">
{stats.testTrades.count} test trade{stats.testTrades.count !== 1 ? 's' : ''} excluded
</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Win Rate</div>
<div className="text-3xl font-bold text-green-400">{stats.realTrades.winRate}</div>
<div className="text-xs text-gray-500 mt-2">
{stats.realTrades.winning}W / {stats.realTrades.losing}L
</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Total P&L</div>
<div className={`text-3xl font-bold ${parseFloat(stats.realTrades.totalPnL.replace('$', '')) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{stats.realTrades.totalPnL}
</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<div className="text-sm text-gray-400 mb-1">Profit Factor</div>
<div className="text-3xl font-bold text-blue-400">{stats.realTrades.profitFactor}</div>
</div>
</div>
{/* Detailed Stats */}
<div className="grid md:grid-cols-2 gap-6">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
<span className="text-2xl mr-2"></span>
Winning Trades
</h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-400">Count:</span>
<span className="text-white font-medium">{stats.realTrades.winning}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Average Win:</span>
<span className="text-green-400 font-medium">{stats.realTrades.avgWin}</span>
</div>
</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
<span className="text-2xl mr-2"></span>
Losing Trades
</h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-400">Count:</span>
<span className="text-white font-medium">{stats.realTrades.losing}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Average Loss:</span>
<span className="text-red-400 font-medium">{stats.realTrades.avgLoss}</span>
</div>
</div>
</div>
</div>
{stats.realTrades.total === 0 && (
<div className="mt-6 bg-gray-800/30 backdrop-blur-sm rounded-xl p-8 border border-gray-700 text-center">
<div className="text-4xl mb-2">📊</div>
<p className="text-gray-400 mb-2">No trading data yet</p>
<p className="text-sm text-gray-500">Start trading to see your performance statistics</p>
</div>
)}
</div>
)}
{/* Info Note */}
<div className="mt-8 bg-blue-900/20 backdrop-blur-sm rounded-xl p-6 border border-blue-500/30">
<div className="flex items-start space-x-3">
<div className="text-2xl">💡</div>
<div>
<h4 className="text-white font-semibold mb-2">Understanding Position Netting</h4>
<p className="text-gray-300 text-sm leading-relaxed">
Drift perpetual futures automatically NET opposite positions in the same market.
If you have both LONG and SHORT positions in SOL-PERP, Drift shows only the net exposure.
The database tracks individual trades for complete history, while this dashboard shows
your actual market exposure.
</p>
</div>
</div>
</div>
</div>
</div>
)
}