/** * Position Scaling DCA API - Proper DCA Implementation * * This API increases existing position size and adjusts SL/TP levels * instead of creating multiple fragmented orders. */ import { NextResponse } from 'next/server'; import { Connection, Keypair } from '@solana/web3.js'; import { Wallet } from '@project-serum/anchor'; import { DriftClient, PositionDirection, OrderType, OrderTriggerCondition, MarketType } from '@drift-labs/sdk'; import { BN } from '@project-serum/anchor'; import { initialize } from '@drift-labs/sdk'; export async function POST(request) { try { const { dcaAmount, // Additional amount to add (in USD) analysis = null // Optional AI analysis for optimal levels } = await request.json(); console.log('๐ŸŽฏ POSITION SCALING DCA STARTED'); console.log(`๐Ÿ’ฐ Adding $${dcaAmount} to existing position`); // 1. Get current position const positionResponse = await fetch(`${process.env.INTERNAL_API_URL || 'http://localhost:3000'}/api/drift/positions`); const positionData = await positionResponse.json(); if (!positionData.success || positionData.positions.length === 0) { return NextResponse.json({ success: false, error: 'No existing position found to scale' }, { status: 400 }); } const currentPosition = positionData.positions[0]; console.log(`๐Ÿ“Š Current position: ${currentPosition.side} ${currentPosition.size} ${currentPosition.symbol} @ $${currentPosition.entryPrice}`); // 2. Initialize Drift client const connection = new Connection(process.env.HELIUS_RPC_URL); const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY); const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray)); const wallet = new Wallet(keypair); const sdkConfig = initialize({ env: 'mainnet-beta' }); const driftClient = new DriftClient({ connection, wallet, programID: sdkConfig.DRIFT_PROGRAM_ID, opts: { commitment: 'confirmed', skipPreflight: false, preflightCommitment: 'confirmed' } }); await driftClient.subscribe(); console.log('โœ… Connected to Drift Protocol'); // 3. Get current market price and calculate DCA parameters const marketIndex = 0; // SOL-PERP const perpMarketAccount = driftClient.getPerpMarketAccount(marketIndex); const currentPrice = Number(perpMarketAccount.amm.lastMarkPriceTwap) / 1e6; console.log(`๐Ÿ“ˆ Current market price: $${currentPrice.toFixed(4)}`); // 4. Calculate new averaged position const currentPositionValue = currentPosition.size * currentPosition.entryPrice; const dcaPositionSize = dcaAmount / currentPrice; const dcaPositionValue = dcaPositionSize * currentPrice; const newTotalSize = currentPosition.size + dcaPositionSize; const newAveragePrice = (currentPositionValue + dcaPositionValue) / newTotalSize; console.log('๐Ÿงฎ Position scaling calculation:'); console.log(` Current: ${currentPosition.size.toFixed(4)} @ $${currentPosition.entryPrice.toFixed(4)} = $${currentPositionValue.toFixed(2)}`); console.log(` DCA Add: ${dcaPositionSize.toFixed(4)} @ $${currentPrice.toFixed(4)} = $${dcaPositionValue.toFixed(2)}`); console.log(` New Total: ${newTotalSize.toFixed(4)} @ $${newAveragePrice.toFixed(4)} = $${(newTotalSize * newAveragePrice).toFixed(2)}`); // 5. Cancel existing stop loss and take profit orders console.log('๐Ÿงน Canceling existing SL/TP orders...'); try { const ordersResponse = await fetch(`${process.env.INTERNAL_API_URL || 'http://localhost:3000'}/api/drift/orders`); const ordersData = await ordersResponse.json(); if (ordersData.success && ordersData.orders.length > 0) { // Find and cancel reduce-only orders (SL/TP) const reduceOnlyOrders = ordersData.orders.filter(order => order.reduceOnly && order.status === 'OPEN' ); console.log(` Found ${reduceOnlyOrders.length} existing SL/TP orders to cancel`); for (const order of reduceOnlyOrders) { try { await driftClient.cancelOrder(order.orderId); console.log(` โœ… Canceled order: ${order.orderType} @ $${order.triggerPrice}`); } catch (cancelError) { console.warn(` โš ๏ธ Failed to cancel order ${order.orderId}:`, cancelError.message); } } } } catch (ordersError) { console.warn('โš ๏ธ Error fetching/canceling orders:', ordersError.message); } // 6. Place DCA order to increase position const dcaBaseAssetAmount = Math.floor(dcaPositionSize * 1e9); // Convert to base units const direction = currentPosition.side.toLowerCase() === 'long' ? PositionDirection.LONG : PositionDirection.SHORT; console.log(`๐Ÿ“ˆ Placing DCA order: ${direction === PositionDirection.LONG ? 'LONG' : 'SHORT'} ${dcaPositionSize.toFixed(4)} SOL`); const dcaOrderParams = { orderType: OrderType.MARKET, marketType: MarketType.PERP, direction, baseAssetAmount: new BN(dcaBaseAssetAmount), marketIndex, }; const dcaTxSig = await driftClient.placeAndTakePerpOrder(dcaOrderParams); console.log('โœ… DCA position increase executed:', dcaTxSig); // Wait for order to settle await new Promise(resolve => setTimeout(resolve, 3000)); // 7. Calculate new stop loss and take profit levels let newStopLoss, newTakeProfit; if (analysis && analysis.stopLoss && analysis.takeProfits) { // Use AI-calculated levels if available console.log('๐Ÿง  Using AI-calculated optimal levels'); newStopLoss = analysis.stopLoss.price || analysis.stopLoss; newTakeProfit = analysis.takeProfits.tp1?.price || analysis.takeProfits.tp1 || analysis.takeProfit; } else { // Calculate adaptive levels based on new average price console.log('๐Ÿ“Š Calculating adaptive levels for new average price'); const stopLossPercent = 2.0; // 2% stop loss const takeProfitPercent = 4.0; // 4% take profit if (direction === PositionDirection.LONG) { newStopLoss = newAveragePrice * (1 - stopLossPercent / 100); newTakeProfit = newAveragePrice * (1 + takeProfitPercent / 100); } else { newStopLoss = newAveragePrice * (1 + stopLossPercent / 100); newTakeProfit = newAveragePrice * (1 - takeProfitPercent / 100); } } console.log('๐ŸŽฏ New risk management levels:'); console.log(` Stop Loss: $${newStopLoss.toFixed(4)}`); console.log(` Take Profit: $${newTakeProfit.toFixed(4)}`); // 8. Place new stop loss order for entire scaled position let stopLossTx = null; if (newStopLoss) { try { console.log('๐Ÿ›ก๏ธ Placing new stop loss for scaled position...'); const stopLossParams = { orderType: OrderType.TRIGGER_LIMIT, marketType: MarketType.PERP, direction: direction === PositionDirection.LONG ? PositionDirection.SHORT : PositionDirection.LONG, baseAssetAmount: new BN(Math.floor(newTotalSize * 1e9)), // Full position size price: new BN(Math.floor(newStopLoss * 0.995 * 1e6)), // 0.5% slippage buffer marketIndex, triggerPrice: new BN(Math.floor(newStopLoss * 1e6)), triggerCondition: direction === PositionDirection.LONG ? OrderTriggerCondition.BELOW : OrderTriggerCondition.ABOVE, reduceOnly: true, }; stopLossTx = await driftClient.placePerpOrder(stopLossParams); console.log('โœ… New stop loss placed:', stopLossTx); } catch (slError) { console.warn('โš ๏ธ Stop loss placement failed:', slError.message); } } // 9. Place new take profit order for entire scaled position let takeProfitTx = null; if (newTakeProfit) { try { console.log('๐ŸŽฏ Placing new take profit for scaled position...'); const takeProfitParams = { orderType: OrderType.TRIGGER_LIMIT, marketType: MarketType.PERP, direction: direction === PositionDirection.LONG ? PositionDirection.SHORT : PositionDirection.LONG, baseAssetAmount: new BN(Math.floor(newTotalSize * 1e9)), // Full position size price: new BN(Math.floor(newTakeProfit * 1.005 * 1e6)), // 0.5% slippage buffer marketIndex, triggerPrice: new BN(Math.floor(newTakeProfit * 1e6)), triggerCondition: direction === PositionDirection.LONG ? OrderTriggerCondition.ABOVE : OrderTriggerCondition.BELOW, reduceOnly: true, }; takeProfitTx = await driftClient.placePerpOrder(takeProfitParams); console.log('โœ… New take profit placed:', takeProfitTx); } catch (tpError) { console.warn('โš ๏ธ Take profit placement failed:', tpError.message); } } await driftClient.unsubscribe(); // 10. Return success result return NextResponse.json({ success: true, message: 'Position successfully scaled with DCA', scalingResult: { dcaTxId: dcaTxSig, stopLossTxId: stopLossTx, takeProfitTxId: takeProfitTx, // Original position originalSize: currentPosition.size, originalEntryPrice: currentPosition.entryPrice, originalValue: currentPositionValue, // DCA addition dcaSize: dcaPositionSize, dcaPrice: currentPrice, dcaValue: dcaPositionValue, // New scaled position newTotalSize: newTotalSize, newAveragePrice: newAveragePrice, newTotalValue: newTotalSize * newAveragePrice, // New risk management newStopLoss: newStopLoss, newTakeProfit: newTakeProfit, // AI data usedAILevels: !!(analysis && analysis.stopLoss && analysis.takeProfits), aiAnalysis: analysis ? { confidence: analysis.confidence, reasoning: analysis.reasoning || analysis.summary } : null } }); } catch (error) { console.error('โŒ Position scaling DCA failed:', error.message); return NextResponse.json({ success: false, error: `Position scaling failed: ${error.message}`, details: error.stack }, { status: 500 }); } }