import { Hono } from 'hono'; import { WebSocketServer } from 'ws'; import Redis from 'ioredis'; import * as cron from 'node-cron'; import { BaseStrategy } from './core/Strategy'; import { StrategyRegistry } from './core/strategies/StrategyRegistry'; import { BacktestService, BacktestRequest } from './core/backtesting/BacktestService'; import { BacktestResult } from './core/backtesting/BacktestEngine'; import { PerformanceAnalytics } from './core/backtesting/PerformanceAnalytics'; import { StrategyController } from './controllers/StrategyController'; const app = new Hono(); const redis = new Redis({ host: process.env.REDIS_HOST || 'localhost', port: parseInt(process.env.REDIS_PORT || '6379'), enableReadyCheck: false, maxRetriesPerRequest: null, }); // WebSocket server for real-time strategy updates const wss = new WebSocketServer({ port: 8082 }); // Initialize strategy registry and backtest service const strategyRegistry = StrategyRegistry.getInstance(); const backtestService = new BacktestService(); // Strategy interfaces interface TradingStrategy { id: string; name: string; description: string; status: 'ACTIVE' | 'INACTIVE' | 'PAUSED' | 'ERROR'; type: 'MOMENTUM' | 'MEAN_REVERSION' | 'ARBITRAGE' | 'CUSTOM'; symbols: string[]; parameters: Record; performance: { totalTrades: number; winRate: number; totalReturn: number; sharpeRatio: number; maxDrawdown: number; }; createdAt: Date; updatedAt: Date; } interface StrategySignal { strategyId: string; symbol: string; action: 'BUY' | 'SELL' | 'HOLD'; confidence: number; price: number; quantity: number; timestamp: Date; metadata: Record; } // In-memory strategy registry (in production, this would be persisted) const strategies = new Map(); // Initialize strategy controller const strategyController = new StrategyController(); // Health check endpoint app.get('/health', (c) => { return c.json({ service: 'strategy-orchestrator', status: 'healthy', timestamp: new Date(), version: '1.0.0', activeStrategies: Array.from(strategies.values()).filter(s => s.status === 'ACTIVE').length, registeredStrategies: strategyRegistry.getAllStrategies().length, connections: wss.clients.size }); }); // API Routes // Strategy management endpoints app.get('/api/strategy-types', async (c) => { try { const types = Object.values(strategyRegistry.getStrategyTypes()); return c.json({ success: true, data: types }); } catch (error) { console.error('Error getting strategy types:', error); return c.json({ success: false, error: 'Failed to get strategy types' }, 500); } }); app.get('/api/strategies', async (c) => { try { const strategies = strategyRegistry.getAllStrategies(); const serializedStrategies = strategies.map(strategy => ({ id: strategy.id, name: strategy.name, description: strategy.description, symbols: strategy.symbols, parameters: strategy.parameters, type: strategyRegistry.getStrategyType(strategy) })); return c.json({ success: true, data: serializedStrategies }); } catch (error) { console.error('Error fetching strategies:', error); return c.json({ success: false, error: 'Failed to fetch strategies' }, 500); } }); app.get('/api/strategies/:id', async (c) => { try { const id = c.req.param('id'); const strategy = strategyRegistry.getStrategyById(id); if (!strategy) { return c.json({ success: false, error: `Strategy with ID ${id} not found` }, 404); } const type = strategyRegistry.getStrategyType(strategy); return c.json({ success: true, data: { id: strategy.id, name: strategy.name, description: strategy.description, symbols: strategy.symbols, parameters: strategy.parameters, type } }); } catch (error) { console.error('Error fetching strategy:', error); return c.json({ success: false, error: 'Failed to fetch strategy' }, 500); } }); app.post('/api/strategies', async (c) => { try { const { name, description, symbols, parameters, type } = await c.req.json(); if (!type) { return c.json({ success: false, error: 'Invalid strategy type' }, 400); } const strategy = strategyRegistry.createStrategy( type, `strategy_${Date.now()}`, // Generate an ID name || `New ${type} Strategy`, description || `Generated ${type} strategy`, symbols || [], parameters || {} ); return c.json({ success: true, data: { id: strategy.id, name: strategy.name, description: strategy.description, symbols: strategy.symbols, parameters: strategy.parameters, type } }, 201); } catch (error) { console.error('Error creating strategy:', error); return c.json({ success: false, error: (error as Error).message }, 500); } }); app.put('/api/strategies/:id', async (c) => { try { const id = c.req.param('id'); const { name, description, symbols, parameters } = await c.req.json(); const strategy = strategyRegistry.getStrategyById(id); if (!strategy) { return c.json({ success: false, error: `Strategy with ID ${id} not found` }, 404); } // Update properties if (name !== undefined) strategy.name = name; if (description !== undefined) strategy.description = description; if (symbols !== undefined) (strategy as any).symbols = symbols; // Hack since symbols is readonly if (parameters !== undefined) strategy.parameters = parameters; return c.json({ success: true, data: { id: strategy.id, name: strategy.name, description: strategy.description, symbols: strategy.symbols, parameters: strategy.parameters, type: strategyRegistry.getStrategyType(strategy) } }); } catch (error) { console.error('Error updating strategy:', error); return c.json({ success: false, error: (error as Error).message }, 500); } }); app.delete('/api/strategies/:id', async (c) => { try { const id = c.req.param('id'); const success = strategyRegistry.deleteStrategy(id); if (!success) { return c.json({ success: false, error: `Strategy with ID ${id} not found` }, 404); } return c.json({ success: true, data: { id } }); } catch (error) { console.error('Error deleting strategy:', error); return c.json({ success: false, error: (error as Error).message }, 500); } }); // Backtesting endpoints app.post('/api/backtest', async (c) => { try { const backtestRequest = await c.req.json() as BacktestRequest; // Validate request if (!backtestRequest.strategyType) { return c.json({ success: false, error: 'Strategy type is required' }, 400); } if (!backtestRequest.symbols || backtestRequest.symbols.length === 0) { return c.json({ success: false, error: 'At least one symbol is required' }, 400); } // Run the backtest const result = await backtestService.runBacktest(backtestRequest); // Enhance results with additional metrics const enhancedResult = PerformanceAnalytics.enhanceResults(result); // Calculate additional analytics const monthlyReturns = PerformanceAnalytics.calculateMonthlyReturns(result.dailyReturns); const drawdowns = PerformanceAnalytics.analyzeDrawdowns(result.dailyReturns); return c.json({ success: true, data: { ...enhancedResult, monthlyReturns, drawdowns } }); } catch (error) { console.error('Backtest error:', error); return c.json({ success: false, error: (error as Error).message }, 500); } }); app.post('/api/optimize', async (c) => { try { const { baseRequest, parameterGrid } = await c.req.json(); // Validate request if (!baseRequest || !parameterGrid) { return c.json({ success: false, error: 'Base request and parameter grid are required' }, 400); } // Run optimization const results = await backtestService.optimizeStrategy(baseRequest, parameterGrid); return c.json({ success: true, data: results }); } catch (error) { console.error('Strategy optimization error:', error); return c.json({ success: false, error: (error as Error).message }, 500); } }); // Create new strategy app.post('/api/strategies', async (c) => { try { const strategyData = await c.req.json(); const strategy: TradingStrategy = { id: `strategy_${Date.now()}`, name: strategyData.name, description: strategyData.description || '', status: 'INACTIVE', type: strategyData.type || 'CUSTOM', symbols: strategyData.symbols || [], parameters: strategyData.parameters || {}, performance: { totalTrades: 0, winRate: 0, totalReturn: 0, sharpeRatio: 0, maxDrawdown: 0 }, createdAt: new Date(), updatedAt: new Date() }; strategies.set(strategy.id, strategy); // Store in Redis for persistence await redis.setex( `strategy:${strategy.id}`, 86400, // 24 hours TTL JSON.stringify(strategy) ); // Broadcast to connected clients broadcastToClients({ type: 'STRATEGY_CREATED', data: strategy, timestamp: new Date() }); return c.json({ success: true, data: strategy }); } catch (error) { console.error('Error creating strategy:', error); return c.json({ success: false, error: 'Failed to create strategy' }, 500); } }); // Update strategy app.put('/api/strategies/:id', async (c) => { try { const id = c.req.param('id'); const updateData = await c.req.json(); const strategy = strategies.get(id); if (!strategy) { return c.json({ success: false, error: 'Strategy not found' }, 404); } const updatedStrategy = { ...strategy, ...updateData, id, // Ensure ID doesn't change updatedAt: new Date() }; strategies.set(id, updatedStrategy); // Update in Redis await redis.setex( `strategy:${id}`, 86400, JSON.stringify(updatedStrategy) ); // Broadcast update broadcastToClients({ type: 'STRATEGY_UPDATED', data: updatedStrategy, timestamp: new Date() }); return c.json({ success: true, data: updatedStrategy }); } catch (error) { console.error('Error updating strategy:', error); return c.json({ success: false, error: 'Failed to update strategy' }, 500); } }); // Start/Stop strategy app.post('/api/strategies/:id/:action', async (c) => { try { const id = c.req.param('id'); const action = c.req.param('action'); const strategy = strategies.get(id); if (!strategy) { return c.json({ success: false, error: 'Strategy not found' }, 404); } if (!['start', 'stop', 'pause'].includes(action)) { return c.json({ success: false, error: 'Invalid action' }, 400); } const statusMap = { start: 'ACTIVE' as const, stop: 'INACTIVE' as const, pause: 'PAUSED' as const }; strategy.status = statusMap[action as keyof typeof statusMap]; strategy.updatedAt = new Date(); strategies.set(id, strategy); // Update in Redis await redis.setex( `strategy:${id}`, 86400, JSON.stringify(strategy) ); // Broadcast status change broadcastToClients({ type: 'STRATEGY_STATUS_CHANGED', data: { id, status: strategy.status, action }, timestamp: new Date() }); return c.json({ success: true, data: strategy }); } catch (error) { console.error('Error changing strategy status:', error); return c.json({ success: false, error: 'Failed to change strategy status' }, 500); } }); // Get strategy signals app.get('/api/strategies/:id/signals', async (c) => { try { const id = c.req.param('id'); const limit = parseInt(c.req.query('limit') || '50'); const signalKeys = await redis.keys(`signal:${id}:*`); const signals: any[] = []; for (const key of signalKeys.slice(0, limit)) { const data = await redis.get(key); if (data) { signals.push(JSON.parse(data)); } } return c.json({ success: true, data: signals.sort((a: any, b: any) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) }); } catch (error) { console.error('Error fetching strategy signals:', error); return c.json({ success: false, error: 'Failed to fetch signals' }, 500); } }); // Get strategy trades app.get('/api/strategies/:id/trades', async (c) => { try { const id = c.req.param('id'); const limit = parseInt(c.req.query('limit') || '50'); const tradeKeys = await redis.keys(`trade:${id}:*`); const trades: any[] = []; for (const key of tradeKeys.slice(0, limit)) { const data = await redis.get(key); if (data) { trades.push(JSON.parse(data)); } } return c.json({ success: true, data: trades.sort((a: any, b: any) => new Date(b.exitTime || b.timestamp).getTime() - new Date(a.exitTime || a.timestamp).getTime()) }); } catch (error) { console.error('Error fetching strategy trades:', error); return c.json({ success: false, error: 'Failed to fetch trades' }, 500); } }); // Generate demo signal (for testing) app.post('/api/strategies/:id/generate-signal', async (c) => { try { const id = c.req.param('id'); const strategy = strategies.get(id); if (!strategy) { return c.json({ success: false, error: 'Strategy not found' }, 404); } if (strategy.status !== 'ACTIVE') { return c.json({ success: false, error: 'Strategy is not active' }, 400); } // Generate demo signal const symbol = strategy.symbols[Math.floor(Math.random() * strategy.symbols.length)] || 'AAPL'; const signal: StrategySignal = { strategyId: id, symbol, action: ['BUY', 'SELL', 'HOLD'][Math.floor(Math.random() * 3)] as any, confidence: Math.random() * 0.4 + 0.6, // 60-100% confidence price: 150 + Math.random() * 50, quantity: Math.floor(Math.random() * 100) + 1, timestamp: new Date(), metadata: { indicator1: Math.random(), indicator2: Math.random(), rsi: Math.random() * 100 } }; // Store signal await redis.setex( `signal:${id}:${Date.now()}`, 3600, // 1 hour TTL JSON.stringify(signal) ); // Broadcast signal broadcastToClients({ type: 'STRATEGY_SIGNAL', data: signal, timestamp: new Date() }); // Publish to trading system await redis.publish('trading:signals', JSON.stringify(signal)); return c.json({ success: true, data: signal }); } catch (error) { console.error('Error generating signal:', error); return c.json({ success: false, error: 'Failed to generate signal' }, 500); } }); // WebSocket connection handling wss.on('connection', (ws) => { console.log('New strategy monitoring client connected'); ws.send(JSON.stringify({ type: 'CONNECTED', message: 'Connected to Strategy Orchestrator', timestamp: new Date() })); ws.on('close', () => { console.log('Strategy monitoring client disconnected'); }); ws.on('error', (error) => { console.error('WebSocket error:', error); }); }); function broadcastToClients(message: any) { const messageStr = JSON.stringify(message); wss.clients.forEach(client => { if (client.readyState === 1) { // WebSocket.OPEN client.send(messageStr); } }); } // Scheduled tasks for strategy management cron.schedule('*/5 * * * *', async () => { // Every 5 minutes: Check strategy health and generate signals for active strategies console.log('Running strategy health check...'); for (const [id, strategy] of strategies.entries()) { if (strategy.status === 'ACTIVE') { try { // Generate signals for active strategies (demo mode) if (Math.random() > 0.7) { // 30% chance to generate a signal const symbol = strategy.symbols[Math.floor(Math.random() * strategy.symbols.length)] || 'AAPL'; const signal: StrategySignal = { strategyId: id, symbol, action: ['BUY', 'SELL'][Math.floor(Math.random() * 2)] as any, confidence: Math.random() * 0.3 + 0.7, price: 150 + Math.random() * 50, quantity: Math.floor(Math.random() * 100) + 1, timestamp: new Date(), metadata: { scheduled: true } }; await redis.setex( `signal:${id}:${Date.now()}`, 3600, JSON.stringify(signal) ); broadcastToClients({ type: 'STRATEGY_SIGNAL', data: signal, timestamp: new Date() }); await redis.publish('trading:signals', JSON.stringify(signal)); } } catch (error) { console.error(`Error in scheduled task for strategy ${id}:`, error); } } } }); // Backtesting API endpoints app.post('/api/backtest', async (c) => { try { const request = await c.req.json() as BacktestRequest; console.log('Received backtest request:', request); const result = await backtestService.runBacktest(request); const enhancedResult = PerformanceAnalytics.enhanceResults(result); // Store backtest result in Redis for persistence await redis.setex( `backtest:${result.strategyId}`, 86400 * 7, // 7 days TTL JSON.stringify(enhancedResult) ); return c.json({ success: true, data: enhancedResult }); } catch (error) { console.error('Backtest error:', error); return c.json({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }, 500); } }); app.post('/api/backtest/optimize', async (c) => { try { const { baseRequest, parameterGrid } = await c.req.json() as { baseRequest: BacktestRequest, parameterGrid: Record }; console.log('Received optimization request:', baseRequest, parameterGrid); const results = await backtestService.optimizeStrategy(baseRequest, parameterGrid); return c.json({ success: true, data: results }); } catch (error) { console.error('Optimization error:', error); return c.json({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }, 500); } }); app.get('/api/backtest/:id', async (c) => { try { const id = c.req.param('id'); const data = await redis.get(`backtest:${id}`); if (!data) { return c.json({ success: false, error: 'Backtest not found' }, 404); } const result = JSON.parse(data) as BacktestResult; return c.json({ success: true, data: result }); } catch (error) { console.error('Error fetching backtest:', error); return c.json({ success: false, error: 'Failed to fetch backtest' }, 500); } }); app.get('/api/strategy-types', (c) => { const types = strategyRegistry.getStrategyTypes(); return c.json({ success: true, data: types }); }); app.get('/api/strategy-parameters/:type', (c) => { try { const type = c.req.param('type') as any; if (!strategyRegistry.hasStrategyType(type)) { return c.json({ success: false, error: 'Strategy type not found' }, 404); } const params = strategyRegistry.getDefaultParameters(type); return c.json({ success: true, data: params }); } catch (error) { console.error('Error fetching strategy parameters:', error); return c.json({ success: false, error: 'Failed to fetch parameters' }, 500); } }); // Load existing strategies from Redis on startup async function loadStrategiesFromRedis() { try { const strategyKeys = await redis.keys('strategy:*'); for (const key of strategyKeys) { const data = await redis.get(key); if (data) { const strategy = JSON.parse(data); strategies.set(strategy.id, strategy); } } console.log(`Loaded ${strategies.size} strategies from Redis`); } catch (error) { console.error('Error loading strategies from Redis:', error); } } const port = parseInt(process.env.PORT || '4001'); console.log(`🎯 Strategy Orchestrator starting on port ${port}`); console.log(`📡 WebSocket server running on port 8082`); // Load existing strategies loadStrategiesFromRedis(); export default { port, fetch: app.fetch, };