import { getLogger } from '@stock-bot/logger'; import { Order } from '@stock-bot/types'; export interface RiskRule { name: string; validate(order: Order, context: RiskContext): Promise; } export interface RiskContext { currentPositions: Map; accountBalance: number; totalExposure: number; maxPositionSize: number; maxDailyLoss: number; } export interface RiskValidationResult { isValid: boolean; reason?: string; severity: 'info' | 'warning' | 'error'; } export class RiskManager { private logger = getLogger('risk-manager'); private rules: RiskRule[] = []; constructor() { this.initializeDefaultRules(); } addRule(rule: RiskRule): void { this.rules.push(rule); } async validateOrder(order: Order, context: RiskContext): Promise { for (const rule of this.rules) { const result = await rule.validate(order, context); if (!result.isValid) { logger.warn(`Risk rule violation: ${rule.name}`, { order, reason: result.reason, }); return result; } } return { isValid: true, severity: 'info' }; } private initializeDefaultRules(): void { // Position size rule this.addRule({ name: 'MaxPositionSize', async validate(order: Order, context: RiskContext): Promise { const orderValue = order.quantity * (order.price || 0); if (orderValue > context.maxPositionSize) { return { isValid: false, reason: `Order size ${orderValue} exceeds maximum position size ${context.maxPositionSize}`, severity: 'error', }; } return { isValid: true, severity: 'info' }; }, }); // Balance check rule this.addRule({ name: 'SufficientBalance', async validate(order: Order, context: RiskContext): Promise { const orderValue = order.quantity * (order.price || 0); if (order.side === 'buy' && orderValue > context.accountBalance) { return { isValid: false, reason: `Insufficient balance: need ${orderValue}, have ${context.accountBalance}`, severity: 'error', }; } return { isValid: true, severity: 'info' }; }, }); // Concentration risk rule this.addRule({ name: 'ConcentrationLimit', async validate(order: Order, context: RiskContext): Promise { const currentPosition = context.currentPositions.get(order.symbol) || 0; const newPosition = order.side === 'buy' ? currentPosition + order.quantity : currentPosition - order.quantity; const positionValue = Math.abs(newPosition) * (order.price || 0); const concentrationRatio = positionValue / context.accountBalance; if (concentrationRatio > 0.25) { // 25% max concentration return { isValid: false, reason: `Position concentration ${(concentrationRatio * 100).toFixed(2)}% exceeds 25% limit`, severity: 'warning', }; } return { isValid: true, severity: 'info' }; }, }); } }