fix: eliminate excessive P&L calculations and restore CoinGecko price source
- Fixed Prisma table name errors in price-monitor.ts (trades vs trade, automation_sessions vs automationSession) - Commented out excessive P&L calculation logging in analysis-details API that was processing all 69 trades - Restored CoinGecko as primary price source (was falling back to Binance due to DB errors) - Optimized analysis-details to skip P&L calculations for FAILED/EXECUTED trades - Added comprehensive cleanup system for orphaned orders - Performance improvement: eliminated unnecessary processing of old trade data Result: Clean logs, efficient price fetching from CoinGecko, no excessive calculations
This commit is contained in:
95
analyze-trade-data.js
Normal file
95
analyze-trade-data.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
|
||||||
|
async function analyzeOldTrades() {
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 Analyzing trade data in database...\n');
|
||||||
|
|
||||||
|
// Count total trades
|
||||||
|
const totalTrades = await prisma.trades.count();
|
||||||
|
console.log('📊 Total trades in database:', totalTrades);
|
||||||
|
|
||||||
|
// Count by status
|
||||||
|
const tradesByStatus = await prisma.trades.groupBy({
|
||||||
|
by: ['status'],
|
||||||
|
_count: {
|
||||||
|
status: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n📈 Trades by status:');
|
||||||
|
tradesByStatus.forEach(group => {
|
||||||
|
console.log(` ${group.status}: ${group._count.status} trades`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find oldest and newest trades
|
||||||
|
const oldestTrade = await prisma.trades.findFirst({
|
||||||
|
orderBy: { createdAt: 'asc' },
|
||||||
|
select: { createdAt: true, symbol: true, status: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const newestTrade = await prisma.trades.findFirst({
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
select: { createdAt: true, symbol: true, status: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n⏰ Trade age range:');
|
||||||
|
if (oldestTrade) {
|
||||||
|
console.log(' Oldest:', oldestTrade.createdAt, '-', oldestTrade.symbol, '-', oldestTrade.status);
|
||||||
|
}
|
||||||
|
if (newestTrade) {
|
||||||
|
console.log(' Newest:', newestTrade.createdAt, '-', newestTrade.symbol, '-', newestTrade.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count trades older than 30 days
|
||||||
|
const thirtyDaysAgo = new Date();
|
||||||
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||||
|
|
||||||
|
const oldTrades = await prisma.trades.count({
|
||||||
|
where: {
|
||||||
|
createdAt: {
|
||||||
|
lt: thirtyDaysAgo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n🗓️ Trades older than 30 days: ${oldTrades} (${((oldTrades/totalTrades)*100).toFixed(1)}%)`);
|
||||||
|
|
||||||
|
// Count currently open trades
|
||||||
|
const openTrades = await prisma.trades.count({
|
||||||
|
where: {
|
||||||
|
status: 'open'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\n🔴 Currently open trades: ${openTrades}`);
|
||||||
|
|
||||||
|
if (openTrades > 0) {
|
||||||
|
const openTradeDetails = await prisma.trades.findMany({
|
||||||
|
where: { status: 'open' },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
symbol: true,
|
||||||
|
side: true,
|
||||||
|
amount: true,
|
||||||
|
price: true,
|
||||||
|
createdAt: true
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n📋 Open trade details:');
|
||||||
|
openTradeDetails.forEach(trade => {
|
||||||
|
console.log(` ${trade.id}: ${trade.side} ${trade.amount} ${trade.symbol} @ $${trade.price} (${trade.createdAt})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error analyzing trades:', error);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeOldTrades().catch(console.error);
|
||||||
@@ -92,14 +92,14 @@ export async function GET() {
|
|||||||
const unrealizedPnL = trade.status === 'OPEN' ?
|
const unrealizedPnL = trade.status === 'OPEN' ?
|
||||||
(priceChange * trade.amount * (actualTradingAmount / storedPositionValue)) : null
|
(priceChange * trade.amount * (actualTradingAmount / storedPositionValue)) : null
|
||||||
|
|
||||||
console.log(`💰 P&L Calculation for trade ${trade.id}:`, {
|
// console.log(`💰 P&L Calculation for trade ${trade.id}:`, {
|
||||||
actualTradingAmount,
|
// actualTradingAmount,
|
||||||
storedPositionValue: storedPositionValue.toFixed(2),
|
// storedPositionValue: storedPositionValue.toFixed(2),
|
||||||
priceChange: priceChange.toFixed(2),
|
// priceChange: priceChange.toFixed(2),
|
||||||
rawPnL: (priceChange * trade.amount).toFixed(2),
|
// rawPnL: (priceChange * trade.amount).toFixed(2),
|
||||||
adjustedPnL: unrealizedPnL?.toFixed(2),
|
// adjustedPnL: unrealizedPnL?.toFixed(2),
|
||||||
adjustment_ratio: (actualTradingAmount / storedPositionValue).toFixed(4)
|
// adjustment_ratio: (actualTradingAmount / storedPositionValue).toFixed(4)
|
||||||
})
|
// })
|
||||||
|
|
||||||
const entryTime = new Date(trade.createdAt)
|
const entryTime = new Date(trade.createdAt)
|
||||||
const exitTime = trade.closedAt ? new Date(trade.closedAt) : null
|
const exitTime = trade.closedAt ? new Date(trade.closedAt) : null
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export async function GET() {
|
|||||||
try {
|
try {
|
||||||
console.log('✅ API CORRECTED: Loading with fixed trade calculations...')
|
console.log('✅ API CORRECTED: Loading with fixed trade calculations...')
|
||||||
|
|
||||||
const sessions = await prisma.automationSession.findMany({
|
const sessions = await prisma.automation_sessions.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: 'default-user',
|
userId: 'default-user',
|
||||||
symbol: 'SOLUSD'
|
symbol: 'SOLUSD'
|
||||||
@@ -32,7 +32,7 @@ export async function GET() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const recentTrades = await prisma.trade.findMany({
|
const recentTrades = await prisma.trades.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: latestSession.userId,
|
userId: latestSession.userId,
|
||||||
symbol: latestSession.symbol
|
symbol: latestSession.symbol
|
||||||
@@ -46,14 +46,60 @@ export async function GET() {
|
|||||||
const totalPnL = completedTrades.reduce((sum, trade) => sum + (trade.profit || 0), 0)
|
const totalPnL = completedTrades.reduce((sum, trade) => sum + (trade.profit || 0), 0)
|
||||||
const winRate = completedTrades.length > 0 ? (successfulTrades.length / completedTrades.length * 100) : 0
|
const winRate = completedTrades.length > 0 ? (successfulTrades.length / completedTrades.length * 100) : 0
|
||||||
|
|
||||||
const currentPrice = 175.82
|
// 🔥 GET REAL CURRENT PRICE - SYNCHRONIZED WITH PRICE MONITOR
|
||||||
|
let currentPrice = 193.54 // Fallback price
|
||||||
|
try {
|
||||||
|
// First try to get price from price-monitor endpoint (most recent and consistent)
|
||||||
|
const priceMonitorResponse = await fetch('http://localhost:3000/api/price-monitor')
|
||||||
|
if (priceMonitorResponse.ok) {
|
||||||
|
const priceMonitorData = await priceMonitorResponse.json()
|
||||||
|
if (priceMonitorData.success && priceMonitorData.data.prices.SOLUSD) {
|
||||||
|
currentPrice = priceMonitorData.data.prices.SOLUSD
|
||||||
|
console.log('📊 Using synchronized price from price monitor:', currentPrice)
|
||||||
|
} else {
|
||||||
|
throw new Error('Price monitor data not available')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Price monitor API not responding')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Price monitor unavailable, fetching directly from Binance:', error.message)
|
||||||
|
try {
|
||||||
|
// Fallback to direct Binance API call
|
||||||
|
const priceResponse = await fetch('https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT')
|
||||||
|
if (priceResponse.ok) {
|
||||||
|
const priceData = await priceResponse.json()
|
||||||
|
currentPrice = parseFloat(priceData.price)
|
||||||
|
console.log('📊 Using backup price from Binance:', currentPrice)
|
||||||
|
}
|
||||||
|
} catch (backupError) {
|
||||||
|
console.error('⚠️ Both price sources failed, using fallback:', backupError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const formattedTrades = recentTrades.map(trade => {
|
const formattedTrades = recentTrades.map(trade => {
|
||||||
const priceChange = trade.side === 'BUY' ?
|
const priceChange = trade.side === 'BUY' ?
|
||||||
(currentPrice - trade.price) :
|
(currentPrice - trade.price) :
|
||||||
(trade.price - currentPrice)
|
(trade.price - currentPrice)
|
||||||
|
|
||||||
|
// 🔥 FIX: Calculate P&L based on ACTUAL investment amount, not position size
|
||||||
|
// Get the actual trading amount from the trade or session settings
|
||||||
|
const actualTradingAmount = trade.tradingAmount || latestSession.settings?.tradingAmount || 100
|
||||||
|
const storedPositionValue = trade.amount * trade.price // What was actually bought
|
||||||
|
|
||||||
|
// Calculate proportional P&L based on actual investment
|
||||||
const realizedPnL = trade.status === 'COMPLETED' ? (trade.profit || 0) : null
|
const realizedPnL = trade.status === 'COMPLETED' ? (trade.profit || 0) : null
|
||||||
const unrealizedPnL = trade.status === 'OPEN' ? (priceChange * trade.amount) : null
|
const unrealizedPnL = trade.status === 'OPEN' ?
|
||||||
|
(priceChange * trade.amount * (actualTradingAmount / storedPositionValue)) : null
|
||||||
|
|
||||||
|
console.log(`💰 P&L Calculation for trade ${trade.id}:`, {
|
||||||
|
actualTradingAmount,
|
||||||
|
storedPositionValue: storedPositionValue.toFixed(2),
|
||||||
|
priceChange: priceChange.toFixed(2),
|
||||||
|
rawPnL: (priceChange * trade.amount).toFixed(2),
|
||||||
|
adjustedPnL: unrealizedPnL?.toFixed(2),
|
||||||
|
adjustment_ratio: (actualTradingAmount / storedPositionValue).toFixed(4)
|
||||||
|
})
|
||||||
|
|
||||||
const entryTime = new Date(trade.createdAt)
|
const entryTime = new Date(trade.createdAt)
|
||||||
const exitTime = trade.closedAt ? new Date(trade.closedAt) : null
|
const exitTime = trade.closedAt ? new Date(trade.closedAt) : null
|
||||||
@@ -71,48 +117,108 @@ export async function GET() {
|
|||||||
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
|
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ CORRECTED CALCULATION: Fix position size for $100 investment
|
// ✅ CORRECTED CALCULATION: Show actual investment amounts
|
||||||
const tradingAmount = 100
|
|
||||||
const leverage = trade.leverage || 1
|
const leverage = trade.leverage || 1
|
||||||
|
const displayPositionSize = actualTradingAmount.toFixed(2)
|
||||||
|
|
||||||
const correctTokenAmount = tradingAmount / trade.price
|
// Mark old trades with wrong data
|
||||||
const displayAmount = correctTokenAmount
|
const isOldWrongTrade = trade.price < 150 && trade.amount > 1.5 // Detect old wrong trades
|
||||||
const displayPositionSize = (tradingAmount * leverage).toFixed(2)
|
|
||||||
|
// Enhanced entry/exit price handling
|
||||||
|
const entryPrice = trade.entryPrice || trade.price
|
||||||
|
let exitPrice = trade.exitPrice
|
||||||
|
let calculatedProfit = trade.profit
|
||||||
|
|
||||||
|
// If exit price is null but trade is completed, try to calculate from profit
|
||||||
|
if (trade.status === 'COMPLETED' && !exitPrice && calculatedProfit !== null && calculatedProfit !== undefined) {
|
||||||
|
// Calculate exit price from profit: profit = (exitPrice - entryPrice) * amount
|
||||||
|
if (trade.side === 'BUY') {
|
||||||
|
exitPrice = entryPrice + (calculatedProfit / trade.amount)
|
||||||
|
} else {
|
||||||
|
exitPrice = entryPrice - (calculatedProfit / trade.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If profit is null but we have both prices, calculate profit
|
||||||
|
if (trade.status === 'COMPLETED' && (calculatedProfit === null || calculatedProfit === undefined) && exitPrice && entryPrice) {
|
||||||
|
if (trade.side === 'BUY') {
|
||||||
|
calculatedProfit = (exitPrice - entryPrice) * trade.amount
|
||||||
|
} else {
|
||||||
|
calculatedProfit = (entryPrice - exitPrice) * trade.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine result based on actual profit - use profit field as fallback
|
||||||
|
let result = 'ACTIVE'
|
||||||
|
if (trade.status === 'COMPLETED') {
|
||||||
|
// First try to use the stored profit field
|
||||||
|
const storedProfit = trade.profit || 0
|
||||||
|
|
||||||
|
if (calculatedProfit !== null && calculatedProfit !== undefined) {
|
||||||
|
// Use calculated profit if available
|
||||||
|
if (Math.abs(calculatedProfit) < 0.01) {
|
||||||
|
result = 'BREAKEVEN'
|
||||||
|
} else if (calculatedProfit > 0) {
|
||||||
|
result = 'WIN'
|
||||||
|
} else {
|
||||||
|
result = 'LOSS'
|
||||||
|
}
|
||||||
|
} else if (storedProfit !== null) {
|
||||||
|
// Fallback to stored profit field
|
||||||
|
if (Math.abs(storedProfit) < 0.01) {
|
||||||
|
result = 'BREAKEVEN'
|
||||||
|
} else if (storedProfit > 0) {
|
||||||
|
result = 'WIN'
|
||||||
|
} else {
|
||||||
|
result = 'LOSS'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = 'UNKNOWN' // When we truly don't have any profit data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: trade.id,
|
id: trade.id,
|
||||||
type: 'MARKET',
|
type: 'MARKET',
|
||||||
side: trade.side,
|
side: trade.side,
|
||||||
amount: displayAmount,
|
amount: trade.amount, // Keep original SOL amount for reference
|
||||||
tradingAmount: tradingAmount,
|
tradingAmount: actualTradingAmount, // Show actual investment amount
|
||||||
|
realTradingAmount: actualTradingAmount, // Show real trading amount
|
||||||
leverage: leverage,
|
leverage: leverage,
|
||||||
positionSize: displayPositionSize,
|
positionSize: displayPositionSize,
|
||||||
price: trade.price,
|
price: trade.price,
|
||||||
status: trade.status,
|
status: trade.status,
|
||||||
pnl: realizedPnL ? realizedPnL.toFixed(2) : (unrealizedPnL ? unrealizedPnL.toFixed(2) : '0.00'),
|
pnl: realizedPnL ? realizedPnL.toFixed(2) : (unrealizedPnL ? unrealizedPnL.toFixed(2) : '0.00'),
|
||||||
pnlPercent: realizedPnL ? `${((realizedPnL / tradingAmount) * 100).toFixed(2)}%` :
|
pnlPercent: realizedPnL ? `${((realizedPnL / actualTradingAmount) * 100).toFixed(2)}%` :
|
||||||
(unrealizedPnL ? `${((unrealizedPnL / tradingAmount) * 100).toFixed(2)}%` : '0.00%'),
|
(unrealizedPnL ? `${((unrealizedPnL / actualTradingAmount) * 100).toFixed(2)}%` : '0.00%'),
|
||||||
createdAt: trade.createdAt,
|
createdAt: trade.createdAt,
|
||||||
entryTime: trade.createdAt,
|
entryTime: trade.createdAt,
|
||||||
exitTime: trade.closedAt,
|
exitTime: trade.closedAt,
|
||||||
actualDuration: durationMs,
|
actualDuration: durationMs,
|
||||||
durationText: formatDuration(durationMinutes) + (trade.status === 'OPEN' ? ' (Active)' : ''),
|
durationText: formatDuration(durationMinutes) + (trade.status === 'OPEN' ? ' (Active)' : ''),
|
||||||
reason: `REAL: ${trade.side} signal with ${trade.confidence || 75}% confidence`,
|
reason: `REAL: ${trade.side} signal with ${trade.confidence || 75}% confidence`,
|
||||||
entryPrice: trade.entryPrice || trade.price,
|
entryPrice: entryPrice,
|
||||||
exitPrice: trade.exitPrice,
|
exitPrice: exitPrice,
|
||||||
currentPrice: trade.status === 'OPEN' ? currentPrice : null,
|
currentPrice: trade.status === 'OPEN' ? currentPrice : null,
|
||||||
unrealizedPnl: unrealizedPnL ? unrealizedPnL.toFixed(2) : null,
|
unrealizedPnl: unrealizedPnL ? unrealizedPnL.toFixed(2) : null,
|
||||||
realizedPnl: realizedPnL ? realizedPnL.toFixed(2) : null,
|
realizedPnl: realizedPnL ? realizedPnL.toFixed(2) : null,
|
||||||
|
calculatedProfit: calculatedProfit,
|
||||||
stopLoss: trade.stopLoss || (trade.side === 'BUY' ? (trade.price * 0.98).toFixed(2) : (trade.price * 1.02).toFixed(2)),
|
stopLoss: trade.stopLoss || (trade.side === 'BUY' ? (trade.price * 0.98).toFixed(2) : (trade.price * 1.02).toFixed(2)),
|
||||||
takeProfit: trade.takeProfit || (trade.side === 'BUY' ? (trade.price * 1.04).toFixed(2) : (trade.price * 0.96).toFixed(2)),
|
takeProfit: trade.takeProfit || (trade.side === 'BUY' ? (trade.price * 1.04).toFixed(2) : (trade.price * 0.96).toFixed(2)),
|
||||||
isActive: trade.status === 'OPEN' || trade.status === 'PENDING',
|
isActive: trade.status === 'OPEN' || trade.status === 'PENDING',
|
||||||
confidence: trade.confidence || 75,
|
confidence: trade.confidence || 75,
|
||||||
result: trade.status === 'COMPLETED' ?
|
result: result,
|
||||||
((trade.profit || 0) > 0 ? 'WIN' : (trade.profit || 0) < 0 ? 'LOSS' : 'BREAKEVEN') :
|
|
||||||
'ACTIVE',
|
|
||||||
resultDescription: trade.status === 'COMPLETED' ?
|
resultDescription: trade.status === 'COMPLETED' ?
|
||||||
`REAL: ${(trade.profit || 0) > 0 ? 'Profitable' : 'Loss'} ${trade.side} trade - Completed` :
|
`REAL: ${result === 'WIN' ? 'Profitable' : result === 'LOSS' ? 'Loss' : result} ${trade.side} trade - Completed` :
|
||||||
`REAL: ${trade.side} position active - ${formatDuration(durationMinutes)}`
|
`REAL: ${trade.side} position active - ${formatDuration(durationMinutes)}`,
|
||||||
|
isOldWrongTrade: isOldWrongTrade,
|
||||||
|
correctedAmount: isOldWrongTrade ? (actualTradingAmount / currentPrice).toFixed(4) : null,
|
||||||
|
originalStoredPrice: trade.price,
|
||||||
|
tradingMode: trade.tradingMode || latestSession.mode, // 🔥 USE ACTUAL TRADING MODE FROM DATABASE
|
||||||
|
driftTxId: trade.driftTxId, // Jupiter DEX transaction ID
|
||||||
|
fees: trade.fees || 0, // Trading fees
|
||||||
|
actualInvestment: actualTradingAmount, // Show the real investment amount
|
||||||
|
positionAdjustment: `${actualTradingAmount}/${storedPositionValue.toFixed(2)}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
167
app/api/drift/position-history/route-clean.js
Normal file
167
app/api/drift/position-history/route-clean.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 real trading history from Drift...')
|
||||||
|
|
||||||
|
// Real trading history fetching would require:
|
||||||
|
// 1. Drift indexer API access
|
||||||
|
// 2. Transaction log parsing
|
||||||
|
// 3. Event listener aggregation
|
||||||
|
// Currently not implemented in SDK
|
||||||
|
|
||||||
|
console.log('⚠️ Real trading history fetch not implemented - returning empty data')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Could not fetch real trading history:', error.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 })
|
||||||
|
}
|
||||||
402
app/api/drift/position-history/route-old.js
Normal file
402
app/api/drift/position-history/route-old.js
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import { executeWithFailover, getRpcStatus } from '../../../../lib/rpc-failover.js'
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
console.log('📊 Getting Drift position history...')
|
||||||
|
|
||||||
|
// Log RPC status
|
||||||
|
const rpcStatus = getRpcStatus()
|
||||||
|
console.log('🌐 RPC Status:', rpcStatus)
|
||||||
|
|
||||||
|
// Check if environment is configured
|
||||||
|
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: 'Drift not configured - missing SOLANA_PRIVATE_KEY'
|
||||||
|
}, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute with RPC failover
|
||||||
|
const result = await executeWithFailover(async (connection) => {
|
||||||
|
// Import Drift SDK components
|
||||||
|
const { DriftClient, initialize } = await import('@drift-labs/sdk')
|
||||||
|
const { Keypair } = await import('@solana/web3.js')
|
||||||
|
const { AnchorProvider } = await import('@coral-xyz/anchor')
|
||||||
|
|
||||||
|
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
|
||||||
|
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
|
||||||
|
|
||||||
|
const { default: NodeWallet } = await import('@coral-xyz/anchor/dist/cjs/nodewallet.js')
|
||||||
|
const wallet = new NodeWallet(keypair)
|
||||||
|
|
||||||
|
// Initialize Drift SDK
|
||||||
|
const env = 'mainnet-beta'
|
||||||
|
const sdkConfig = initialize({ env })
|
||||||
|
|
||||||
|
const driftClient = new DriftClient({
|
||||||
|
connection,
|
||||||
|
wallet,
|
||||||
|
programID: sdkConfig.DRIFT_PROGRAM_ID,
|
||||||
|
opts: {
|
||||||
|
commitment: 'confirmed',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await driftClient.subscribe()
|
||||||
|
console.log('✅ Connected to Drift for position history')
|
||||||
|
|
||||||
|
// Check if user has account
|
||||||
|
let userAccount
|
||||||
|
try {
|
||||||
|
userAccount = await driftClient.getUserAccount()
|
||||||
|
} catch (accountError) {
|
||||||
|
await driftClient.unsubscribe()
|
||||||
|
throw new Error('No Drift user account found. Please initialize your account first.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get real trade records from your actual Drift account
|
||||||
|
console.log('🔍 Fetching real trading history from Drift account...')
|
||||||
|
|
||||||
|
// Market symbols mapping
|
||||||
|
const marketSymbols = {
|
||||||
|
0: 'SOL-PERP',
|
||||||
|
1: 'BTC-PERP',
|
||||||
|
2: 'ETH-PERP',
|
||||||
|
3: 'APT-PERP',
|
||||||
|
4: 'BNB-PERP'
|
||||||
|
}
|
||||||
|
|
||||||
|
let realTradeHistory = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user account data which contains position history
|
||||||
|
const userAccountData = await driftClient.getUserAccount()
|
||||||
|
console.log('<27> Got user account data')
|
||||||
|
|
||||||
|
// Try to get historical trade data using different methods
|
||||||
|
let tradeHistory = []
|
||||||
|
|
||||||
|
// Method 1: Check if user account has trade history
|
||||||
|
if (userAccountData && userAccountData.orders) {
|
||||||
|
console.log('📝 Found orders in user account:', userAccountData.orders.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 2: Try to get trade records via program
|
||||||
|
try {
|
||||||
|
const connection = driftClient.connection
|
||||||
|
const programId = driftClient.program.programId
|
||||||
|
|
||||||
|
// Get all accounts related to this user
|
||||||
|
console.log('🔍 Searching for trade records...')
|
||||||
|
|
||||||
|
// For now, we'll indicate that real data is not accessible via SDK
|
||||||
|
console.log('⚠️ Real trade history requires direct blockchain parsing')
|
||||||
|
console.log('📊 Using demo data until real history API is implemented')
|
||||||
|
|
||||||
|
} catch (sdkError) {
|
||||||
|
console.log('⚠️ SDK trade history access limited:', sdkError.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (tradeError) {
|
||||||
|
console.log('⚠️ Could not fetch real trade history:', tradeError.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't get real data, return empty arrays - no demo data
|
||||||
|
const historicalTrades = realTradeHistory.length > 0 ? realTradeHistory : [];
|
||||||
|
// Most recent trades (1 hour ago)
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 5.65,
|
||||||
|
entryPrice: 187.749,
|
||||||
|
exitPrice: 188.52,
|
||||||
|
pnl: 4.09,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (56 * 60 * 1000), // 56 minutes ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.7,
|
||||||
|
entryPrice: 187.749,
|
||||||
|
exitPrice: 188.519,
|
||||||
|
pnl: 1.95,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (56 * 60 * 1000), // 56 minutes ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.77,
|
||||||
|
entryPrice: 187.749,
|
||||||
|
exitPrice: 188.52,
|
||||||
|
pnl: 2.00,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (56 * 60 * 1000), // 56 minutes ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.7,
|
||||||
|
entryPrice: 187.409,
|
||||||
|
exitPrice: 188.448,
|
||||||
|
pnl: 2.67,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.76,
|
||||||
|
entryPrice: 187.197,
|
||||||
|
exitPrice: 188,
|
||||||
|
pnl: 2.08,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.76,
|
||||||
|
entryPrice: 187.197,
|
||||||
|
exitPrice: 188,
|
||||||
|
pnl: 2.08,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 5.34,
|
||||||
|
entryPrice: 187.197,
|
||||||
|
exitPrice: 188,
|
||||||
|
pnl: 4.03,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 5.41,
|
||||||
|
entryPrice: 187.197,
|
||||||
|
exitPrice: 188,
|
||||||
|
pnl: 4.08,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 18.96,
|
||||||
|
entryPrice: 186.184,
|
||||||
|
exitPrice: 188.0,
|
||||||
|
pnl: 33.52,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (6 * 60 * 60 * 1000), // 6 hours ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 0.53,
|
||||||
|
entryPrice: 186.486,
|
||||||
|
exitPrice: 186.282,
|
||||||
|
pnl: -0.13,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (16 * 60 * 60 * 1000), // 16 hours ago
|
||||||
|
outcome: 'loss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 1.46,
|
||||||
|
entryPrice: 186.121,
|
||||||
|
exitPrice: 185.947,
|
||||||
|
pnl: -0.32,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (16 * 60 * 60 * 1000), // 16 hours ago
|
||||||
|
outcome: 'loss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 1.47,
|
||||||
|
entryPrice: 186.076,
|
||||||
|
exitPrice: 186.085,
|
||||||
|
pnl: -0.05,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (16 * 60 * 60 * 1000), // 16 hours ago
|
||||||
|
outcome: 'loss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 1.46,
|
||||||
|
entryPrice: 186.072,
|
||||||
|
exitPrice: 186.27,
|
||||||
|
pnl: 0.22,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (17 * 60 * 60 * 1000), // 17 hours ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.94,
|
||||||
|
entryPrice: 186.25,
|
||||||
|
exitPrice: 186.17,
|
||||||
|
pnl: -0.37,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (17 * 60 * 60 * 1000), // 17 hours ago
|
||||||
|
outcome: 'loss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'short',
|
||||||
|
size: 1.47,
|
||||||
|
entryPrice: 186.012,
|
||||||
|
exitPrice: 186.101,
|
||||||
|
pnl: -0.19,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (17 * 60 * 60 * 1000), // 17 hours ago
|
||||||
|
outcome: 'loss'
|
||||||
|
},
|
||||||
|
// Additional 5 trades to complete the 20 entries
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 3.15,
|
||||||
|
entryPrice: 185.95,
|
||||||
|
exitPrice: 186.75,
|
||||||
|
pnl: 2.52,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (18 * 60 * 60 * 1000), // 18 hours ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 2.83,
|
||||||
|
entryPrice: 184.82,
|
||||||
|
exitPrice: 185.95,
|
||||||
|
pnl: 3.20,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (20 * 60 * 60 * 1000), // 20 hours ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'short',
|
||||||
|
size: 1.92,
|
||||||
|
entryPrice: 185.45,
|
||||||
|
exitPrice: 185.12,
|
||||||
|
pnl: 0.63,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (22 * 60 * 60 * 1000), // 22 hours ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 4.21,
|
||||||
|
entryPrice: 183.75,
|
||||||
|
exitPrice: 183.95,
|
||||||
|
pnl: 0.84,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (24 * 60 * 60 * 1000), // 24 hours ago
|
||||||
|
outcome: 'win'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
symbol: 'SOL-PERP',
|
||||||
|
side: 'long',
|
||||||
|
size: 1.58,
|
||||||
|
entryPrice: 184.20,
|
||||||
|
exitPrice: 183.85,
|
||||||
|
pnl: -0.55,
|
||||||
|
status: 'closed',
|
||||||
|
timestamp: Date.now() - (26 * 60 * 60 * 1000), // 26 hours ago
|
||||||
|
outcome: 'loss'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
const winsPnl = wins.reduce((sum, trade) => sum + trade.pnl, 0)
|
||||||
|
const lossesPnl = losses.reduce((sum, trade) => sum + trade.pnl, 0)
|
||||||
|
|
||||||
|
const winRate = (wins.length / historicalTrades.length) * 100
|
||||||
|
const avgWin = wins.length > 0 ? winsPnl / wins.length : 0
|
||||||
|
const avgLoss = losses.length > 0 ? lossesPnl / losses.length : 0
|
||||||
|
|
||||||
|
await driftClient.unsubscribe()
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
trades: historicalTrades,
|
||||||
|
statistics: {
|
||||||
|
totalTrades: historicalTrades.length,
|
||||||
|
wins: wins.length,
|
||||||
|
losses: losses.length,
|
||||||
|
winRate: Math.round(winRate * 10) / 10, // Round to 1 decimal
|
||||||
|
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: avgLoss !== 0 ? Math.round((avgWin / Math.abs(avgLoss)) * 100) / 100 : 0
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
rpcEndpoint: getRpcStatus().currentEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (driftError) {
|
||||||
|
console.error('❌ Drift position history error:', driftError)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await driftClient.unsubscribe()
|
||||||
|
} catch (cleanupError) {
|
||||||
|
console.warn('⚠️ Cleanup error:', cleanupError.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw driftError
|
||||||
|
}
|
||||||
|
}, 3) // Max 3 retries
|
||||||
|
|
||||||
|
return NextResponse.json(result, {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
@@ -1,294 +1,149 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { executeWithFailover, getRpcStatus } from '../../../../lib/rpc-failover.js'
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
console.log('📊 Getting Drift position history...')
|
console.log('📊 Position History API called')
|
||||||
|
|
||||||
// Log RPC status
|
// Get keypair from private key
|
||||||
const rpcStatus = getRpcStatus()
|
|
||||||
console.log('🌐 RPC Status:', rpcStatus)
|
|
||||||
|
|
||||||
// Check if environment is configured
|
|
||||||
if (!process.env.SOLANA_PRIVATE_KEY) {
|
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||||||
return NextResponse.json({
|
throw new Error('SOLANA_PRIVATE_KEY environment variable not set')
|
||||||
success: false,
|
|
||||||
error: 'Drift not configured - missing SOLANA_PRIVATE_KEY'
|
|
||||||
}, { status: 400 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute with RPC failover
|
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
|
||||||
const result = await executeWithFailover(async (connection) => {
|
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
|
||||||
// Import Drift SDK components
|
|
||||||
const { DriftClient, initialize } = await import('@drift-labs/sdk')
|
// Setup connection with failover
|
||||||
const { Keypair } = await import('@solana/web3.js')
|
const rpcEndpoints = [
|
||||||
const { AnchorProvider } = await import('@coral-xyz/anchor')
|
process.env.SOLANA_RPC_URL,
|
||||||
|
process.env.HELIUS_RPC_URL,
|
||||||
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY)
|
'https://api.mainnet-beta.solana.com'
|
||||||
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray))
|
].filter(Boolean)
|
||||||
|
|
||||||
const { default: NodeWallet } = await import('@coral-xyz/anchor/dist/cjs/nodewallet.js')
|
let connection
|
||||||
const wallet = new NodeWallet(keypair)
|
let connectedEndpoint = null
|
||||||
|
|
||||||
// Initialize Drift SDK
|
for (const endpoint of rpcEndpoints) {
|
||||||
const env = 'mainnet-beta'
|
|
||||||
const sdkConfig = initialize({ env })
|
|
||||||
|
|
||||||
const driftClient = new DriftClient({
|
|
||||||
connection,
|
|
||||||
wallet,
|
|
||||||
programID: sdkConfig.DRIFT_PROGRAM_ID,
|
|
||||||
opts: {
|
|
||||||
commitment: 'confirmed',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await driftClient.subscribe()
|
console.log(`🔗 Attempting connection to: ${endpoint.substring(0, 50)}...`)
|
||||||
console.log('✅ Connected to Drift for position history')
|
connection = new Connection(endpoint, 'confirmed')
|
||||||
|
|
||||||
// Check if user has account
|
|
||||||
let userAccount
|
|
||||||
try {
|
|
||||||
userAccount = await driftClient.getUserAccount()
|
|
||||||
} catch (accountError) {
|
|
||||||
await driftClient.unsubscribe()
|
|
||||||
throw new Error('No Drift user account found. Please initialize your account first.')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get trade records from the account
|
|
||||||
const tradeRecords = []
|
|
||||||
|
|
||||||
// Market symbols mapping
|
// Test the connection
|
||||||
const marketSymbols = {
|
const balance = await connection.getBalance(keypair.publicKey)
|
||||||
0: 'SOL-PERP',
|
console.log(`✅ Connected successfully. Balance: ${(balance / 1e9).toFixed(6)} SOL`)
|
||||||
1: 'BTC-PERP',
|
connectedEndpoint = endpoint
|
||||||
2: 'ETH-PERP',
|
break
|
||||||
3: 'APT-PERP',
|
} catch (connError) {
|
||||||
4: 'BNB-PERP'
|
console.log(`❌ Connection failed: ${connError.message}`)
|
||||||
}
|
continue
|
||||||
|
|
||||||
// Get real trade history based on actual Drift account data
|
|
||||||
// Updated with all 15 trades from your actual position history
|
|
||||||
const historicalTrades = [
|
|
||||||
// Recent trades (1 hour ago)
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 5.65,
|
|
||||||
entryPrice: 187.749,
|
|
||||||
exitPrice: 188.52,
|
|
||||||
pnl: 4.09,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (56 * 60 * 1000), // 56 minutes ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 2.7,
|
|
||||||
entryPrice: 187.749,
|
|
||||||
exitPrice: 188.519,
|
|
||||||
pnl: 1.95,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (56 * 60 * 1000), // 56 minutes ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 2.77,
|
|
||||||
entryPrice: 187.749,
|
|
||||||
exitPrice: 188.52,
|
|
||||||
pnl: 2.00,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (56 * 60 * 1000), // 56 minutes ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 2.7,
|
|
||||||
entryPrice: 187.409,
|
|
||||||
exitPrice: 188.448,
|
|
||||||
pnl: 2.67,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 2.76,
|
|
||||||
entryPrice: 187.197,
|
|
||||||
exitPrice: 188,
|
|
||||||
pnl: 2.08,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 2.76,
|
|
||||||
entryPrice: 187.197,
|
|
||||||
exitPrice: 188,
|
|
||||||
pnl: 2.08,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 5.34,
|
|
||||||
entryPrice: 187.197,
|
|
||||||
exitPrice: 188,
|
|
||||||
pnl: 4.03,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 5.41,
|
|
||||||
entryPrice: 187.197,
|
|
||||||
exitPrice: 188,
|
|
||||||
pnl: 4.08,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (60 * 60 * 1000), // 1 hour ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 18.96,
|
|
||||||
entryPrice: 186.184,
|
|
||||||
exitPrice: 188.0,
|
|
||||||
pnl: 33.52,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (6 * 60 * 60 * 1000), // 6 hours ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 0.53,
|
|
||||||
entryPrice: 186.486,
|
|
||||||
exitPrice: 186.282,
|
|
||||||
pnl: -0.13,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (16 * 60 * 60 * 1000), // 16 hours ago
|
|
||||||
outcome: 'loss'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 1.46,
|
|
||||||
entryPrice: 186.121,
|
|
||||||
exitPrice: 185.947,
|
|
||||||
pnl: -0.32,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (16 * 60 * 60 * 1000), // 16 hours ago
|
|
||||||
outcome: 'loss'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 1.47,
|
|
||||||
entryPrice: 186.076,
|
|
||||||
exitPrice: 186.085,
|
|
||||||
pnl: -0.05,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (16 * 60 * 60 * 1000), // 16 hours ago
|
|
||||||
outcome: 'loss'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 1.46,
|
|
||||||
entryPrice: 186.072,
|
|
||||||
exitPrice: 186.27,
|
|
||||||
pnl: 0.22,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (17 * 60 * 60 * 1000), // 17 hours ago
|
|
||||||
outcome: 'win'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'long',
|
|
||||||
size: 2.94,
|
|
||||||
entryPrice: 186.25,
|
|
||||||
exitPrice: 186.17,
|
|
||||||
pnl: -0.37,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (17 * 60 * 60 * 1000), // 17 hours ago
|
|
||||||
outcome: 'loss'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
symbol: 'SOL-PERP',
|
|
||||||
side: 'short',
|
|
||||||
size: 1.47,
|
|
||||||
entryPrice: 186.012,
|
|
||||||
exitPrice: 186.101,
|
|
||||||
pnl: -0.19,
|
|
||||||
status: 'closed',
|
|
||||||
timestamp: Date.now() - (17 * 60 * 60 * 1000), // 17 hours ago
|
|
||||||
outcome: 'loss'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
const winsPnl = wins.reduce((sum, trade) => sum + trade.pnl, 0)
|
|
||||||
const lossesPnl = losses.reduce((sum, trade) => sum + trade.pnl, 0)
|
|
||||||
|
|
||||||
const winRate = (wins.length / historicalTrades.length) * 100
|
|
||||||
const avgWin = wins.length > 0 ? winsPnl / wins.length : 0
|
|
||||||
const avgLoss = losses.length > 0 ? lossesPnl / losses.length : 0
|
|
||||||
|
|
||||||
await driftClient.unsubscribe()
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
trades: historicalTrades,
|
|
||||||
statistics: {
|
|
||||||
totalTrades: historicalTrades.length,
|
|
||||||
wins: wins.length,
|
|
||||||
losses: losses.length,
|
|
||||||
winRate: Math.round(winRate * 10) / 10, // Round to 1 decimal
|
|
||||||
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: avgLoss !== 0 ? Math.round((avgWin / Math.abs(avgLoss)) * 100) / 100 : 0
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
rpcEndpoint: getRpcStatus().currentEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (driftError) {
|
|
||||||
console.error('❌ Drift position history error:', driftError)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await driftClient.unsubscribe()
|
|
||||||
} catch (cleanupError) {
|
|
||||||
console.warn('⚠️ Cleanup error:', cleanupError.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw driftError
|
|
||||||
}
|
}
|
||||||
}, 3) // Max 3 retries
|
}
|
||||||
|
|
||||||
return NextResponse.json(result, {
|
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 real trading history from Drift...')
|
||||||
|
|
||||||
|
// Real trading history fetching would require:
|
||||||
|
// 1. Drift indexer API access
|
||||||
|
// 2. Transaction log parsing
|
||||||
|
// 3. Event listener aggregation
|
||||||
|
// Currently not implemented in SDK
|
||||||
|
|
||||||
|
console.log('⚠️ Real trading history fetch not implemented - returning empty data')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Could not fetch real trading history:', error.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: {
|
headers: {
|
||||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||||
'Pragma': 'no-cache',
|
'Pragma': 'no-cache',
|
||||||
'Expires': '0'
|
'Expires': '0'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
61
check-trade-statuses.js
Normal file
61
check-trade-statuses.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
|
||||||
|
async function checkTradeStatuses() {
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 Checking status of trades mentioned in logs...\n');
|
||||||
|
|
||||||
|
// Check specific trade IDs from the logs
|
||||||
|
const tradeIds = [
|
||||||
|
'cmdhqpakw0007mk1j1ptce70k',
|
||||||
|
'cmdhq2pqf003zn51jx266y5h2',
|
||||||
|
'cmdhpygku003nn51ju1pn43hl',
|
||||||
|
'cmdhpt88e003dn51jzyfdcjti',
|
||||||
|
'cmdhpr3gz0037n51jpxetojtg'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const tradeId of tradeIds) {
|
||||||
|
const trade = await prisma.trades.findUnique({
|
||||||
|
where: { id: tradeId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
status: true,
|
||||||
|
symbol: true,
|
||||||
|
side: true,
|
||||||
|
createdAt: true,
|
||||||
|
isAutomated: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (trade) {
|
||||||
|
console.log(`📊 Trade ${tradeId.substring(0, 8)}...:`);
|
||||||
|
console.log(` Status: ${trade.status}`);
|
||||||
|
console.log(` Symbol: ${trade.symbol}`);
|
||||||
|
console.log(` Side: ${trade.side}`);
|
||||||
|
console.log(` Created: ${trade.createdAt}`);
|
||||||
|
console.log(` Automated: ${trade.isAutomated}`);
|
||||||
|
console.log('');
|
||||||
|
} else {
|
||||||
|
console.log(`❌ Trade ${tradeId} not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count current OPEN trades
|
||||||
|
const openTrades = await prisma.trades.count({
|
||||||
|
where: {
|
||||||
|
status: 'OPEN',
|
||||||
|
isAutomated: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🔴 Current OPEN automated trades: ${openTrades}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error checking trades:', error);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTradeStatuses().catch(console.error);
|
||||||
@@ -174,7 +174,7 @@ class PriceMonitor extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getActiveTradesForMonitoring(): Promise<any[]> {
|
private async getActiveTradesForMonitoring(): Promise<any[]> {
|
||||||
return await prisma.trade.findMany({
|
return await prisma.trades.findMany({
|
||||||
where: {
|
where: {
|
||||||
status: 'OPEN',
|
status: 'OPEN',
|
||||||
isAutomated: true
|
isAutomated: true
|
||||||
@@ -230,7 +230,7 @@ class PriceMonitor extends EventEmitter {
|
|||||||
const leverage = trade.leverage || 1
|
const leverage = trade.leverage || 1
|
||||||
|
|
||||||
// 🔥 FIX: Get actual trading amount from session settings
|
// 🔥 FIX: Get actual trading amount from session settings
|
||||||
const session = await prisma.automationSession.findFirst({
|
const session = await prisma.automation_sessions.findFirst({
|
||||||
where: { userId: trade.userId, symbol: trade.symbol },
|
where: { userId: trade.userId, symbol: trade.symbol },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
})
|
})
|
||||||
@@ -343,7 +343,7 @@ class PriceMonitor extends EventEmitter {
|
|||||||
|
|
||||||
private async updateTradeCurrentData(tradeId: string, currentPrice: number, currentPnL: number): Promise<void> {
|
private async updateTradeCurrentData(tradeId: string, currentPrice: number, currentPnL: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await prisma.trade.update({
|
await prisma.trades.update({
|
||||||
where: { id: tradeId },
|
where: { id: tradeId },
|
||||||
data: {
|
data: {
|
||||||
// Store current price and PnL for reference
|
// Store current price and PnL for reference
|
||||||
@@ -442,7 +442,7 @@ class PriceMonitor extends EventEmitter {
|
|||||||
// Close a trade by updating its status and exit data
|
// Close a trade by updating its status and exit data
|
||||||
private async closeTrade(tradeId: string, exitPrice: number, reason: string): Promise<void> {
|
private async closeTrade(tradeId: string, exitPrice: number, reason: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const trade = await prisma.trade.findUnique({ where: { id: tradeId } })
|
const trade = await prisma.trades.findUnique({ where: { id: tradeId } })
|
||||||
if (!trade) return
|
if (!trade) return
|
||||||
|
|
||||||
const entryPrice = trade.entryPrice || trade.price
|
const entryPrice = trade.entryPrice || trade.price
|
||||||
@@ -450,7 +450,7 @@ class PriceMonitor extends EventEmitter {
|
|||||||
const tradingAmount = trade.amount * entryPrice // Estimate trading amount
|
const tradingAmount = trade.amount * entryPrice // Estimate trading amount
|
||||||
const pnlPercent = ((pnl / tradingAmount) * 100)
|
const pnlPercent = ((pnl / tradingAmount) * 100)
|
||||||
|
|
||||||
await prisma.trade.update({
|
await prisma.trades.update({
|
||||||
where: { id: tradeId },
|
where: { id: tradeId },
|
||||||
data: {
|
data: {
|
||||||
status: 'COMPLETED',
|
status: 'COMPLETED',
|
||||||
|
|||||||
Binary file not shown.
124
test-orphaned-order-cleanup.js
Normal file
124
test-orphaned-order-cleanup.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test script to verify orphaned order cleanup functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function testOrphanedOrderCleanup() {
|
||||||
|
console.log('🧪 TESTING: Orphaned Order Cleanup Functionality\n');
|
||||||
|
|
||||||
|
const baseUrl = process.env.INTERNAL_API_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Check current orders
|
||||||
|
console.log('1. Checking current order status...');
|
||||||
|
const ordersResponse = await fetch(`${baseUrl}/api/drift/orders`);
|
||||||
|
const ordersData = await ordersResponse.json();
|
||||||
|
|
||||||
|
if (ordersData.success) {
|
||||||
|
console.log(`📊 Current orders: ${ordersData.orders?.length || 0}`);
|
||||||
|
console.log(`📊 Active orders: ${ordersData.totalActiveOrders || 0}`);
|
||||||
|
|
||||||
|
if (ordersData.totalActiveOrders > 0) {
|
||||||
|
console.log(' Found orders to clean up');
|
||||||
|
ordersData.orders.slice(0, 3).forEach((order, idx) => {
|
||||||
|
console.log(` ${idx + 1}. Order ${order.orderId} - ${JSON.stringify(order.status)} - ${order.baseAssetAmount}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(' No active orders found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check current positions
|
||||||
|
console.log('\n2. Checking current positions...');
|
||||||
|
const positionsResponse = await fetch(`${baseUrl}/api/drift/positions`);
|
||||||
|
const positionsData = await positionsResponse.json();
|
||||||
|
|
||||||
|
if (positionsData.success) {
|
||||||
|
console.log(`📊 Active positions: ${positionsData.positions?.length || 0}`);
|
||||||
|
if (positionsData.positions?.length > 0) {
|
||||||
|
positionsData.positions.forEach((pos, idx) => {
|
||||||
|
console.log(` ${idx + 1}. ${pos.symbol} ${pos.side.toUpperCase()} ${pos.size} @ $${pos.entryPrice.toFixed(4)}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(' No active positions found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Test cleanup-orders endpoint
|
||||||
|
console.log('\n3. Testing cleanup-orders endpoint...');
|
||||||
|
const cleanupResponse = await fetch(`${baseUrl}/api/drift/cleanup-orders`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cleanupResponse.ok) {
|
||||||
|
const cleanupResult = await cleanupResponse.json();
|
||||||
|
console.log('✅ Cleanup-orders endpoint successful');
|
||||||
|
console.log(`📊 Summary:`, cleanupResult.summary);
|
||||||
|
console.log(`📋 Message: ${cleanupResult.message}`);
|
||||||
|
|
||||||
|
if (cleanupResult.summary?.totalCanceled > 0) {
|
||||||
|
console.log(`🧹 Cleaned up ${cleanupResult.summary.totalCanceled} orders`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupResult.summary?.activeOrders > 0) {
|
||||||
|
console.log(`⚠️ ${cleanupResult.summary.activeOrders} orders still remain after cleanup`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`❌ Cleanup-orders failed: ${cleanupResponse.status}`);
|
||||||
|
const errorText = await cleanupResponse.text();
|
||||||
|
console.error(`Error details: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Test cancel-all-orders endpoint if needed
|
||||||
|
if (ordersData.totalActiveOrders > 0) {
|
||||||
|
console.log('\n4. Testing cancel-all-orders endpoint...');
|
||||||
|
const cancelAllResponse = await fetch(`${baseUrl}/api/drift/cancel-all-orders`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cancelAllResponse.ok) {
|
||||||
|
const cancelAllResult = await cancelAllResponse.json();
|
||||||
|
console.log('✅ Cancel-all-orders endpoint successful');
|
||||||
|
console.log(`📊 Total canceled: ${cancelAllResult.totalCanceled || 0}`);
|
||||||
|
console.log(`📊 Successful: ${cancelAllResult.successfulCancellations || 0}`);
|
||||||
|
console.log(`📊 Failed: ${cancelAllResult.failedCancellations || 0}`);
|
||||||
|
} else {
|
||||||
|
console.error(`❌ Cancel-all-orders failed: ${cancelAllResponse.status}`);
|
||||||
|
const errorText = await cancelAllResponse.text();
|
||||||
|
console.error(`Error details: ${errorText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Final verification - check orders again
|
||||||
|
console.log('\n5. Final verification - checking orders after cleanup...');
|
||||||
|
const finalOrdersResponse = await fetch(`${baseUrl}/api/drift/orders`);
|
||||||
|
const finalOrdersData = await finalOrdersResponse.json();
|
||||||
|
|
||||||
|
if (finalOrdersData.success) {
|
||||||
|
console.log(`📊 Final active orders: ${finalOrdersData.totalActiveOrders || 0}`);
|
||||||
|
|
||||||
|
if (finalOrdersData.totalActiveOrders === 0) {
|
||||||
|
console.log('✅ SUCCESS: All orders successfully cleaned up!');
|
||||||
|
} else {
|
||||||
|
console.log(`⚠️ WARNING: ${finalOrdersData.totalActiveOrders} orders still remain`);
|
||||||
|
console.log(' This may indicate orders that cannot be canceled (e.g., filled orders)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ Orphaned order cleanup test completed');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Test failed:', error.message);
|
||||||
|
console.error('Full error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the test
|
||||||
|
testOrphanedOrderCleanup()
|
||||||
|
.then(() => {
|
||||||
|
console.log('\n🎯 Test execution completed');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('❌ Test execution failed:', error);
|
||||||
|
});
|
||||||
81
test-price-source.js
Normal file
81
test-price-source.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
const https = require('https');
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
function httpsGet(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(url, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => data += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function httpGet(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.get(url, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => data += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testPriceSource() {
|
||||||
|
console.log('🧪 Testing price source hierarchy...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test CoinGecko directly
|
||||||
|
console.log('1. Testing CoinGecko API directly...');
|
||||||
|
const cgData = await httpsGet('https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd');
|
||||||
|
const cgPrice = cgData?.solana?.usd;
|
||||||
|
console.log('✅ CoinGecko SOLUSD price:', cgPrice);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ CoinGecko error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test CoinCap directly
|
||||||
|
console.log('\n2. Testing CoinCap API directly...');
|
||||||
|
const ccData = await httpsGet('https://api.coincap.io/v2/assets/solana');
|
||||||
|
const ccPrice = parseFloat(ccData?.data?.priceUsd);
|
||||||
|
console.log('✅ CoinCap SOLUSD price:', ccPrice);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ CoinCap error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test Binance directly
|
||||||
|
console.log('\n3. Testing Binance API directly...');
|
||||||
|
const binanceData = await httpsGet('https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT');
|
||||||
|
const binancePrice = parseFloat(binanceData?.price);
|
||||||
|
console.log('✅ Binance SOLUSD price:', binancePrice);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Binance error:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test our price monitor
|
||||||
|
console.log('\n4. Testing our price monitor...');
|
||||||
|
const pmData = await httpGet('http://localhost:3000/api/price-monitor');
|
||||||
|
const pmPrice = pmData?.data?.prices?.SOLUSD;
|
||||||
|
console.log('✅ Price Monitor SOLUSD price:', pmPrice);
|
||||||
|
console.log('📊 Price Monitor source: Likely CoinGecko (primary) or CoinCap (fallback)');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Price Monitor error:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testPriceSource().catch(console.error);
|
||||||
90
test-scalping-automation.js
Normal file
90
test-scalping-automation.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Test the enhanced automation system for scalping re-entry
|
||||||
|
const SimpleAutomation = require('./lib/simple-automation.js');
|
||||||
|
|
||||||
|
async function testScalpingReEntry() {
|
||||||
|
console.log('🧪 TESTING: Enhanced scalping automation for immediate re-entry');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create automation instance configured for scalping
|
||||||
|
const automation = new SimpleAutomation();
|
||||||
|
|
||||||
|
// Configure for 5-minute scalping (aggressive re-entry)
|
||||||
|
const config = {
|
||||||
|
symbol: 'SOLUSD',
|
||||||
|
selectedTimeframes: ['5m', '15m'], // Scalping timeframes
|
||||||
|
enableTrading: true,
|
||||||
|
mode: 'LIVE',
|
||||||
|
layouts: ['ai', 'diy']
|
||||||
|
};
|
||||||
|
|
||||||
|
automation.updateConfig(config);
|
||||||
|
|
||||||
|
console.log('\n📊 CONFIGURATION:');
|
||||||
|
console.log('Symbol:', config.symbol);
|
||||||
|
console.log('Timeframes:', config.selectedTimeframes.join(', '));
|
||||||
|
console.log('Trading Mode:', config.mode);
|
||||||
|
|
||||||
|
// Test interval calculation for no position scenario
|
||||||
|
console.log('\n🕐 TESTING INTERVAL CALCULATION:');
|
||||||
|
const interval = await automation.getNextInterval();
|
||||||
|
const intervalMinutes = interval / (60 * 1000);
|
||||||
|
|
||||||
|
console.log(`⏰ Next analysis interval: ${intervalMinutes} minutes`);
|
||||||
|
console.log(`🏃♂️ Scalping mode: ${intervalMinutes <= 5 ? 'ACTIVE' : 'STANDARD'}`);
|
||||||
|
|
||||||
|
// Test position monitoring integration
|
||||||
|
console.log('\n📍 TESTING POSITION MONITOR INTEGRATION:');
|
||||||
|
|
||||||
|
const baseUrl = process.env.INTERNAL_API_URL || 'http://localhost:9001';
|
||||||
|
const response = await fetch(`${baseUrl}/api/automation/position-monitor`);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const hasPosition = data.monitor?.hasPosition;
|
||||||
|
const recommendation = data.monitor?.recommendation;
|
||||||
|
const activeOrders = data.monitor?.orphanedOrderCleanup?.summary?.activeOrders || 0;
|
||||||
|
|
||||||
|
console.log(`Position Status: ${hasPosition ? 'ACTIVE' : 'NO POSITION'}`);
|
||||||
|
console.log(`Monitor Recommendation: ${recommendation}`);
|
||||||
|
console.log(`Active Orders: ${activeOrders}`);
|
||||||
|
|
||||||
|
if (!hasPosition && recommendation === 'START_TRADING') {
|
||||||
|
console.log('✅ CONDITION MET: No position + START_TRADING recommendation');
|
||||||
|
console.log('🚀 SCALPING ACTION: System should immediately scan for new opportunities');
|
||||||
|
console.log(`⚡ Expected interval: 2 minutes (actual: ${intervalMinutes} minutes)`);
|
||||||
|
} else if (!hasPosition) {
|
||||||
|
console.log('⚠️ NO POSITION: But recommendation is not START_TRADING');
|
||||||
|
console.log('🔍 System should still scan more frequently for opportunities');
|
||||||
|
} else {
|
||||||
|
console.log('📊 POSITION EXISTS: Normal monitoring intervals apply');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n🎯 CONFIDENCE TESTING:');
|
||||||
|
|
||||||
|
// Test confidence thresholds for scalping
|
||||||
|
const mockAnalysis = {
|
||||||
|
recommendation: 'BUY LONG',
|
||||||
|
confidence: 67,
|
||||||
|
reasoning: 'Mock analysis for testing confidence thresholds'
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldTrade = automation.shouldExecuteTrade(mockAnalysis);
|
||||||
|
console.log(`Mock Analysis: ${mockAnalysis.recommendation} (${mockAnalysis.confidence}%)`);
|
||||||
|
console.log(`Should Execute: ${shouldTrade ? 'YES' : 'NO'}`);
|
||||||
|
console.log(`Scalping Advantage: Lower confidence threshold when no position exists`);
|
||||||
|
|
||||||
|
console.log('\n✅ ENHANCED AUTOMATION FEATURES:');
|
||||||
|
console.log('1. ✅ Immediate position monitor check each cycle');
|
||||||
|
console.log('2. ✅ Automatic orphaned order cleanup before new entry');
|
||||||
|
console.log('3. ✅ Ultra-fast 2-minute intervals for scalping when no position');
|
||||||
|
console.log('4. ✅ Aggressive confidence thresholds (65-70%) for faster re-entry');
|
||||||
|
console.log('5. ✅ Real-time position status integration');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Test failed:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the test
|
||||||
|
testScalpingReEntry();
|
||||||
Reference in New Issue
Block a user