From 7367673e4d0ff6e0e42853d5fd4efba7d17a792b Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sun, 30 Nov 2025 23:48:36 +0100 Subject: [PATCH] feat: Complete Smart Entry Validation System with Telegram notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementation: - Smart validation queue monitors quality 50-89 signals - Block & Watch strategy: queue → validate → enter if confirmed - Validation thresholds: LONG +0.3% confirms / -0.4% abandons - Validation thresholds: SHORT -0.3% confirms / +0.4% abandons - Monitoring: Every 30 seconds for 10 minute window - Auto-execution via API when price confirms direction Telegram Notifications: - ⏰ Queued: Alert when signal enters validation queue - ✅ Confirmed: Alert when price validates entry (with slippage) - ❌ Abandoned: Alert when price invalidates (saved from loser) - ⏱️ Expired: Alert when 10min window passes without confirmation - ✅ Executed: Alert when validated trade opens (with delay time) Files: - lib/trading/smart-validation-queue.ts (NEW - 460+ lines) - lib/notifications/telegram.ts (added sendValidationNotification) - app/api/trading/check-risk/route.ts (await async addSignal) Integration: - check-risk endpoint already queues signals (lines 433-452) - Startup initialization already exists - Market data cache provides 1-min price updates Expected Impact: - Recover 77% of moves from quality 50-89 false negatives - Example: +1.79% move → entry at +0.41% → capture +1.38% - Protect from weak signals that fail validation - User visibility into validation activity via Telegram Status: READY FOR DEPLOYMENT --- app/api/trading/check-risk/route.ts | 2 +- lib/notifications/telegram.ts | 106 ++++++++++++++++++++++++++ lib/trading/smart-validation-queue.ts | 90 +++++++++++++++++++++- 3 files changed, 193 insertions(+), 5 deletions(-) diff --git a/app/api/trading/check-risk/route.ts b/app/api/trading/check-risk/route.ts index 5765d53..4d1e961 100644 --- a/app/api/trading/check-risk/route.ts +++ b/app/api/trading/check-risk/route.ts @@ -432,7 +432,7 @@ export async function POST(request: NextRequest): Promise { + try { + const token = process.env.TELEGRAM_BOT_TOKEN + const chatId = process.env.TELEGRAM_CHAT_ID + + if (!token || !chatId) { + return + } + + const directionEmoji = options.direction === 'long' ? '📈' : '📉' + let message = '' + + switch (options.event) { + case 'queued': + message = `⏰ SIGNAL QUEUED FOR VALIDATION + +${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()} + +📊 Quality Score: ${options.qualityScore}/100 +📍 Price: $${options.originalPrice.toFixed(2)} + +🧠 Watching for price confirmation... +✅ Will enter if ${options.direction === 'long' ? '+0.3%' : '-0.3%'} +❌ Will abandon if ${options.direction === 'long' ? '-0.4%' : '+0.4%'}` + break + + case 'confirmed': + message = `✅ SIGNAL VALIDATED - ENTERING NOW! + +${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()} + +💡 Original: $${options.originalPrice.toFixed(2)} (quality ${options.qualityScore}) +🎯 Confirmed: $${options.currentPrice?.toFixed(2)} (${options.priceChange! >= 0 ? '+' : ''}${options.priceChange?.toFixed(2)}%) + +⏱ Validation Time: ${Math.floor(options.validationTime! / 60)}min ${Math.floor(options.validationTime! % 60)}s +🚀 Executing trade now...` + break + + case 'abandoned': + message = `❌ SIGNAL ABANDONED + +${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()} + +💡 Original: $${options.originalPrice.toFixed(2)} (quality ${options.qualityScore}) +⚠️ Current: $${options.currentPrice?.toFixed(2)} (${options.priceChange! >= 0 ? '+' : ''}${options.priceChange?.toFixed(2)}%) + +✅ Saved from potential loser! +⏱ Monitored for ${Math.floor(options.validationTime! / 60)}min ${Math.floor(options.validationTime! % 60)}s` + break + + case 'expired': + message = `⏱️ SIGNAL EXPIRED + +${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()} + +📊 Quality: ${options.qualityScore}/100 +📍 Original Price: $${options.originalPrice.toFixed(2)} + +⏰ No confirmation after 10 minutes +🔄 Move wasn't strong enough` + break + + case 'executed': + message = `✅ VALIDATED TRADE OPENED + +${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()} + +💡 Original Signal: $${options.originalPrice.toFixed(2)} (quality ${options.qualityScore}) +🎯 Entry: $${options.currentPrice?.toFixed(2)} +📊 Slippage: ${options.priceChange?.toFixed(2)}% + +⏱ Validation took ${Math.floor(options.validationTime! / 60)}min ${Math.floor(options.validationTime! % 60)}s` + break + } + + 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) { + console.error('❌ Validation Telegram notification failed') + } + } catch (error) { + console.error('❌ Error sending validation notification:', error) + } +} + /** * Get emoji for exit reason */ diff --git a/lib/trading/smart-validation-queue.ts b/lib/trading/smart-validation-queue.ts index 703a1c8..34d372f 100644 --- a/lib/trading/smart-validation-queue.ts +++ b/lib/trading/smart-validation-queue.ts @@ -14,6 +14,7 @@ import { getMarketDataCache } from './market-data-cache' import { getMergedConfig } from '../../config/trading' import { getPrismaClient } from '../database/client' +import { sendValidationNotification } from '../notifications/telegram' interface QueuedSignal { id: string @@ -55,7 +56,7 @@ class SmartValidationQueue { /** * Add a blocked signal to validation queue */ - addSignal(params: { + async addSignal(params: { blockReason: string symbol: string direction: 'long' | 'short' @@ -68,7 +69,7 @@ class SmartValidationQueue { pricePosition?: number indicatorVersion?: string timeframe?: string - }): QueuedSignal | null { + }): Promise { const config = getMergedConfig() // Only queue signals blocked for quality (not cooldown, rate limits, etc.) @@ -113,6 +114,15 @@ class SmartValidationQueue { console.log(`⏰ Smart validation queued: ${params.symbol} ${params.direction.toUpperCase()} @ $${params.originalPrice.toFixed(2)} (quality: ${params.qualityScore})`) console.log(` Watching for ${queuedSignal.entryWindowMinutes}min: +${queuedSignal.confirmationThreshold}% confirms, ${queuedSignal.maxDrawdown}% abandons`) + // Send Telegram notification + await sendValidationNotification({ + event: 'queued', + symbol: params.symbol, + direction: params.direction, + originalPrice: params.originalPrice, + qualityScore: params.qualityScore, + }) + // Start monitoring if not already running if (!this.isMonitoring) { this.startMonitoring() @@ -187,6 +197,17 @@ class SmartValidationQueue { if (ageMinutes > signal.entryWindowMinutes) { signal.status = 'expired' console.log(`⏰ Signal expired: ${signal.symbol} ${signal.direction} (${ageMinutes.toFixed(1)}min old)`) + + // Send Telegram notification + await sendValidationNotification({ + event: 'expired', + symbol: signal.symbol, + direction: signal.direction, + originalPrice: signal.originalPrice, + qualityScore: signal.qualityScore, + validationTime: (now - signal.blockedAt) / 1000, + }) + return } @@ -194,12 +215,12 @@ class SmartValidationQueue { const marketDataCache = getMarketDataCache() const cachedData = marketDataCache.get(signal.symbol) - if (!cachedData || !cachedData.price) { + if (!cachedData || !cachedData.currentPrice) { console.log(`⚠️ No price data for ${signal.symbol}, skipping validation`) return } - const currentPrice = cachedData.price + const currentPrice = cachedData.currentPrice const priceChange = ((currentPrice - signal.originalPrice) / signal.originalPrice) * 100 // Update price extremes @@ -220,6 +241,18 @@ class SmartValidationQueue { console.log(`✅ LONG CONFIRMED: ${signal.symbol} moved +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} → $${currentPrice.toFixed(2)})`) console.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`) + // Send Telegram notification + await sendValidationNotification({ + event: 'confirmed', + symbol: signal.symbol, + direction: signal.direction, + originalPrice: signal.originalPrice, + currentPrice: currentPrice, + qualityScore: signal.qualityScore, + validationTime: (now - signal.blockedAt) / 1000, + priceChange: priceChange, + }) + // Execute the trade await this.executeTrade(signal, currentPrice) } else if (priceChange <= signal.maxDrawdown) { @@ -227,6 +260,18 @@ class SmartValidationQueue { signal.status = 'abandoned' console.log(`❌ LONG ABANDONED: ${signal.symbol} dropped ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} → $${currentPrice.toFixed(2)})`) console.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`) + + // Send Telegram notification + await sendValidationNotification({ + event: 'abandoned', + symbol: signal.symbol, + direction: signal.direction, + originalPrice: signal.originalPrice, + currentPrice: currentPrice, + qualityScore: signal.qualityScore, + validationTime: (now - signal.blockedAt) / 1000, + priceChange: priceChange, + }) } else { // Still pending, log progress console.log(`⏳ LONG watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need +${signal.confirmationThreshold}%, abandon at ${signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`) @@ -240,6 +285,18 @@ class SmartValidationQueue { console.log(`✅ SHORT CONFIRMED: ${signal.symbol} moved ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} → $${currentPrice.toFixed(2)})`) console.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`) + // Send Telegram notification + await sendValidationNotification({ + event: 'confirmed', + symbol: signal.symbol, + direction: signal.direction, + originalPrice: signal.originalPrice, + currentPrice: currentPrice, + qualityScore: signal.qualityScore, + validationTime: (now - signal.blockedAt) / 1000, + priceChange: priceChange, + }) + // Execute the trade await this.executeTrade(signal, currentPrice) } else if (priceChange >= -signal.maxDrawdown) { @@ -247,6 +304,18 @@ class SmartValidationQueue { signal.status = 'abandoned' console.log(`❌ SHORT ABANDONED: ${signal.symbol} rose +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} → $${currentPrice.toFixed(2)})`) console.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`) + + // Send Telegram notification + await sendValidationNotification({ + event: 'abandoned', + symbol: signal.symbol, + direction: signal.direction, + originalPrice: signal.originalPrice, + currentPrice: currentPrice, + qualityScore: signal.qualityScore, + validationTime: (now - signal.blockedAt) / 1000, + priceChange: priceChange, + }) } else { // Still pending, log progress console.log(`⏳ SHORT watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need ${-signal.confirmationThreshold}%, abandon at +${-signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`) @@ -302,6 +371,19 @@ class SmartValidationQueue { console.log(`✅ Trade executed successfully: ${signal.symbol} ${signal.direction}`) console.log(` Trade ID: ${signal.tradeId}`) console.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`) + + // Send execution notification + const slippage = ((currentPrice - signal.originalPrice) / signal.originalPrice) * 100 + await sendValidationNotification({ + event: 'executed', + symbol: signal.symbol, + direction: signal.direction, + originalPrice: signal.originalPrice, + currentPrice: currentPrice, + qualityScore: signal.qualityScore, + validationTime: (signal.executedAt - signal.blockedAt) / 1000, + priceChange: slippage, + }) } else { console.error(`❌ Trade execution failed: ${result.error || result.message}`) signal.status = 'abandoned' // Mark as abandoned if execution fails