diff --git a/lib/drift/orders.ts b/lib/drift/orders.ts index f679398..458e9ff 100644 --- a/lib/drift/orders.ts +++ b/lib/drift/orders.ts @@ -46,6 +46,8 @@ export interface ClosePositionResult { closePrice?: number closedSize?: number realizedPnL?: number + fullyClosed?: boolean + remainingSize?: number error?: string } @@ -602,13 +604,21 @@ export async function closePosition( console.log(` Closed notional: $${closedNotional.toFixed(2)}`) console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) - // If closing 100%, cancel all remaining orders for this market - if (params.percentToClose === 100) { + // 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) { + 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 { @@ -617,6 +627,8 @@ export async function closePosition( closePrice: oraclePrice, closedSize: sizeToClose, realizedPnL, + fullyClosed, + remainingSize, } } catch (error) { diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 3a8e277..5cfa700 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -682,7 +682,7 @@ export class PositionManager { } // 5. Take profit 2 (remaining position) - if (trade.tp1Hit && this.shouldTakeProfit2(currentPrice, trade)) { + if (trade.tp1Hit && !trade.tp2Hit && this.shouldTakeProfit2(currentPrice, trade)) { console.log(`🎊 TP2 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) // Calculate how much to close based on TP2 size percent @@ -776,18 +776,31 @@ export class PositionManager { return } + const closePriceForCalc = result.closePrice || currentPrice + const closedSizeBase = result.closedSize || 0 + const closedUSD = closedSizeBase * closePriceForCalc + const wasForcedFullClose = !!result.fullyClosed && percentToClose < 100 + const treatAsFullClose = percentToClose >= 100 || result.fullyClosed + // Update trade state - if (percentToClose >= 100) { - // Full close - remove from monitoring + if (treatAsFullClose) { trade.realizedPnL += result.realizedPnL || 0 - - // Save to database (only for valid exit reasons) + trade.currentSize = 0 + trade.trailingStopActive = false + + if (reason === 'TP2') { + trade.tp2Hit = true + } + if (reason === 'TP1') { + trade.tp1Hit = true + } + if (reason !== 'error') { try { const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000) await updateTradeExit({ positionId: trade.positionId, - exitPrice: result.closePrice || currentPrice, + exitPrice: closePriceForCalc, exitReason: reason as 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' | 'manual' | 'emergency', realizedPnL: trade.realizedPnL, exitOrderTx: result.transactionSignature || 'MANUAL_CLOSE', @@ -802,22 +815,22 @@ export class PositionManager { console.log('💾 Trade saved to database') } catch (dbError) { console.error('❌ Failed to save trade exit to database:', dbError) - // Don't fail the close if database fails } } - + await this.removeTrade(trade.id) - console.log(`✅ Position closed | P&L: $${trade.realizedPnL.toFixed(2)} | Reason: ${reason}`) + const closeLabel = wasForcedFullClose + ? '✅ Forced full close (below Drift minimum)' + : '✅ Position closed' + console.log(`${closeLabel} | P&L: $${trade.realizedPnL.toFixed(2)} | Reason: ${reason}`) } else { // Partial close (TP1) trade.realizedPnL += result.realizedPnL || 0 - // result.closedSize is returned in base asset units (e.g., SOL), convert to USD using closePrice - const closePriceForCalc = result.closePrice || currentPrice - const closedSizeBase = result.closedSize || 0 - const closedUSD = closedSizeBase * closePriceForCalc trade.currentSize = Math.max(0, trade.currentSize - closedUSD) - console.log(`✅ Partial close executed | Realized: $${(result.realizedPnL || 0).toFixed(2)} | Closed (base): ${closedSizeBase.toFixed(6)} | Closed (USD): $${closedUSD.toFixed(2)} | Remaining USD: $${trade.currentSize.toFixed(2)}`) + console.log( + `✅ Partial close executed | Realized: $${(result.realizedPnL || 0).toFixed(2)} | Closed (base): ${closedSizeBase.toFixed(6)} | Closed (USD): $${closedUSD.toFixed(2)} | Remaining USD: $${trade.currentSize.toFixed(2)}` + ) } // TODO: Send notification