Files
trading_bot_v3/app/api/drift/position-history/route.js
mindesbunister 6c02d39f0a fix: AI-calculated SL/TP system with fallback risk management
- Added fallback SL/TP calculation when AI values missing (rate limits)
- Stop loss: 1.5% from entry (scalping-optimized)
- Take profit: 3% from entry (2:1 risk/reward)
- Relaxed API validation to require only stop loss (most critical)
- Disabled problematic import in position-history route
- System now guarantees risk management on every trade

 No more unprotected positions - works with or without AI analysis
2025-07-29 17:18:26 +02:00

395 lines
15 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
// 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 })
}