| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- 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;
|