Prevent repeated TP2 cleanup loops
This commit is contained in:
@@ -46,6 +46,8 @@ export interface ClosePositionResult {
|
|||||||
closePrice?: number
|
closePrice?: number
|
||||||
closedSize?: number
|
closedSize?: number
|
||||||
realizedPnL?: number
|
realizedPnL?: number
|
||||||
|
fullyClosed?: boolean
|
||||||
|
remainingSize?: number
|
||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,13 +604,21 @@ export async function closePosition(
|
|||||||
console.log(` Closed notional: $${closedNotional.toFixed(2)}`)
|
console.log(` Closed notional: $${closedNotional.toFixed(2)}`)
|
||||||
console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`)
|
console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`)
|
||||||
|
|
||||||
// If closing 100%, cancel all remaining orders for this market
|
// Check remaining position size after close
|
||||||
if (params.percentToClose === 100) {
|
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...')
|
console.log('🗑️ Position fully closed, cancelling remaining orders...')
|
||||||
const cancelResult = await cancelAllOrders(params.symbol)
|
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`)
|
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 {
|
return {
|
||||||
@@ -617,6 +627,8 @@ export async function closePosition(
|
|||||||
closePrice: oraclePrice,
|
closePrice: oraclePrice,
|
||||||
closedSize: sizeToClose,
|
closedSize: sizeToClose,
|
||||||
realizedPnL,
|
realizedPnL,
|
||||||
|
fullyClosed,
|
||||||
|
remainingSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ export class PositionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Take profit 2 (remaining position)
|
// 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)}%`)
|
console.log(`🎊 TP2 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`)
|
||||||
|
|
||||||
// Calculate how much to close based on TP2 size percent
|
// Calculate how much to close based on TP2 size percent
|
||||||
@@ -776,18 +776,31 @@ export class PositionManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update trade state
|
const closePriceForCalc = result.closePrice || currentPrice
|
||||||
if (percentToClose >= 100) {
|
const closedSizeBase = result.closedSize || 0
|
||||||
// Full close - remove from monitoring
|
const closedUSD = closedSizeBase * closePriceForCalc
|
||||||
trade.realizedPnL += result.realizedPnL || 0
|
const wasForcedFullClose = !!result.fullyClosed && percentToClose < 100
|
||||||
|
const treatAsFullClose = percentToClose >= 100 || result.fullyClosed
|
||||||
|
|
||||||
|
// Update trade state
|
||||||
|
if (treatAsFullClose) {
|
||||||
|
trade.realizedPnL += result.realizedPnL || 0
|
||||||
|
trade.currentSize = 0
|
||||||
|
trade.trailingStopActive = false
|
||||||
|
|
||||||
|
if (reason === 'TP2') {
|
||||||
|
trade.tp2Hit = true
|
||||||
|
}
|
||||||
|
if (reason === 'TP1') {
|
||||||
|
trade.tp1Hit = true
|
||||||
|
}
|
||||||
|
|
||||||
// Save to database (only for valid exit reasons)
|
|
||||||
if (reason !== 'error') {
|
if (reason !== 'error') {
|
||||||
try {
|
try {
|
||||||
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)
|
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)
|
||||||
await updateTradeExit({
|
await updateTradeExit({
|
||||||
positionId: trade.positionId,
|
positionId: trade.positionId,
|
||||||
exitPrice: result.closePrice || currentPrice,
|
exitPrice: closePriceForCalc,
|
||||||
exitReason: reason as 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' | 'manual' | 'emergency',
|
exitReason: reason as 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' | 'manual' | 'emergency',
|
||||||
realizedPnL: trade.realizedPnL,
|
realizedPnL: trade.realizedPnL,
|
||||||
exitOrderTx: result.transactionSignature || 'MANUAL_CLOSE',
|
exitOrderTx: result.transactionSignature || 'MANUAL_CLOSE',
|
||||||
@@ -802,22 +815,22 @@ export class PositionManager {
|
|||||||
console.log('💾 Trade saved to database')
|
console.log('💾 Trade saved to database')
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
console.error('❌ Failed to save trade exit to database:', dbError)
|
console.error('❌ Failed to save trade exit to database:', dbError)
|
||||||
// Don't fail the close if database fails
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.removeTrade(trade.id)
|
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 {
|
} else {
|
||||||
// Partial close (TP1)
|
// Partial close (TP1)
|
||||||
trade.realizedPnL += result.realizedPnL || 0
|
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)
|
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
|
// TODO: Send notification
|
||||||
|
|||||||
Reference in New Issue
Block a user