added initial py analytics / rust core / ts orchestrator services

This commit is contained in:
Boki 2025-07-01 11:16:25 -04:00
parent 680b5fd2ae
commit c862ed496b
62 changed files with 13459 additions and 0 deletions

View file

@ -0,0 +1,180 @@
import { Hono } from 'hono';
import { z } from 'zod';
import { logger } from '@stock-bot/logger';
import { AnalyticsService } from '../../services/AnalyticsService';
import { container } from '../../container';
const DateRangeSchema = z.object({
startDate: z.string().datetime(),
endDate: z.string().datetime()
});
const OptimizationRequestSchema = z.object({
symbols: z.array(z.string()),
returns: z.array(z.array(z.number())),
constraints: z.object({
minWeight: z.number().optional(),
maxWeight: z.number().optional(),
targetReturn: z.number().optional(),
maxRisk: z.number().optional()
}).optional()
});
export function createAnalyticsRoutes(): Hono {
const app = new Hono();
const analyticsService = container.get('AnalyticsService') as AnalyticsService;
// Get performance metrics
app.get('/performance/:portfolioId', async (c) => {
try {
const portfolioId = c.req.param('portfolioId');
const query = c.req.query();
const { startDate, endDate } = DateRangeSchema.parse({
startDate: query.start_date,
endDate: query.end_date
});
const metrics = await analyticsService.getPerformanceMetrics(
portfolioId,
new Date(startDate),
new Date(endDate)
);
return c.json(metrics);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid date range',
details: error.errors
}, 400);
}
logger.error('Error getting performance metrics:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get performance metrics'
}, 500);
}
});
// Portfolio optimization
app.post('/optimize', async (c) => {
try {
const body = await c.req.json();
const request = OptimizationRequestSchema.parse(body);
const result = await analyticsService.optimizePortfolio({
returns: request.returns,
constraints: request.constraints
});
return c.json(result);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid optimization request',
details: error.errors
}, 400);
}
logger.error('Error optimizing portfolio:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to optimize portfolio'
}, 500);
}
});
// Get risk metrics
app.get('/risk/:portfolioId', async (c) => {
try {
const portfolioId = c.req.param('portfolioId');
const metrics = await analyticsService.getRiskMetrics(portfolioId);
return c.json(metrics);
} catch (error) {
logger.error('Error getting risk metrics:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get risk metrics'
}, 500);
}
});
// Market regime detection
app.get('/regime', async (c) => {
try {
const regime = await analyticsService.detectMarketRegime();
return c.json({
regime,
timestamp: new Date().toISOString()
});
} catch (error) {
logger.error('Error detecting market regime:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to detect market regime'
}, 500);
}
});
// Calculate correlation matrix
app.post('/correlation', async (c) => {
try {
const body = await c.req.json();
const { symbols } = z.object({
symbols: z.array(z.string()).min(2)
}).parse(body);
const matrix = await analyticsService.calculateCorrelationMatrix(symbols);
return c.json({
symbols,
matrix
});
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid correlation request',
details: error.errors
}, 400);
}
logger.error('Error calculating correlation:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to calculate correlation'
}, 500);
}
});
// ML model prediction
app.post('/predict', async (c) => {
try {
const body = await c.req.json();
const { modelId, features } = z.object({
modelId: z.string(),
features: z.record(z.number())
}).parse(body);
const prediction = await analyticsService.predictWithModel(modelId, features);
if (prediction) {
return c.json(prediction);
} else {
return c.json({ error: 'Model not found or prediction failed' }, 404);
}
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid prediction request',
details: error.errors
}, 400);
}
logger.error('Error making prediction:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to make prediction'
}, 500);
}
});
return app;
}

View file

@ -0,0 +1,162 @@
import { Hono } from 'hono';
import { z } from 'zod';
import { logger } from '@stock-bot/logger';
import { BacktestConfigSchema } from '../../types';
import { BacktestEngine } from '../../backtest/BacktestEngine';
import { ModeManager } from '../../core/ModeManager';
import { container } from '../../container';
const BacktestIdSchema = z.object({
backtestId: z.string()
});
export function createBacktestRoutes(): Hono {
const app = new Hono();
const backtestEngine = container.get('BacktestEngine') as BacktestEngine;
const modeManager = container.get('ModeManager') as ModeManager;
// Run new backtest
app.post('/run', async (c) => {
try {
const body = await c.req.json();
const config = BacktestConfigSchema.parse(body);
// Initialize backtest mode
await modeManager.initializeMode(config);
// Run backtest
const result = await backtestEngine.runBacktest(config);
return c.json(result, 201);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid backtest configuration',
details: error.errors
}, 400);
}
logger.error('Error running backtest:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to run backtest'
}, 500);
}
});
// Stop running backtest
app.post('/stop', async (c) => {
try {
await backtestEngine.stopBacktest();
return c.json({
message: 'Backtest stop requested',
timestamp: new Date().toISOString()
});
} catch (error) {
logger.error('Error stopping backtest:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to stop backtest'
}, 500);
}
});
// Get backtest progress
app.get('/progress', async (c) => {
try {
// In real implementation, would track progress
return c.json({
status: 'running',
progress: 0.5,
processed: 10000,
total: 20000,
currentTime: new Date().toISOString()
});
} catch (error) {
logger.error('Error getting backtest progress:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get progress'
}, 500);
}
});
// Stream backtest events (Server-Sent Events)
app.get('/stream', async (c) => {
c.header('Content-Type', 'text/event-stream');
c.header('Cache-Control', 'no-cache');
c.header('Connection', 'keep-alive');
const stream = new ReadableStream({
start(controller) {
// Listen for backtest events
const onProgress = (data: any) => {
controller.enqueue(`data: ${JSON.stringify(data)}\n\n`);
};
const onComplete = (data: any) => {
controller.enqueue(`data: ${JSON.stringify({ event: 'complete', data })}\n\n`);
controller.close();
};
backtestEngine.on('progress', onProgress);
backtestEngine.on('complete', onComplete);
// Cleanup on close
c.req.raw.signal.addEventListener('abort', () => {
backtestEngine.off('progress', onProgress);
backtestEngine.off('complete', onComplete);
controller.close();
});
}
});
return new Response(stream);
});
// Validate backtest configuration
app.post('/validate', async (c) => {
try {
const body = await c.req.json();
const config = BacktestConfigSchema.parse(body);
// Additional validation logic
const validation = {
valid: true,
warnings: [] as string[],
estimatedDuration: 0
};
// Check data availability
const startDate = new Date(config.startDate);
const endDate = new Date(config.endDate);
const days = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24);
if (days > 365) {
validation.warnings.push('Large date range may take significant time to process');
}
if (config.symbols.length > 100) {
validation.warnings.push('Large number of symbols may impact performance');
}
// Estimate duration (simplified)
validation.estimatedDuration = days * config.symbols.length * 0.1; // seconds
return c.json(validation);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
valid: false,
error: 'Invalid configuration',
details: error.errors
}, 400);
}
return c.json({
valid: false,
error: error instanceof Error ? error.message : 'Validation failed'
}, 500);
}
});
return app;
}

View file

@ -0,0 +1,112 @@
import { Hono } from 'hono';
import { z } from 'zod';
import { logger } from '@stock-bot/logger';
import { OrderRequestSchema } from '../../types';
import { ExecutionService } from '../../services/ExecutionService';
import { container } from '../../container';
const OrderIdSchema = z.object({
orderId: z.string()
});
export function createOrderRoutes(): Hono {
const app = new Hono();
const executionService = container.get('ExecutionService') as ExecutionService;
// Submit new order
app.post('/', async (c) => {
try {
const body = await c.req.json();
const orderRequest = OrderRequestSchema.parse(body);
const result = await executionService.submitOrder(orderRequest);
return c.json(result, 201);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid order request',
details: error.errors
}, 400);
}
logger.error('Error submitting order:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to submit order'
}, 500);
}
});
// Cancel order
app.delete('/:orderId', async (c) => {
try {
const { orderId } = OrderIdSchema.parse(c.req.param());
const success = await executionService.cancelOrder(orderId);
if (success) {
return c.json({ message: 'Order cancelled successfully' });
} else {
return c.json({ error: 'Order not found or already filled' }, 404);
}
} catch (error) {
logger.error('Error cancelling order:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to cancel order'
}, 500);
}
});
// Get order status
app.get('/:orderId', async (c) => {
try {
const { orderId } = OrderIdSchema.parse(c.req.param());
const status = await executionService.getOrderStatus(orderId);
if (status) {
return c.json(status);
} else {
return c.json({ error: 'Order not found' }, 404);
}
} catch (error) {
logger.error('Error getting order status:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get order status'
}, 500);
}
});
// Batch order submission
app.post('/batch', async (c) => {
try {
const body = await c.req.json();
const orders = z.array(OrderRequestSchema).parse(body);
const results = await Promise.allSettled(
orders.map(order => executionService.submitOrder(order))
);
const response = results.map((result, index) => ({
order: orders[index],
result: result.status === 'fulfilled' ? result.value : { error: result.reason }
}));
return c.json(response, 201);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({
error: 'Invalid batch order request',
details: error.errors
}, 400);
}
logger.error('Error submitting batch orders:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to submit batch orders'
}, 500);
}
});
return app;
}

View file

@ -0,0 +1,122 @@
import { Hono } from 'hono';
import { z } from 'zod';
import { logger } from '@stock-bot/logger';
import { ModeManager } from '../../core/ModeManager';
import { container } from '../../container';
const SymbolSchema = z.object({
symbol: z.string()
});
export function createPositionRoutes(): Hono {
const app = new Hono();
const modeManager = container.get('ModeManager') as ModeManager;
// Get all positions
app.get('/', async (c) => {
try {
const tradingEngine = modeManager.getTradingEngine();
const positions = JSON.parse(tradingEngine.getAllPositions());
return c.json({
mode: modeManager.getCurrentMode(),
positions
});
} catch (error) {
logger.error('Error getting positions:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get positions'
}, 500);
}
});
// Get open positions only
app.get('/open', async (c) => {
try {
const tradingEngine = modeManager.getTradingEngine();
const positions = JSON.parse(tradingEngine.getOpenPositions());
return c.json({
mode: modeManager.getCurrentMode(),
positions
});
} catch (error) {
logger.error('Error getting open positions:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get open positions'
}, 500);
}
});
// Get position for specific symbol
app.get('/:symbol', async (c) => {
try {
const { symbol } = SymbolSchema.parse(c.req.param());
const tradingEngine = modeManager.getTradingEngine();
const positionJson = tradingEngine.getPosition(symbol);
const position = positionJson ? JSON.parse(positionJson) : null;
if (position) {
return c.json({
mode: modeManager.getCurrentMode(),
position
});
} else {
return c.json({
error: 'Position not found',
symbol
}, 404);
}
} catch (error) {
logger.error('Error getting position:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get position'
}, 500);
}
});
// Get P&L summary
app.get('/pnl/summary', async (c) => {
try {
const tradingEngine = modeManager.getTradingEngine();
const [realizedPnl, unrealizedPnl] = tradingEngine.getTotalPnl();
return c.json({
mode: modeManager.getCurrentMode(),
pnl: {
realized: realizedPnl,
unrealized: unrealizedPnl,
total: realizedPnl + unrealizedPnl
},
timestamp: new Date().toISOString()
});
} catch (error) {
logger.error('Error getting P&L:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get P&L'
}, 500);
}
});
// Get risk metrics
app.get('/risk/metrics', async (c) => {
try {
const tradingEngine = modeManager.getTradingEngine();
const metrics = JSON.parse(tradingEngine.getRiskMetrics());
return c.json({
mode: modeManager.getCurrentMode(),
risk: metrics,
timestamp: new Date().toISOString()
});
} catch (error) {
logger.error('Error getting risk metrics:', error);
return c.json({
error: error instanceof Error ? error.message : 'Failed to get risk metrics'
}, 500);
}
});
return app;
}

View file

@ -0,0 +1,195 @@
import { Server as SocketIOServer, Socket } from 'socket.io';
import { logger } from '@stock-bot/logger';
import { z } from 'zod';
import { MarketDataService } from '../../services/MarketDataService';
import { ExecutionService } from '../../services/ExecutionService';
import { ModeManager } from '../../core/ModeManager';
import { Container } from '@stock-bot/di';
const SubscribeSchema = z.object({
symbols: z.array(z.string()),
dataTypes: z.array(z.enum(['quote', 'trade', 'bar'])).optional()
});
const UnsubscribeSchema = z.object({
symbols: z.array(z.string())
});
export function setupWebSocketHandlers(io: SocketIOServer, container: Container): void {
const marketDataService = container.get('MarketDataService') as MarketDataService;
const executionService = container.get('ExecutionService') as ExecutionService;
const modeManager = container.get('ModeManager') as ModeManager;
// Track client subscriptions
const clientSubscriptions = new Map<string, Set<string>>();
io.on('connection', (socket: Socket) => {
logger.info(`WebSocket client connected: ${socket.id}`);
clientSubscriptions.set(socket.id, new Set());
// Send initial connection info
socket.emit('connected', {
mode: modeManager.getCurrentMode(),
timestamp: new Date().toISOString()
});
// Handle market data subscriptions
socket.on('subscribe', async (data: any, callback?: Function) => {
try {
const { symbols, dataTypes } = SubscribeSchema.parse(data);
const subscriptions = clientSubscriptions.get(socket.id)!;
for (const symbol of symbols) {
await marketDataService.subscribeToSymbol(symbol);
subscriptions.add(symbol);
}
logger.debug(`Client ${socket.id} subscribed to: ${symbols.join(', ')}`);
if (callback) {
callback({ success: true, symbols });
}
} catch (error) {
logger.error('Subscription error:', error);
if (callback) {
callback({
success: false,
error: error instanceof Error ? error.message : 'Subscription failed'
});
}
}
});
// Handle unsubscribe
socket.on('unsubscribe', async (data: any, callback?: Function) => {
try {
const { symbols } = UnsubscribeSchema.parse(data);
const subscriptions = clientSubscriptions.get(socket.id)!;
for (const symbol of symbols) {
subscriptions.delete(symbol);
// Check if any other clients are subscribed
let othersSubscribed = false;
for (const [clientId, subs] of clientSubscriptions) {
if (clientId !== socket.id && subs.has(symbol)) {
othersSubscribed = true;
break;
}
}
if (!othersSubscribed) {
await marketDataService.unsubscribeFromSymbol(symbol);
}
}
logger.debug(`Client ${socket.id} unsubscribed from: ${symbols.join(', ')}`);
if (callback) {
callback({ success: true, symbols });
}
} catch (error) {
logger.error('Unsubscribe error:', error);
if (callback) {
callback({
success: false,
error: error instanceof Error ? error.message : 'Unsubscribe failed'
});
}
}
});
// Handle order submission via WebSocket
socket.on('submitOrder', async (order: any, callback?: Function) => {
try {
const result = await executionService.submitOrder(order);
if (callback) {
callback({ success: true, result });
}
} catch (error) {
logger.error('Order submission error:', error);
if (callback) {
callback({
success: false,
error: error instanceof Error ? error.message : 'Order submission failed'
});
}
}
});
// Handle position queries
socket.on('getPositions', async (callback?: Function) => {
try {
const tradingEngine = modeManager.getTradingEngine();
const positions = JSON.parse(tradingEngine.getAllPositions());
if (callback) {
callback({ success: true, positions });
}
} catch (error) {
logger.error('Error getting positions:', error);
if (callback) {
callback({
success: false,
error: error instanceof Error ? error.message : 'Failed to get positions'
});
}
}
});
// Handle disconnection
socket.on('disconnect', async () => {
logger.info(`WebSocket client disconnected: ${socket.id}`);
// Unsubscribe from all symbols for this client
const subscriptions = clientSubscriptions.get(socket.id);
if (subscriptions) {
for (const symbol of subscriptions) {
// Check if any other clients are subscribed
let othersSubscribed = false;
for (const [clientId, subs] of clientSubscriptions) {
if (clientId !== socket.id && subs.has(symbol)) {
othersSubscribed = true;
break;
}
}
if (!othersSubscribed) {
await marketDataService.unsubscribeFromSymbol(symbol);
}
}
}
clientSubscriptions.delete(socket.id);
});
});
// Forward market data to subscribed clients
marketDataService.on('marketData', (data: any) => {
for (const [clientId, subscriptions] of clientSubscriptions) {
if (subscriptions.has(data.data.symbol)) {
io.to(clientId).emit('marketData', data);
}
}
});
// Forward order updates to all clients
executionService.on('orderUpdate', (update: any) => {
io.emit('orderUpdate', update);
});
// Forward fills to all clients
executionService.on('fill', (fill: any) => {
io.emit('fill', fill);
});
// Mode change notifications
modeManager.on('modeChanged', (config: any) => {
io.emit('modeChanged', {
mode: config.mode,
timestamp: new Date().toISOString()
});
});
logger.info('WebSocket handlers initialized');
}