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:
@@ -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>
|
||||
|
||||
50
app/api/analytics/last-trade/route.ts
Normal file
50
app/api/analytics/last-trade/route.ts
Normal 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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user