messy work. backtests / mock-data
This commit is contained in:
parent
4e4a048988
commit
fa70ada2bb
51 changed files with 2576 additions and 887 deletions
|
|
@ -1,4 +1,6 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('PerformanceAnalyzer');
|
||||
import * as stats from 'simple-statistics';
|
||||
|
||||
export interface Trade {
|
||||
|
|
@ -168,6 +170,19 @@ export class PerformanceAnalyzer {
|
|||
analyzeDrawdowns(): DrawdownAnalysis {
|
||||
const drawdowns: number[] = [];
|
||||
const underwaterCurve: Array<{ date: Date; drawdown: number }> = [];
|
||||
|
||||
// Handle empty equity curve
|
||||
if (this.equityCurve.length === 0) {
|
||||
return {
|
||||
maxDrawdown: 0,
|
||||
averageDrawdown: 0,
|
||||
maxDrawdownDuration: 0,
|
||||
underwaterTime: 0,
|
||||
drawdownPeriods: [],
|
||||
currentDrawdown: 0
|
||||
};
|
||||
}
|
||||
|
||||
let peak = this.equityCurve[0].value;
|
||||
let maxDrawdown = 0;
|
||||
let currentDrawdownStart: Date | null = null;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { AnalyticsService } from '../../services/AnalyticsService';
|
||||
import { container } from '../../container';
|
||||
|
||||
const DateRangeSchema = z.object({
|
||||
startDate: z.string().datetime(),
|
||||
|
|
@ -20,9 +19,9 @@ const OptimizationRequestSchema = z.object({
|
|||
}).optional()
|
||||
});
|
||||
|
||||
export function createAnalyticsRoutes(): Hono {
|
||||
export function createAnalyticsRoutes(container: IServiceContainer): Hono {
|
||||
const app = new Hono();
|
||||
const analyticsService = container.get('AnalyticsService') as AnalyticsService;
|
||||
const analyticsService = container.custom?.AnalyticsService as AnalyticsService;
|
||||
|
||||
// Get performance metrics
|
||||
app.get('/performance/:portfolioId', async (c) => {
|
||||
|
|
@ -50,7 +49,7 @@ export function createAnalyticsRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error getting performance metrics:', error);
|
||||
container.logger.error('Error getting performance metrics:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get performance metrics'
|
||||
}, 500);
|
||||
|
|
@ -77,7 +76,7 @@ export function createAnalyticsRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error optimizing portfolio:', error);
|
||||
container.logger.error('Error optimizing portfolio:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to optimize portfolio'
|
||||
}, 500);
|
||||
|
|
@ -92,7 +91,7 @@ export function createAnalyticsRoutes(): Hono {
|
|||
|
||||
return c.json(metrics);
|
||||
} catch (error) {
|
||||
logger.error('Error getting risk metrics:', error);
|
||||
container.logger.error('Error getting risk metrics:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get risk metrics'
|
||||
}, 500);
|
||||
|
|
@ -109,7 +108,7 @@ export function createAnalyticsRoutes(): Hono {
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error detecting market regime:', error);
|
||||
container.logger.error('Error detecting market regime:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to detect market regime'
|
||||
}, 500);
|
||||
|
|
@ -138,7 +137,7 @@ export function createAnalyticsRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error calculating correlation:', error);
|
||||
container.logger.error('Error calculating correlation:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to calculate correlation'
|
||||
}, 500);
|
||||
|
|
@ -169,7 +168,7 @@ export function createAnalyticsRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error making prediction:', error);
|
||||
container.logger.error('Error making prediction:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to make prediction'
|
||||
}, 500);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,48 @@
|
|||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
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 {
|
||||
export function createBacktestRoutes(container: IServiceContainer): Hono {
|
||||
const app = new Hono();
|
||||
const backtestEngine = container.get('BacktestEngine') as BacktestEngine;
|
||||
const modeManager = container.get('ModeManager') as ModeManager;
|
||||
const backtestEngine = container.custom?.BacktestEngine as BacktestEngine;
|
||||
const modeManager = container.custom?.ModeManager as ModeManager;
|
||||
|
||||
// Run new backtest
|
||||
// Default POST to / is the same as /run for backward compatibility
|
||||
app.post('/', 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);
|
||||
}
|
||||
|
||||
container.logger.error('Error running backtest:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to run backtest'
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Run new backtest (same as above but at /run)
|
||||
app.post('/run', async (c) => {
|
||||
try {
|
||||
const body = await c.req.json();
|
||||
|
|
@ -36,7 +63,7 @@ export function createBacktestRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error running backtest:', error);
|
||||
container.logger.error('Error running backtest:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to run backtest'
|
||||
}, 500);
|
||||
|
|
@ -53,7 +80,7 @@ export function createBacktestRoutes(): Hono {
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error stopping backtest:', error);
|
||||
container.logger.error('Error stopping backtest:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to stop backtest'
|
||||
}, 500);
|
||||
|
|
@ -72,7 +99,7 @@ export function createBacktestRoutes(): Hono {
|
|||
currentTime: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting backtest progress:', error);
|
||||
container.logger.error('Error getting backtest progress:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get progress'
|
||||
}, 500);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { OrderRequestSchema } from '../../types';
|
||||
import { ExecutionService } from '../../services/ExecutionService';
|
||||
import { container } from '../../container';
|
||||
|
||||
const OrderIdSchema = z.object({
|
||||
orderId: z.string()
|
||||
});
|
||||
|
||||
export function createOrderRoutes(): Hono {
|
||||
export function createOrderRoutes(container: IServiceContainer): Hono {
|
||||
const app = new Hono();
|
||||
const executionService = container.get('ExecutionService') as ExecutionService;
|
||||
const executionService = container.custom?.ExecutionService as ExecutionService;
|
||||
|
||||
// Submit new order
|
||||
app.post('/', async (c) => {
|
||||
|
|
@ -30,7 +29,7 @@ export function createOrderRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error submitting order:', error);
|
||||
container.logger.error('Error submitting order:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to submit order'
|
||||
}, 500);
|
||||
|
|
@ -50,7 +49,7 @@ export function createOrderRoutes(): Hono {
|
|||
return c.json({ error: 'Order not found or already filled' }, 404);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error cancelling order:', error);
|
||||
container.logger.error('Error cancelling order:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to cancel order'
|
||||
}, 500);
|
||||
|
|
@ -70,7 +69,7 @@ export function createOrderRoutes(): Hono {
|
|||
return c.json({ error: 'Order not found' }, 404);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error getting order status:', error);
|
||||
container.logger.error('Error getting order status:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get order status'
|
||||
}, 500);
|
||||
|
|
@ -101,7 +100,7 @@ export function createOrderRoutes(): Hono {
|
|||
}, 400);
|
||||
}
|
||||
|
||||
logger.error('Error submitting batch orders:', error);
|
||||
container.logger.error('Error submitting batch orders:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to submit batch orders'
|
||||
}, 500);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { ModeManager } from '../../core/ModeManager';
|
||||
import { container } from '../../container';
|
||||
|
||||
const SymbolSchema = z.object({
|
||||
symbol: z.string()
|
||||
});
|
||||
|
||||
export function createPositionRoutes(): Hono {
|
||||
export function createPositionRoutes(container: IServiceContainer): Hono {
|
||||
const app = new Hono();
|
||||
const modeManager = container.get('ModeManager') as ModeManager;
|
||||
const modeManager = container.custom?.ModeManager as ModeManager;
|
||||
|
||||
// Get all positions
|
||||
app.get('/', async (c) => {
|
||||
|
|
@ -23,7 +22,7 @@ export function createPositionRoutes(): Hono {
|
|||
positions
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting positions:', error);
|
||||
container.logger.error('Error getting positions:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get positions'
|
||||
}, 500);
|
||||
|
|
@ -41,7 +40,7 @@ export function createPositionRoutes(): Hono {
|
|||
positions
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting open positions:', error);
|
||||
container.logger.error('Error getting open positions:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get open positions'
|
||||
}, 500);
|
||||
|
|
@ -69,7 +68,7 @@ export function createPositionRoutes(): Hono {
|
|||
}, 404);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error getting position:', error);
|
||||
container.logger.error('Error getting position:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get position'
|
||||
}, 500);
|
||||
|
|
@ -92,7 +91,7 @@ export function createPositionRoutes(): Hono {
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting P&L:', error);
|
||||
container.logger.error('Error getting P&L:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get P&L'
|
||||
}, 500);
|
||||
|
|
@ -111,7 +110,7 @@ export function createPositionRoutes(): Hono {
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting risk metrics:', error);
|
||||
container.logger.error('Error getting risk metrics:', error);
|
||||
return c.json({
|
||||
error: error instanceof Error ? error.message : 'Failed to get risk metrics'
|
||||
}, 500);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { Server as SocketIOServer, Socket } from 'socket.io';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { z } from 'zod';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
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()),
|
||||
|
|
@ -15,16 +14,16 @@ 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;
|
||||
export function setupWebSocketHandlers(io: SocketIOServer, container: IServiceContainer): void {
|
||||
const marketDataService = container.custom?.MarketDataService as MarketDataService;
|
||||
const executionService = container.custom?.ExecutionService as ExecutionService;
|
||||
const modeManager = container.custom?.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}`);
|
||||
container.logger.info(`WebSocket client connected: ${socket.id}`);
|
||||
clientSubscriptions.set(socket.id, new Set());
|
||||
|
||||
// Send initial connection info
|
||||
|
|
@ -44,13 +43,13 @@ export function setupWebSocketHandlers(io: SocketIOServer, container: Container)
|
|||
subscriptions.add(symbol);
|
||||
}
|
||||
|
||||
logger.debug(`Client ${socket.id} subscribed to: ${symbols.join(', ')}`);
|
||||
container.logger.debug(`Client ${socket.id} subscribed to: ${symbols.join(', ')}`);
|
||||
|
||||
if (callback) {
|
||||
callback({ success: true, symbols });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Subscription error:', error);
|
||||
container.logger.error('Subscription error:', error);
|
||||
if (callback) {
|
||||
callback({
|
||||
success: false,
|
||||
|
|
@ -83,13 +82,13 @@ export function setupWebSocketHandlers(io: SocketIOServer, container: Container)
|
|||
}
|
||||
}
|
||||
|
||||
logger.debug(`Client ${socket.id} unsubscribed from: ${symbols.join(', ')}`);
|
||||
container.logger.debug(`Client ${socket.id} unsubscribed from: ${symbols.join(', ')}`);
|
||||
|
||||
if (callback) {
|
||||
callback({ success: true, symbols });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Unsubscribe error:', error);
|
||||
container.logger.error('Unsubscribe error:', error);
|
||||
if (callback) {
|
||||
callback({
|
||||
success: false,
|
||||
|
|
@ -107,7 +106,7 @@ export function setupWebSocketHandlers(io: SocketIOServer, container: Container)
|
|||
callback({ success: true, result });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Order submission error:', error);
|
||||
container.logger.error('Order submission error:', error);
|
||||
if (callback) {
|
||||
callback({
|
||||
success: false,
|
||||
|
|
@ -127,7 +126,7 @@ export function setupWebSocketHandlers(io: SocketIOServer, container: Container)
|
|||
callback({ success: true, positions });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error getting positions:', error);
|
||||
container.logger.error('Error getting positions:', error);
|
||||
if (callback) {
|
||||
callback({
|
||||
success: false,
|
||||
|
|
@ -139,7 +138,7 @@ export function setupWebSocketHandlers(io: SocketIOServer, container: Container)
|
|||
|
||||
// Handle disconnection
|
||||
socket.on('disconnect', async () => {
|
||||
logger.info(`WebSocket client disconnected: ${socket.id}`);
|
||||
container.logger.info(`WebSocket client disconnected: ${socket.id}`);
|
||||
|
||||
// Unsubscribe from all symbols for this client
|
||||
const subscriptions = clientSubscriptions.get(socket.id);
|
||||
|
|
@ -191,5 +190,5 @@ export function setupWebSocketHandlers(io: SocketIOServer, container: Container)
|
|||
});
|
||||
});
|
||||
|
||||
logger.info('WebSocket handlers initialized');
|
||||
container.logger.info('WebSocket handlers initialized');
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { EventEmitter } from 'events';
|
||||
import { MarketData, BacktestConfigSchema, PerformanceMetrics, MarketMicrostructure } from '../types';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { PerformanceAnalyzer } from '../analytics/PerformanceAnalyzer';
|
||||
import { DataManager } from '../data/DataManager';
|
||||
import { StorageService } from '../services/StorageService';
|
||||
import { StrategyManager } from '../strategies/StrategyManager';
|
||||
import { TradingEngine } from '../../core';
|
||||
import { DataManager } from '../data/DataManager';
|
||||
import { BacktestConfigSchema, MarketData, MarketMicrostructure, PerformanceMetrics } from '../types';
|
||||
import { MarketSimulator } from './MarketSimulator';
|
||||
import { PerformanceAnalyzer } from '../analytics/PerformanceAnalyzer';
|
||||
|
||||
interface BacktestEvent {
|
||||
timestamp: number;
|
||||
|
|
@ -35,12 +34,16 @@ export class BacktestEngine extends EventEmitter {
|
|||
private marketSimulator: MarketSimulator;
|
||||
private performanceAnalyzer: PerformanceAnalyzer;
|
||||
private microstructures: Map<string, MarketMicrostructure> = new Map();
|
||||
private container: IServiceContainer;
|
||||
private initialCapital: number = 100000;
|
||||
|
||||
constructor(
|
||||
container: IServiceContainer,
|
||||
private storageService: StorageService,
|
||||
private strategyManager: StrategyManager
|
||||
) {
|
||||
super();
|
||||
this.container = container;
|
||||
this.dataManager = new DataManager(storageService);
|
||||
this.marketSimulator = new MarketSimulator({
|
||||
useHistoricalSpreads: true,
|
||||
|
|
@ -55,11 +58,24 @@ export class BacktestEngine extends EventEmitter {
|
|||
// Validate config
|
||||
const validatedConfig = BacktestConfigSchema.parse(config);
|
||||
|
||||
logger.info(`Starting backtest from ${validatedConfig.startDate} to ${validatedConfig.endDate}`);
|
||||
this.container.logger.info(`Starting backtest from ${validatedConfig.startDate} to ${validatedConfig.endDate}`);
|
||||
|
||||
// Reset state
|
||||
this.reset();
|
||||
this.isRunning = true;
|
||||
this.initialCapital = validatedConfig.initialCapital;
|
||||
|
||||
// Initialize equity curve with starting capital
|
||||
this.equityCurve.push({
|
||||
timestamp: new Date(validatedConfig.startDate).getTime(),
|
||||
value: this.initialCapital
|
||||
});
|
||||
|
||||
// Initialize performance analyzer with starting capital
|
||||
this.performanceAnalyzer.addEquityPoint(
|
||||
new Date(validatedConfig.startDate),
|
||||
this.initialCapital
|
||||
);
|
||||
|
||||
// Generate backtest ID
|
||||
const backtestId = `backtest_${Date.now()}`;
|
||||
|
|
@ -84,7 +100,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
});
|
||||
marketData.sort((a, b) => a.data.timestamp - b.data.timestamp);
|
||||
|
||||
logger.info(`Loaded ${marketData.length} market data points`);
|
||||
this.container.logger.info(`Loaded ${marketData.length} market data points`);
|
||||
|
||||
// Initialize strategies
|
||||
await this.strategyManager.initializeStrategies(validatedConfig.strategies || []);
|
||||
|
|
@ -110,17 +126,18 @@ export class BacktestEngine extends EventEmitter {
|
|||
equityCurve: this.equityCurve,
|
||||
drawdown: this.calculateDrawdown(),
|
||||
dailyReturns: this.calculateDailyReturns(),
|
||||
finalPositions
|
||||
finalPositions,
|
||||
ohlcData: this.getOHLCData(marketData, validatedConfig.symbols)
|
||||
};
|
||||
|
||||
await this.storeResults(result);
|
||||
|
||||
logger.info(`Backtest completed: ${performance.totalTrades} trades, ${performance.totalReturn}% return`);
|
||||
this.container.logger.info(`Backtest completed: ${performance.totalTrades} trades, ${performance.totalReturn}% return`);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Backtest failed:', error);
|
||||
this.container.logger.error('Backtest failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.isRunning = false;
|
||||
|
|
@ -133,30 +150,65 @@ export class BacktestEngine extends EventEmitter {
|
|||
const startDate = new Date(config.startDate);
|
||||
const endDate = new Date(config.endDate);
|
||||
|
||||
for (const symbol of config.symbols) {
|
||||
const bars = await this.storageService.getHistoricalBars(
|
||||
symbol,
|
||||
startDate,
|
||||
endDate,
|
||||
config.dataFrequency
|
||||
);
|
||||
|
||||
// Convert to MarketData format
|
||||
bars.forEach(bar => {
|
||||
data.push({
|
||||
type: 'bar',
|
||||
data: {
|
||||
symbol,
|
||||
open: bar.open,
|
||||
high: bar.high,
|
||||
low: bar.low,
|
||||
close: bar.close,
|
||||
volume: bar.volume,
|
||||
vwap: bar.vwap,
|
||||
timestamp: new Date(bar.timestamp).getTime()
|
||||
try {
|
||||
for (const symbol of config.symbols) {
|
||||
const bars = await this.storageService.getHistoricalBars(
|
||||
symbol,
|
||||
startDate,
|
||||
endDate,
|
||||
config.dataFrequency
|
||||
);
|
||||
|
||||
// If no data found, use mock data
|
||||
if (!bars || bars.length === 0) {
|
||||
this.container.logger.warn(`No historical data found for ${symbol}, using mock data`);
|
||||
|
||||
// Tell the Rust core to generate mock data
|
||||
const tradingEngine = this.strategyManager.getTradingEngine();
|
||||
if (tradingEngine && tradingEngine.generateMockData) {
|
||||
await tradingEngine.generateMockData(
|
||||
symbol,
|
||||
startDate.getTime(),
|
||||
endDate.getTime(),
|
||||
42 // seed for reproducibility
|
||||
);
|
||||
|
||||
// For now, we'll generate mock data on the TypeScript side
|
||||
// as the Rust integration needs more work
|
||||
const mockData = this.generateMockData(symbol, startDate, endDate);
|
||||
data.push(...mockData);
|
||||
} else {
|
||||
// Fallback to TypeScript mock data generation
|
||||
const mockData = this.generateMockData(symbol, startDate, endDate);
|
||||
data.push(...mockData);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Convert to MarketData format
|
||||
bars.forEach(bar => {
|
||||
data.push({
|
||||
type: 'bar',
|
||||
data: {
|
||||
symbol,
|
||||
open: bar.open,
|
||||
high: bar.high,
|
||||
low: bar.low,
|
||||
close: bar.close,
|
||||
volume: bar.volume,
|
||||
vwap: bar.vwap,
|
||||
timestamp: new Date(bar.timestamp).getTime()
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.container.logger.warn('Error loading historical data, using mock data:', error);
|
||||
|
||||
// Generate mock data for all symbols
|
||||
for (const symbol of config.symbols) {
|
||||
const mockData = this.generateMockData(symbol, startDate, endDate);
|
||||
data.push(...mockData);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by timestamp
|
||||
|
|
@ -168,6 +220,48 @@ export class BacktestEngine extends EventEmitter {
|
|||
|
||||
return data;
|
||||
}
|
||||
|
||||
private generateMockData(symbol: string, startDate: Date, endDate: Date): MarketData[] {
|
||||
const data: MarketData[] = [];
|
||||
const startTime = startDate.getTime();
|
||||
const endTime = endDate.getTime();
|
||||
const interval = 24 * 60 * 60 * 1000; // 1 day in milliseconds
|
||||
|
||||
let price = 100; // Base price
|
||||
let currentTime = startTime;
|
||||
|
||||
while (currentTime <= endTime) {
|
||||
// Generate random price movement
|
||||
const changePercent = (Math.random() - 0.5) * 0.04; // +/- 2% daily
|
||||
price = price * (1 + changePercent);
|
||||
|
||||
// Generate OHLC
|
||||
const open = price;
|
||||
const high = price * (1 + Math.random() * 0.02);
|
||||
const low = price * (1 - Math.random() * 0.02);
|
||||
const close = price * (1 + (Math.random() - 0.5) * 0.01);
|
||||
const volume = Math.random() * 1000000 + 500000;
|
||||
|
||||
data.push({
|
||||
type: 'bar',
|
||||
data: {
|
||||
symbol,
|
||||
open,
|
||||
high,
|
||||
low,
|
||||
close,
|
||||
volume,
|
||||
vwap: (open + high + low + close) / 4,
|
||||
timestamp: currentTime
|
||||
}
|
||||
});
|
||||
|
||||
currentTime += interval;
|
||||
price = close; // Next bar opens at previous close
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private populateEventQueue(marketData: MarketData[]): void {
|
||||
// Convert market data to events
|
||||
|
|
@ -234,7 +328,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
|
||||
private async processMarketData(data: MarketData): Promise<void> {
|
||||
const tradingEngine = this.strategyManager.getTradingEngine();
|
||||
if (!tradingEngine) return;
|
||||
if (!tradingEngine) {return;}
|
||||
|
||||
// Process through market simulator for realistic orderbook
|
||||
const orderbook = this.marketSimulator.processMarketData(data);
|
||||
|
|
@ -300,7 +394,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
// Track performance
|
||||
this.performanceAnalyzer.addEquityPoint(
|
||||
new Date(this.currentTime),
|
||||
this.getPortfolioValue()
|
||||
await this.getPortfolioValue()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -321,18 +415,24 @@ export class BacktestEngine extends EventEmitter {
|
|||
}
|
||||
|
||||
private async updateEquityCurve(): Promise<void> {
|
||||
const tradingEngine = this.strategyManager.getTradingEngine();
|
||||
if (!tradingEngine) return;
|
||||
|
||||
// Get current P&L
|
||||
const [realized, unrealized] = tradingEngine.getTotalPnl();
|
||||
const totalEquity = 100000 + realized + unrealized; // Assuming 100k starting capital
|
||||
const totalEquity = await this.getPortfolioValue();
|
||||
|
||||
this.equityCurve.push({
|
||||
timestamp: this.currentTime,
|
||||
value: totalEquity
|
||||
});
|
||||
}
|
||||
|
||||
private async getPortfolioValue(): Promise<number> {
|
||||
const tradingEngine = this.strategyManager.getTradingEngine();
|
||||
if (!tradingEngine) {
|
||||
return this.initialCapital;
|
||||
}
|
||||
|
||||
// Get current P&L
|
||||
const [realized, unrealized] = tradingEngine.getTotalPnl();
|
||||
return this.initialCapital + realized + unrealized;
|
||||
}
|
||||
|
||||
private calculatePerformance(): PerformanceMetrics {
|
||||
// Use sophisticated performance analyzer
|
||||
|
|
@ -365,49 +465,6 @@ export class BacktestEngine extends EventEmitter {
|
|||
maxDrawdownDuration: drawdownAnalysis.maxDrawdownDuration
|
||||
};
|
||||
}
|
||||
|
||||
const initialEquity = this.equityCurve[0].value;
|
||||
const finalEquity = this.equityCurve[this.equityCurve.length - 1].value;
|
||||
const totalReturn = ((finalEquity - initialEquity) / initialEquity) * 100;
|
||||
|
||||
// Calculate daily returns
|
||||
const dailyReturns = this.calculateDailyReturns();
|
||||
|
||||
// Sharpe ratio (assuming 0% risk-free rate)
|
||||
const avgReturn = dailyReturns.reduce((a, b) => a + b, 0) / dailyReturns.length;
|
||||
const stdDev = Math.sqrt(
|
||||
dailyReturns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / dailyReturns.length
|
||||
);
|
||||
const sharpeRatio = stdDev > 0 ? (avgReturn / stdDev) * Math.sqrt(252) : 0; // Annualized
|
||||
|
||||
// Win rate and profit factor
|
||||
const winningTrades = this.trades.filter(t => t.pnl > 0);
|
||||
const losingTrades = this.trades.filter(t => t.pnl < 0);
|
||||
const winRate = this.trades.length > 0 ? (winningTrades.length / this.trades.length) * 100 : 0;
|
||||
|
||||
const totalWins = winningTrades.reduce((sum, t) => sum + t.pnl, 0);
|
||||
const totalLosses = Math.abs(losingTrades.reduce((sum, t) => sum + t.pnl, 0));
|
||||
const profitFactor = totalLosses > 0 ? totalWins / totalLosses : totalWins > 0 ? Infinity : 0;
|
||||
|
||||
const avgWin = winningTrades.length > 0 ? totalWins / winningTrades.length : 0;
|
||||
const avgLoss = losingTrades.length > 0 ? totalLosses / losingTrades.length : 0;
|
||||
|
||||
// Max drawdown
|
||||
const drawdowns = this.calculateDrawdown();
|
||||
const maxDrawdown = Math.min(...drawdowns.map(d => d.value));
|
||||
|
||||
return {
|
||||
totalReturn,
|
||||
sharpeRatio,
|
||||
sortinoRatio: sharpeRatio * 0.8, // Simplified for now
|
||||
maxDrawdown: Math.abs(maxDrawdown),
|
||||
winRate,
|
||||
profitFactor,
|
||||
avgWin,
|
||||
avgLoss,
|
||||
totalTrades: this.trades.length
|
||||
};
|
||||
}
|
||||
|
||||
private calculateDrawdown(): { timestamp: number; value: number }[] {
|
||||
const drawdowns: { timestamp: number; value: number }[] = [];
|
||||
|
|
@ -451,7 +508,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
|
||||
private async getFinalPositions(): Promise<any[]> {
|
||||
const tradingEngine = this.strategyManager.getTradingEngine();
|
||||
if (!tradingEngine) return [];
|
||||
if (!tradingEngine) {return [];}
|
||||
|
||||
const positions = JSON.parse(tradingEngine.getOpenPositions());
|
||||
return positions;
|
||||
|
|
@ -465,7 +522,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
);
|
||||
|
||||
// Could also store detailed results in a separate table or file
|
||||
logger.debug(`Backtest results stored with ID: ${result.id}`);
|
||||
this.container.logger.debug(`Backtest results stored with ID: ${result.id}`);
|
||||
}
|
||||
|
||||
private reset(): void {
|
||||
|
|
@ -521,7 +578,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
|
||||
private getPortfolioValue(): number {
|
||||
const tradingEngine = this.strategyManager.getTradingEngine();
|
||||
if (!tradingEngine) return 100000; // Default initial capital
|
||||
if (!tradingEngine) {return 100000;} // Default initial capital
|
||||
|
||||
const [realized, unrealized] = tradingEngine.getTotalPnl();
|
||||
return 100000 + realized + unrealized;
|
||||
|
|
@ -529,7 +586,7 @@ export class BacktestEngine extends EventEmitter {
|
|||
|
||||
async stopBacktest(): Promise<void> {
|
||||
this.isRunning = false;
|
||||
logger.info('Backtest stop requested');
|
||||
this.container.logger.info('Backtest stop requested');
|
||||
}
|
||||
|
||||
async exportResults(format: 'json' | 'csv' | 'html' = 'json'): Promise<string> {
|
||||
|
|
@ -631,4 +688,25 @@ export class BacktestEngine extends EventEmitter {
|
|||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
private getOHLCData(marketData: MarketData[], symbols: string[]): Record<string, any[]> {
|
||||
const ohlcData: Record<string, any[]> = {};
|
||||
|
||||
symbols.forEach(symbol => {
|
||||
const symbolData = marketData
|
||||
.filter(d => d.type === 'bar' && d.data.symbol === symbol)
|
||||
.map(d => ({
|
||||
time: Math.floor(d.data.timestamp / 1000), // Convert to seconds for lightweight-charts
|
||||
open: d.data.open,
|
||||
high: d.data.high,
|
||||
low: d.data.low,
|
||||
close: d.data.close,
|
||||
volume: d.data.volume
|
||||
}));
|
||||
|
||||
ohlcData[symbol] = symbolData;
|
||||
});
|
||||
|
||||
return ohlcData;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('MarketSimulator');
|
||||
import { MarketData, Quote, Trade, Bar, OrderBookSnapshot, PriceLevel } from '../types';
|
||||
import { MarketMicrostructure } from '../types/MarketMicrostructure';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Container } from '@stock-bot/di';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { ModeManager } from './core/ModeManager';
|
||||
import { MarketDataService } from './services/MarketDataService';
|
||||
import { ExecutionService } from './services/ExecutionService';
|
||||
|
|
@ -9,39 +8,94 @@ import { StrategyManager } from './strategies/StrategyManager';
|
|||
import { BacktestEngine } from './backtest/BacktestEngine';
|
||||
import { PaperTradingManager } from './paper/PaperTradingManager';
|
||||
|
||||
// Create and configure the DI container
|
||||
export const container = new Container();
|
||||
/**
|
||||
* Register orchestrator-specific services in the DI container
|
||||
*/
|
||||
export async function registerOrchestratorServices(container: any): Promise<void> {
|
||||
// Create a service adapter that provides the expected interface
|
||||
const services: IServiceContainer = {
|
||||
logger: container.cradle.logger,
|
||||
cache: container.cradle.cache,
|
||||
globalCache: container.cradle.globalCache,
|
||||
mongodb: container.cradle.mongoClient,
|
||||
postgres: container.cradle.postgresClient,
|
||||
questdb: container.cradle.questdbClient,
|
||||
browser: container.cradle.browser,
|
||||
proxy: container.cradle.proxyManager,
|
||||
queueManager: container.cradle.queueManager,
|
||||
queue: undefined, // Will be set if needed
|
||||
custom: {}
|
||||
};
|
||||
|
||||
// Create storage service first as it's needed by other services
|
||||
const storageService = new StorageService(
|
||||
services,
|
||||
services.mongodb!,
|
||||
services.postgres!,
|
||||
services.questdb || null
|
||||
);
|
||||
|
||||
// Create other services
|
||||
const marketDataService = new MarketDataService(services);
|
||||
const executionService = new ExecutionService(services, storageService);
|
||||
const analyticsService = new AnalyticsService(services, storageService);
|
||||
const strategyManager = new StrategyManager(services);
|
||||
const backtestEngine = new BacktestEngine(services, storageService, strategyManager);
|
||||
const paperTradingManager = new PaperTradingManager(
|
||||
services,
|
||||
storageService,
|
||||
marketDataService,
|
||||
executionService
|
||||
);
|
||||
const modeManager = new ModeManager(
|
||||
services,
|
||||
marketDataService,
|
||||
executionService,
|
||||
storageService
|
||||
);
|
||||
|
||||
// Store custom services
|
||||
services.custom = {
|
||||
StorageService: storageService,
|
||||
MarketDataService: marketDataService,
|
||||
ExecutionService: executionService,
|
||||
AnalyticsService: analyticsService,
|
||||
StrategyManager: strategyManager,
|
||||
BacktestEngine: backtestEngine,
|
||||
PaperTradingManager: paperTradingManager,
|
||||
ModeManager: modeManager
|
||||
};
|
||||
|
||||
// Register services in the Awilix container for resolution
|
||||
container.register({
|
||||
StorageService: { value: storageService },
|
||||
MarketDataService: { value: marketDataService },
|
||||
ExecutionService: { value: executionService },
|
||||
AnalyticsService: { value: analyticsService },
|
||||
StrategyManager: { value: strategyManager },
|
||||
BacktestEngine: { value: backtestEngine },
|
||||
PaperTradingManager: { value: paperTradingManager },
|
||||
ModeManager: { value: modeManager },
|
||||
orchestratorServices: { value: services }
|
||||
});
|
||||
|
||||
// Update the serviceContainer to include our custom services
|
||||
const serviceContainer = container.cradle.serviceContainer;
|
||||
if (serviceContainer && serviceContainer.custom) {
|
||||
Object.assign(serviceContainer.custom, services.custom);
|
||||
}
|
||||
|
||||
// Setup event listeners after all services are registered
|
||||
strategyManager.setupEventListeners();
|
||||
|
||||
// Initialize mode manager with default paper trading mode
|
||||
await modeManager.initializeMode({
|
||||
mode: 'paper',
|
||||
startingCapital: 100000
|
||||
});
|
||||
}
|
||||
|
||||
// Register core services
|
||||
container.singleton('Logger', () => logger);
|
||||
|
||||
container.singleton('ModeManager', () => new ModeManager(
|
||||
container.get('MarketDataService'),
|
||||
container.get('ExecutionService'),
|
||||
container.get('StorageService')
|
||||
));
|
||||
|
||||
container.singleton('MarketDataService', () => new MarketDataService());
|
||||
|
||||
container.singleton('ExecutionService', () => new ExecutionService(
|
||||
container.get('ModeManager')
|
||||
));
|
||||
|
||||
container.singleton('AnalyticsService', () => new AnalyticsService());
|
||||
|
||||
container.singleton('StorageService', () => new StorageService());
|
||||
|
||||
container.singleton('StrategyManager', () => new StrategyManager(
|
||||
container.get('ModeManager'),
|
||||
container.get('MarketDataService'),
|
||||
container.get('ExecutionService')
|
||||
));
|
||||
|
||||
container.singleton('BacktestEngine', () => new BacktestEngine(
|
||||
container.get('StorageService'),
|
||||
container.get('StrategyManager')
|
||||
));
|
||||
|
||||
container.singleton('PaperTradingManager', () => new PaperTradingManager(
|
||||
container.get('ExecutionService')
|
||||
));
|
||||
// For backward compatibility, export a container getter
|
||||
export function getContainer(): IServiceContainer {
|
||||
throw new Error('Container should be accessed through ServiceApplication. Update your code to use dependency injection.');
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { TradingEngine } from '../../core';
|
||||
import { TradingEngine } from '@stock-bot/core';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { TradingMode, ModeConfig, BacktestConfigSchema, PaperConfigSchema, LiveConfigSchema } from '../types';
|
||||
import { MarketDataService } from '../services/MarketDataService';
|
||||
import { ExecutionService } from '../services/ExecutionService';
|
||||
|
|
@ -11,13 +11,16 @@ export class ModeManager extends EventEmitter {
|
|||
private config: ModeConfig | null = null;
|
||||
private tradingEngine: TradingEngine | null = null;
|
||||
private isInitialized = false;
|
||||
private container: IServiceContainer;
|
||||
|
||||
constructor(
|
||||
container: IServiceContainer,
|
||||
private marketDataService: MarketDataService,
|
||||
private executionService: ExecutionService,
|
||||
private storageService: StorageService
|
||||
) {
|
||||
super();
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async initializeMode(config: ModeConfig): Promise<void> {
|
||||
|
|
@ -52,7 +55,7 @@ export class ModeManager extends EventEmitter {
|
|||
this.isInitialized = true;
|
||||
this.emit('modeChanged', config);
|
||||
|
||||
logger.info(`Trading mode initialized: ${config.mode}`);
|
||||
this.container.logger.info(`Trading mode initialized: ${config.mode}`);
|
||||
}
|
||||
|
||||
private createEngineConfig(config: ModeConfig): any {
|
||||
|
|
@ -127,7 +130,7 @@ export class ModeManager extends EventEmitter {
|
|||
async transitionMode(fromMode: TradingMode, toMode: TradingMode, config: ModeConfig): Promise<void> {
|
||||
if (fromMode === 'paper' && toMode === 'live') {
|
||||
// Special handling for paper to live transition
|
||||
logger.info('Transitioning from paper to live trading...');
|
||||
this.container.logger.info('Transitioning from paper to live trading...');
|
||||
|
||||
// 1. Get current paper positions
|
||||
const paperPositions = await this.tradingEngine!.getOpenPositions();
|
||||
|
|
@ -136,7 +139,7 @@ export class ModeManager extends EventEmitter {
|
|||
await this.initializeMode(config);
|
||||
|
||||
// 3. Reconcile positions (this would be handled by a reconciliation service)
|
||||
logger.info(`Paper positions to reconcile: ${paperPositions}`);
|
||||
this.container.logger.info(`Paper positions to reconcile: ${paperPositions}`);
|
||||
} else {
|
||||
// Standard mode switch
|
||||
await this.initializeMode(config);
|
||||
|
|
@ -146,7 +149,7 @@ export class ModeManager extends EventEmitter {
|
|||
async shutdown(): Promise<void> {
|
||||
if (!this.isInitialized) return;
|
||||
|
||||
logger.info(`Shutting down ${this.mode} mode...`);
|
||||
this.container.logger.info(`Shutting down ${this.mode} mode...`);
|
||||
|
||||
// Shutdown services
|
||||
await this.marketDataService.shutdown();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('DataManager');
|
||||
import { StorageService } from '../services/StorageService';
|
||||
import { MarketData, Bar } from '../types';
|
||||
import { EventEmitter } from 'events';
|
||||
|
|
@ -62,13 +64,19 @@ export class DataManager extends EventEmitter {
|
|||
for (const symbol of symbols) {
|
||||
try {
|
||||
// Load raw data
|
||||
const data = await this.storageService.getHistoricalBars(
|
||||
let data = await this.storageService.getHistoricalBars(
|
||||
symbol,
|
||||
startDate,
|
||||
endDate,
|
||||
resolution
|
||||
);
|
||||
|
||||
// If no data found, generate mock data
|
||||
if (!data || data.length === 0) {
|
||||
logger.warn(`No historical data found for ${symbol}, generating mock data`);
|
||||
data = this.generateMockBars(symbol, startDate, endDate, resolution);
|
||||
}
|
||||
|
||||
// Apply corporate actions
|
||||
const adjustedData = await this.applyCorporateActions(symbol, data, startDate, endDate);
|
||||
|
||||
|
|
@ -432,4 +440,62 @@ export class DataManager extends EventEmitter {
|
|||
this.aggregatedCache.clear();
|
||||
this.dataQualityIssues = [];
|
||||
}
|
||||
|
||||
private generateMockBars(
|
||||
symbol: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
resolution: string
|
||||
): any[] {
|
||||
const bars: any[] = [];
|
||||
const resolutionMs = DataManager.RESOLUTIONS[resolution]?.milliseconds || 86400000; // Default to 1 day
|
||||
|
||||
let currentTime = startDate.getTime();
|
||||
const endTime = endDate.getTime();
|
||||
|
||||
// Base price varies by symbol
|
||||
let basePrice = 100;
|
||||
if (symbol === 'AAPL') basePrice = 150;
|
||||
else if (symbol === 'GOOGL') basePrice = 140;
|
||||
else if (symbol === 'MSFT') basePrice = 380;
|
||||
else if (symbol === 'TSLA') basePrice = 250;
|
||||
|
||||
let price = basePrice;
|
||||
|
||||
while (currentTime <= endTime) {
|
||||
// Generate realistic intraday movement
|
||||
const volatility = 0.02; // 2% daily volatility
|
||||
const trend = 0.0001; // Slight upward trend
|
||||
|
||||
// Random walk with trend
|
||||
const changePercent = (Math.random() - 0.5) * volatility + trend;
|
||||
price = price * (1 + changePercent);
|
||||
|
||||
// Generate OHLC
|
||||
const open = price;
|
||||
const intraDayVolatility = volatility / 4;
|
||||
const high = price * (1 + Math.random() * intraDayVolatility);
|
||||
const low = price * (1 - Math.random() * intraDayVolatility);
|
||||
const close = low + Math.random() * (high - low);
|
||||
|
||||
// Volume with some randomness
|
||||
const baseVolume = 10000000; // 10M shares
|
||||
const volume = baseVolume * (0.5 + Math.random());
|
||||
|
||||
bars.push({
|
||||
timestamp: new Date(currentTime),
|
||||
open: Number(open.toFixed(2)),
|
||||
high: Number(high.toFixed(2)),
|
||||
low: Number(low.toFixed(2)),
|
||||
close: Number(close.toFixed(2)),
|
||||
volume: Math.floor(volume),
|
||||
vwap: Number(((high + low + close) / 3).toFixed(2))
|
||||
});
|
||||
|
||||
currentTime += resolutionMs;
|
||||
price = close; // Next bar opens at previous close
|
||||
}
|
||||
|
||||
return bars;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
import {
|
||||
BaseHandler,
|
||||
Handler,
|
||||
Operation,
|
||||
} from '@stock-bot/handlers';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import type { OrchestratorServices } from '../../types';
|
||||
|
||||
const logger = getLogger('backtest-handler');
|
||||
|
||||
interface BacktestPayload {
|
||||
backtestId: string;
|
||||
strategy: string;
|
||||
symbols: string[];
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
initialCapital: number;
|
||||
config?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface CancelBacktestPayload {
|
||||
backtestId: string;
|
||||
}
|
||||
|
||||
@Handler('orchestrator')
|
||||
export class BacktestHandler extends BaseHandler<OrchestratorServices> {
|
||||
|
||||
@Operation('run-backtest')
|
||||
async runBacktest(payload: BacktestPayload) {
|
||||
const { backtestId, strategy, symbols, startDate, endDate, initialCapital, config } = payload;
|
||||
|
||||
logger.info('Starting backtest', { backtestId, strategy, symbolCount: symbols.length });
|
||||
|
||||
try {
|
||||
// Update status in web-api (via Redis or direct DB update)
|
||||
await this.updateBacktestStatus(backtestId, 'running');
|
||||
|
||||
// TODO: Call Rust core via NAPI bindings
|
||||
// For now, we'll simulate the backtest
|
||||
const results = await this.simulateBacktest({
|
||||
strategy,
|
||||
symbols,
|
||||
startDate,
|
||||
endDate,
|
||||
initialCapital,
|
||||
config,
|
||||
});
|
||||
|
||||
// Store results
|
||||
await this.storeBacktestResults(backtestId, results);
|
||||
|
||||
// Update status to completed
|
||||
await this.updateBacktestStatus(backtestId, 'completed');
|
||||
|
||||
logger.info('Backtest completed', { backtestId });
|
||||
|
||||
return { success: true, backtestId, results };
|
||||
} catch (error) {
|
||||
logger.error('Backtest failed', { backtestId, error });
|
||||
|
||||
await this.updateBacktestStatus(backtestId, 'failed', error.message);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@Operation('cancel-backtest')
|
||||
async cancelBacktest(payload: CancelBacktestPayload) {
|
||||
const { backtestId } = payload;
|
||||
|
||||
logger.info('Cancelling backtest', { backtestId });
|
||||
|
||||
// TODO: Implement actual cancellation logic
|
||||
// For now, just update the status
|
||||
await this.updateBacktestStatus(backtestId, 'cancelled');
|
||||
|
||||
return { success: true, backtestId };
|
||||
}
|
||||
|
||||
private async updateBacktestStatus(backtestId: string, status: string, error?: string) {
|
||||
// TODO: Update in MongoDB or notify web-api
|
||||
logger.info('Updating backtest status', { backtestId, status, error });
|
||||
}
|
||||
|
||||
private async storeBacktestResults(backtestId: string, results: any) {
|
||||
// TODO: Store in MongoDB
|
||||
logger.info('Storing backtest results', { backtestId });
|
||||
}
|
||||
|
||||
private async simulateBacktest(params: Omit<BacktestPayload, 'backtestId'>) {
|
||||
// Simulate some processing time
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Return mock results
|
||||
return {
|
||||
metrics: {
|
||||
totalReturn: 0.15,
|
||||
sharpeRatio: 1.2,
|
||||
maxDrawdown: -0.08,
|
||||
winRate: 0.55,
|
||||
totalTrades: 150,
|
||||
profitFactor: 1.8,
|
||||
},
|
||||
equity: [
|
||||
{ date: params.startDate, value: params.initialCapital },
|
||||
{ date: params.endDate, value: params.initialCapital * 1.15 },
|
||||
],
|
||||
trades: [
|
||||
{
|
||||
symbol: params.symbols[0],
|
||||
entryDate: params.startDate,
|
||||
exitDate: params.endDate,
|
||||
entryPrice: 100,
|
||||
exitPrice: 115,
|
||||
quantity: 100,
|
||||
pnl: 1500,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +1,150 @@
|
|||
/**
|
||||
* 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 { Server as SocketIOServer } from 'socket.io';
|
||||
import { createServer } from 'http';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { ModeManager } from './core/ModeManager';
|
||||
import { createOrderRoutes } from './api/rest/orders';
|
||||
import { createPositionRoutes } from './api/rest/positions';
|
||||
import { createAnalyticsRoutes } from './api/rest/analytics';
|
||||
import { createBacktestRoutes } from './api/rest/backtest';
|
||||
import { setupWebSocketHandlers } from './api/websocket';
|
||||
import { container } from './container';
|
||||
import { createRoutes } from './routes/create-routes';
|
||||
import { createContainer } from './simple-container';
|
||||
|
||||
const PORT = process.env.PORT || 3002;
|
||||
// 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() {
|
||||
// Initialize Hono app
|
||||
const app = new Hono();
|
||||
let server: any; // Declare server in outer scope for shutdown
|
||||
|
||||
// Middleware
|
||||
app.use('*', cors());
|
||||
app.use('*', async (c, next) => {
|
||||
const start = Date.now();
|
||||
await next();
|
||||
const ms = Date.now() - start;
|
||||
logger.debug(`${c.req.method} ${c.req.url} - ${ms}ms`);
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/health', (c) => {
|
||||
const modeManager = container.get('ModeManager');
|
||||
return c.json({
|
||||
status: 'healthy',
|
||||
mode: modeManager.getCurrentMode(),
|
||||
timestamp: new Date().toISOString()
|
||||
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`);
|
||||
});
|
||||
});
|
||||
|
||||
// Mount routes
|
||||
app.route('/api/orders', createOrderRoutes());
|
||||
app.route('/api/positions', createPositionRoutes());
|
||||
app.route('/api/analytics', createAnalyticsRoutes());
|
||||
app.route('/api/backtest', createBacktestRoutes());
|
||||
// Create and mount routes (without Socket.IO for now)
|
||||
const routes = createRoutes(services);
|
||||
app.route('/', routes);
|
||||
|
||||
// Create HTTP server and Socket.IO
|
||||
const server = createServer(app.fetch);
|
||||
const io = new SocketIOServer(server, {
|
||||
cors: {
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST']
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
|
||||
// Setup WebSocket handlers
|
||||
setupWebSocketHandlers(io, container);
|
||||
// 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);
|
||||
});
|
||||
|
||||
// Initialize mode manager
|
||||
const modeManager = container.get('ModeManager') as ModeManager;
|
||||
|
||||
// Default to paper trading mode
|
||||
await modeManager.initializeMode({
|
||||
mode: 'paper',
|
||||
startingCapital: 100000
|
||||
});
|
||||
|
||||
// Start server
|
||||
server.listen(PORT, () => {
|
||||
logger.info(`Trading orchestrator running on port ${PORT}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
logger.info('Shutting down trading orchestrator...');
|
||||
await modeManager.shutdown();
|
||||
server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to start orchestrator service:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
logger.error('Failed to start trading orchestrator:', error);
|
||||
// Start the service
|
||||
main().catch(error => {
|
||||
logger.error('Unhandled error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { EventEmitter } from 'events';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { OrderRequest, Position } from '../types';
|
||||
import { StorageService } from '../services/StorageService';
|
||||
import { MarketDataService } from '../services/MarketDataService';
|
||||
import { ExecutionService } from '../services/ExecutionService';
|
||||
|
||||
interface VirtualAccount {
|
||||
|
|
@ -49,12 +51,17 @@ export class PaperTradingManager extends EventEmitter {
|
|||
private marketPrices = new Map<string, { bid: number; ask: number }>();
|
||||
private readonly COMMISSION_RATE = 0.001; // 0.1%
|
||||
private readonly MARGIN_REQUIREMENT = 0.25; // 25% margin requirement
|
||||
private container: IServiceContainer;
|
||||
|
||||
constructor(
|
||||
container: IServiceContainer,
|
||||
private storageService: StorageService,
|
||||
private marketDataService: MarketDataService,
|
||||
private executionService: ExecutionService,
|
||||
initialBalance: number = 100000
|
||||
) {
|
||||
super();
|
||||
this.container = container;
|
||||
|
||||
this.account = {
|
||||
balance: initialBalance,
|
||||
|
|
@ -362,6 +369,6 @@ export class PaperTradingManager extends EventEmitter {
|
|||
marginUsed: 0
|
||||
};
|
||||
|
||||
logger.info('Paper trading account reset');
|
||||
this.container.logger.info('Paper trading account reset');
|
||||
}
|
||||
}
|
||||
52
apps/stock/orchestrator/src/routes/create-routes.ts
Normal file
52
apps/stock/orchestrator/src/routes/create-routes.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { Hono } from 'hono';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { createOrderRoutes } from '../api/rest/orders';
|
||||
import { createPositionRoutes } from '../api/rest/positions';
|
||||
import { createAnalyticsRoutes } from '../api/rest/analytics';
|
||||
import { createBacktestRoutes } from '../api/rest/backtest';
|
||||
import { setupWebSocketHandlers } from '../api/websocket';
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
|
||||
/**
|
||||
* Create all routes for the orchestrator service
|
||||
*/
|
||||
export function createRoutes(
|
||||
services: IServiceContainer,
|
||||
io?: SocketIOServer
|
||||
): Hono {
|
||||
const app = new Hono();
|
||||
services.logger.info('Creating orchestrator routes');
|
||||
|
||||
// Health check with mode status
|
||||
app.get('/health', (c) => {
|
||||
const modeManager = services.custom?.ModeManager;
|
||||
return c.json({
|
||||
status: 'healthy',
|
||||
service: 'orchestrator',
|
||||
mode: modeManager?.getCurrentMode() || 'unknown',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Mount REST API routes
|
||||
app.route('/api/orders', createOrderRoutes(services));
|
||||
app.route('/api/positions', createPositionRoutes(services));
|
||||
app.route('/api/analytics', createAnalyticsRoutes(services));
|
||||
app.route('/api/backtest', createBacktestRoutes(services));
|
||||
|
||||
// Setup WebSocket handlers if Socket.IO is provided
|
||||
if (io) {
|
||||
setupWebSocketHandlers(io, services);
|
||||
services.logger.info('WebSocket handlers configured');
|
||||
}
|
||||
|
||||
// Add request 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`);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import axios from 'axios';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { PerformanceMetrics, RiskMetrics } from '../types';
|
||||
import { StorageService } from './StorageService';
|
||||
|
||||
interface OptimizationParams {
|
||||
returns: number[][];
|
||||
|
|
@ -24,8 +25,12 @@ export class AnalyticsService {
|
|||
private analyticsUrl: string;
|
||||
private cache = new Map<string, { data: any; timestamp: number }>();
|
||||
private readonly CACHE_TTL_MS = 60000; // 1 minute cache
|
||||
private container: IServiceContainer;
|
||||
private storageService: StorageService;
|
||||
|
||||
constructor() {
|
||||
constructor(container: IServiceContainer, storageService: StorageService) {
|
||||
this.container = container;
|
||||
this.storageService = storageService;
|
||||
this.analyticsUrl = process.env.ANALYTICS_SERVICE_URL || 'http://localhost:3003';
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +55,7 @@ export class AnalyticsService {
|
|||
this.setCache(cacheKey, metrics);
|
||||
return metrics;
|
||||
} catch (error) {
|
||||
logger.error('Error fetching performance metrics:', error);
|
||||
this.container.logger.error('Error fetching performance metrics:', error);
|
||||
// Return default metrics if analytics service is unavailable
|
||||
return this.getDefaultPerformanceMetrics();
|
||||
}
|
||||
|
|
@ -61,7 +66,7 @@ export class AnalyticsService {
|
|||
const response = await axios.post(`${this.analyticsUrl}/optimize/portfolio`, params);
|
||||
return response.data as PortfolioWeights;
|
||||
} catch (error) {
|
||||
logger.error('Error optimizing portfolio:', error);
|
||||
this.container.logger.error('Error optimizing portfolio:', error);
|
||||
// Return equal weights as fallback
|
||||
return this.getEqualWeights(params.returns[0].length);
|
||||
}
|
||||
|
|
@ -78,7 +83,7 @@ export class AnalyticsService {
|
|||
this.setCache(cacheKey, metrics);
|
||||
return metrics;
|
||||
} catch (error) {
|
||||
logger.error('Error fetching risk metrics:', error);
|
||||
this.container.logger.error('Error fetching risk metrics:', error);
|
||||
return this.getDefaultRiskMetrics();
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +99,7 @@ export class AnalyticsService {
|
|||
this.setCache(cacheKey, regime, 300000); // Cache for 5 minutes
|
||||
return regime;
|
||||
} catch (error) {
|
||||
logger.error('Error detecting market regime:', error);
|
||||
this.container.logger.error('Error detecting market regime:', error);
|
||||
return 'normal'; // Default regime
|
||||
}
|
||||
}
|
||||
|
|
@ -104,7 +109,7 @@ export class AnalyticsService {
|
|||
const response = await axios.post(`${this.analyticsUrl}/analytics/correlation`, { symbols });
|
||||
return response.data.matrix as number[][];
|
||||
} catch (error) {
|
||||
logger.error('Error calculating correlation matrix:', error);
|
||||
this.container.logger.error('Error calculating correlation matrix:', error);
|
||||
// Return identity matrix as fallback
|
||||
return this.getIdentityMatrix(symbols.length);
|
||||
}
|
||||
|
|
@ -115,7 +120,7 @@ export class AnalyticsService {
|
|||
const response = await axios.get(`${this.analyticsUrl}/analytics/backtest/${backtestId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
logger.error('Error running backtest analysis:', error);
|
||||
this.container.logger.error('Error running backtest analysis:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +133,7 @@ export class AnalyticsService {
|
|||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
logger.error('Error getting model prediction:', error);
|
||||
this.container.logger.error('Error getting model prediction:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { EventEmitter } from 'events';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { ModeConfig, OrderRequest, OrderRequestSchema } from '../types';
|
||||
import { TradingEngine } from '../../core';
|
||||
import { TradingEngine } from '@stock-bot/core';
|
||||
import axios from 'axios';
|
||||
import { StorageService } from './StorageService';
|
||||
|
||||
interface ExecutionReport {
|
||||
orderId: string;
|
||||
|
|
@ -29,9 +30,13 @@ export class ExecutionService extends EventEmitter {
|
|||
private tradingEngine: TradingEngine | null = null;
|
||||
private brokerClient: any = null; // Would be specific broker API client
|
||||
private pendingOrders = new Map<string, OrderRequest>();
|
||||
private container: IServiceContainer;
|
||||
private storageService: StorageService;
|
||||
|
||||
constructor(private modeManager: any) {
|
||||
constructor(container: IServiceContainer, storageService: StorageService) {
|
||||
super();
|
||||
this.container = container;
|
||||
this.storageService = storageService;
|
||||
}
|
||||
|
||||
async initialize(config: ModeConfig, tradingEngine: TradingEngine): Promise<void> {
|
||||
|
|
@ -47,7 +52,7 @@ export class ExecutionService extends EventEmitter {
|
|||
private async initializeBroker(broker: string, accountId: string): Promise<void> {
|
||||
// In real implementation, would initialize specific broker API
|
||||
// For example: Alpaca, Interactive Brokers, etc.
|
||||
logger.info(`Initializing ${broker} broker connection for account ${accountId}`);
|
||||
this.container.logger.info(`Initializing ${broker} broker connection for account ${accountId}`);
|
||||
}
|
||||
|
||||
async submitOrder(orderRequest: OrderRequest): Promise<ExecutionReport> {
|
||||
|
|
@ -97,7 +102,7 @@ export class ExecutionService extends EventEmitter {
|
|||
return result;
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error submitting order:', error);
|
||||
this.container.logger.error('Error submitting order:', error);
|
||||
return this.createRejectionReport(
|
||||
orderId,
|
||||
clientOrderId,
|
||||
|
|
@ -171,7 +176,7 @@ export class ExecutionService extends EventEmitter {
|
|||
): Promise<ExecutionReport> {
|
||||
// In real implementation, would submit to actual broker
|
||||
// This is a placeholder
|
||||
logger.info(`Submitting order ${orderId} to broker`);
|
||||
this.container.logger.info(`Submitting order ${orderId} to broker`);
|
||||
|
||||
// Simulate broker response
|
||||
return {
|
||||
|
|
@ -189,7 +194,7 @@ export class ExecutionService extends EventEmitter {
|
|||
async cancelOrder(orderId: string): Promise<boolean> {
|
||||
const order = this.pendingOrders.get(orderId);
|
||||
if (!order) {
|
||||
logger.warn(`Order ${orderId} not found`);
|
||||
this.container.logger.warn(`Order ${orderId} not found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +227,7 @@ export class ExecutionService extends EventEmitter {
|
|||
return true;
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error cancelling order ${orderId}:`, error);
|
||||
this.container.logger.error(`Error cancelling order ${orderId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -286,7 +291,7 @@ export class ExecutionService extends EventEmitter {
|
|||
async routeOrderToExchange(order: OrderRequest, exchange: string): Promise<void> {
|
||||
// This would route orders to specific exchanges in live mode
|
||||
// For now, just a placeholder
|
||||
logger.info(`Routing order to ${exchange}:`, order);
|
||||
this.container.logger.info(`Routing order to ${exchange}:`, order);
|
||||
}
|
||||
|
||||
async getOrderStatus(orderId: string): Promise<ExecutionReport | null> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { EventEmitter } from 'events';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { ModeConfig, MarketData, QuoteSchema, TradeSchema, BarSchema } from '../types';
|
||||
import { QuestDBClient } from '@stock-bot/questdb';
|
||||
|
||||
|
|
@ -13,6 +13,12 @@ export class MarketDataService extends EventEmitter {
|
|||
private batchTimer: NodeJS.Timeout | null = null;
|
||||
private readonly BATCH_SIZE = 100;
|
||||
private readonly BATCH_INTERVAL_MS = 50;
|
||||
private container: IServiceContainer;
|
||||
|
||||
constructor(container: IServiceContainer) {
|
||||
super();
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async initialize(config: ModeConfig): Promise<void> {
|
||||
this.mode = config.mode;
|
||||
|
|
@ -41,7 +47,7 @@ export class MarketDataService extends EventEmitter {
|
|||
});
|
||||
|
||||
this.dataIngestionSocket.on('connect', () => {
|
||||
logger.info('Connected to data-ingestion service');
|
||||
this.container.logger.info('Connected to data-ingestion service');
|
||||
// Re-subscribe to symbols
|
||||
this.subscriptions.forEach(symbol => {
|
||||
this.dataIngestionSocket!.emit('subscribe', { symbol });
|
||||
|
|
@ -49,7 +55,7 @@ export class MarketDataService extends EventEmitter {
|
|||
});
|
||||
|
||||
this.dataIngestionSocket.on('disconnect', () => {
|
||||
logger.warn('Disconnected from data-ingestion service');
|
||||
this.container.logger.warn('Disconnected from data-ingestion service');
|
||||
});
|
||||
|
||||
this.dataIngestionSocket.on('marketData', (data: any) => {
|
||||
|
|
@ -57,7 +63,7 @@ export class MarketDataService extends EventEmitter {
|
|||
});
|
||||
|
||||
this.dataIngestionSocket.on('error', (error: any) => {
|
||||
logger.error('Data ingestion socket error:', error);
|
||||
this.container.logger.error('Data ingestion socket error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +74,7 @@ export class MarketDataService extends EventEmitter {
|
|||
this.dataIngestionSocket.emit('subscribe', { symbol });
|
||||
}
|
||||
|
||||
logger.debug(`Subscribed to ${symbol}`);
|
||||
this.container.logger.debug(`Subscribed to ${symbol}`);
|
||||
}
|
||||
|
||||
async unsubscribeFromSymbol(symbol: string): Promise<void> {
|
||||
|
|
@ -78,7 +84,7 @@ export class MarketDataService extends EventEmitter {
|
|||
this.dataIngestionSocket.emit('unsubscribe', { symbol });
|
||||
}
|
||||
|
||||
logger.debug(`Unsubscribed from ${symbol}`);
|
||||
this.container.logger.debug(`Unsubscribed from ${symbol}`);
|
||||
}
|
||||
|
||||
private handleMarketData(data: any): void {
|
||||
|
|
@ -118,7 +124,7 @@ export class MarketDataService extends EventEmitter {
|
|||
});
|
||||
marketData = { type: 'bar', data: bar };
|
||||
} else {
|
||||
logger.warn('Unknown market data format:', data);
|
||||
this.container.logger.warn('Unknown market data format:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +140,7 @@ export class MarketDataService extends EventEmitter {
|
|||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error handling market data:', error);
|
||||
this.container.logger.error('Error handling market data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +276,7 @@ export class MarketDataService extends EventEmitter {
|
|||
|
||||
// Close QuestDB connection
|
||||
if (this.questdbClient) {
|
||||
await this.questdbClient.close();
|
||||
await this.questdbClient.disconnect();
|
||||
this.questdbClient = null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { QuestDBClient } from '@stock-bot/questdb';
|
||||
import { PostgresClient } from '@stock-bot/postgres';
|
||||
import { MongoDBClient } from '@stock-bot/mongodb';
|
||||
import { ModeConfig, MarketData, Position } from '../types';
|
||||
|
||||
export class StorageService {
|
||||
private questdb: QuestDBClient | null = null;
|
||||
private postgres: PostgresClient | null = null;
|
||||
private mongodb: MongoDBClient | null = null;
|
||||
private mode: 'backtest' | 'paper' | 'live' = 'paper';
|
||||
private container: IServiceContainer;
|
||||
|
||||
constructor(
|
||||
container: IServiceContainer,
|
||||
mongoClient: MongoDBClient,
|
||||
postgresClient: PostgresClient,
|
||||
questdbClient: QuestDBClient | null
|
||||
) {
|
||||
this.container = container;
|
||||
this.mongodb = mongoClient;
|
||||
this.postgres = postgresClient;
|
||||
this.questdb = questdbClient;
|
||||
}
|
||||
|
||||
async initialize(config: ModeConfig): Promise<void> {
|
||||
this.mode = config.mode;
|
||||
|
||||
// Initialize QuestDB for time-series data
|
||||
this.questdb = new QuestDBClient({
|
||||
host: process.env.QUESTDB_HOST || 'localhost',
|
||||
port: parseInt(process.env.QUESTDB_PORT || '9000'),
|
||||
database: process.env.QUESTDB_DATABASE || 'trading'
|
||||
});
|
||||
|
||||
// Initialize PostgreSQL for relational data
|
||||
this.postgres = new PostgresClient({
|
||||
host: process.env.POSTGRES_HOST || 'localhost',
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
database: process.env.POSTGRES_DATABASE || 'trading',
|
||||
user: process.env.POSTGRES_USER || 'postgres',
|
||||
password: process.env.POSTGRES_PASSWORD || 'postgres'
|
||||
});
|
||||
|
||||
// Clients are already injected via DI
|
||||
await this.createTables();
|
||||
}
|
||||
|
||||
|
|
@ -281,12 +280,12 @@ export class StorageService {
|
|||
|
||||
async shutdown(): Promise<void> {
|
||||
if (this.questdb) {
|
||||
await this.questdb.close();
|
||||
await this.questdb.disconnect();
|
||||
this.questdb = null;
|
||||
}
|
||||
|
||||
if (this.postgres) {
|
||||
await this.postgres.close();
|
||||
await this.postgres.disconnect();
|
||||
this.postgres = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
137
apps/stock/orchestrator/src/simple-container.ts
Normal file
137
apps/stock/orchestrator/src/simple-container.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { getLogger } from '@stock-bot/logger';
|
||||
import { MongoDBClient } from '@stock-bot/mongodb';
|
||||
import { PostgreSQLClient } from '@stock-bot/postgres';
|
||||
import { createCache } from '@stock-bot/cache';
|
||||
import { QueueManager, Queue } from '@stock-bot/queue';
|
||||
import { IServiceContainer } from '@stock-bot/handlers';
|
||||
import { ModeManager } from './core/ModeManager';
|
||||
import { MarketDataService } from './services/MarketDataService';
|
||||
import { ExecutionService } from './services/ExecutionService';
|
||||
import { AnalyticsService } from './services/AnalyticsService';
|
||||
import { StorageService } from './services/StorageService';
|
||||
import { StrategyManager } from './strategies/StrategyManager';
|
||||
import { BacktestEngine } from './backtest/BacktestEngine';
|
||||
import { PaperTradingManager } from './paper/PaperTradingManager';
|
||||
|
||||
/**
|
||||
* Creates a simplified service container without the DI framework
|
||||
* All configuration comes from the config object
|
||||
*/
|
||||
export async function createContainer(config: any): Promise<any> {
|
||||
const logger = getLogger('orchestrator');
|
||||
|
||||
// Initialize database clients if enabled
|
||||
let mongoClient: MongoDBClient | undefined;
|
||||
if (config.database.mongodb.enabled) {
|
||||
mongoClient = new MongoDBClient(config.database.mongodb, logger);
|
||||
await mongoClient.connect();
|
||||
}
|
||||
|
||||
let postgresClient: PostgreSQLClient | undefined;
|
||||
if (config.database.postgres.enabled) {
|
||||
postgresClient = new PostgreSQLClient({
|
||||
host: config.database.postgres.host,
|
||||
port: config.database.postgres.port,
|
||||
database: config.database.postgres.database,
|
||||
username: config.database.postgres.user,
|
||||
password: config.database.postgres.password,
|
||||
poolSettings: {
|
||||
min: 1,
|
||||
max: config.database.postgres.poolSize || 20,
|
||||
idleTimeoutMillis: config.database.postgres.idleTimeout || 10000
|
||||
}
|
||||
});
|
||||
await postgresClient.connect();
|
||||
}
|
||||
|
||||
// Initialize cache if enabled
|
||||
let cache: any;
|
||||
if (config.database.dragonfly.enabled) {
|
||||
cache = createCache({
|
||||
redisConfig: {
|
||||
host: config.database.dragonfly.host,
|
||||
port: config.database.dragonfly.port,
|
||||
db: config.database.dragonfly.db,
|
||||
keyPrefix: config.database.dragonfly.keyPrefix
|
||||
},
|
||||
ttl: 3600,
|
||||
enableMetrics: true
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize queue if enabled
|
||||
let queue: Queue | undefined;
|
||||
let queueManager: QueueManager | undefined;
|
||||
if (config.queue.enabled) {
|
||||
queue = new Queue('orchestrator', {
|
||||
redis: {
|
||||
host: config.queue.redis.host,
|
||||
port: config.queue.redis.port,
|
||||
db: config.queue.redis.db
|
||||
}
|
||||
});
|
||||
queueManager = new QueueManager(config, logger);
|
||||
}
|
||||
|
||||
// Create base services container
|
||||
const services: IServiceContainer = {
|
||||
logger,
|
||||
cache,
|
||||
mongodb: mongoClient,
|
||||
postgres: postgresClient,
|
||||
queue,
|
||||
queueManager,
|
||||
config,
|
||||
custom: {}
|
||||
};
|
||||
|
||||
// Create orchestrator services
|
||||
const storageService = new StorageService(
|
||||
services,
|
||||
mongoClient!,
|
||||
postgresClient!,
|
||||
null // QuestDB not needed for now
|
||||
);
|
||||
|
||||
const marketDataService = new MarketDataService(services);
|
||||
const executionService = new ExecutionService(services, storageService);
|
||||
const analyticsService = new AnalyticsService(services, storageService);
|
||||
const strategyManager = new StrategyManager(services);
|
||||
const backtestEngine = new BacktestEngine(services, storageService, strategyManager);
|
||||
const paperTradingManager = new PaperTradingManager(
|
||||
services,
|
||||
storageService,
|
||||
marketDataService,
|
||||
executionService
|
||||
);
|
||||
const modeManager = new ModeManager(
|
||||
services,
|
||||
marketDataService,
|
||||
executionService,
|
||||
storageService
|
||||
);
|
||||
|
||||
// Store custom services
|
||||
services.custom = {
|
||||
StorageService: storageService,
|
||||
MarketDataService: marketDataService,
|
||||
ExecutionService: executionService,
|
||||
AnalyticsService: analyticsService,
|
||||
StrategyManager: strategyManager,
|
||||
BacktestEngine: backtestEngine,
|
||||
PaperTradingManager: paperTradingManager,
|
||||
ModeManager: modeManager
|
||||
};
|
||||
|
||||
// Setup event listeners after all services are registered
|
||||
strategyManager.setupEventListeners();
|
||||
|
||||
// Initialize mode manager with configured default mode
|
||||
const serviceConfig = config.services?.orchestrator || {};
|
||||
await modeManager.initializeMode({
|
||||
mode: serviceConfig.defaultMode || 'paper',
|
||||
startingCapital: serviceConfig.paperTradingCapital || 100000
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('BaseStrategy');
|
||||
import { MarketData, StrategyConfig, OrderRequest } from '../types';
|
||||
import { ModeManager } from '../core/ModeManager';
|
||||
import { ExecutionService } from '../services/ExecutionService';
|
||||
|
|
|
|||
|
|
@ -1,39 +1,41 @@
|
|||
import { logger } from '@stock-bot/logger';
|
||||
import { EventEmitter } from 'events';
|
||||
import { IServiceContainer } from '@stock-bot/di';
|
||||
import { MarketData, StrategyConfig, OrderRequest } from '../types';
|
||||
import { BaseStrategy } from './BaseStrategy';
|
||||
import { ModeManager } from '../core/ModeManager';
|
||||
import { MarketDataService } from '../services/MarketDataService';
|
||||
import { ExecutionService } from '../services/ExecutionService';
|
||||
import { TradingEngine } from '../../core';
|
||||
import { TradingEngine } from '@stock-bot/core';
|
||||
|
||||
export class StrategyManager extends EventEmitter {
|
||||
private strategies = new Map<string, BaseStrategy>();
|
||||
private activeStrategies = new Set<string>();
|
||||
private tradingEngine: TradingEngine | null = null;
|
||||
private container: IServiceContainer;
|
||||
|
||||
constructor(
|
||||
private modeManager: ModeManager,
|
||||
private marketDataService: MarketDataService,
|
||||
private executionService: ExecutionService
|
||||
) {
|
||||
constructor(container: IServiceContainer) {
|
||||
super();
|
||||
this.setupEventListeners();
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
private setupEventListeners(): void {
|
||||
setupEventListeners(): void {
|
||||
const marketDataService = this.container.custom?.MarketDataService;
|
||||
const executionService = this.container.custom?.ExecutionService;
|
||||
|
||||
if (!marketDataService || !executionService) {
|
||||
this.container.logger.error('Required services not found in container');
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for market data
|
||||
this.marketDataService.on('marketData', (data: MarketData) => {
|
||||
marketDataService.on('marketData', (data: MarketData) => {
|
||||
this.handleMarketData(data);
|
||||
});
|
||||
|
||||
// Listen for market data batches (more efficient)
|
||||
this.marketDataService.on('marketDataBatch', (batch: MarketData[]) => {
|
||||
marketDataService.on('marketDataBatch', (batch: MarketData[]) => {
|
||||
this.handleMarketDataBatch(batch);
|
||||
});
|
||||
|
||||
// Listen for fills
|
||||
this.executionService.on('fill', (fill: any) => {
|
||||
executionService.on('fill', (fill: any) => {
|
||||
this.handleFill(fill);
|
||||
});
|
||||
}
|
||||
|
|
@ -47,7 +49,10 @@ export class StrategyManager extends EventEmitter {
|
|||
this.activeStrategies.clear();
|
||||
|
||||
// Get trading engine from mode manager
|
||||
this.tradingEngine = this.modeManager.getTradingEngine();
|
||||
const modeManager = this.container.custom?.ModeManager;
|
||||
if (modeManager) {
|
||||
this.tradingEngine = modeManager.getTradingEngine();
|
||||
}
|
||||
|
||||
// Initialize new strategies
|
||||
for (const config of configs) {
|
||||
|
|
@ -59,9 +64,9 @@ export class StrategyManager extends EventEmitter {
|
|||
await this.enableStrategy(config.id);
|
||||
}
|
||||
|
||||
logger.info(`Initialized strategy: ${config.name} (${config.id})`);
|
||||
this.container.logger.info(`Initialized strategy: ${config.name} (${config.id})`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to initialize strategy ${config.name}:`, error);
|
||||
this.container.logger.error(`Failed to initialize strategy ${config.name}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,8 +76,8 @@ export class StrategyManager extends EventEmitter {
|
|||
// For now, create a base strategy instance
|
||||
const strategy = new BaseStrategy(
|
||||
config,
|
||||
this.modeManager,
|
||||
this.executionService
|
||||
this.container.custom?.ModeManager,
|
||||
this.container.custom?.ExecutionService
|
||||
);
|
||||
|
||||
// Set up strategy event handlers
|
||||
|
|
@ -80,12 +85,10 @@ export class StrategyManager extends EventEmitter {
|
|||
this.handleStrategySignal(config.id, signal);
|
||||
});
|
||||
|
||||
strategy.on('order', (order: OrderRequest) => {
|
||||
this.handleStrategyOrder(config.id, order);
|
||||
strategy.on('error', (error: Error) => {
|
||||
this.container.logger.error(`Strategy ${config.id} error:`, error);
|
||||
});
|
||||
|
||||
await strategy.initialize();
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
|
|
@ -94,10 +97,10 @@ export class StrategyManager extends EventEmitter {
|
|||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
await strategy.start();
|
||||
|
||||
await strategy.initialize();
|
||||
this.activeStrategies.add(strategyId);
|
||||
logger.info(`Enabled strategy: ${strategyId}`);
|
||||
this.container.logger.info(`Enabled strategy: ${strategyId}`);
|
||||
}
|
||||
|
||||
async disableStrategy(strategyId: string): Promise<void> {
|
||||
|
|
@ -105,119 +108,80 @@ export class StrategyManager extends EventEmitter {
|
|||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
await strategy.stop();
|
||||
|
||||
await strategy.shutdown();
|
||||
this.activeStrategies.delete(strategyId);
|
||||
logger.info(`Disabled strategy: ${strategyId}`);
|
||||
this.container.logger.info(`Disabled strategy: ${strategyId}`);
|
||||
}
|
||||
|
||||
private async handleMarketData(data: MarketData): Promise<void> {
|
||||
// Forward to active strategies
|
||||
// Forward to all active strategies
|
||||
for (const strategyId of this.activeStrategies) {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (strategy && strategy.isInterestedInSymbol(data.data.symbol)) {
|
||||
if (strategy) {
|
||||
try {
|
||||
await strategy.onMarketData(data);
|
||||
} catch (error) {
|
||||
logger.error(`Strategy ${strategyId} error processing market data:`, error);
|
||||
this.container.logger.error(`Error processing market data for strategy ${strategyId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleMarketDataBatch(batch: MarketData[]): Promise<void> {
|
||||
// Group by symbol for efficiency
|
||||
const bySymbol = new Map<string, MarketData[]>();
|
||||
|
||||
for (const data of batch) {
|
||||
const symbol = data.data.symbol;
|
||||
if (!bySymbol.has(symbol)) {
|
||||
bySymbol.set(symbol, []);
|
||||
}
|
||||
bySymbol.get(symbol)!.push(data);
|
||||
}
|
||||
|
||||
// Forward to strategies
|
||||
// Process batch more efficiently
|
||||
for (const strategyId of this.activeStrategies) {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (!strategy) continue;
|
||||
|
||||
const relevantData: MarketData[] = [];
|
||||
for (const [symbol, data] of bySymbol) {
|
||||
if (strategy.isInterestedInSymbol(symbol)) {
|
||||
relevantData.push(...data);
|
||||
}
|
||||
}
|
||||
|
||||
if (relevantData.length > 0) {
|
||||
if (strategy) {
|
||||
try {
|
||||
await strategy.onMarketDataBatch(relevantData);
|
||||
await strategy.onMarketDataBatch(batch);
|
||||
} catch (error) {
|
||||
logger.error(`Strategy ${strategyId} error processing batch:`, error);
|
||||
this.container.logger.error(`Error processing market data batch for strategy ${strategyId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleFill(fill: any): Promise<void> {
|
||||
// Notify relevant strategies about fills
|
||||
for (const strategyId of this.activeStrategies) {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (strategy && strategy.hasPosition(fill.symbol)) {
|
||||
// Forward fill to the strategy that created the order
|
||||
for (const [strategyId, strategy] of this.strategies) {
|
||||
if (strategy.hasOrder(fill.orderId)) {
|
||||
try {
|
||||
await strategy.onFill(fill);
|
||||
} catch (error) {
|
||||
logger.error(`Strategy ${strategyId} error processing fill:`, error);
|
||||
this.container.logger.error(`Error processing fill for strategy ${strategyId}:`, error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleStrategySignal(strategyId: string, signal: any): Promise<void> {
|
||||
logger.debug(`Strategy ${strategyId} generated signal:`, signal);
|
||||
this.container.logger.info(`Strategy ${strategyId} generated signal:`, signal);
|
||||
|
||||
// Emit for monitoring/logging
|
||||
this.emit('strategySignal', {
|
||||
strategyId,
|
||||
signal,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
// Convert signal to order request
|
||||
const orderRequest: OrderRequest = {
|
||||
symbol: signal.symbol,
|
||||
quantity: signal.quantity,
|
||||
side: signal.side,
|
||||
type: signal.orderType || 'market',
|
||||
timeInForce: signal.timeInForce || 'day',
|
||||
strategyId
|
||||
};
|
||||
|
||||
private async handleStrategyOrder(strategyId: string, order: OrderRequest): Promise<void> {
|
||||
logger.info(`Strategy ${strategyId} placing order:`, order);
|
||||
|
||||
try {
|
||||
// Submit order through execution service
|
||||
const result = await this.executionService.submitOrder(order);
|
||||
|
||||
// Notify strategy of order result
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (strategy) {
|
||||
await strategy.onOrderUpdate(result);
|
||||
}
|
||||
|
||||
// Emit for monitoring
|
||||
this.emit('strategyOrder', {
|
||||
strategyId,
|
||||
order,
|
||||
result,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Failed to submit order from strategy ${strategyId}:`, error);
|
||||
|
||||
// Notify strategy of failure
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (strategy) {
|
||||
await strategy.onOrderError(order, error);
|
||||
// Submit order through execution service
|
||||
const executionService = this.container.custom?.ExecutionService;
|
||||
if (executionService) {
|
||||
try {
|
||||
const result = await executionService.submitOrder(orderRequest);
|
||||
this.container.logger.info(`Order submitted for strategy ${strategyId}:`, result);
|
||||
} catch (error) {
|
||||
this.container.logger.error(`Failed to submit order for strategy ${strategyId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onMarketData(data: MarketData): Promise<void> {
|
||||
// Called by backtest engine
|
||||
await this.handleMarketData(data);
|
||||
}
|
||||
|
||||
|
|
@ -225,52 +189,25 @@ export class StrategyManager extends EventEmitter {
|
|||
return this.tradingEngine;
|
||||
}
|
||||
|
||||
getActiveStrategies(): string[] {
|
||||
return Array.from(this.activeStrategies);
|
||||
}
|
||||
|
||||
getStrategy(strategyId: string): BaseStrategy | undefined {
|
||||
return this.strategies.get(strategyId);
|
||||
}
|
||||
|
||||
getAllStrategies(): Map<string, BaseStrategy> {
|
||||
return new Map(this.strategies);
|
||||
}
|
||||
|
||||
getActiveStrategies(): Set<string> {
|
||||
return new Set(this.activeStrategies);
|
||||
}
|
||||
|
||||
async updateStrategyConfig(strategyId: string, updates: Partial<StrategyConfig>): Promise<void> {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
await strategy.updateConfig(updates);
|
||||
logger.info(`Updated configuration for strategy ${strategyId}`);
|
||||
}
|
||||
|
||||
async getStrategyPerformance(strategyId: string): Promise<any> {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
return strategy.getPerformance();
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
logger.info('Shutting down strategy manager...');
|
||||
this.container.logger.info('Shutting down strategy manager...');
|
||||
|
||||
// Disable all strategies
|
||||
for (const strategyId of this.activeStrategies) {
|
||||
await this.disableStrategy(strategyId);
|
||||
}
|
||||
|
||||
// Shutdown all strategies
|
||||
for (const [id, strategy] of this.strategies) {
|
||||
await strategy.shutdown();
|
||||
}
|
||||
|
||||
// Clear all strategies
|
||||
this.strategies.clear();
|
||||
this.activeStrategies.clear();
|
||||
this.removeAllListeners();
|
||||
this.tradingEngine = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import { BaseStrategy, Signal } from '../BaseStrategy';
|
||||
import { MarketData } from '../../types';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('MLEnhancedStrategy');
|
||||
import * as tf from '@tensorflow/tfjs-node';
|
||||
|
||||
interface MLModelConfig {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { BaseStrategy, Signal } from '../BaseStrategy';
|
||||
import { MarketData } from '../../types';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
|
||||
const logger = getLogger('MeanReversionStrategy');
|
||||
|
||||
interface MeanReversionIndicators {
|
||||
sma20: number;
|
||||
|
|
|
|||
|
|
@ -162,4 +162,4 @@ export const RiskMetricsSchema = z.object({
|
|||
export type RiskMetrics = z.infer<typeof RiskMetricsSchema>;
|
||||
|
||||
// Re-export specialized types
|
||||
export { MarketMicrostructure, PriceLevel, OrderBookSnapshot } from './types/MarketMicrostructure';
|
||||
export type { MarketMicrostructure, PriceLevel, OrderBookSnapshot } from './types/MarketMicrostructure';
|
||||
Loading…
Add table
Add a link
Reference in a new issue