Files
trading_bot_v4/lib/notifications/telegram.ts
mindesbunister b1ca454a6f 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
2025-11-16 00:51:56 +01:00

110 lines
3.3 KiB
TypeScript

/**
* Telegram notification utilities
*
* Sends direct notifications to Telegram bot without requiring n8n
*/
interface TelegramNotificationOptions {
symbol: string
direction: 'long' | 'short'
entryPrice: number
exitPrice: number
positionSize: number
realizedPnL: number
exitReason: string
holdTimeSeconds: number
maxDrawdown?: number
maxGain?: number
}
/**
* Send Telegram notification for position closure
*/
export async function sendPositionClosedNotification(options: TelegramNotificationOptions): Promise<void> {
try {
const token = process.env.TELEGRAM_BOT_TOKEN
const chatId = process.env.TELEGRAM_CHAT_ID
if (!token || !chatId) {
console.log('⚠️ Telegram credentials not configured, skipping notification')
return
}
const profitEmoji = options.realizedPnL >= 0 ? '💚' : '🔴'
const exitReasonEmoji = getExitReasonEmoji(options.exitReason)
const directionEmoji = options.direction === 'long' ? '📈' : '📉'
const priceChange = ((options.exitPrice - options.entryPrice) / options.entryPrice * 100) * (options.direction === 'long' ? 1 : -1)
const holdTime = formatHoldTime(options.holdTimeSeconds)
const message = `${exitReasonEmoji} POSITION CLOSED
${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()}
💰 P&L: $${options.realizedPnL.toFixed(2)} (${priceChange >= 0 ? '+' : ''}${priceChange.toFixed(2)}%)
📊 Size: $${options.positionSize.toFixed(2)}
📍 Entry: $${options.entryPrice.toFixed(2)}
🎯 Exit: $${options.exitPrice.toFixed(2)}
⏱ Hold Time: ${holdTime}
🔚 Exit: ${options.exitReason.toUpperCase()}
${options.maxGain ? `\n📈 Max Gain: +${options.maxGain.toFixed(2)}%` : ''}
${options.maxDrawdown ? `\n📉 Max Drawdown: -${options.maxDrawdown.toFixed(2)}%` : ''}`
const url = `https://api.telegram.org/bot${token}/sendMessage`
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text: message,
parse_mode: 'HTML'
})
})
if (!response.ok) {
const errorData = await response.json()
console.error('❌ Telegram notification failed:', errorData)
} else {
console.log('✅ Telegram notification sent')
}
} catch (error) {
console.error('❌ Error sending Telegram notification:', error)
// Don't throw - notification failure shouldn't break position closing
}
}
/**
* Get emoji for exit reason
*/
function getExitReasonEmoji(reason: string): string {
const reasonUpper = reason.toUpperCase()
if (reasonUpper.includes('TP1')) return '🎯'
if (reasonUpper.includes('TP2')) return '🎯🎯'
if (reasonUpper.includes('SL') || reasonUpper.includes('STOP')) return '🛑'
if (reasonUpper.includes('MANUAL')) return '👤'
if (reasonUpper.includes('EMERGENCY')) return '🚨'
if (reasonUpper.includes('GHOST')) return '👻'
return '✅'
}
/**
* Format hold time in human-readable format
*/
function formatHoldTime(seconds: number): string {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
if (hours > 0) {
return `${hours}h ${minutes}m`
} else if (minutes > 0) {
return `${minutes}m ${secs}s`
} else {
return `${secs}s`
}
}