/** * ML Overlay Controller * Handles chart overlay endpoints for trading visualization */ import { Request, Response, NextFunction } from 'express'; import { mlOverlayService, OverlayConfig } from '../services/ml-overlay.service'; // ============================================================================ // Overlay Endpoints // ============================================================================ /** * Get complete chart overlay for a symbol */ export async function getChartOverlay( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbol } = req.params; const config = parseOverlayConfig(req.query); const overlay = await mlOverlayService.getChartOverlay(symbol.toUpperCase(), config); res.json({ success: true, data: overlay, }); } catch (error) { next(error); } } /** * Get overlays for multiple symbols */ export async function getBatchOverlays( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbols } = req.body; const config = parseOverlayConfig(req.body); if (!symbols || !Array.isArray(symbols) || symbols.length === 0) { res.status(400).json({ success: false, error: { message: 'Symbols array is required', code: 'VALIDATION_ERROR' }, }); return; } if (symbols.length > 20) { res.status(400).json({ success: false, error: { message: 'Maximum 20 symbols allowed per request', code: 'VALIDATION_ERROR' }, }); return; } const overlays = await mlOverlayService.getBatchOverlays( symbols.map((s: string) => s.toUpperCase()), config ); // Convert Map to object for JSON response const result: Record = {}; overlays.forEach((value, key) => { result[key] = value; }); res.json({ success: true, data: result, }); } catch (error) { next(error); } } /** * Get price levels only */ export async function getPriceLevels( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbol } = req.params; const levels = await mlOverlayService.getPriceLevels(symbol.toUpperCase()); res.json({ success: true, data: { symbol: symbol.toUpperCase(), levels, timestamp: new Date().toISOString(), }, }); } catch (error) { next(error); } } /** * Get signal markers */ export async function getSignalMarkers( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbol } = req.params; const limit = Math.min(Number(req.query.limit) || 20, 100); const markers = await mlOverlayService.getSignalMarkers(symbol.toUpperCase(), limit); res.json({ success: true, data: { symbol: symbol.toUpperCase(), markers, timestamp: new Date().toISOString(), }, }); } catch (error) { next(error); } } /** * Get AMD phase overlay */ export async function getAMDPhaseOverlay( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbol } = req.params; const amdPhase = await mlOverlayService.getAMDPhaseOverlay(symbol.toUpperCase()); res.json({ success: true, data: { symbol: symbol.toUpperCase(), ...amdPhase, timestamp: new Date().toISOString(), }, }); } catch (error) { next(error); } } /** * Get prediction bands */ export async function getPredictionBands( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbol } = req.params; const horizonMinutes = Math.min(Number(req.query.horizon) || 90, 480); const intervals = Math.min(Number(req.query.intervals) || 10, 50); const bands = await mlOverlayService.getPredictionBands( symbol.toUpperCase(), horizonMinutes, intervals ); res.json({ success: true, data: { symbol: symbol.toUpperCase(), horizonMinutes, bands, timestamp: new Date().toISOString(), }, }); } catch (error) { next(error); } } /** * Clear overlay cache */ export async function clearCache( req: Request, res: Response, next: NextFunction ): Promise { try { const { symbol } = req.params; mlOverlayService.clearCache(symbol?.toUpperCase()); res.json({ success: true, message: symbol ? `Cache cleared for ${symbol.toUpperCase()}` : 'All cache cleared', }); } catch (error) { next(error); } } // ============================================================================ // Helper Functions // ============================================================================ function parseOverlayConfig(query: Record): Partial { const config: Partial = {}; if (query.showPriceLevels !== undefined) { config.showPriceLevels = query.showPriceLevels === 'true' || query.showPriceLevels === true; } if (query.showTrendLines !== undefined) { config.showTrendLines = query.showTrendLines === 'true' || query.showTrendLines === true; } if (query.showSignalMarkers !== undefined) { config.showSignalMarkers = query.showSignalMarkers === 'true' || query.showSignalMarkers === true; } if (query.showZones !== undefined) { config.showZones = query.showZones === 'true' || query.showZones === true; } if (query.showPredictionBands !== undefined) { config.showPredictionBands = query.showPredictionBands === 'true' || query.showPredictionBands === true; } if (query.showIndicators !== undefined) { config.showIndicators = query.showIndicators === 'true' || query.showIndicators === true; } if (query.showAMDPhase !== undefined) { config.showAMDPhase = query.showAMDPhase === 'true' || query.showAMDPhase === true; } return config; }