feat: Add Telegram notifications for position closures
Implemented direct Telegram notifications when Position Manager closes positions: - New helper: lib/notifications/telegram.ts with sendPositionClosedNotification() - Integrated into Position Manager's executeExit() for all closure types - Also sends notifications for ghost position cleanups Notification includes: - Symbol, direction, entry/exit prices - P&L amount and percentage - Position size and hold time - Exit reason (TP1, TP2, SL, manual, ghost cleanup, etc.) - MAE/MFE stats (max gain/drawdown during trade) User request: Receive P&L notifications on position closures via Telegram bot Previously: Only opening notifications via n8n workflow Now: All closures (TP/SL/manual/ghost) send notifications directly
This commit is contained in:
@@ -9,6 +9,7 @@ import { closePosition } from '../drift/orders'
|
||||
import { getPythPriceMonitor, PriceUpdate } from '../pyth/price-monitor'
|
||||
import { getMergedConfig, TradingConfig, getMarketConfig } from '../../config/trading'
|
||||
import { updateTradeExit, updateTradeState, getOpenTrades } from '../database/trades'
|
||||
import { sendPositionClosedNotification } from '../notifications/telegram'
|
||||
|
||||
export interface ActiveTrade {
|
||||
id: string
|
||||
@@ -328,6 +329,20 @@ export class PositionManager {
|
||||
maxAdversePrice: trade.maxAdversePrice,
|
||||
})
|
||||
console.log(`💾 Ghost closure saved to database`)
|
||||
|
||||
// Send Telegram notification for ghost closure
|
||||
await sendPositionClosedNotification({
|
||||
symbol: trade.symbol,
|
||||
direction: trade.direction,
|
||||
entryPrice: trade.entryPrice,
|
||||
exitPrice: trade.lastPrice,
|
||||
positionSize: trade.currentSize,
|
||||
realizedPnL: estimatedPnL,
|
||||
exitReason: reason, // e.g., "Ghost position cleanup", "Layer 2: Ghost detected via Drift API"
|
||||
holdTimeSeconds: Math.floor((Date.now() - trade.entryTime) / 1000),
|
||||
maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)),
|
||||
maxGain: Math.max(0, trade.maxFavorableExcursion),
|
||||
})
|
||||
} catch (dbError) {
|
||||
console.error('❌ Failed to save ghost closure:', dbError)
|
||||
}
|
||||
@@ -1214,6 +1229,20 @@ export class PositionManager {
|
||||
|
||||
await this.removeTrade(trade.id)
|
||||
console.log(`✅ Position closed | P&L: $${trade.realizedPnL.toFixed(2)} | Reason: ${reason}`)
|
||||
|
||||
// Send Telegram notification
|
||||
await sendPositionClosedNotification({
|
||||
symbol: trade.symbol,
|
||||
direction: trade.direction,
|
||||
entryPrice: trade.entryPrice,
|
||||
exitPrice: result.closePrice || currentPrice,
|
||||
positionSize: trade.positionSize,
|
||||
realizedPnL: trade.realizedPnL,
|
||||
exitReason: reason,
|
||||
holdTimeSeconds: Math.floor((Date.now() - trade.entryTime) / 1000),
|
||||
maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)),
|
||||
maxGain: Math.max(0, trade.maxFavorableExcursion),
|
||||
})
|
||||
} else {
|
||||
// Partial close (TP1)
|
||||
trade.realizedPnL += result.realizedPnL || 0
|
||||
|
||||
Reference in New Issue
Block a user