Files
trading_bot_v3/app/api/drift/cleanup-orders/route.js
mindesbunister 167d7ff5bc feat: implement comprehensive AI decision display and reasoning panel
Major Features Added:
- Complete AI decision tracking system with detailed reasoning display
- Prominent gradient-styled AI reasoning panel on automation-v2 page
- Test AI decision generator with realistic trading scenarios
- Enhanced decision transparency showing entry/exit logic and leverage calculations

- Fixed orphaned order cleanup to preserve reduce-only SL/TP orders
- Integrated AI leverage calculator with 100x capability (up from 10x limit)
- Added lastDecision property to automation status for UI display
- Enhanced position monitoring with better cleanup triggers

- Beautiful gradient-styled AI Trading Analysis panel
- Color-coded confidence levels and recommendation displays
- Detailed breakdown of entry strategy, stop loss logic, and take profit targets
- Real-time display of AI leverage reasoning with safety buffer explanations
- Test AI button for demonstration of decision-making process

- SL/TP orders now execute properly (fixed cleanup interference)
- AI calculates sophisticated leverage (8.8x-42.2x vs previous 1x hardcoded)
- Complete decision audit trail with execution details
- Risk management transparency with liquidation safety calculations

- Why This Decision? - Prominent reasoning section
- Entry & Exit Strategy - Price levels with color coding
- AI Leverage Decision - Detailed calculation explanations
- Execution status with success/failure indicators
- Transaction IDs and comprehensive trade details

All systems now provide full transparency of AI decision-making process.
2025-07-26 22:41:55 +02:00

254 lines
9.2 KiB
JavaScript

import { NextResponse } from 'next/server'
import { executeWithFailover, getRpcStatus } from '../../../../lib/rpc-failover.js'
export async function POST() {
try {
console.log('🧹 Starting orphaned order cleanup...')
// 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 cleanup 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))
// Use the correct Wallet class
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 cleanup')
// Get user 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 current positions
const perpPositions = userAccount.perpPositions || []
const activePositions = perpPositions.filter(pos =>
pos.baseAssetAmount && !pos.baseAssetAmount.isZero()
)
// Get current orders
const orders = userAccount.orders || []
// Filter for active orders - handle both numeric and object status formats
const activeOrders = orders.filter(order => {
if (order.baseAssetAmount.isZero()) return false
// Handle object-based status (new format)
if (typeof order.status === 'object') {
return order.status.hasOwnProperty('open')
}
// Handle numeric status (old format)
return order.status === 0
})
console.log(`📋 Raw orders in cleanup: ${orders.length}`);
orders.forEach((order, index) => {
if (!order.baseAssetAmount.isZero()) {
console.log(`📋 Cleanup Order ${index}:`, {
orderId: order.orderId,
status: order.status,
baseAssetAmount: order.baseAssetAmount.toString()
});
}
});
console.log(`📊 Analysis: ${activePositions.length} active positions, ${activeOrders.length} active orders`)
// Map positions by market index
const positionMarkets = new Set(activePositions.map(pos => pos.marketIndex))
// Find orphaned orders (orders for markets where we have no position)
const orphanedOrders = activeOrders.filter(order => {
// Check if this order is for a market where we have no position
const hasPosition = positionMarkets.has(order.marketIndex)
// CRITICAL FIX: Only cancel reduce-only orders if there's NO position
// Stop Loss and Take Profit orders are reduce-only but should EXIST when we have a position
const isReduceOnly = order.reduceOnly
// Only cancel orders that are truly orphaned (no position for that market)
// Do NOT cancel reduce-only orders when we have a position (these are SL/TP!)
return !hasPosition && !isReduceOnly
})
// Additionally, find lingering SL/TP orders when position has changed significantly
const conflictingOrders = []
for (const order of activeOrders) {
// Find corresponding position
const position = activePositions.find(pos => pos.marketIndex === order.marketIndex)
if (position) {
const positionSide = Number(position.baseAssetAmount) > 0 ? 'long' : 'short'
const orderDirection = order.direction === 0 ? 'long' : 'short'
// Check for conflicting reduce-only orders
if (order.reduceOnly) {
// Reduce-only order should be opposite direction to position
const correctDirection = positionSide === 'long' ? 'short' : 'long'
if (orderDirection !== correctDirection) {
console.log(`⚠️ Found conflicting reduce-only order: ${orderDirection} order for ${positionSide} position`)
conflictingOrders.push(order)
}
}
}
}
const ordersToCancel = [...orphanedOrders, ...conflictingOrders]
console.log(`🎯 Found ${orphanedOrders.length} orphaned orders and ${conflictingOrders.length} conflicting orders`)
const cancelResults = []
if (ordersToCancel.length > 0) {
console.log('🧹 Canceling orphaned/conflicting orders...')
for (const order of ordersToCancel) {
try {
const marketIndex = order.marketIndex
const orderId = order.orderId
// Get market symbol for logging
const marketSymbols = {
0: 'SOL-PERP',
1: 'BTC-PERP',
2: 'ETH-PERP',
3: 'APT-PERP',
4: 'BNB-PERP'
}
const symbol = marketSymbols[marketIndex] || `MARKET-${marketIndex}`
console.log(`❌ Canceling order: ${symbol} Order ID ${orderId}`)
// Cancel the order
const txSig = await driftClient.cancelOrder(orderId)
console.log(`✅ Canceled order ${orderId} for ${symbol}, tx: ${txSig}`)
cancelResults.push({
orderId: orderId,
marketIndex: marketIndex,
symbol: symbol,
txSignature: txSig,
success: true,
reason: orphanedOrders.includes(order) ? 'orphaned' : 'conflicting'
})
// Small delay between cancellations to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 100))
} catch (cancelError) {
console.error(`❌ Failed to cancel order ${order.orderId}:`, cancelError)
cancelResults.push({
orderId: order.orderId,
marketIndex: order.marketIndex,
success: false,
error: cancelError.message,
reason: orphanedOrders.includes(order) ? 'orphaned' : 'conflicting'
})
}
}
} else {
console.log('✅ No orphaned or conflicting orders found')
}
await driftClient.unsubscribe()
const cleanupResult = {
success: true,
summary: {
activePositions: activePositions.length,
activeOrders: activeOrders.length,
orphanedOrders: orphanedOrders.length,
conflictingOrders: conflictingOrders.length,
totalCanceled: cancelResults.filter(r => r.success).length,
totalFailed: cancelResults.filter(r => !r.success).length
},
canceledOrders: cancelResults,
timestamp: Date.now(),
rpcEndpoint: getRpcStatus().currentEndpoint
}
console.log('🧹 Cleanup completed:', cleanupResult.summary)
return cleanupResult
} catch (driftError) {
console.error('❌ Drift cleanup error:', driftError)
try {
await driftClient.unsubscribe()
} catch (cleanupError) {
console.warn('⚠️ Cleanup error:', cleanupError.message)
}
throw driftError
}
}, 3) // Max 3 retries across different RPCs
return NextResponse.json(result)
} catch (error) {
console.error('❌ Orphaned order cleanup API error:', error)
return NextResponse.json({
success: false,
error: 'Failed to cleanup orphaned orders',
details: error.message,
rpcStatus: getRpcStatus()
}, { status: 500 })
}
}
export async function GET() {
return NextResponse.json({
message: 'Drift Orphaned Order Cleanup API',
description: 'Automatically cancels orphaned orders when SL/TP hits but leaves opposite orders open',
usage: 'POST /api/drift/cleanup-orders',
features: [
'Detects orphaned orders (orders for markets with no position)',
'Finds conflicting reduce-only orders',
'Automatically cancels problematic orders',
'Prevents manual cleanup requirement'
]
})
}