import { describe, it, expect, beforeEach, afterEach } from '@jest/globals' import { RiskMonitoringService } from '../../src/services/RiskMonitoringService.js' import { MonitoringEventManager } from '../../src/models/MonitoringEvent.js' /** * RiskMonitoringService 单元测试 * 测试风险监控服务的核心功能 */ describe('RiskMonitoringService Unit Tests', () => { let riskService: RiskMonitoringService let mockEventManager: jest.Mocked beforeEach(() => { // 创建模拟的事件管理器 mockEventManager = { addEvent: jest.fn(), getRecentEvents: jest.fn().mockReturnValue([]), getEventsByType: jest.fn().mockReturnValue([]), clearOldEvents: jest.fn(), } as any riskService = new RiskMonitoringService(mockEventManager) }) afterEach(async () => { await riskService.stop() }) describe('Service Lifecycle', () => { it('should initialize with stopped status', () => { expect(riskService.getStatus()).toBe('stopped') }) it('should start monitoring successfully', async () => { await riskService.start() expect(riskService.getStatus()).toBe('running') }) it('should stop monitoring successfully', async () => { await riskService.start() await riskService.stop() expect(riskService.getStatus()).toBe('stopped') }) }) describe('Risk Metrics Calculation', () => { beforeEach(async () => { await riskService.start() }) it('should return null for non-existent account', async () => { const metrics = await riskService.getRiskMetrics('non-existent-account') expect(metrics).toBeNull() }) it('should calculate risk metrics for valid account state', async () => { // 更新账户状态 await riskService.updateAccountRisk('test-account', { netPosition: 0.5, // 0.5 BTC净仓位 unrealizedPnl: 100, // $100 未实现盈亏 leverage: 2, // 2x杠杆 entryPrice: 50000, // $50,000入场价 currentPrice: 51000, // $51,000当前价格 }) const metrics = await riskService.getRiskMetrics('test-account') expect(metrics).not.toBeNull() expect(metrics!.accountId).toBe('test-account') expect(metrics!.deltaDeviation).toBe(0.5) // 净仓位偏差 expect(metrics!.utilizationRate).toBeGreaterThan(0) expect(metrics!.leverageRatio).toBe(2) expect(metrics!.unrealizedPnl).toBe(100) }) it('should track maximum drawdown correctly', async () => { await riskService.updateAccountRisk('test-account', { netPosition: 1.0, unrealizedPnl: -500, // 负的P&L leverage: 3, }) const metrics = await riskService.getRiskMetrics('test-account') expect(metrics!.maxDrawdown).toBe(500) // 最大回撤为正值 }) }) describe('Risk Alert Generation', () => { beforeEach(async () => { await riskService.start() }) it('should generate delta alert for high position deviation', async () => { await riskService.updateAccountRisk('test-account', { netPosition: 2.5, // 高净仓位偏差 unrealizedPnl: 0, leverage: 1, }) const alerts = riskService.getActiveAlerts('test-account') const deltaAlert = alerts.find(alert => alert.type === 'delta-alert') expect(deltaAlert).toBeDefined() expect(deltaAlert!.severity).toBe('WARN') }) it('should generate utilization alert for high usage', async () => { await riskService.updateAccountRisk('test-account', { netPosition: 0.1, unrealizedPnl: 0, leverage: 1, utilizationRate: 0.85, // 85% 高利用率 }) const alerts = riskService.getActiveAlerts('test-account') const utilizationAlert = alerts.find(alert => alert.type === 'utilization-alert') expect(utilizationAlert).toBeDefined() expect(utilizationAlert!.severity).toBe('WARN') }) it('should generate critical alert for excessive drawdown', async () => { await riskService.updateAccountRisk('test-account', { netPosition: 0.5, unrealizedPnl: -2500, // 高回撤 leverage: 5, }) const alerts = riskService.getActiveAlerts('test-account') const criticalAlert = alerts.find(alert => alert.severity === 'CRITICAL') expect(criticalAlert).toBeDefined() }) }) describe('Historical Risk Tracking', () => { beforeEach(async () => { await riskService.start() }) it('should maintain risk metrics history', async () => { // 添加第一个风险指标 await riskService.updateAccountRisk('test-account', { netPosition: 0.5, unrealizedPnl: 100, leverage: 2, }) // 等待一小段时间 await new Promise(resolve => setTimeout(resolve, 10)) // 添加第二个风险指标 await riskService.updateAccountRisk('test-account', { netPosition: 0.3, unrealizedPnl: 150, leverage: 2, }) const history = riskService.getRiskHistory('test-account') expect(history.length).toBeGreaterThanOrEqual(2) expect(history[0].deltaDeviation).toBe(0.5) expect(history[history.length - 1].deltaDeviation).toBe(0.3) }) it('should limit risk history to maximum size', async () => { // 添加大量风险指标 (超过限制) for (let i = 0; i < 1100; i++) { await riskService.updateAccountRisk('test-account', { netPosition: i * 0.001, unrealizedPnl: i, leverage: 1, }) } const history = riskService.getRiskHistory('test-account') expect(history.length).toBeLessThanOrEqual(1000) // 最大1000条历史记录 }) }) describe('Event Integration', () => { beforeEach(async () => { await riskService.start() }) it('should log risk events to monitoring system', async () => { await riskService.updateAccountRisk('test-account', { netPosition: 2.0, // 触发风险警报 unrealizedPnl: -1000, leverage: 5, }) // 验证事件被记录 expect(mockEventManager.addEvent).toHaveBeenCalled() const eventCall = mockEventManager.addEvent.mock.calls[0][0] expect(eventCall.type).toBe('delta-alert') expect(eventCall.accountId).toBe('test-account') }) it('should include detailed risk data in events', async () => { await riskService.updateAccountRisk('test-account', { netPosition: 1.5, unrealizedPnl: -800, leverage: 4, entryPrice: 50000, currentPrice: 49000, }) expect(mockEventManager.addEvent).toHaveBeenCalled() const eventCall = mockEventManager.addEvent.mock.calls[0][0] expect(eventCall.data).toMatchObject({ deltaDeviation: 1.5, unrealizedPnl: -800, leverageRatio: 4, maxDrawdown: 800, }) }) }) describe('Multi-Account Risk Management', () => { beforeEach(async () => { await riskService.start() }) it('should handle multiple accounts independently', async () => { await riskService.updateAccountRisk('account-1', { netPosition: 0.5, unrealizedPnl: 100, leverage: 2, }) await riskService.updateAccountRisk('account-2', { netPosition: -0.3, unrealizedPnl: -50, leverage: 1, }) const metrics1 = await riskService.getRiskMetrics('account-1') const metrics2 = await riskService.getRiskMetrics('account-2') expect(metrics1!.deltaDeviation).toBe(0.5) expect(metrics2!.deltaDeviation).toBe(0.3) expect(metrics1!.unrealizedPnl).toBe(100) expect(metrics2!.unrealizedPnl).toBe(-50) }) it('should return all active alerts across accounts', async () => { await riskService.updateAccountRisk('account-1', { netPosition: 2.0, // 触发警报 unrealizedPnl: 0, leverage: 1, }) await riskService.updateAccountRisk('account-2', { netPosition: 1.8, // 触发警报 unrealizedPnl: 0, leverage: 1, }) const allAlerts = riskService.getActiveAlerts() expect(allAlerts.length).toBeGreaterThanOrEqual(2) const account1Alerts = riskService.getActiveAlerts('account-1') const account2Alerts = riskService.getActiveAlerts('account-2') expect(account1Alerts.length).toBeGreaterThan(0) expect(account2Alerts.length).toBeGreaterThan(0) }) }) describe('Performance and Cleanup', () => { beforeEach(async () => { await riskService.start() }) it('should clean up old alerts automatically', async () => { // 创建过期警报 await riskService.updateAccountRisk('test-account', { netPosition: 2.0, // 触发警报 unrealizedPnl: 0, leverage: 1, }) // 获取初始警报数量 const initialAlerts = riskService.getActiveAlerts() expect(initialAlerts.length).toBeGreaterThan(0) // 手动触发清理 (模拟时间流逝) await new Promise(resolve => setTimeout(resolve, 100)) // 验证服务正在运行清理过程 expect(riskService.getStatus()).toBe('running') }) it('should handle concurrent risk updates safely', async () => { const promises = [] // 并发更新同一账户的风险状态 for (let i = 0; i < 10; i++) { promises.push( riskService.updateAccountRisk('test-account', { netPosition: i * 0.1, unrealizedPnl: i * 10, leverage: 1, }) ) } await Promise.all(promises) const metrics = await riskService.getRiskMetrics('test-account') expect(metrics).not.toBeNull() expect(metrics!.accountId).toBe('test-account') }) }) })