import { EventEmitter } from 'eventemitter3'; import { createLogger, Logger } from '@stock-bot/logger'; import { EventBus } from '@stock-bot/event-bus'; import { DataFrame } from '@stock-bot/data-frame'; // Core types export interface MarketData { symbol: string; timestamp: number; open: number; high: number; low: number; close: number; volume: number; [key: string]: any; } export interface TradingSignal { type: 'BUY' | 'SELL' | 'HOLD'; symbol: string; timestamp: number; price: number; quantity: number; confidence: number; reason: string; metadata?: Record; } export interface StrategyContext { symbol: string; timeframe: string; data: DataFrame; indicators: Record; position?: Position; portfolio: PortfolioSummary; timestamp: number; } export interface Position { symbol: string; quantity: number; averagePrice: number; currentPrice: number; unrealizedPnL: number; side: 'LONG' | 'SHORT'; } export interface PortfolioSummary { totalValue: number; cash: number; positions: Position[]; totalPnL: number; dayPnL: number; } export interface StrategyConfig { id: string; name: string; description?: string; symbols: string[]; timeframes: string[]; parameters: Record; riskLimits: RiskLimits; enabled: boolean; } export interface RiskLimits { maxPositionSize: number; maxDailyLoss: number; maxDrawdown: number; stopLoss?: number; takeProfit?: number; } // Abstract base strategy class export abstract class BaseStrategy extends EventEmitter { protected logger; protected eventBus: EventBus; protected config: StrategyConfig; protected isActive: boolean = false; constructor(config: StrategyConfig, eventBus: EventBus) { super(); this.config = config; this.eventBus = eventBus; this.logger = createLogger(`strategy:${config.id}`); } // Abstract methods that must be implemented by concrete strategies abstract initialize(): Promise; abstract onMarketData(context: StrategyContext): Promise; abstract onSignal(signal: TradingSignal): Promise; abstract cleanup(): Promise; // Optional lifecycle methods onStart?(): Promise; onStop?(): Promise; onError?(error: Error): Promise; // Control methods async start(): Promise { if (this.isActive) { this.logger.warn('Strategy already active'); return; } try { await this.initialize(); if (this.onStart) { await this.onStart(); } this.isActive = true; this.logger.info('Strategy started', { strategyId: this.config.id }); this.emit('started'); } catch (error) { this.logger.error('Failed to start strategy', { error, strategyId: this.config.id }); throw error; } } async stop(): Promise { if (!this.isActive) { this.logger.warn('Strategy not active'); return; } try { if (this.onStop) { await this.onStop(); } await this.cleanup(); this.isActive = false; this.logger.info('Strategy stopped', { strategyId: this.config.id }); this.emit('stopped'); } catch (error) { this.logger.error('Failed to stop strategy', { error, strategyId: this.config.id }); throw error; } } // Utility methods protected async emitSignal(signal: TradingSignal): Promise { await this.eventBus.publish(this.config.id, signal); this.emit('signal', signal); this.logger.info('Signal generated', { signal: signal.type, symbol: signal.symbol, confidence: signal.confidence }); } protected checkRiskLimits(signal: TradingSignal, context: StrategyContext): boolean { const limits = this.config.riskLimits; // Check position size limit if (signal.quantity > limits.maxPositionSize) { this.logger.warn('Signal exceeds max position size', { requested: signal.quantity, limit: limits.maxPositionSize }); return false; } // Check daily loss limit if (context.portfolio.dayPnL <= -limits.maxDailyLoss) { this.logger.warn('Daily loss limit reached', { dayPnL: context.portfolio.dayPnL, limit: -limits.maxDailyLoss }); return false; } return true; } // Getters get id(): string { return this.config.id; } get name(): string { return this.config.name; } get active(): boolean { return this.isActive; } get configuration(): StrategyConfig { return { ...this.config }; } } // Strategy execution engine export class StrategyEngine extends EventEmitter { private strategies: Map = new Map(); private logger; private eventBus: EventBus; private isRunning: boolean = false; constructor(eventBus: EventBus) { super(); this.eventBus = eventBus; this.logger = createLogger('strategy-engine'); } async initialize(): Promise { // Subscribe to market data events await this.eventBus.subscribe('market.data', this.handleMarketData.bind(this)); await this.eventBus.subscribe('order.update', this.handleOrderUpdate.bind(this)); await this.eventBus.subscribe('portfolio.update', this.handlePortfolioUpdate.bind(this)); this.logger.info('Strategy engine initialized'); } async registerStrategy(strategy: BaseStrategy): Promise { if (this.strategies.has(strategy.id)) { throw new Error(`Strategy ${strategy.id} already registered`); } this.strategies.set(strategy.id, strategy); // Forward strategy events strategy.on('signal', (signal) => this.emit('signal', signal)); strategy.on('error', (error) => this.emit('error', error)); this.logger.info('Strategy registered', { strategyId: strategy.id }); } async unregisterStrategy(strategyId: string): Promise { const strategy = this.strategies.get(strategyId); if (!strategy) { throw new Error(`Strategy ${strategyId} not found`); } if (strategy.active) { await strategy.stop(); } strategy.removeAllListeners(); this.strategies.delete(strategyId); this.logger.info('Strategy unregistered', { strategyId }); } async startStrategy(strategyId: string): Promise { const strategy = this.strategies.get(strategyId); if (!strategy) { throw new Error(`Strategy ${strategyId} not found`); } await strategy.start(); } async stopStrategy(strategyId: string): Promise { const strategy = this.strategies.get(strategyId); if (!strategy) { throw new Error(`Strategy ${strategyId} not found`); } await strategy.stop(); } async startAll(): Promise { if (this.isRunning) { this.logger.warn('Engine already running'); return; } const startPromises = Array.from(this.strategies.values()) .filter(strategy => strategy.configuration.enabled) .map(strategy => strategy.start()); await Promise.all(startPromises); this.isRunning = true; this.logger.info('All strategies started'); this.emit('started'); } async stopAll(): Promise { if (!this.isRunning) { this.logger.warn('Engine not running'); return; } const stopPromises = Array.from(this.strategies.values()) .filter(strategy => strategy.active) .map(strategy => strategy.stop()); await Promise.all(stopPromises); this.isRunning = false; this.logger.info('All strategies stopped'); this.emit('stopped'); } private async handleMarketData(message: any): Promise { const { symbol, ...data } = message.data; // Find strategies that trade this symbol const relevantStrategies = Array.from(this.strategies.values()) .filter(strategy => strategy.active && strategy.configuration.symbols.includes(symbol) ); for (const strategy of relevantStrategies) { try { // Create context for this strategy const context: StrategyContext = { symbol, timeframe: '1m', // TODO: Get from strategy config data: new DataFrame([data]), // TODO: Use historical data indicators: {}, portfolio: { totalValue: 100000, // TODO: Get real portfolio data cash: 50000, positions: [], totalPnL: 0, dayPnL: 0 }, timestamp: data.timestamp }; const signals = await strategy.onMarketData(context); for (const signal of signals) { await strategy.onSignal(signal); } } catch (error) { this.logger.error('Error processing market data for strategy', { error, strategyId: strategy.id, symbol }); } } } private async handleOrderUpdate(message: any): Promise { // Handle order updates - notify relevant strategies this.logger.debug('Order update received', { data: message.data }); } private async handlePortfolioUpdate(message: any): Promise { // Handle portfolio updates - notify relevant strategies this.logger.debug('Portfolio update received', { data: message.data }); } getStrategy(strategyId: string): BaseStrategy | undefined { return this.strategies.get(strategyId); } getStrategies(): BaseStrategy[] { return Array.from(this.strategies.values()); } getActiveStrategies(): BaseStrategy[] { return this.getStrategies().filter(strategy => strategy.active); } async shutdown(): Promise { await this.stopAll(); this.strategies.clear(); this.removeAllListeners(); this.logger.info('Strategy engine shutdown'); } }