stock-bot/apps/stock/orchestrator/src/index.ts

150 lines
No EOL
5 KiB
TypeScript

/**
* Stock Bot Orchestrator Service
* Coordinates between Rust core, data feeds, and analytics
*/
import { getLogger } from '@stock-bot/logger';
import { initializeStockConfig } from '@stock-bot/stock-config';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { createRoutes } from './routes/create-routes';
import { createContainer } from './simple-container';
// Initialize configuration with service-specific overrides
const config = initializeStockConfig('orchestrator');
const logger = getLogger('orchestrator');
// Get service-specific config
const serviceConfig = config.services?.orchestrator || {
port: 2004,
defaultMode: 'paper',
paperTradingCapital: 100000,
enableWebSocket: true
};
const PORT = serviceConfig.port;
// Log the configuration
logger.info('Service configuration:', {
port: PORT,
defaultMode: serviceConfig.defaultMode,
enableWebSocket: serviceConfig.enableWebSocket,
backtesting: serviceConfig.backtesting,
strategies: serviceConfig.strategies
});
async function main() {
let server: any; // Declare server in outer scope for shutdown
try {
// Initialize container with all services using configuration
const services = await createContainer(config);
// Initialize Hono app
const app = new Hono();
// CORS middleware - use config for origins
app.use('*', cors({
origin: config.services?.webApi?.cors?.origins || ['http://localhost:4200', 'http://localhost:3000', 'http://localhost:5173', 'http://localhost:5174'],
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: config.services?.webApi?.cors?.credentials ?? true,
}));
// Logging middleware
app.use('*', async (c, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
services.logger.debug(`${c.req.method} ${c.req.url} - ${ms}ms`);
});
// Create and mount routes (without Socket.IO for now)
const routes = createRoutes(services);
app.route('/', routes);
// Start Bun server
try {
server = Bun.serve({
port: PORT,
hostname: '0.0.0.0', // Explicitly bind to all interfaces
fetch: async (req) => {
services.logger.debug(`Incoming request: ${req.method} ${req.url}`);
try {
const response = await app.fetch(req);
return response;
} catch (error) {
services.logger.error('Request handling error:', error);
return new Response('Internal Server Error', { status: 500 });
}
},
error: (error) => {
services.logger.error('Server error:', error);
return new Response('Internal Server Error', { status: 500 });
},
});
services.logger.info(`Orchestrator service started on port ${server.port}`);
services.logger.info(`Server hostname: ${server.hostname}`);
services.logger.info(`Server URL: http://${server.hostname}:${server.port}`);
// Test that server is actually listening
setTimeout(async () => {
try {
const testResponse = await fetch(`http://localhost:${server.port}/health`);
services.logger.info(`Server self-test: ${testResponse.status} ${testResponse.statusText}`);
} catch (error) {
services.logger.error('Server self-test failed:', error);
}
}, 1000);
} catch (error) {
services.logger.error('Failed to start Bun server:', error);
throw error;
}
services.logger.info('Service metadata:', {
version: '1.0.0',
description: 'Trading System Orchestrator',
defaultMode: serviceConfig.defaultMode,
enableWebSocket: serviceConfig.enableWebSocket,
endpoints: {
health: '/health',
orders: '/api/orders',
positions: '/api/positions',
analytics: '/api/analytics',
backtest: '/api/backtest',
}
});
// Note: Socket.IO with Bun requires a different setup
// For now, we'll disable Socket.IO to avoid the CORS error
if (serviceConfig.enableWebSocket) {
services.logger.info('WebSocket support is enabled but Socket.IO integration with Bun requires additional setup');
}
// Graceful shutdown
process.on('SIGINT', async () => {
services.logger.info('Orchestrator service shutting down...');
// Cleanup any active trading sessions
const modeManager = services.custom?.ModeManager;
if (modeManager) {
await modeManager.shutdown();
}
if (server) {
server.stop();
}
process.exit(0);
});
} catch (error) {
logger.error('Failed to start orchestrator service:', error);
process.exit(1);
}
}
// Start the service
main().catch(error => {
logger.error('Unhandled error:', error);
process.exit(1);
});