import { NextResponse } from 'next/server' import { Connection, Keypair, PublicKey } from '@solana/web3.js' import { DriftClient, getUserAccountPublicKey, initialize } from '@drift-labs/sdk' const getRpcStatus = () => { const rpcEndpoints = [ process.env.SOLANA_RPC_URL, process.env.HELIUS_RPC_URL, 'https://api.mainnet-beta.solana.com' ].filter(Boolean) return { primary: rpcEndpoints[0] || 'Not configured', fallbacks: rpcEndpoints.slice(1), total: rpcEndpoints.length } } // Function to record recently closed positions for learning async function recordRecentlyClosedPosition() { try { // Check if there's a recent automation decision that should be closed // Note: simple-automation import disabled to prevent API issues // const { simpleAutomation } = await import('../../../lib/simple-automation.js'); // Temporarily disabled automation integration if (false) { // simpleAutomation.lastDecision && simpleAutomation.lastDecision.executed) { const decision = simpleAutomation.lastDecision; const timeSinceDecision = Date.now() - new Date(decision.timestamp).getTime(); // If decision was executed recently (within 1 hour) and no position exists, record as closed if (timeSinceDecision < 3600000) { // 1 hour console.log('🔍 Found recent executed decision - checking if position was closed'); // Estimate profit based on current price vs entry const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd'); const priceData = await response.json(); const currentPrice = priceData.solana.usd; const entryPrice = decision.executionDetails.currentPrice; const side = decision.executionDetails.side.toLowerCase(); const amount = decision.executionDetails.amount; // Calculate P&L based on side and price movement let pnl = 0; let outcome = 'UNKNOWN'; if (side === 'long') { pnl = (currentPrice - entryPrice) * (amount / entryPrice); outcome = currentPrice > entryPrice ? 'WIN' : 'LOSS'; } else if (side === 'short') { pnl = (entryPrice - currentPrice) * (amount / entryPrice); outcome = currentPrice < entryPrice ? 'WIN' : 'LOSS'; } const pnlPercent = (pnl / amount) * 100; // Record the trade in database const { PrismaClient } = await import('@prisma/client'); const prisma = new PrismaClient(); try { const tradeRecord = await prisma.trades.create({ data: { id: `trade_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, userId: 'automation_user', // Default automation user symbol: decision.executionDetails.symbol || 'SOL-PERP', side: side.toUpperCase(), amount: amount, price: entryPrice, entryPrice: entryPrice, exitPrice: currentPrice, stopLoss: decision.executionDetails.stopLoss, takeProfit: decision.executionDetails.takeProfit, leverage: decision.executionDetails.leverage || 1, profit: pnl, pnlPercent: pnlPercent, outcome: outcome, status: 'COMPLETED', confidence: decision.confidence, aiAnalysis: decision.reasoning, isAutomated: true, tradingMode: 'PERP', driftTxId: decision.executionDetails.txId, executedAt: new Date(decision.timestamp), closedAt: new Date(), createdAt: new Date(decision.timestamp), updatedAt: new Date() } }); console.log('✅ Recorded completed trade:', tradeRecord.id); // Clear the decision to avoid re-recording simpleAutomation.lastDecision = null; return tradeRecord; } finally { await prisma.$disconnect(); } } } return null; } catch (error) { console.error('❌ Error recording closed position:', error.message); return null; } } export async function GET() { try { console.log('📊 Position History API called') // Get keypair from private key if (!process.env.SOLANA_PRIVATE_KEY) { throw new Error('SOLANA_PRIVATE_KEY environment variable not set') } const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY) const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray)) // Setup connection with failover const rpcEndpoints = [ process.env.SOLANA_RPC_URL, process.env.HELIUS_RPC_URL, 'https://api.mainnet-beta.solana.com' ].filter(Boolean) let connection let connectedEndpoint = null for (const endpoint of rpcEndpoints) { try { console.log(`🔗 Attempting connection to: ${endpoint.substring(0, 50)}...`) connection = new Connection(endpoint, 'confirmed') // Test the connection const balance = await connection.getBalance(keypair.publicKey) console.log(`✅ Connected successfully. Balance: ${(balance / 1e9).toFixed(6)} SOL`) connectedEndpoint = endpoint break } catch (connError) { console.log(`❌ Connection failed: ${connError.message}`) continue } } if (!connection || !connectedEndpoint) { throw new Error('All RPC endpoints failed') } // Initialize Drift SDK await initialize({ env: 'mainnet-beta' }) const userAccountPDA = getUserAccountPublicKey( new PublicKey('dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH'), keypair.publicKey, 0 ) console.log('🏦 User PDA:', userAccountPDA.toString()) // Create Drift client const driftClient = new DriftClient({ connection, wallet: { publicKey: keypair.publicKey, signTransaction: () => Promise.reject(new Error('Read-only')), signAllTransactions: () => Promise.reject(new Error('Read-only')) }, programID: new PublicKey('dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH'), opts: { commitment: 'confirmed' } }) // Try to get real trading history let realTradeHistory = [] try { console.log('🔍 Attempting to fetch trading history from database...') // Import Prisma client const { PrismaClient } = await import('@prisma/client'); const prisma = new PrismaClient(); try { // Get all relevant trades (both completed and executed) const allTrades = await prisma.trades.findMany({ where: { status: { in: ['COMPLETED', 'EXECUTED'] } // Include both completed and executed trades }, orderBy: { updatedAt: 'desc' // Order by updatedAt to get most recently modified trades }, take: 200 // Increased to get more trades }); console.log(`📊 Found ${allTrades.length} trades with relevant data`); // Filter out simulation trades after fetching const realTrades = allTrades.filter(trade => { // Exclude if driftTxId starts with SIM_ if (trade.driftTxId && trade.driftTxId.startsWith('SIM_')) { console.log(`🚫 Excluding simulation trade: ${trade.driftTxId}`); return false; } // Exclude if tradingMode is explicitly SIMULATION if (trade.tradingMode === 'SIMULATION') { console.log(`🚫 Excluding simulation mode trade: ${trade.id}`); return false; } console.log(`✅ Including real trade: ${trade.id} (${trade.status}) - ${trade.tradingMode || 'REAL'}`); return true; }); console.log(`📊 After filtering simulations: ${realTrades.length} real trades`); // Convert to standardized format realTradeHistory = realTrades.map(trade => { // Calculate outcome if missing let outcome = trade.outcome; let pnl = trade.profit; // For EXECUTED trades without profit, estimate current P&L if possible if (trade.status === 'EXECUTED' && !pnl && trade.entryPrice) { // These are open positions, we'll show them as "OPEN" outcome = 'OPEN'; pnl = 0; // Will be calculated when position closes } else if (!outcome && pnl !== null) { outcome = pnl > 0 ? 'WIN' : 'LOSS'; } return { id: trade.id, symbol: trade.symbol, side: trade.side, amount: trade.amount, entryPrice: trade.entryPrice, exitPrice: trade.exitPrice, pnl: pnl, pnlPercent: trade.pnlPercent, outcome: outcome, leverage: trade.leverage || 1, stopLoss: trade.stopLoss, takeProfit: trade.takeProfit, entryTime: trade.executedAt || trade.createdAt, exitTime: trade.closedAt, txId: trade.driftTxId, confidence: trade.confidence, aiAnalysis: trade.aiAnalysis, status: trade.status // Add status to distinguish COMPLETED vs EXECUTED }; }); console.log(`✅ Successfully processed ${realTradeHistory.length} real trades from database`); // Try to enhance trades with recent AI analysis data try { const recentAnalyses = await prisma.ai_learning_data.findMany({ where: { timeframe: { not: 'DECISION' }, timeframe: { not: 'OUTCOME' }, analysisData: { not: null } }, orderBy: { createdAt: 'desc' }, take: 20 // Get recent analysis records }); console.log(`Found ${recentAnalyses.length} recent AI analysis records`); // Link analysis to trades based on timing and symbol realTradeHistory.forEach(trade => { if (!trade.aiAnalysis) { const tradeTime = new Date(trade.entryTime); // Find analysis within 1 hour of trade time and same symbol const matchingAnalysis = recentAnalyses.find(analysis => { const analysisTime = new Date(analysis.createdAt); const timeDiff = Math.abs(tradeTime.getTime() - analysisTime.getTime()); const isWithinTimeWindow = timeDiff <= 3600000; // 1 hour const symbolMatch = analysis.symbol === trade.symbol || analysis.symbol === trade.symbol.replace('USD', '') || analysis.symbol === trade.symbol.replace('USDT', ''); return isWithinTimeWindow && symbolMatch; }); if (matchingAnalysis) { try { const analysisData = JSON.parse(matchingAnalysis.analysisData); trade.aiAnalysis = analysisData.reasoning || analysisData.summary || `AI Confidence: ${matchingAnalysis.confidenceScore}%`; } catch (e) { trade.aiAnalysis = `AI Analysis (Confidence: ${matchingAnalysis.confidenceScore}%)`; } } } }); } catch (analysisError) { console.log('⚠️ Could not enhance trades with AI analysis:', analysisError.message); } } finally { await prisma.$disconnect(); } } catch (error) { console.log('❌ Could not fetch trading history from database:', error.message) // Fallback: Try to detect recently closed position and record it try { console.log('🔍 Checking for recently closed positions to record...'); await recordRecentlyClosedPosition(); } catch (recordError) { console.log('⚠️ Could not record recent position:', recordError.message); } } // Only use real data - no demo/mock data const historicalTrades = realTradeHistory // Calculate statistics (case-insensitive matching, exclude OPEN positions) const completedTrades = historicalTrades.filter(trade => trade.outcome && trade.outcome.toUpperCase() !== 'OPEN' ) const wins = completedTrades.filter(trade => trade.outcome && trade.outcome.toUpperCase() === 'WIN' ) const losses = completedTrades.filter(trade => trade.outcome && trade.outcome.toUpperCase() === 'LOSS' ) const totalPnl = completedTrades.reduce((sum, trade) => sum + (trade.pnl || 0), 0) const winsPnl = wins.reduce((sum, trade) => sum + (trade.pnl || 0), 0) const lossesPnl = losses.reduce((sum, trade) => sum + (trade.pnl || 0), 0) const winRate = completedTrades.length > 0 ? (wins.length / completedTrades.length) * 100 : 0 const avgWin = wins.length > 0 ? winsPnl / wins.length : 0 const avgLoss = losses.length > 0 ? lossesPnl / losses.length : 0 const profitFactor = Math.abs(lossesPnl) > 0 ? Math.abs(winsPnl / lossesPnl) : 0 const statistics = { totalTrades: historicalTrades.length, // Include all trades (OPEN + COMPLETED) completedTrades: completedTrades.length, // Only completed trades openTrades: historicalTrades.filter(t => t.outcome === 'OPEN').length, wins: wins.length, losses: losses.length, winRate: Math.round(winRate), totalPnl: Math.round(totalPnl * 100) / 100, winsPnl: Math.round(winsPnl * 100) / 100, lossesPnl: Math.round(lossesPnl * 100) / 100, avgWin: Math.round(avgWin * 100) / 100, avgLoss: Math.round(avgLoss * 100) / 100, profitFactor: Math.round(profitFactor * 100) / 100 } console.log('📈 Trading Statistics:', statistics) return NextResponse.json({ success: true, trades: historicalTrades, statistics, rpcStatus: { connected: connectedEndpoint, status: getRpcStatus() }, timestamp: new Date().toISOString(), note: "Real trading history API - showing only actual trades when available" }, { headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' } }) } catch (error) { console.error('❌ Position history API error:', error) return NextResponse.json({ success: false, error: 'Failed to get position history', details: error.message, rpcStatus: getRpcStatus() }, { status: 500 }) } } export async function POST() { return NextResponse.json({ message: 'Use GET method to retrieve position history' }, { status: 405 }) }