/** * Drift Order Execution * * Handles opening and closing positions with market orders */ import { getDriftService, initializeDriftService } 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 fillNotionalUSD?: number slippage?: number error?: string isPhantom?: boolean // Position opened but size mismatch detected actualSizeUSD?: number // Actual position size if different from requested } export interface ClosePositionParams { symbol: string percentToClose: number // 0-100 slippageTolerance: number } export interface ClosePositionResult { success: boolean transactionSignature?: string closePrice?: number closedSize?: number realizedPnL?: number fullyClosed?: boolean remainingSize?: number error?: string } export interface PlaceExitOrdersResult { success: boolean signatures?: string[] error?: string } export interface PlaceExitOrdersOptions { symbol: string positionSizeUSD: number entryPrice: number // CRITICAL: Entry price for calculating position size in base assets tp1Price: number tp2Price: number stopLossPrice: number tp1SizePercent: number tp2SizePercent: number direction: 'long' | 'short' useStopLimit?: boolean // Optional: use TRIGGER_LIMIT instead of TRIGGER_MARKET for SL stopLimitBuffer?: number // Optional: buffer percentage for stop-limit (default 0.5%) // Dual Stop System useDualStops?: boolean // Enable dual stop system softStopPrice?: number // Soft stop trigger price (TRIGGER_LIMIT) softStopBuffer?: number // Buffer for soft stop limit price hardStopPrice?: number // Hard stop trigger price (TRIGGER_MARKET) } /** * 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, fillNotionalUSD: baseAssetSize * oraclePrice, 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(`๐Ÿ“ Transaction submitted: ${txSig}`) // CRITICAL: Confirm transaction actually executed on-chain console.log('โณ Confirming transaction on-chain...') const connection = driftService.getConnection() try { const confirmation = await connection.confirmTransaction(txSig, 'confirmed') if (confirmation.value.err) { console.error(`โŒ Transaction failed on-chain:`, confirmation.value.err) return { success: false, error: `Transaction failed: ${JSON.stringify(confirmation.value.err)}`, } } console.log(`โœ… Transaction confirmed on-chain: ${txSig}`) } catch (confirmError) { console.error(`โŒ Failed to confirm transaction:`, confirmError) return { success: false, error: `Transaction confirmation failed: ${confirmError instanceof Error ? confirmError.message : 'Unknown error'}`, } } // 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 const position = await driftService.getPosition(marketConfig.driftMarketIndex) if (position && position.side !== 'none') { const fillPrice = position.entryPrice const filledBaseSize = Math.abs(position.size) const fillNotionalUSD = filledBaseSize * fillPrice const slippage = Math.abs((fillPrice - oraclePrice) / oraclePrice) * 100 // CRITICAL: Validate actual position size vs expected // Phantom trade detection: Check if position is significantly smaller than expected const expectedSizeUSD = params.sizeUSD const sizeRatio = expectedSizeUSD > 0 ? fillNotionalUSD / expectedSizeUSD : 1 console.log(`๐Ÿ’ฐ Fill details:`) console.log(` Fill price: $${fillPrice.toFixed(4)}`) console.log(` Filled base size: ${filledBaseSize.toFixed(4)} ${params.symbol.split('-')[0]}`) console.log(` Filled notional: $${fillNotionalUSD.toFixed(2)}`) console.log(` Slippage: ${slippage.toFixed(3)}%`) console.log(` Expected size: $${expectedSizeUSD.toFixed(2)}`) console.log(` Actual size: $${fillNotionalUSD.toFixed(2)}`) console.log(` Size ratio: ${(sizeRatio * 100).toFixed(1)}%`) // Flag as phantom if actual size is less than 50% of expected const isPhantom = sizeRatio < 0.5 if (isPhantom) { console.error(`๐Ÿšจ PHANTOM POSITION DETECTED!`) console.error(` Expected: $${expectedSizeUSD.toFixed(2)}`) console.error(` Actual: $${fillNotionalUSD.toFixed(2)}`) console.error(` This indicates the order was rejected or partially filled by Drift`) } return { success: true, transactionSignature: txSig, fillPrice, fillSize: filledBaseSize, fillNotionalUSD, slippage, isPhantom, actualSizeUSD: fillNotionalUSD, } } 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, fillNotionalUSD: baseAssetSize * oraclePrice, slippage: 0, } } } catch (error) { console.error('โŒ Failed to open position:', error) return { success: false, error: error instanceof Error ? error.message : 'Unknown error', } } } /** * Place on-chain exit orders (reduce-only orders) so TP/SL show up in Drift UI. * * Stop Loss Strategy: * - Default: TRIGGER_MARKET (guaranteed execution, recommended for most traders) * - Optional: TRIGGER_LIMIT with buffer (protects against extreme wicks in liquid markets) * * Take Profit Strategy: * - Always uses LIMIT orders to lock in desired prices */ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise { try { console.log('๐Ÿ›ก๏ธ Placing exit orders on-chain:', options.symbol) const driftService = getDriftService() const driftClient = driftService.getClient() const marketConfig = getMarketConfig(options.symbol) const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { console.log('๐Ÿงช DRY RUN: Simulating placement of exit orders') return { success: true, signatures: [ `DRY_TP1_${Date.now()}`, `DRY_TP2_${Date.now()}`, `DRY_SL_${Date.now()}`, ], } } const signatures: string[] = [] // Helper to compute base asset amount from USD notional and price // CRITICAL: Use ENTRY price to calculate position size, not TP price! // This ensures we close the correct percentage of the actual position const usdToBase = (usd: number) => { const base = usd / options.entryPrice // Use entry price for size calculation return Math.floor(base * 1e9) // 9 decimals expected by SDK } // Calculate sizes in USD for each TP // CRITICAL FIX: TP2 must be percentage of REMAINING size after TP1, not original size const tp1USD = (options.positionSizeUSD * options.tp1SizePercent) / 100 const remainingAfterTP1 = options.positionSizeUSD - tp1USD const tp2USD = (remainingAfterTP1 * options.tp2SizePercent) / 100 console.log(`๐Ÿ“Š Exit order sizes:`) console.log(` TP1: ${options.tp1SizePercent}% of $${options.positionSizeUSD.toFixed(2)} = $${tp1USD.toFixed(2)}`) console.log(` Remaining after TP1: $${remainingAfterTP1.toFixed(2)}`) console.log(` TP2: ${options.tp2SizePercent}% of remaining = $${tp2USD.toFixed(2)}`) console.log(` Runner (if any): $${(remainingAfterTP1 - tp2USD).toFixed(2)}`) // For orders that close a long, the order direction should be SHORT (sell) const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG // Place TP1 LIMIT reduce-only if (tp1USD > 0) { const baseAmount = usdToBase(tp1USD) if (baseAmount >= Math.floor(marketConfig.minOrderSize * 1e9)) { const orderParams: any = { orderType: OrderType.LIMIT, marketIndex: marketConfig.driftMarketIndex, direction: orderDirection, baseAssetAmount: new BN(baseAmount), price: new BN(Math.floor(options.tp1Price * 1e6)), // price in 1e6 reduceOnly: true, } console.log('๐Ÿšง Placing TP1 limit order (reduce-only)...') const sig = await (driftClient as any).placePerpOrder(orderParams) console.log('โœ… TP1 order placed:', sig) signatures.push(sig) } else { console.log('โš ๏ธ TP1 size below market min, skipping on-chain TP1') } } // Place TP2 LIMIT reduce-only if (tp2USD > 0) { const baseAmount = usdToBase(tp2USD) if (baseAmount >= Math.floor(marketConfig.minOrderSize * 1e9)) { const orderParams: any = { orderType: OrderType.LIMIT, marketIndex: marketConfig.driftMarketIndex, direction: orderDirection, baseAssetAmount: new BN(baseAmount), price: new BN(Math.floor(options.tp2Price * 1e6)), reduceOnly: true, } console.log('๐Ÿšง Placing TP2 limit order (reduce-only)...') const sig = await (driftClient as any).placePerpOrder(orderParams) console.log('โœ… TP2 order placed:', sig) signatures.push(sig) } else { console.log('โš ๏ธ TP2 size below market min, skipping on-chain TP2') } } // Place Stop-Loss order(s) // Supports three modes: // 1. Dual Stop System (soft stop-limit + hard stop-market) // 2. Single TRIGGER_LIMIT (for liquid markets) // 3. Single TRIGGER_MARKET (default, guaranteed execution) const slUSD = options.positionSizeUSD const slBaseAmount = usdToBase(slUSD) if (slBaseAmount >= Math.floor(marketConfig.minOrderSize * 1e9)) { const useDualStops = options.useDualStops ?? false if (useDualStops && options.softStopPrice && options.hardStopPrice) { // ============== DUAL STOP SYSTEM ============== console.log('๐Ÿ›ก๏ธ๐Ÿ›ก๏ธ Placing DUAL STOP SYSTEM...') // 1. Soft Stop (TRIGGER_LIMIT) - Avoids wicks const softStopBuffer = options.softStopBuffer ?? 0.4 const softStopMultiplier = options.direction === 'long' ? (1 - softStopBuffer / 100) : (1 + softStopBuffer / 100) const softStopParams: any = { orderType: OrderType.TRIGGER_LIMIT, marketIndex: marketConfig.driftMarketIndex, direction: orderDirection, baseAssetAmount: new BN(slBaseAmount), triggerPrice: new BN(Math.floor(options.softStopPrice * 1e6)), price: new BN(Math.floor(options.softStopPrice * softStopMultiplier * 1e6)), triggerCondition: options.direction === 'long' ? OrderTriggerCondition.BELOW : OrderTriggerCondition.ABOVE, reduceOnly: true, } console.log(` 1๏ธโƒฃ Soft Stop (TRIGGER_LIMIT):`) console.log(` Trigger: $${options.softStopPrice.toFixed(4)}`) console.log(` Limit: $${(options.softStopPrice * softStopMultiplier).toFixed(4)}`) console.log(` Purpose: Avoid false breakouts/wicks`) const softStopSig = await (driftClient as any).placePerpOrder(softStopParams) console.log(` โœ… Soft stop placed: ${softStopSig}`) signatures.push(softStopSig) // 2. Hard Stop (TRIGGER_MARKET) - Guarantees exit const hardStopParams: any = { orderType: OrderType.TRIGGER_MARKET, marketIndex: marketConfig.driftMarketIndex, direction: orderDirection, baseAssetAmount: new BN(slBaseAmount), triggerPrice: new BN(Math.floor(options.hardStopPrice * 1e6)), triggerCondition: options.direction === 'long' ? OrderTriggerCondition.BELOW : OrderTriggerCondition.ABOVE, reduceOnly: true, } console.log(` 2๏ธโƒฃ Hard Stop (TRIGGER_MARKET):`) console.log(` Trigger: $${options.hardStopPrice.toFixed(4)}`) console.log(` Purpose: Guaranteed exit if soft stop doesn't fill`) const hardStopSig = await (driftClient as any).placePerpOrder(hardStopParams) console.log(` โœ… Hard stop placed: ${hardStopSig}`) signatures.push(hardStopSig) console.log(`๐ŸŽฏ Dual stop system active: Soft @ $${options.softStopPrice.toFixed(2)} | Hard @ $${options.hardStopPrice.toFixed(2)}`) } else { // ============== SINGLE STOP SYSTEM ============== const useStopLimit = options.useStopLimit ?? false const stopLimitBuffer = options.stopLimitBuffer ?? 0.5 if (useStopLimit) { // TRIGGER_LIMIT: For liquid markets const limitPriceMultiplier = options.direction === 'long' ? (1 - stopLimitBuffer / 100) : (1 + stopLimitBuffer / 100) const orderParams: any = { orderType: OrderType.TRIGGER_LIMIT, marketIndex: marketConfig.driftMarketIndex, direction: orderDirection, baseAssetAmount: new BN(slBaseAmount), triggerPrice: new BN(Math.floor(options.stopLossPrice * 1e6)), price: new BN(Math.floor(options.stopLossPrice * limitPriceMultiplier * 1e6)), triggerCondition: options.direction === 'long' ? OrderTriggerCondition.BELOW : OrderTriggerCondition.ABOVE, reduceOnly: true, } console.log(`๐Ÿ›ก๏ธ Placing SL as TRIGGER_LIMIT (${stopLimitBuffer}% buffer)...`) console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) console.log(` Limit: $${(options.stopLossPrice * limitPriceMultiplier).toFixed(4)}`) console.log(` โš ๏ธ May not fill during fast moves - use for liquid markets only!`) const sig = await (driftClient as any).placePerpOrder(orderParams) console.log('โœ… SL trigger-limit order placed:', sig) signatures.push(sig) } else { // TRIGGER_MARKET: Default, guaranteed execution const orderParams: any = { orderType: OrderType.TRIGGER_MARKET, marketIndex: marketConfig.driftMarketIndex, direction: orderDirection, baseAssetAmount: new BN(slBaseAmount), triggerPrice: new BN(Math.floor(options.stopLossPrice * 1e6)), triggerCondition: options.direction === 'long' ? OrderTriggerCondition.BELOW : OrderTriggerCondition.ABOVE, reduceOnly: true, } console.log(`๐Ÿ›ก๏ธ Placing SL as TRIGGER_MARKET (guaranteed execution - RECOMMENDED)...`) console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) console.log(` โœ… Will execute at market price when triggered (may slip but WILL fill)`) const sig = await (driftClient as any).placePerpOrder(orderParams) console.log('โœ… SL trigger-market order placed:', sig) signatures.push(sig) } } } else { console.log('โš ๏ธ SL size below market min, skipping on-chain SL') } return { success: true, signatures } } catch (error) { console.error('โŒ Failed to place exit orders:', 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 let sizeToClose = position.size * (params.percentToClose / 100) // CRITICAL FIX: If calculated size is below minimum, close 100% instead // This prevents "runner" positions from being too small to close if (sizeToClose < marketConfig.minOrderSize) { console.log(`โš ๏ธ Calculated close size ${sizeToClose.toFixed(4)} is below minimum ${marketConfig.minOrderSize}`) console.log(` Forcing 100% close to avoid Drift rejection`) sizeToClose = position.size // Close entire position } 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 with leverage (default 10x in dry run) const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1) const closedNotional = sizeToClose * oraclePrice const realizedPnL = (closedNotional * profitPercent) / 100 const accountPnLPercent = profitPercent * 10 // display using default leverage 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(` Profit %: ${profitPercent.toFixed(3)}% โ†’ Account P&L (10x): ${accountPnLPercent.toFixed(2)}%`) 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}`) // CRITICAL: Confirm transaction on-chain to prevent phantom closes console.log('โณ Confirming transaction on-chain...') const connection = driftService.getConnection() const confirmation = await connection.confirmTransaction(txSig, 'confirmed') if (confirmation.value.err) { console.error('โŒ Transaction failed on-chain:', confirmation.value.err) throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) } console.log('โœ… Transaction confirmed on-chain') // Calculate realized P&L with leverage // CRITICAL: P&L must account for leverage and be calculated on USD notional, not base asset size const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1) // Get leverage from user account (defaults to 10x if not found) let leverage = 10 try { const userAccount = driftClient.getUserAccount() if (userAccount && userAccount.maxMarginRatio) { // maxMarginRatio is in 1e4 scale, leverage = 1 / (margin / 10000) leverage = 10000 / Number(userAccount.maxMarginRatio) } } catch (err) { console.log('โš ๏ธ Could not determine leverage from account, using 10x default') } // Calculate closed notional value (USD) const closedNotional = sizeToClose * oraclePrice const realizedPnL = (closedNotional * profitPercent) / 100 const accountPnLPercent = profitPercent * leverage console.log(`๐Ÿ’ฐ Close details:`) console.log(` Close price: $${oraclePrice.toFixed(4)}`) console.log(` Profit %: ${profitPercent.toFixed(3)}% | Account P&L (${leverage}x): ${accountPnLPercent.toFixed(2)}%`) console.log(` Closed notional: $${closedNotional.toFixed(2)}`) console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) // Check remaining position size after close const updatedPosition = await driftService.getPosition(marketConfig.driftMarketIndex) const remainingSize = updatedPosition ? Math.abs(updatedPosition.size) : 0 const fullyClosed = !updatedPosition || remainingSize === 0 if (fullyClosed) { console.log('๐Ÿ—‘๏ธ Position fully closed, cancelling remaining orders...') const cancelResult = await cancelAllOrders(params.symbol) if (cancelResult.success && (cancelResult.cancelledCount || 0) > 0) { console.log(`โœ… Cancelled ${cancelResult.cancelledCount} orders`) } } else if (params.percentToClose === 100) { console.log( `โš ๏ธ Requested 100% close but ${remainingSize.toFixed(4)} base remains on-chain` ) } return { success: true, transactionSignature: txSig, closePrice: oraclePrice, closedSize: sizeToClose, realizedPnL, fullyClosed, remainingSize, } } catch (error) { console.error('โŒ Failed to close position:', error) return { success: false, error: error instanceof Error ? error.message : 'Unknown error', } } } /** * Cancel all open orders for a specific market */ export async function cancelAllOrders( symbol: string ): Promise<{ success: boolean; cancelledCount?: number; error?: string }> { try { console.log(`๐Ÿ—‘๏ธ Cancelling all orders for ${symbol}...`) // Ensure Drift service is initialized let driftService = getDriftService() if (!driftService) { console.log('โš ๏ธ Drift service not initialized, initializing now...') driftService = await initializeDriftService() } const driftClient = driftService.getClient() const marketConfig = getMarketConfig(symbol) const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { console.log('๐Ÿงช DRY RUN: Simulating order cancellation') return { success: true, cancelledCount: 0 } } // Get user account to check for orders const userAccount = driftClient.getUserAccount() if (!userAccount) { throw new Error('User account not found') } // Filter orders for this market (check for active orders, not just status) // Note: Trigger orders may have different status values, so we check for non-zero orderId const ordersToCancel = userAccount.orders.filter( (order: any) => order.marketIndex === marketConfig.driftMarketIndex && order.orderId > 0 // Active orders have orderId > 0 ) if (ordersToCancel.length === 0) { console.log('โœ… No open orders to cancel') return { success: true, cancelledCount: 0 } } console.log(`๐Ÿ“‹ Found ${ordersToCancel.length} open orders to cancel (including trigger orders)`) // Cancel all orders for this market (cancels all types: LIMIT, TRIGGER_MARKET, TRIGGER_LIMIT) const txSig = await driftClient.cancelOrders( undefined, // Cancel by market type marketConfig.driftMarketIndex, undefined // No specific direction filter ) console.log(`โœ… Orders cancelled! Transaction: ${txSig}`) return { success: true, cancelledCount: ordersToCancel.length, } } catch (error) { console.error('โŒ Failed to cancel orders:', 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: [], } } }