Files
trading_bot_v3/app/api/drift/position-history/route.js
mindesbunister fd25f4c8e9 feat: implement automation restart and trade recording systems
- Added auto-restart detection in position monitor
  - Triggers when no position + START_TRADING recommendation
  - Provides autoRestart status for UI integration
  - Enables automatic new cycle initiation after cleanup

- Implemented real trade recording in position history API
  - Fetches completed trades from database for AI learning
  - Filters out simulation trades (excludes SIM_ prefix)
  - Records automated trade outcomes for learning enhancement
  - Provides accurate statistics for AI system

- Enhanced trade recording with proper P&L calculation
  - Records recent closed positions automatically
  - Calculates win/loss outcomes based on price movement
  - Integrates with existing automation decision tracking

Resolves: No new cycle after cleanup + Missing trade data for AI learning
System now properly restarts and records real trading history for learning.
2025-07-28 18:06:23 +02:00

309 lines
11 KiB
JavaScript

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
const { simpleAutomation } = await import('../../../lib/simple-automation.js');
if (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 completed trades from database
const completedTrades = await prisma.trades.findMany({
where: {
status: 'COMPLETED',
driftTxId: {
not: null,
not: { startsWith: 'SIM_' } // Exclude simulation trades
},
tradingMode: 'PERP' // Only perpetual trades
},
orderBy: {
closedAt: 'desc'
},
take: 50 // Last 50 trades
});
console.log(`📊 Found ${completedTrades.length} completed trades in database`);
// Convert to standardized format
realTradeHistory = completedTrades.map(trade => ({
id: trade.id,
symbol: trade.symbol,
side: trade.side,
amount: trade.amount,
entryPrice: trade.entryPrice,
exitPrice: trade.exitPrice,
pnl: trade.profit,
pnlPercent: trade.pnlPercent,
outcome: trade.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
}));
console.log(`✅ Successfully processed ${realTradeHistory.length} trades from database`);
} 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
const wins = historicalTrades.filter(trade => trade.outcome === 'win')
const losses = historicalTrades.filter(trade => trade.outcome === 'loss')
const totalPnl = historicalTrades.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 = historicalTrades.length > 0 ? (wins.length / historicalTrades.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,
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 })
}