Add last trade details to analytics dashboard

- Add getLastTrade() function to database service
- Create /api/analytics/last-trade endpoint
- Display last trade with full details on analytics page
- Show entry/exit prices, P&L, position size, targets
- Visual indicators for trade direction and exit reason
- Helps quickly diagnose where trades went (TP1, TP2, or SL)
This commit is contained in:
mindesbunister
2025-10-31 10:47:19 +01:00
parent 8a17c2cf90
commit aecdc108f6
3 changed files with 193 additions and 1 deletions

View File

@@ -26,6 +26,25 @@ interface Stats {
}
}
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
}
interface NetPosition {
symbol: string
longUSD: number
@@ -50,6 +69,7 @@ interface PositionSummary {
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 [loading, setLoading] = useState(true)
const [selectedDays, setSelectedDays] = useState(30)
@@ -60,16 +80,19 @@ export default function AnalyticsPage() {
const loadData = async () => {
setLoading(true)
try {
const [statsRes, positionsRes] = await Promise.all([
const [statsRes, positionsRes, lastTradeRes] = await Promise.all([
fetch(`/api/analytics/stats?days=${selectedDays}`),
fetch('/api/analytics/positions'),
fetch('/api/analytics/last-trade'),
])
const statsData = await statsRes.json()
const positionsData = await positionsRes.json()
const lastTradeData = await lastTradeRes.json()
setStats(statsData.stats)
setPositions(positionsData.summary)
setLastTrade(lastTradeData.trade)
} catch (error) {
console.error('Failed to load analytics:', error)
}
@@ -194,6 +217,107 @@ export default function AnalyticsPage() {
</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>
{lastTrade.exitTime && lastTrade.exitPrice && (
<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 bg-blue-900/20 rounded-lg border border-blue-500/30">
<span className="text-sm text-gray-400">Exit Reason: </span>
<span className="text-sm font-semibold text-blue-400">{lastTrade.exitReason}</span>
</div>
)}
</div>
</div>
)}
{/* Trading Statistics */}
{stats && (
<div>

View File

@@ -0,0 +1,50 @@
/**
* Last Trade API Endpoint
*
* Returns details of the most recent trade
*/
import { NextResponse } from 'next/server'
import { getLastTrade } from '@/lib/database/trades'
export async function GET() {
try {
const trade = await getLastTrade()
if (!trade) {
return NextResponse.json({
trade: null,
})
}
// Format the trade data for the frontend
const formattedTrade = {
id: trade.id,
symbol: trade.symbol,
direction: trade.direction,
entryPrice: trade.entryPrice,
entryTime: trade.entryTime.toISOString(),
exitPrice: trade.exitPrice || undefined,
exitTime: trade.exitTime?.toISOString() || undefined,
exitReason: trade.exitReason || undefined,
realizedPnL: trade.realizedPnL || undefined,
realizedPnLPercent: trade.realizedPnLPercent || undefined,
positionSizeUSD: trade.positionSizeUSD,
leverage: trade.leverage,
stopLossPrice: trade.stopLossPrice,
takeProfit1Price: trade.takeProfit1Price,
takeProfit2Price: trade.takeProfit2Price,
isTestTrade: trade.isTestTrade || false,
}
return NextResponse.json({
trade: formattedTrade,
})
} catch (error) {
console.error('Failed to fetch last trade:', error)
return NextResponse.json(
{ error: 'Failed to fetch last trade' },
{ status: 500 }
)
}
}

View File

@@ -272,6 +272,24 @@ export async function getLastTradeTime(): Promise<Date | null> {
}
}
/**
* Get the most recent trade with full details
*/
export async function getLastTrade() {
const prisma = getPrismaClient()
try {
const lastTrade = await prisma.trade.findFirst({
orderBy: { createdAt: 'desc' },
})
return lastTrade
} catch (error) {
console.error('❌ Failed to get last trade:', error)
return null
}
}
/**
* Get count of trades in the last hour
*/