const express = require('express'); const router = express.Router(); const { catchAsync } = require('../middleware/errorHandler'); const { logger } = require('../utils/logger'); const { getCurrentPrice, getCurrentPrices, getHistoricalData, getPriceUpdateStats } = require('../services/priceUpdateService'); const { getHistoricalCandles, validateCandles, generateTestCandles } = require('../services/candlestickService'); // GET /api/prices/current/:symbol router.get('/current/:symbol', catchAsync(async (req, res) => { const { symbol } = req.params; if (!symbol) { return res.status(400).json({ status: 'error', message: 'Symbol parameter is required' }); } const priceData = getCurrentPrice(symbol.toUpperCase()); if (!priceData) { return res.status(404).json({ status: 'error', message: `Price data not found for symbol: ${symbol}` }); } res.json({ status: 'success', data: { symbol: priceData.symbol, price: priceData.close, change: priceData.close - priceData.open, changePercent: ((priceData.close - priceData.open) / priceData.open) * 100, high: priceData.high, low: priceData.low, volume: priceData.volume, timestamp: priceData.timestamp, timeframe: priceData.timeframe } }); })); // GET /api/prices/current router.get('/current', catchAsync(async (req, res) => { const { symbols } = req.query; let targetSymbols = null; if (symbols) { targetSymbols = symbols.split(',').map(s => s.trim().toUpperCase()); } const pricesData = getCurrentPrices(targetSymbols); const formattedData = Object.entries(pricesData).map(([symbol, data]) => ({ symbol, price: data.close, change: data.close - data.open, changePercent: ((data.close - data.open) / data.open) * 100, high: data.high, low: data.low, volume: data.volume, timestamp: data.timestamp, timeframe: data.timeframe })); res.json({ status: 'success', data: formattedData, count: formattedData.length }); })); // GET /api/prices/historical/:symbol router.get('/historical/:symbol', catchAsync(async (req, res) => { const { symbol } = req.params; const { timeframe = 'H1', limit = 1000 } = req.query; if (!symbol) { return res.status(400).json({ status: 'error', message: 'Symbol parameter is required' }); } const historicalData = await getHistoricalData( symbol.toUpperCase(), timeframe, parseInt(limit) ); if (historicalData.length === 0) { return res.status(404).json({ status: 'error', message: `Historical data not found for symbol: ${symbol}` }); } res.json({ status: 'success', data: historicalData, symbol: symbol.toUpperCase(), timeframe, count: historicalData.length, meta: { requestedLimit: parseInt(limit), actualCount: historicalData.length, startDate: historicalData[0]?.timestamp, endDate: historicalData[historicalData.length - 1]?.timestamp } }); })); // GET /api/prices/bulk-historical router.get('/bulk-historical', catchAsync(async (req, res) => { const { symbols, timeframe = 'H1', limit = 1000 } = req.query; if (!symbols) { return res.status(400).json({ status: 'error', message: 'Symbols parameter is required (comma-separated)' }); } const symbolList = symbols.split(',').map(s => s.trim().toUpperCase()); const results = {}; for (const symbol of symbolList) { try { const data = await getHistoricalData(symbol, timeframe, parseInt(limit)); results[symbol] = { status: 'success', data, count: data.length }; } catch (error) { logger.error(`Error fetching historical data for ${symbol}:`, error); results[symbol] = { status: 'error', message: error.message, count: 0 }; } } res.json({ status: 'success', data: results, timeframe, requestedLimit: parseInt(limit), symbols: symbolList }); })); // GET /api/prices/stats router.get('/stats', catchAsync(async (req, res) => { const stats = getPriceUpdateStats(); res.json({ status: 'success', data: stats }); })); // POST /api/prices/subscribe router.post('/subscribe', catchAsync(async (req, res) => { const { symbols } = req.body; if (!Array.isArray(symbols) || symbols.length === 0) { return res.status(400).json({ status: 'error', message: 'Symbols array is required and cannot be empty' }); } const validSymbols = symbols.map(s => s.toUpperCase()); res.json({ status: 'success', message: `Subscribed to ${validSymbols.length} symbols`, data: { symbols: validSymbols, timestamp: new Date().toISOString() } }); })); // GET /api/prices/search router.get('/search', catchAsync(async (req, res) => { const { q: query, limit = 50 } = req.query; if (!query) { return res.status(400).json({ status: 'error', message: 'Search query parameter (q) is required' }); } // This would typically search your database or external API // For now, we'll return a placeholder response res.json({ status: 'success', data: [], query, count: 0, message: 'Search functionality not yet implemented' }); })); // GET /api/prices/validate-candles/:symbol router.get('/validate-candles/:symbol', catchAsync(async (req, res) => { const { symbol } = req.params; const { timeframe = 'H1', limit = 100 } = req.query; if (!symbol) { return res.status(400).json({ status: 'error', message: 'Symbol parameter is required' }); } try { // Get candles for validation const candles = await getHistoricalCandles(symbol.toUpperCase(), timeframe, parseInt(limit)); if (candles.length === 0) { return res.status(404).json({ status: 'error', message: `No candle data found for symbol: ${symbol}` }); } // Validate the candles const validationResults = validateCandles(candles); res.json({ status: 'success', data: validationResults, symbol: symbol.toUpperCase(), timeframe, count: candles.length, meta: { validationPassed: validationResults.valid === validationResults.total, validCandles: validationResults.valid, invalidCandles: validationResults.invalid, successRate: `${((validationResults.valid / validationResults.total) * 100).toFixed(2)}%` } }); } catch (error) { logger.error(`Error validating candles for ${symbol}:`, error); res.status(500).json({ status: 'error', message: 'Error validating candles', error: error.message }); } })); // GET /api/prices/generate-test-candles/:symbol router.get('/generate-test-candles/:symbol', catchAsync(async (req, res) => { const { symbol } = req.params; const { timeframe = 'H1', count = 100 } = req.query; if (!symbol) { return res.status(400).json({ status: 'error', message: 'Symbol parameter is required' }); } try { const testCandles = generateTestCandles(symbol.toUpperCase(), timeframe, parseInt(count)); // Validate the generated test candles const validationResults = validateCandles(testCandles); res.json({ status: 'success', data: testCandles, validation: validationResults, symbol: symbol.toUpperCase(), timeframe, count: testCandles.length, meta: { description: 'Test candles generated with realistic price movements for validation purposes', validationPassed: validationResults.valid === validationResults.total, successRate: `${((validationResults.valid / validationResults.total) * 100).toFixed(2)}%` } }); } catch (error) { logger.error(`Error generating test candles for ${symbol}:`, error); res.status(500).json({ status: 'error', message: 'Error generating test candles', error: error.message }); } })); // GET /api/prices/candle-info/:symbol router.get('/candle-info/:symbol', catchAsync(async (req, res) => { const { symbol } = req.params; const { timeframe = 'H1' } = req.query; if (!symbol) { return res.status(400).json({ status: 'error', message: 'Symbol parameter is required' }); } try { const candles = await getHistoricalCandles(symbol.toUpperCase(), timeframe, 1000); if (candles.length === 0) { return res.status(404).json({ status: 'error', message: `No candle data found for symbol: ${symbol}` }); } // Calculate statistics const closes = candles.map(c => parseFloat(c.close)); const highs = candles.map(c => parseFloat(c.high)); const lows = candles.map(c => parseFloat(c.low)); const volumes = candles.map(c => parseInt(c.volume)); const stats = { count: candles.length, priceRange: { min: Math.min(...closes), max: Math.max(...closes), avg: closes.reduce((a, b) => a + b, 0) / closes.length }, highLowRange: { overallHigh: Math.max(...highs), overallLow: Math.min(...lows) }, volume: { total: volumes.reduce((a, b) => a + b, 0), avg: volumes.reduce((a, b) => a + b, 0) / volumes.length, max: Math.max(...volumes), min: Math.min(...volumes) }, timeframe, symbol: symbol.toUpperCase(), lastUpdated: candles[candles.length - 1]?.timestamp }; res.json({ status: 'success', data: stats }); } catch (error) { logger.error(`Error getting candle info for ${symbol}:`, error); res.status(500).json({ status: 'error', message: 'Error getting candle information', error: error.message }); } })); module.exports = router;