/** * Drift Order Execution * * Handles opening and closing positions with market orders */ import { getDriftService } from './client' import { getMarketConfig } from '../../config/trading' import BN from 'bn.js' import { MarketType, PositionDirection, OrderType, OrderParams, OrderTriggerCondition, } from '@drift-labs/sdk' export interface OpenPositionParams { symbol: string // e.g., 'SOL-PERP' direction: 'long' | 'short' sizeUSD: number // USD notional size slippageTolerance: number // Percentage (e.g., 1.0 for 1%) } export interface OpenPositionResult { success: boolean transactionSignature?: string fillPrice?: number fillSize?: number slippage?: number error?: string } export interface ClosePositionParams { symbol: string percentToClose: number // 0-100 slippageTolerance: number } export interface ClosePositionResult { success: boolean transactionSignature?: string closePrice?: number closedSize?: number realizedPnL?: number error?: string } /** * Open a position with a market order */ export async function openPosition( params: OpenPositionParams ): Promise { try { console.log('๐Ÿ“Š Opening position:', params) const driftService = getDriftService() const marketConfig = getMarketConfig(params.symbol) const driftClient = driftService.getClient() // Get current oracle price const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) console.log(`๐Ÿ’ฐ Current ${params.symbol} price: $${oraclePrice.toFixed(4)}`) // Calculate position size in base asset const baseAssetSize = params.sizeUSD / oraclePrice // Validate minimum order size if (baseAssetSize < marketConfig.minOrderSize) { throw new Error( `Order size ${baseAssetSize.toFixed(4)} is below minimum ${marketConfig.minOrderSize}` ) } // Calculate worst acceptable price (with slippage) const slippageMultiplier = params.direction === 'long' ? 1 + (params.slippageTolerance / 100) : 1 - (params.slippageTolerance / 100) const worstPrice = oraclePrice * slippageMultiplier console.log(`๐Ÿ“ Order details:`) console.log(` Size: ${baseAssetSize.toFixed(4)} ${params.symbol.split('-')[0]}`) console.log(` Notional: $${params.sizeUSD.toFixed(2)}`) console.log(` Oracle price: $${oraclePrice.toFixed(4)}`) console.log(` Worst price (${params.slippageTolerance}% slippage): $${worstPrice.toFixed(4)}`) // Check DRY_RUN mode const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { console.log('๐Ÿงช DRY RUN MODE: Simulating order (not executing on blockchain)') const mockTxSig = `DRY_RUN_${Date.now()}_${Math.random().toString(36).substring(7)}` return { success: true, transactionSignature: mockTxSig, fillPrice: oraclePrice, fillSize: baseAssetSize, slippage: 0, } } // Prepare order parameters - use simple structure like v3 const orderParams = { orderType: OrderType.MARKET, marketIndex: marketConfig.driftMarketIndex, direction: params.direction === 'long' ? PositionDirection.LONG : PositionDirection.SHORT, baseAssetAmount: new BN(Math.floor(baseAssetSize * 1e9)), // 9 decimals reduceOnly: false, } // Place market order using simple placePerpOrder (like v3) console.log('๐Ÿš€ Placing REAL market order...') const txSig = await driftClient.placePerpOrder(orderParams) console.log(`โœ… Order placed! Transaction: ${txSig}`) // Wait a moment for position to update console.log('โณ Waiting for position to update...') await new Promise(resolve => setTimeout(resolve, 2000)) // Get actual fill price from position (optional - may not be immediate in DRY_RUN) const position = await driftService.getPosition(marketConfig.driftMarketIndex) if (position && position.side !== 'none') { const fillPrice = position.entryPrice const slippage = Math.abs((fillPrice - oraclePrice) / oraclePrice) * 100 console.log(`๐Ÿ’ฐ Fill details:`) console.log(` Fill price: $${fillPrice.toFixed(4)}`) console.log(` Slippage: ${slippage.toFixed(3)}%`) return { success: true, transactionSignature: txSig, fillPrice, fillSize: baseAssetSize, slippage, } } else { // Position not found yet (may be DRY_RUN mode) console.log(`โš ๏ธ Position not immediately visible (may be DRY_RUN mode)`) console.log(` Using oracle price as estimate: $${oraclePrice.toFixed(4)}`) return { success: true, transactionSignature: txSig, fillPrice: oraclePrice, fillSize: baseAssetSize, slippage: 0, } } } catch (error) { console.error('โŒ Failed to open position:', error) return { success: false, error: error instanceof Error ? error.message : 'Unknown error', } } } /** * Close a position (partially or fully) with a market order */ export async function closePosition( params: ClosePositionParams ): Promise { try { console.log('๐Ÿ“Š Closing position:', params) const driftService = getDriftService() const marketConfig = getMarketConfig(params.symbol) const driftClient = driftService.getClient() // Get current position const position = await driftService.getPosition(marketConfig.driftMarketIndex) if (!position || position.side === 'none') { throw new Error(`No active position for ${params.symbol}`) } // Calculate size to close const sizeToClose = position.size * (params.percentToClose / 100) console.log(`๐Ÿ“ Close order details:`) console.log(` Current position: ${position.size.toFixed(4)} ${position.side}`) console.log(` Closing: ${params.percentToClose}% (${sizeToClose.toFixed(4)})`) console.log(` Entry price: $${position.entryPrice.toFixed(4)}`) console.log(` Unrealized P&L: $${position.unrealizedPnL.toFixed(2)}`) // Get current oracle price const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) console.log(` Current price: $${oraclePrice.toFixed(4)}`) // Check DRY_RUN mode const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { console.log('๐Ÿงช DRY RUN MODE: Simulating close order (not executing on blockchain)') // Calculate realized P&L const pnlPerUnit = oraclePrice - position.entryPrice const realizedPnL = pnlPerUnit * sizeToClose * (position.side === 'long' ? 1 : -1) const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}` console.log(`๐Ÿ’ฐ Simulated close:`) console.log(` Close price: $${oraclePrice.toFixed(4)}`) console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) return { success: true, transactionSignature: mockTxSig, closePrice: oraclePrice, closedSize: sizeToClose, realizedPnL, } } // Prepare close order (opposite direction) - use simple structure like v3 const orderParams = { orderType: OrderType.MARKET, marketIndex: marketConfig.driftMarketIndex, direction: position.side === 'long' ? PositionDirection.SHORT : PositionDirection.LONG, baseAssetAmount: new BN(Math.floor(sizeToClose * 1e9)), // 9 decimals reduceOnly: true, // Important: only close existing position } // Place market close order using simple placePerpOrder (like v3) console.log('๐Ÿš€ Placing REAL market close order...') const txSig = await driftClient.placePerpOrder(orderParams) console.log(`โœ… Close order placed! Transaction: ${txSig}`) // Wait for confirmation (transaction is likely already confirmed by placeAndTakePerpOrder) console.log('โณ Waiting for transaction confirmation...') console.log('โœ… Transaction confirmed') // Calculate realized P&L const pnlPerUnit = oraclePrice - position.entryPrice const realizedPnL = pnlPerUnit * sizeToClose * (position.side === 'long' ? 1 : -1) console.log(`๐Ÿ’ฐ Close details:`) console.log(` Close price: $${oraclePrice.toFixed(4)}`) console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) return { success: true, transactionSignature: txSig, closePrice: oraclePrice, closedSize: sizeToClose, realizedPnL, } } catch (error) { console.error('โŒ Failed to close position:', error) return { success: false, error: error instanceof Error ? error.message : 'Unknown error', } } } /** * Close entire position for a market */ export async function closeEntirePosition( symbol: string, slippageTolerance: number = 1.0 ): Promise { return closePosition({ symbol, percentToClose: 100, slippageTolerance, }) } /** * Emergency close all positions */ export async function emergencyCloseAll(): Promise<{ success: boolean results: Array<{ symbol: string result: ClosePositionResult }> }> { console.log('๐Ÿšจ EMERGENCY: Closing all positions') try { const driftService = getDriftService() const positions = await driftService.getAllPositions() if (positions.length === 0) { console.log('โœ… No positions to close') return { success: true, results: [] } } const results = [] for (const position of positions) { console.log(`๐Ÿ”ด Emergency closing ${position.symbol}...`) const result = await closeEntirePosition(position.symbol, 2.0) // Allow 2% slippage results.push({ symbol: position.symbol, result, }) } console.log('โœ… Emergency close complete') return { success: true, results, } } catch (error) { console.error('โŒ Emergency close failed:', error) return { success: false, results: [], } } }