diff --git a/app/api/price-monitor/route.js b/app/api/price-monitor/route.js new file mode 100644 index 0000000..ccc7b92 --- /dev/null +++ b/app/api/price-monitor/route.js @@ -0,0 +1,84 @@ +import { NextResponse } from 'next/server' +import priceMonitor from '../../../lib/price-monitor' + +export async function GET() { + try { + // Get current monitoring data for all active trades + const monitoringData = await priceMonitor.getTradeMonitoringData() + const activeAlerts = priceMonitor.getActiveAlerts() + + // Get price cache for quick reference + const priceData = {} + const symbols = [...new Set(monitoringData.map(trade => trade.symbol))] + + for (const symbol of symbols) { + const price = priceMonitor.getCurrentPrice(symbol) + if (price) { + priceData[symbol] = price + } + } + + return NextResponse.json({ + success: true, + data: { + trades: monitoringData, + alerts: activeAlerts, + prices: priceData, + lastUpdated: new Date().toISOString(), + monitoringActive: true + } + }) + } catch (error) { + console.error('Error getting trade monitoring data:', error) + return NextResponse.json({ + success: false, + error: 'Failed to get monitoring data', + details: error.message + }, { status: 500 }) + } +} + +export async function POST(request) { + try { + const { action, symbol } = await request.json() + + switch (action) { + case 'force_update': + if (symbol) { + const price = await priceMonitor.forceUpdatePrice(symbol) + return NextResponse.json({ + success: true, + data: { symbol, price, updated: new Date().toISOString() } + }) + } + break + + case 'start_monitoring': + await priceMonitor.startMonitoring() + return NextResponse.json({ + success: true, + message: 'Price monitoring started' + }) + + case 'stop_monitoring': + await priceMonitor.stopMonitoring() + return NextResponse.json({ + success: true, + message: 'Price monitoring stopped' + }) + + default: + return NextResponse.json({ + success: false, + error: 'Invalid action' + }, { status: 400 }) + } + } catch (error) { + console.error('Error in price monitoring action:', error) + return NextResponse.json({ + success: false, + error: 'Failed to execute action', + details: error.message + }, { status: 500 }) + } +} diff --git a/app/automation/page.js b/app/automation/page.js index 42036d1..803cc97 100644 --- a/app/automation/page.js +++ b/app/automation/page.js @@ -1,5 +1,6 @@ 'use client' import React, { useState, useEffect } from 'react' +import RealTimePriceMonitor from '../../components/RealTimePriceMonitor' export default function AutomationPage() { const [config, setConfig] = useState({ @@ -539,6 +540,9 @@ export default function AutomationPage() { )} + + {/* Real-Time Price Monitor */} + {/* Status and Performance */} diff --git a/components/RealTimePriceMonitor.tsx b/components/RealTimePriceMonitor.tsx new file mode 100644 index 0000000..ccb1582 --- /dev/null +++ b/components/RealTimePriceMonitor.tsx @@ -0,0 +1,330 @@ +"use client" +import React, { useState, useEffect } from 'react' + +interface TradeMonitoring { + tradeId: string + symbol: string + side: 'BUY' | 'SELL' + entryPrice: number + stopLoss?: number + takeProfit?: number + currentPrice?: number + currentPnL?: number + pnlPercentage?: number + distanceToTP?: number + distanceToSL?: number + status: 'ACTIVE' | 'APPROACHING_TP' | 'APPROACHING_SL' | 'CRITICAL' +} + +interface PriceAlert { + id: string + symbol: string + tradeId: string + alertType: 'TP_APPROACH' | 'SL_APPROACH' | 'BREAKOUT' | 'REVERSAL' + currentPrice: number + targetPrice: number + threshold: number + triggered: boolean + createdAt: string +} + +export default function RealTimePriceMonitor() { + const [monitoringData, setMonitoringData] = useState([]) + const [alerts, setAlerts] = useState([]) + const [prices, setPrices] = useState>({}) + const [lastUpdated, setLastUpdated] = useState('') + const [monitoringActive, setMonitoringActive] = useState(false) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetchMonitoringData() + + // Update every 30 seconds for UI refresh + const interval = setInterval(fetchMonitoringData, 30000) + + return () => clearInterval(interval) + }, []) + + const fetchMonitoringData = async () => { + try { + const response = await fetch('/api/price-monitor') + const data = await response.json() + + if (data.success) { + setMonitoringData(data.data.trades || []) + setAlerts(data.data.alerts || []) + setPrices(data.data.prices || {}) + setLastUpdated(data.data.lastUpdated || '') + setMonitoringActive(data.data.monitoringActive || false) + } + } catch (error) { + console.error('Error fetching monitoring data:', error) + } finally { + setLoading(false) + } + } + + const startMonitoring = async () => { + try { + const response = await fetch('/api/price-monitor', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'start_monitoring' }) + }) + + if (response.ok) { + setMonitoringActive(true) + await fetchMonitoringData() + } + } catch (error) { + console.error('Error starting monitoring:', error) + } + } + + const stopMonitoring = async () => { + try { + const response = await fetch('/api/price-monitor', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'stop_monitoring' }) + }) + + if (response.ok) { + setMonitoringActive(false) + } + } catch (error) { + console.error('Error stopping monitoring:', error) + } + } + + const forceUpdatePrice = async (symbol: string) => { + try { + const response = await fetch('/api/price-monitor', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'force_update', symbol }) + }) + + if (response.ok) { + await fetchMonitoringData() + } + } catch (error) { + console.error('Error force updating price:', error) + } + } + + const getStatusColor = (status: TradeMonitoring['status']) => { + switch (status) { + case 'CRITICAL': return 'text-red-400 bg-red-900/20 border-red-600' + case 'APPROACHING_TP': return 'text-green-400 bg-green-900/20 border-green-600' + case 'APPROACHING_SL': return 'text-yellow-400 bg-yellow-900/20 border-yellow-600' + default: return 'text-blue-400 bg-blue-900/20 border-blue-600' + } + } + + const getPnLColor = (pnl?: number) => { + if (!pnl) return 'text-gray-400' + return pnl >= 0 ? 'text-green-400' : 'text-red-400' + } + + const formatCurrency = (amount?: number) => { + if (amount === undefined || amount === null) return 'N/A' + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 4 + }).format(amount) + } + + const formatPercentage = (pct?: number) => { + if (pct === undefined || pct === null) return 'N/A' + return `${pct >= 0 ? '+' : ''}${pct.toFixed(2)}%` + } + + const formatDistance = (distance?: number) => { + if (distance === undefined || distance === null) return 'N/A' + return `${(distance * 100).toFixed(1)}%` + } + + if (loading) { + return ( +
+
+
+
+
+
+
+
+
+ ) + } + + return ( +
+ {/* Header Controls */} +
+
+
+

Real-Time Price Monitor

+

+ Tracks active trades and triggers analysis when approaching TP/SL +

+
+
+
+ {monitoringActive ? '🟒 Active' : 'πŸ”΄ Stopped'} +
+ {monitoringActive ? ( + + ) : ( + + )} +
+
+ + {lastUpdated && ( +
+ Last updated: {new Date(lastUpdated).toLocaleString()} +
+ )} +
+ + {/* Active Alerts */} + {alerts.length > 0 && ( +
+

🚨 Active Alerts

+
+ {alerts.map((alert) => ( +
+
+
+ {alert.symbol} + + {alert.alertType.replace('_', ' ')} + +
+
+ Current: {formatCurrency(alert.currentPrice)} β†’ Target: {formatCurrency(alert.targetPrice)} +
+
+
+ ))} +
+
+ )} + + {/* Current Prices */} +
+

πŸ’° Current Prices

+
+ {Object.entries(prices).map(([symbol, price]) => ( +
+
+ {symbol} + +
+
+ {formatCurrency(price)} +
+
+ ))} +
+
+ + {/* Active Trades Monitoring */} +
+

πŸ“Š Active Trades Monitor

+ + {monitoringData.length === 0 ? ( +

No active trades to monitor

+ ) : ( +
+ {monitoringData.map((trade) => ( +
+ {/* Trade Header */} +
+
+ + {trade.side} + + {trade.symbol} + + {trade.status.replace('_', ' ')} + +
+
+
+ Current: {formatCurrency(trade.currentPrice)} +
+
+ Entry: {formatCurrency(trade.entryPrice)} +
+
+
+ + {/* Trade Details Grid */} +
+
+
P&L
+
+ {formatCurrency(trade.currentPnL)} +
+
+ {formatPercentage(trade.pnlPercentage)} +
+
+ +
+
Take Profit
+
{formatCurrency(trade.takeProfit)}
+
+ {trade.distanceToTP ? `${formatDistance(trade.distanceToTP)} away` : 'N/A'} +
+
+ +
+
Stop Loss
+
{formatCurrency(trade.stopLoss)}
+
+ {trade.distanceToSL ? `${formatDistance(trade.distanceToSL)} away` : 'N/A'} +
+
+ +
+
Status
+
+ {trade.status === 'CRITICAL' ? '🚨 Critical' : + trade.status === 'APPROACHING_TP' ? '🎯 Near TP' : + trade.status === 'APPROACHING_SL' ? '⚠️ Near SL' : 'βœ… Normal'} +
+
+
+
+ ))} +
+ )} +
+
+ ) +} diff --git a/lib/automation-service-simple.ts b/lib/automation-service-simple.ts index b2fff84..eacbfe8 100644 --- a/lib/automation-service-simple.ts +++ b/lib/automation-service-simple.ts @@ -3,9 +3,10 @@ import { aiAnalysisService, AnalysisResult } from './ai-analysis' import { jupiterDEXService } from './jupiter-dex-service' import { enhancedScreenshotService } from './enhanced-screenshot-simple' import { TradingViewCredentials } from './tradingview-automation' -import { progressTracker } from './progress-tracker' +import { progressTracker, ProgressStatus } from './progress-tracker' import aggressiveCleanup from './aggressive-cleanup' import { analysisCompletionFlag } from './analysis-completion-flag' +import priceMonitorService from './price-monitor' const prisma = new PrismaClient() @@ -110,6 +111,31 @@ export class AutomationService { // Start automation cycle this.startAutomationCycle() + // Start price monitoring + await priceMonitorService.startMonitoring() + + // Set up price monitor event listeners for automatic analysis triggering + priceMonitorService.on('tp_approach', async (data) => { + if (data.symbol === this.config?.symbol) { + console.log(`🎯 TP approach detected for ${data.symbol}, triggering analysis...`) + await this.triggerPriceBasedAnalysis('TP_APPROACH', data) + } + }) + + priceMonitorService.on('sl_approach', async (data) => { + if (data.symbol === this.config?.symbol) { + console.log(`⚠️ SL approach detected for ${data.symbol}, triggering analysis...`) + await this.triggerPriceBasedAnalysis('SL_APPROACH', data) + } + }) + + priceMonitorService.on('critical_level', async (data) => { + if (data.symbol === this.config?.symbol) { + console.log(`🚨 Critical level reached for ${data.symbol}, triggering urgent analysis...`) + await this.triggerPriceBasedAnalysis('CRITICAL', data) + } + }) + return true } catch (error) { console.error('Failed to start automation:', error) @@ -791,6 +817,14 @@ ${validResults.map(r => `β€’ ${r.timeframe}: ${r.analysis?.recommendation} (${r. this.intervalId = null } + // Stop price monitoring + try { + await priceMonitorService.stopMonitoring() + console.log('πŸ“Š Price monitoring stopped') + } catch (error) { + console.error('Failed to stop price monitoring:', error) + } + // Update database session status to STOPPED if (this.config) { await prisma.automationSession.updateMany({ @@ -951,6 +985,111 @@ ${validResults.map(r => `β€’ ${r.timeframe}: ${r.analysis?.recommendation} (${r. } } } + + /** + * Trigger analysis based on price movement alerts + */ + private async triggerPriceBasedAnalysis( + trigger: 'TP_APPROACH' | 'SL_APPROACH' | 'CRITICAL', + data: any + ): Promise { + if (!this.config || !this.isRunning) { + console.log('❌ Cannot trigger price-based analysis: automation not running') + return + } + + const sessionId = `price-trigger-${Date.now()}` + + try { + console.log(`πŸ”₯ Price-based analysis triggered by ${trigger} for ${data.symbol}`) + console.log(`πŸ“Š Current price: $${data.currentPrice}, Target: $${data.targetPrice}`) + + // Create progress tracker for this analysis + const steps = [ + { id: 'trigger', title: 'Triggered by price movement', description: 'Analysis initiated by price alert', status: 'pending' as ProgressStatus }, + { id: 'screenshot', title: 'Capturing screenshots', description: 'Taking fresh market screenshots', status: 'pending' as ProgressStatus }, + { id: 'analysis', title: 'Running AI analysis', description: 'Analyzing current market conditions', status: 'pending' as ProgressStatus }, + { id: 'evaluation', title: 'Evaluating position', description: 'Checking position adjustments', status: 'pending' as ProgressStatus }, + { id: 'complete', title: 'Analysis complete', description: 'Price-based analysis finished', status: 'pending' as ProgressStatus } + ] + + progressTracker.createSession(sessionId, steps) + + progressTracker.updateStep(sessionId, 'trigger', 'active', `${trigger}: ${data.symbol} at $${data.currentPrice}`) + + // Run enhanced screenshot capture with current symbol/timeframe + progressTracker.updateStep(sessionId, 'screenshot', 'active') + + const screenshotConfig = { + symbol: this.config.symbol, + timeframe: this.config.timeframe, + layouts: ['ai', 'diy'], + sessionId + } + + const screenshots = await enhancedScreenshotService.captureWithLogin(screenshotConfig) + + if (!screenshots || screenshots.length === 0) { + throw new Error('Failed to capture screenshots for price-based analysis') + } + + progressTracker.updateStep(sessionId, 'screenshot', 'completed', `Captured ${screenshots.length} screenshots`) + progressTracker.updateStep(sessionId, 'analysis', 'active') + + // Simplified analysis call - just use the first screenshot + const analysisResult = await aiAnalysisService.analyzeScreenshot(screenshots[0]) + + if (!analysisResult) { + throw new Error('AI analysis returned null result') + } + + progressTracker.updateStep(sessionId, 'analysis', 'completed', `Analysis: ${analysisResult.recommendation}`) + progressTracker.updateStep(sessionId, 'evaluation', 'active') + + // Store the triggered analysis in trading journal + await prisma.tradingJournal.create({ + data: { + userId: this.config.userId, + screenshotUrl: screenshots[0] || '', + aiAnalysis: analysisResult.reasoning || 'No analysis available', + confidence: analysisResult.confidence || 0, + recommendation: analysisResult.recommendation || 'HOLD', + symbol: this.config.symbol, + timeframe: this.config.timeframe, + sessionId, + notes: `Price-triggered analysis: ${trigger} - Current: $${data.currentPrice}, Target: $${data.targetPrice}`, + marketSentiment: analysisResult.marketSentiment || 'Unknown', + tradingMode: this.config.mode, + isAutomated: true, + priceAtAnalysis: data.currentPrice, + marketCondition: trigger, + createdAt: new Date() + } + }) + + // Log important insights for potential position adjustments + if (analysisResult.recommendation === 'SELL' && trigger === 'SL_APPROACH') { + console.log('⚠️ AI recommends SELL while approaching Stop Loss - consider early exit') + } else if (analysisResult.recommendation === 'BUY' && trigger === 'TP_APPROACH') { + console.log('🎯 AI recommends BUY while approaching Take Profit - consider extending position') + } + + progressTracker.updateStep(sessionId, 'evaluation', 'completed') + progressTracker.updateStep(sessionId, 'complete', 'completed', + `${analysisResult.recommendation} (${analysisResult.confidence}% confidence)`) + + console.log(`βœ… Price-based analysis completed (${trigger}): ${analysisResult.recommendation} with ${analysisResult.confidence}% confidence`) + + } catch (error) { + console.error(`❌ Price-based analysis failed (${trigger}):`, error) + + progressTracker.updateStep(sessionId, 'complete', 'error', + `Error: ${error instanceof Error ? error.message : 'Unknown error'}`) + + this.stats.errorCount++ + this.stats.lastError = error instanceof Error ? error.message : 'Unknown error' + } + } } export const automationService = new AutomationService() diff --git a/lib/price-monitor.ts b/lib/price-monitor.ts new file mode 100644 index 0000000..8608a19 --- /dev/null +++ b/lib/price-monitor.ts @@ -0,0 +1,377 @@ +// Real-time price monitoring service with automatic analysis triggering +import { EventEmitter } from 'events' +import { PrismaClient } from '@prisma/client' +import PriceFetcher from './price-fetcher' + +const prisma = new PrismaClient() + +export interface PriceAlert { + id: string + symbol: string + tradeId: string + alertType: 'TP_APPROACH' | 'SL_APPROACH' | 'BREAKOUT' | 'REVERSAL' + currentPrice: number + targetPrice: number + threshold: number + triggered: boolean + createdAt: Date +} + +export interface TradeMonitoring { + tradeId: string + symbol: string + side: 'BUY' | 'SELL' + entryPrice: number + stopLoss?: number + takeProfit?: number + currentPrice?: number + currentPnL?: number + pnlPercentage?: number + distanceToTP?: number + distanceToSL?: number + status: 'ACTIVE' | 'APPROACHING_TP' | 'APPROACHING_SL' | 'CRITICAL' +} + +class PriceMonitor extends EventEmitter { + private static instance: PriceMonitor + private monitoringInterval: NodeJS.Timeout | null = null + private priceCache: Map = new Map() + private alerts: Map = new Map() + private isRunning = false + private readonly UPDATE_INTERVAL = 5 * 60 * 1000 // 5 minutes + private readonly CACHE_DURATION = 6 * 60 * 1000 // 6 minutes (slightly longer than update) + + // Thresholds for triggering analysis + private readonly TP_APPROACH_THRESHOLD = 0.15 // 15% away from TP + private readonly SL_APPROACH_THRESHOLD = 0.25 // 25% away from SL + private readonly CRITICAL_THRESHOLD = 0.05 // 5% away from TP/SL + + private constructor() { + super() + } + + static getInstance(): PriceMonitor { + if (!PriceMonitor.instance) { + PriceMonitor.instance = new PriceMonitor() + } + return PriceMonitor.instance + } + + async startMonitoring(): Promise { + if (this.isRunning) { + console.log('πŸ“Š Price monitor already running') + return + } + + this.isRunning = true + console.log('πŸš€ Starting real-time price monitoring service') + console.log(`⏱️ Update interval: ${this.UPDATE_INTERVAL / 1000}s (5 minutes)`) + + // Initial price check + await this.updatePricesAndAnalyze() + + // Set up periodic monitoring + this.monitoringInterval = setInterval(async () => { + try { + await this.updatePricesAndAnalyze() + } catch (error) { + console.error('❌ Error in price monitoring cycle:', error) + } + }, this.UPDATE_INTERVAL) + + console.log('βœ… Price monitoring service started') + } + + async stopMonitoring(): Promise { + if (this.monitoringInterval) { + clearInterval(this.monitoringInterval) + this.monitoringInterval = null + } + this.isRunning = false + console.log('⏹️ Price monitoring service stopped') + } + + private async updatePricesAndAnalyze(): Promise { + console.log('πŸ“Š Price monitor: Updating prices and checking positions...') + + try { + // Get all active trades + const activeTrades = await this.getActiveTradesForMonitoring() + + if (activeTrades.length === 0) { + console.log('πŸ“Š No active trades to monitor') + return + } + + console.log(`πŸ“Š Monitoring ${activeTrades.length} active trades`) + + // Get unique symbols + const symbols = [...new Set(activeTrades.map(trade => trade.symbol))] + + // Update prices for all symbols + const priceUpdates = await this.fetchPricesForSymbols(symbols) + + // Analyze each trade + const analysisRequests: string[] = [] + const updatedTrades: TradeMonitoring[] = [] + + for (const trade of activeTrades) { + const currentPrice = priceUpdates.get(trade.symbol) + + if (!currentPrice) { + console.log(`⚠️ Could not get price for ${trade.symbol}`) + continue + } + + const monitoring = await this.analyzeTradePosition(trade, currentPrice) + updatedTrades.push(monitoring) + + // Update trade in database with current PnL + await this.updateTradeCurrentData(trade.id, currentPrice, monitoring.currentPnL!) + + // Check if analysis is needed + const needsAnalysis = this.shouldTriggerAnalysis(monitoring) + if (needsAnalysis) { + analysisRequests.push(trade.symbol) + console.log(`🎯 Analysis triggered for ${trade.symbol}: ${monitoring.status}`) + } + } + + // Emit events for UI updates + this.emit('tradesUpdated', updatedTrades) + this.emit('pricesUpdated', priceUpdates) + + // Trigger analysis for symbols that need it (deduplicated) + const uniqueAnalysisRequests = [...new Set(analysisRequests)] + for (const symbol of uniqueAnalysisRequests) { + await this.triggerSmartAnalysis(symbol, 'PRICE_MOVEMENT') + } + + } catch (error) { + console.error('❌ Error in price monitoring update:', error) + } + } + + private async getActiveTradesForMonitoring(): Promise { + return await prisma.trade.findMany({ + where: { + status: 'OPEN', + isAutomated: true + }, + select: { + id: true, + symbol: true, + side: true, + amount: true, + price: true, + entryPrice: true, + stopLoss: true, + takeProfit: true, + leverage: true, + createdAt: true + } + }) + } + + private async fetchPricesForSymbols(symbols: string[]): Promise> { + const priceUpdates = new Map() + + for (const symbol of symbols) { + try { + // Check cache first + const cached = this.priceCache.get(symbol) + if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) { + priceUpdates.set(symbol, cached.price) + continue + } + + // Fetch new price + const price = await PriceFetcher.getCurrentPrice(symbol) + if (price > 0) { + priceUpdates.set(symbol, price) + this.priceCache.set(symbol, { price, timestamp: Date.now() }) + console.log(`πŸ’° ${symbol}: $${price.toFixed(2)}`) + } + + // Rate limiting - small delay between requests + await new Promise(resolve => setTimeout(resolve, 100)) + + } catch (error) { + console.error(`❌ Error fetching price for ${symbol}:`, error) + } + } + + return priceUpdates + } + + private async analyzeTradePosition(trade: any, currentPrice: number): Promise { + const entryPrice = trade.entryPrice || trade.price + const leverage = trade.leverage || 1 + + // Calculate PnL + const priceChange = trade.side === 'BUY' ? + (currentPrice - entryPrice) : + (entryPrice - currentPrice) + + const currentPnL = priceChange * trade.amount * leverage + const pnlPercentage = (priceChange / entryPrice) * 100 * leverage + + // Calculate distances to TP/SL + let distanceToTP: number | undefined + let distanceToSL: number | undefined + + if (trade.takeProfit) { + distanceToTP = Math.abs(currentPrice - trade.takeProfit) / Math.abs(trade.takeProfit - entryPrice) + } + + if (trade.stopLoss) { + distanceToSL = Math.abs(currentPrice - trade.stopLoss) / Math.abs(entryPrice - trade.stopLoss) + } + + // Determine status + let status: TradeMonitoring['status'] = 'ACTIVE' + + if (distanceToTP !== undefined && distanceToTP <= this.CRITICAL_THRESHOLD) { + status = 'CRITICAL' + } else if (distanceToSL !== undefined && distanceToSL <= this.CRITICAL_THRESHOLD) { + status = 'CRITICAL' + } else if (distanceToTP !== undefined && distanceToTP <= this.TP_APPROACH_THRESHOLD) { + status = 'APPROACHING_TP' + } else if (distanceToSL !== undefined && distanceToSL <= this.SL_APPROACH_THRESHOLD) { + status = 'APPROACHING_SL' + } + + return { + tradeId: trade.id, + symbol: trade.symbol, + side: trade.side, + entryPrice, + stopLoss: trade.stopLoss, + takeProfit: trade.takeProfit, + currentPrice, + currentPnL, + pnlPercentage, + distanceToTP, + distanceToSL, + status + } + } + + private shouldTriggerAnalysis(monitoring: TradeMonitoring): boolean { + // Generate alert ID + const alertId = `${monitoring.tradeId}_${monitoring.status}_${Date.now()}` + + // Check if we already triggered for this situation recently + const recentAlert = Array.from(this.alerts.values()).find(alert => + alert.tradeId === monitoring.tradeId && + alert.alertType.includes(monitoring.status.split('_')[1] || monitoring.status) && + Date.now() - alert.createdAt.getTime() < 30 * 60 * 1000 // 30 minutes cooldown + ) + + if (recentAlert) { + return false + } + + // Trigger analysis for critical situations or approaches + const shouldTrigger = monitoring.status === 'CRITICAL' || + monitoring.status === 'APPROACHING_TP' || + monitoring.status === 'APPROACHING_SL' + + if (shouldTrigger) { + // Create alert + const alert: PriceAlert = { + id: alertId, + symbol: monitoring.symbol, + tradeId: monitoring.tradeId, + alertType: monitoring.status === 'APPROACHING_TP' ? 'TP_APPROACH' : + monitoring.status === 'APPROACHING_SL' ? 'SL_APPROACH' : 'BREAKOUT', + currentPrice: monitoring.currentPrice!, + targetPrice: monitoring.status === 'APPROACHING_TP' ? monitoring.takeProfit! : monitoring.stopLoss!, + threshold: monitoring.status === 'APPROACHING_TP' ? monitoring.distanceToTP! : monitoring.distanceToSL!, + triggered: true, + createdAt: new Date() + } + + this.alerts.set(alertId, alert) + this.emit('alert', alert) + } + + return shouldTrigger + } + + private async updateTradeCurrentData(tradeId: string, currentPrice: number, currentPnL: number): Promise { + try { + await prisma.trade.update({ + where: { id: tradeId }, + data: { + // Store current price and PnL for reference + learningData: { + currentPrice, + currentPnL, + lastUpdated: new Date().toISOString() + } + } + }) + } catch (error) { + console.error(`❌ Error updating trade ${tradeId}:`, error) + } + } + + private async triggerSmartAnalysis(symbol: string, reason: string): Promise { + try { + console.log(`🎯 Triggering smart analysis for ${symbol} (${reason})`) + + // Import automation service + const { AutomationService } = await import('./automation-service-simple') + + // Get the service instance or create analysis request + // This would trigger a new analysis cycle for the specific symbol + this.emit('analysisRequested', { symbol, reason, timestamp: new Date() }) + + } catch (error) { + console.error(`❌ Error triggering analysis for ${symbol}:`, error) + } + } + + // Public methods for external access + getCurrentPrice(symbol: string): number | null { + const cached = this.priceCache.get(symbol) + return cached ? cached.price : null + } + + getActiveAlerts(): PriceAlert[] { + return Array.from(this.alerts.values()).filter(alert => alert.triggered) + } + + async getTradeMonitoringData(): Promise { + const activeTrades = await this.getActiveTradesForMonitoring() + const results: TradeMonitoring[] = [] + + for (const trade of activeTrades) { + const currentPrice = this.getCurrentPrice(trade.symbol) + if (currentPrice) { + const monitoring = await this.analyzeTradePosition(trade, currentPrice) + results.push(monitoring) + } + } + + return results + } + + // Manual price update (for immediate checks) + async forceUpdatePrice(symbol: string): Promise { + try { + const price = await PriceFetcher.getCurrentPrice(symbol) + if (price > 0) { + this.priceCache.set(symbol, { price, timestamp: Date.now() }) + return price + } + } catch (error) { + console.error(`❌ Error force updating price for ${symbol}:`, error) + } + return null + } +} + +export const priceMonitor = PriceMonitor.getInstance() +export default priceMonitor diff --git a/prisma/prisma/dev.db b/prisma/prisma/dev.db index 0a41776..b79865d 100644 Binary files a/prisma/prisma/dev.db and b/prisma/prisma/dev.db differ diff --git a/test-price-monitor-basic.mjs b/test-price-monitor-basic.mjs new file mode 100644 index 0000000..726b9d5 --- /dev/null +++ b/test-price-monitor-basic.mjs @@ -0,0 +1,117 @@ +import priceMonitorService from './lib/price-monitor.js'; +import { PriceFetcher } from './lib/price-fetcher.js'; + +async function testPriceMonitorBasic() { + console.log('πŸ§ͺ Testing Basic Price Monitor Functionality...\n'); + + try { + // Test 1: Price fetcher integration + console.log('1️⃣ Testing PriceFetcher integration...'); + const priceFetcher = new PriceFetcher(); + const solPrice = await priceFetcher.getPrice('SOL'); + console.log(`SOL Price: $${solPrice}`); + console.log('βœ… PriceFetcher working\n'); + + // Test 2: Price monitor initialization + console.log('2️⃣ Testing price monitor initialization...'); + const initialStatus = priceMonitorService.isMonitoring(); + console.log(`Initial status: ${initialStatus ? 'Active' : 'Inactive'}`); + console.log('βœ… Price monitor initialized\n'); + + // Test 3: Event listener setup + console.log('3️⃣ Testing event listener setup...'); + let eventReceived = false; + + priceMonitorService.on('test_event', (data) => { + console.log('Event received:', data); + eventReceived = true; + }); + + priceMonitorService.emit('test_event', { test: 'data' }); + + // Wait for event + await new Promise(resolve => setTimeout(resolve, 100)); + console.log(`Event test: ${eventReceived ? 'βœ… Success' : '❌ Failed'}\n`); + + // Test 4: Start monitoring + console.log('4️⃣ Testing start monitoring...'); + await priceMonitorService.startMonitoring(); + const monitoringStatus = priceMonitorService.isMonitoring(); + console.log(`Monitoring status: ${monitoringStatus ? 'βœ… Active' : '❌ Inactive'}\n`); + + // Test 5: Get monitoring data + console.log('5️⃣ Testing get monitoring data...'); + const monitoringData = await priceMonitorService.getMonitoringData(); + console.log('Monitoring data structure:', { + hasTradesArray: Array.isArray(monitoringData.trades), + hasAlertsArray: Array.isArray(monitoringData.alerts), + hasPricesObject: typeof monitoringData.prices === 'object', + hasLastUpdated: !!monitoringData.lastUpdated, + monitoringActive: monitoringData.monitoringActive + }); + console.log('βœ… Monitoring data structure valid\n'); + + // Test 6: Price update simulation + console.log('6️⃣ Testing price update simulation...'); + let priceUpdateReceived = false; + + priceMonitorService.on('price_update', (data) => { + console.log('Price update received:', data.symbol, data.price); + priceUpdateReceived = true; + }); + + // Manually trigger a price update for testing + priceMonitorService.emit('price_update', { + symbol: 'SOLUSD', + price: 189.50, + timestamp: new Date().toISOString() + }); + + await new Promise(resolve => setTimeout(resolve, 100)); + console.log(`Price update test: ${priceUpdateReceived ? 'βœ… Success' : '❌ Failed'}\n`); + + // Test 7: Stop monitoring + console.log('7️⃣ Testing stop monitoring...'); + await priceMonitorService.stopMonitoring(); + const stoppedStatus = priceMonitorService.isMonitoring(); + console.log(`Stopped status: ${stoppedStatus ? '❌ Still active' : 'βœ… Stopped'}\n`); + + console.log('πŸŽ‰ All basic price monitor tests completed successfully!'); + + return { + priceFetcher: !!solPrice, + initialization: true, + eventListeners: eventReceived, + startMonitoring: monitoringStatus, + monitoringData: !!monitoringData, + priceUpdates: priceUpdateReceived, + stopMonitoring: !stoppedStatus + }; + + } catch (error) { + console.error('❌ Basic test failed:', error); + console.error('Stack trace:', error.stack); + throw error; + } +} + +// Run the test +if (import.meta.url === `file://${process.argv[1]}`) { + testPriceMonitorBasic() + .then((results) => { + console.log('\nπŸ“Š Test Results Summary:'); + Object.entries(results).forEach(([test, passed]) => { + console.log(` ${test}: ${passed ? 'βœ…' : '❌'}`); + }); + + const allPassed = Object.values(results).every(result => result); + console.log(`\nOverall: ${allPassed ? 'βœ… All tests passed' : '❌ Some tests failed'}`); + process.exit(allPassed ? 0 : 1); + }) + .catch((error) => { + console.error('\n❌ Test suite failed:', error.message); + process.exit(1); + }); +} + +export { testPriceMonitorBasic }; diff --git a/test-price-monitor-integration.js b/test-price-monitor-integration.js new file mode 100644 index 0000000..f2e5586 --- /dev/null +++ b/test-price-monitor-integration.js @@ -0,0 +1,119 @@ +const priceMonitorService = require('./lib/price-monitor').default; +const { automationService } = require('./lib/automation-service-simple'); + +async function testPriceMonitorIntegration() { + console.log('πŸ§ͺ Testing Price Monitor Integration...\n'); + + try { + // Test 1: Start price monitoring + console.log('πŸ“Š Starting price monitoring...'); + await priceMonitorService.startMonitoring(); + console.log('βœ… Price monitoring started\n'); + + // Test 2: Check monitoring status + console.log('πŸ” Checking monitoring status...'); + const isMonitoring = priceMonitorService.isMonitoring(); + console.log(`Status: ${isMonitoring ? '🟒 Active' : 'πŸ”΄ Inactive'}\n`); + + // Test 3: Get monitoring data + console.log('πŸ“ˆ Getting monitoring data...'); + const monitoringData = await priceMonitorService.getMonitoringData(); + console.log('Monitoring Data:', { + trades: monitoringData.trades.length, + alerts: monitoringData.alerts.length, + prices: Object.keys(monitoringData.prices).length, + lastUpdated: monitoringData.lastUpdated + }); + console.log(); + + // Test 4: Simulate a price alert + console.log('🚨 Testing price alert simulation...'); + priceMonitorService.emit('tp_approach', { + symbol: 'SOLUSD', + currentPrice: 190.50, + targetPrice: 200.00, + distance: 0.05, + tradeId: 'test-trade-123' + }); + + // Wait a moment for event processing + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log('βœ… Price alert simulation completed\n'); + + // Test 5: Test automation service integration + console.log('πŸ€– Testing automation service configuration...'); + const testConfig = { + userId: 'test-user', + mode: 'SIMULATION', + symbol: 'SOLUSD', + timeframe: '1h', + tradingAmount: 100, + maxLeverage: 3, + stopLossPercent: 2, + takeProfitPercent: 6, + maxDailyTrades: 5, + riskPercentage: 2 + }; + + console.log('Starting automation with test config...'); + const startResult = await automationService.startAutomation(testConfig); + console.log(`Automation start result: ${startResult ? 'βœ… Success' : '❌ Failed'}\n`); + + if (startResult) { + // Wait a moment then stop + console.log('⏸️ Stopping automation...'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const stopResult = await automationService.stopAutomation(); + console.log(`Automation stop result: ${stopResult ? 'βœ… Success' : '❌ Failed'}\n`); + } + + // Test 6: Test API endpoint + console.log('🌐 Testing price monitor API...'); + try { + const response = await fetch('http://localhost:9001/api/price-monitor', { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }); + + if (response.ok) { + const apiData = await response.json(); + console.log('API Response:', { + success: apiData.success, + dataKeys: Object.keys(apiData.data || {}), + message: apiData.message + }); + } else { + console.log(`API Error: ${response.status} ${response.statusText}`); + } + } catch (error) { + console.log('API test failed (server might not be running):', error.message); + } + console.log(); + + // Test 7: Stop monitoring + console.log('πŸ›‘ Stopping price monitoring...'); + await priceMonitorService.stopMonitoring(); + console.log('βœ… Price monitoring stopped\n'); + + console.log('πŸŽ‰ All price monitor integration tests completed successfully!'); + + } catch (error) { + console.error('❌ Test failed:', error); + console.error('Stack trace:', error.stack); + } +} + +// Run the test +if (require.main === module) { + testPriceMonitorIntegration() + .then(() => { + console.log('\nβœ… Test suite completed'); + process.exit(0); + }) + .catch((error) => { + console.error('\n❌ Test suite failed:', error); + process.exit(1); + }); +} + +module.exports = { testPriceMonitorIntegration };