Fix position sizing and stop loss placement issues
- Fixed insufficient collateral error by implementing proper position sizing - Added balance fetching before trade decisions for accurate calculations - Implemented minimum order size enforcement (0.01 SOL for Drift) - Improved stop loss placement with 2% minimum risk and better error logging - Enhanced risk management with conservative slippage buffers - Fixed 'Order Amount Too Small' errors by ensuring minimum order requirements Position sizing now: - Uses actual Drift account balance (5.69) - Calculates appropriate position size based on risk percentage - Ensures minimum 0.01 SOL order size for Drift compatibility - Provides detailed calculation logging for debugging
This commit is contained in:
163
app/api/drift/orders/route.js
Normal file
163
app/api/drift/orders/route.js
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import { executeWithFailover, getRpcStatus } from '../../../../lib/rpc-failover.js'
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
console.log('📋 Getting Drift open orders...')
|
||||||
|
|
||||||
|
// 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 orders check 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, BN } = 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 from @coral-xyz/anchor/dist/cjs/nodewallet
|
||||||
|
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 orders check')
|
||||||
|
|
||||||
|
// 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 open orders
|
||||||
|
const orders = userAccount.orders || []
|
||||||
|
|
||||||
|
// Show ALL orders for debugging - not just status 0
|
||||||
|
const allOrders = orders.filter(order =>
|
||||||
|
!order.baseAssetAmount.isZero() // Only filter out empty orders
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(`📋 Found ${allOrders.length} total orders (${orders.length} order slots)`)
|
||||||
|
|
||||||
|
// Debug: log all order statuses
|
||||||
|
const statusCounts = orders.reduce((acc, order) => {
|
||||||
|
acc[order.status] = (acc[order.status] || 0) + 1
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
console.log('📊 Order status breakdown:', statusCounts)
|
||||||
|
|
||||||
|
const formattedOrders = allOrders.map(order => {
|
||||||
|
const marketIndex = order.marketIndex
|
||||||
|
const symbol = ['SOL', 'BTC', 'ETH', 'APT', 'AVAX', 'BNB', 'MATIC', 'ARB', 'DOGE', 'OP'][marketIndex] || `UNKNOWN_${marketIndex}`
|
||||||
|
|
||||||
|
let orderType = 'UNKNOWN'
|
||||||
|
switch (order.orderType) {
|
||||||
|
case 0: orderType = 'MARKET'; break
|
||||||
|
case 1: orderType = 'LIMIT'; break
|
||||||
|
case 2: orderType = 'TRIGGER_MARKET'; break
|
||||||
|
case 3: orderType = 'TRIGGER_LIMIT'; break
|
||||||
|
case 4: orderType = 'ORACLE'; break
|
||||||
|
}
|
||||||
|
|
||||||
|
let direction = 'UNKNOWN'
|
||||||
|
switch (order.direction) {
|
||||||
|
case 0: direction = 'LONG'; break
|
||||||
|
case 1: direction = 'SHORT'; break
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
orderId: order.orderId,
|
||||||
|
symbol: `${symbol}-PERP`,
|
||||||
|
orderType,
|
||||||
|
direction,
|
||||||
|
size: (Number(order.baseAssetAmount) / 1e9).toFixed(6),
|
||||||
|
price: order.price ? (Number(order.price) / 1e6).toFixed(4) : null,
|
||||||
|
triggerPrice: order.triggerPrice ? (Number(order.triggerPrice) / 1e6).toFixed(4) : null,
|
||||||
|
reduceOnly: order.reduceOnly,
|
||||||
|
status: order.status,
|
||||||
|
marketIndex
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ordersResult = {
|
||||||
|
success: true,
|
||||||
|
orders: formattedOrders,
|
||||||
|
totalOrders: activeOrders.length,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
rpcEndpoint: getRpcStatus().currentEndpoint,
|
||||||
|
details: {
|
||||||
|
wallet: keypair.publicKey.toString(),
|
||||||
|
totalOrderSlots: orders.length,
|
||||||
|
activeOrderSlots: activeOrders.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await driftClient.unsubscribe()
|
||||||
|
|
||||||
|
console.log('📋 Orders retrieved:', {
|
||||||
|
totalActiveOrders: activeOrders.length,
|
||||||
|
rpcEndpoint: getRpcStatus().currentEndpoint
|
||||||
|
})
|
||||||
|
|
||||||
|
return ordersResult
|
||||||
|
|
||||||
|
} catch (driftError) {
|
||||||
|
console.error('❌ Drift orders 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('❌ Orders API error:', error)
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to get Drift orders',
|
||||||
|
details: error.message,
|
||||||
|
rpcStatus: getRpcStatus()
|
||||||
|
}, { status: 500 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Use GET method to retrieve Drift orders'
|
||||||
|
}, { status: 405 })
|
||||||
|
}
|
||||||
@@ -258,7 +258,7 @@ export async function POST(request) {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||||
|
|
||||||
// 2. Calculate stop loss and take profit prices
|
// 2. Calculate stop loss and take profit prices
|
||||||
const stopLossPercent = riskPercent / 100 // Convert 2% to 0.02
|
const stopLossPercent = Math.max(riskPercent / 100, 0.02) // Minimum 2% to avoid "too close" issues
|
||||||
const takeProfitPercent = stopLossPercent * 2 // 2:1 risk reward ratio
|
const takeProfitPercent = stopLossPercent * 2 // 2:1 risk reward ratio
|
||||||
|
|
||||||
let stopLossPrice, takeProfitPrice
|
let stopLossPrice, takeProfitPrice
|
||||||
@@ -274,8 +274,9 @@ export async function POST(request) {
|
|||||||
console.log(`🎯 Risk management:`, {
|
console.log(`🎯 Risk management:`, {
|
||||||
stopLossPrice: stopLossPrice.toFixed(4),
|
stopLossPrice: stopLossPrice.toFixed(4),
|
||||||
takeProfitPrice: takeProfitPrice.toFixed(4),
|
takeProfitPrice: takeProfitPrice.toFixed(4),
|
||||||
riskPercent: `${riskPercent}%`,
|
riskPercent: `${stopLossPercent * 100}%`,
|
||||||
rewardRatio: '2:1'
|
rewardRatio: '2:1',
|
||||||
|
priceDifference: Math.abs(currentPrice - stopLossPrice).toFixed(4)
|
||||||
})
|
})
|
||||||
|
|
||||||
let stopLossTx = null, takeProfitTx = null
|
let stopLossTx = null, takeProfitTx = null
|
||||||
@@ -286,7 +287,13 @@ export async function POST(request) {
|
|||||||
console.log('🛡️ Placing stop loss order...')
|
console.log('🛡️ Placing stop loss order...')
|
||||||
|
|
||||||
const stopLossTriggerPrice = new BN(Math.floor(stopLossPrice * 1e6))
|
const stopLossTriggerPrice = new BN(Math.floor(stopLossPrice * 1e6))
|
||||||
const stopLossOrderPrice = new BN(Math.floor(stopLossPrice * 0.99 * 1e6)) // Slightly worse price to ensure execution
|
const stopLossOrderPrice = new BN(Math.floor(stopLossPrice * 0.995 * 1e6)) // 0.5% slippage buffer
|
||||||
|
|
||||||
|
console.log(`🛡️ Stop Loss Details:`, {
|
||||||
|
triggerPrice: (stopLossTriggerPrice.toNumber() / 1e6).toFixed(4),
|
||||||
|
orderPrice: (stopLossOrderPrice.toNumber() / 1e6).toFixed(4),
|
||||||
|
baseAssetAmount: baseAssetAmount.toString()
|
||||||
|
})
|
||||||
|
|
||||||
stopLossTx = await driftClient.placePerpOrder({
|
stopLossTx = await driftClient.placePerpOrder({
|
||||||
orderType: OrderType.TRIGGER_LIMIT,
|
orderType: OrderType.TRIGGER_LIMIT,
|
||||||
@@ -301,6 +308,13 @@ export async function POST(request) {
|
|||||||
console.log('✅ Stop loss placed:', stopLossTx)
|
console.log('✅ Stop loss placed:', stopLossTx)
|
||||||
} catch (slError) {
|
} catch (slError) {
|
||||||
console.warn('⚠️ Stop loss failed:', slError.message)
|
console.warn('⚠️ Stop loss failed:', slError.message)
|
||||||
|
// Log more details about the stop loss failure
|
||||||
|
console.warn('🛡️ Stop loss failure details:', {
|
||||||
|
stopLossPrice,
|
||||||
|
currentPrice,
|
||||||
|
priceDiff: Math.abs(currentPrice - stopLossPrice),
|
||||||
|
percentDiff: ((Math.abs(currentPrice - stopLossPrice) / currentPrice) * 100).toFixed(2) + '%'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +324,14 @@ export async function POST(request) {
|
|||||||
console.log('🎯 Placing take profit order...')
|
console.log('🎯 Placing take profit order...')
|
||||||
|
|
||||||
const takeProfitTriggerPrice = new BN(Math.floor(takeProfitPrice * 1e6))
|
const takeProfitTriggerPrice = new BN(Math.floor(takeProfitPrice * 1e6))
|
||||||
const takeProfitOrderPrice = new BN(Math.floor(takeProfitPrice * 1.01 * 1e6)) // Slightly better price to ensure execution
|
const takeProfitOrderPrice = new BN(Math.floor(takeProfitPrice * 1.005 * 1e6)) // 0.5% slippage for execution
|
||||||
|
|
||||||
|
console.log('🎯 Take Profit Details:', {
|
||||||
|
takeProfitPrice: takeProfitPrice.toFixed(4),
|
||||||
|
triggerPrice: (Number(takeProfitTriggerPrice) / 1e6).toFixed(4),
|
||||||
|
orderPrice: (Number(takeProfitOrderPrice) / 1e6).toFixed(4),
|
||||||
|
baseAssetAmount: baseAssetAmount.toString()
|
||||||
|
})
|
||||||
|
|
||||||
takeProfitTx = await driftClient.placePerpOrder({
|
takeProfitTx = await driftClient.placePerpOrder({
|
||||||
orderType: OrderType.TRIGGER_LIMIT,
|
orderType: OrderType.TRIGGER_LIMIT,
|
||||||
@@ -322,9 +343,13 @@ export async function POST(request) {
|
|||||||
reduceOnly: true,
|
reduceOnly: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ Take profit placed:', takeProfitTx)
|
console.log('✅ Take profit placed successfully:', takeProfitTx)
|
||||||
} catch (tpError) {
|
} catch (tpError) {
|
||||||
console.warn('⚠️ Take profit failed:', tpError.message)
|
console.error('❌ Take profit placement failed:', {
|
||||||
|
error: tpError.message,
|
||||||
|
code: tpError.code,
|
||||||
|
logs: tpError.logs || 'No logs available'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user