adding data-services

This commit is contained in:
Bojan Kucera 2025-06-03 07:42:48 -04:00
parent e3bfd05b90
commit 405b818c86
139 changed files with 55943 additions and 416 deletions

View file

@ -0,0 +1,139 @@
import { describe, test, expect, beforeEach } from 'bun:test';
import { BacktestService, BacktestRequest } from '../../core/backtesting/BacktestService';
import { StrategyRegistry, StrategyType } from '../../core/strategies/StrategyRegistry';
import { MarketDataFeed } from '../../core/backtesting/MarketDataFeed';
// Mock dependencies
jest.mock('../../core/backtesting/MarketDataFeed');
describe('BacktestService', () => {
let backtestService: BacktestService;
let mockRequest: BacktestRequest;
beforeEach(() => {
// Reset mocks and create fresh service instance
jest.clearAllMocks();
backtestService = new BacktestService('http://test.api');
// Create a standard backtest request for tests
mockRequest = {
strategyType: 'MEAN_REVERSION' as StrategyType,
strategyParams: {
lookback: 20,
entryDeviation: 1.5,
exitDeviation: 0.5,
lookbackPeriod: 100
},
symbols: ['AAPL'],
startDate: new Date('2023-01-01'),
endDate: new Date('2023-02-01'),
initialCapital: 100000,
dataResolution: '1d',
commission: 0.001,
slippage: 0.001,
mode: 'vector'
};
// Mock the MarketDataFeed implementation
(MarketDataFeed.prototype.getHistoricalData as jest.Mock).mockResolvedValue([
// Generate some sample data
...Array(30).fill(0).map((_, i) => ({
symbol: 'AAPL',
timestamp: new Date(`2023-01-${(i + 1).toString().padStart(2, '0')}`),
open: 150 + Math.random() * 10,
high: 155 + Math.random() * 10,
low: 145 + Math.random() * 10,
close: 150 + Math.random() * 10,
volume: 1000000 + Math.random() * 500000
}))
]);
});
test('should run a backtest successfully', async () => {
// Act
const result = await backtestService.runBacktest(mockRequest);
// Assert
expect(result).toBeDefined();
expect(result.strategyId).toBeDefined();
expect(result.initialCapital).toBe(100000);
expect(result.trades).toBeDefined();
expect(result.dailyReturns).toBeDefined();
// Verify market data was requested
expect(MarketDataFeed.prototype.getHistoricalData).toHaveBeenCalledTimes(mockRequest.symbols.length);
});
test('should optimize strategy parameters', async () => {
// Arrange
const parameterGrid = {
lookback: [10, 20],
entryDeviation: [1.0, 1.5, 2.0]
};
// We should get 2×3 = 6 combinations
// Act
const results = await backtestService.optimizeStrategy(mockRequest, parameterGrid);
// Assert
expect(results).toHaveLength(6);
expect(results[0].parameters).toBeDefined();
// Check that results are sorted by performance (sharpe ratio)
for (let i = 0; i < results.length - 1; i++) {
expect(results[i].sharpeRatio).toBeGreaterThanOrEqual(results[i + 1].sharpeRatio);
}
});
test('should handle errors during backtest', async () => {
// Arrange
(MarketDataFeed.prototype.getHistoricalData as jest.Mock).mockRejectedValue(
new Error('Data source error')
);
// Act & Assert
await expect(backtestService.runBacktest(mockRequest))
.rejects
.toThrow();
});
test('should generate correct parameter combinations', () => {
// Arrange
const grid = {
param1: [1, 2],
param2: ['a', 'b'],
param3: [true, false]
};
// Act
const combinations = (backtestService as any).generateParameterCombinations(grid, Object.keys(grid));
// Assert - should get 2×2×2 = 8 combinations
expect(combinations).toHaveLength(8);
// Check that all combinations are generated
expect(combinations).toContainEqual({ param1: 1, param2: 'a', param3: true });
expect(combinations).toContainEqual({ param1: 1, param2: 'a', param3: false });
expect(combinations).toContainEqual({ param1: 1, param2: 'b', param3: true });
expect(combinations).toContainEqual({ param1: 1, param2: 'b', param3: false });
expect(combinations).toContainEqual({ param1: 2, param2: 'a', param3: true });
expect(combinations).toContainEqual({ param1: 2, param2: 'a', param3: false });
expect(combinations).toContainEqual({ param1: 2, param2: 'b', param3: true });
expect(combinations).toContainEqual({ param1: 2, param2: 'b', param3: false });
});
test('should track active backtests', () => {
// Arrange
const activeBacktests = (backtestService as any).activeBacktests;
// Act
let promise = backtestService.runBacktest(mockRequest);
// Assert
expect(activeBacktests.size).toBe(1);
// Clean up
return promise;
});
});

View file

@ -0,0 +1,169 @@
import { describe, test, expect } from 'bun:test';
import { PerformanceAnalytics } from '../../core/backtesting/PerformanceAnalytics';
import { BacktestResult } from '../../core/backtesting/BacktestEngine';
describe('PerformanceAnalytics', () => {
// Sample backtest result for testing
const sampleResult: BacktestResult = {
strategyId: 'test-strategy',
startDate: new Date('2023-01-01'),
endDate: new Date('2023-12-31'),
duration: 31536000000, // 1 year in ms
initialCapital: 100000,
finalCapital: 125000,
totalReturn: 0.25, // 25% return
annualizedReturn: 0.25,
sharpeRatio: 1.5,
maxDrawdown: 0.10, // 10% drawdown
maxDrawdownDuration: 30, // 30 days
winRate: 0.6, // 60% win rate
totalTrades: 50,
winningTrades: 30,
losingTrades: 20,
averageWinningTrade: 0.05, // 5% average win
averageLosingTrade: -0.03, // 3% average loss
profitFactor: 2.5,
dailyReturns: [
// Generate 365 days of sample daily returns with some randomness
...Array(365).fill(0).map((_, i) => ({
date: new Date(new Date('2023-01-01').getTime() + i * 24 * 3600 * 1000),
return: 0.001 + (Math.random() - 0.45) * 0.01 // Mean positive return with noise
}))
],
trades: [
// Generate sample trades
...Array(50).fill(0).map((_, i) => ({
symbol: 'AAPL',
entryTime: new Date(`2023-${Math.floor(i / 4) + 1}-${(i % 28) + 1}`),
entryPrice: 150 + Math.random() * 10,
exitTime: new Date(`2023-${Math.floor(i / 4) + 1}-${(i % 28) + 5}`),
exitPrice: 155 + Math.random() * 10,
quantity: 10,
pnl: 500 * (Math.random() - 0.3), // Some wins, some losses
pnlPercent: 0.05 * (Math.random() - 0.3)
}))
]
};
test('should calculate advanced metrics', () => {
// Act
const enhancedResult = PerformanceAnalytics.enhanceResults(sampleResult);
// Assert
expect(enhancedResult.sortinoRatio).toBeDefined();
expect(enhancedResult.calmarRatio).toBeDefined();
expect(enhancedResult.omegaRatio).toBeDefined();
expect(enhancedResult.cagr).toBeDefined();
expect(enhancedResult.volatility).toBeDefined();
expect(enhancedResult.ulcerIndex).toBeDefined();
// Check that the original result properties are preserved
expect(enhancedResult.strategyId).toBe(sampleResult.strategyId);
expect(enhancedResult.totalReturn).toBe(sampleResult.totalReturn);
// Validate some calculations
expect(enhancedResult.calmarRatio).toBeCloseTo(sampleResult.annualizedReturn / sampleResult.maxDrawdown);
expect(typeof enhancedResult.sortinoRatio).toBe('number');
});
test('should calculate monthly returns', () => {
// Act
const monthlyReturns = PerformanceAnalytics.calculateMonthlyReturns(sampleResult.dailyReturns);
// Assert
expect(monthlyReturns).toBeDefined();
expect(monthlyReturns.length).toBe(12); // 12 months in a year
expect(monthlyReturns[0].year).toBe(2023);
expect(monthlyReturns[0].month).toBe(0); // January is 0
// Verify sorting
let lastDate = { year: 0, month: 0 };
for (const mr of monthlyReturns) {
expect(mr.year >= lastDate.year).toBeTruthy();
if (mr.year === lastDate.year) {
expect(mr.month >= lastDate.month).toBeTruthy();
}
lastDate = { year: mr.year, month: mr.month };
}
});
test('should analyze drawdowns', () => {
// Act
const drawdowns = PerformanceAnalytics.analyzeDrawdowns(sampleResult.dailyReturns);
// Assert
expect(drawdowns).toBeDefined();
expect(drawdowns.length).toBeGreaterThan(0);
// Check drawdown properties
for (const dd of drawdowns) {
expect(dd.startDate).toBeInstanceOf(Date);
expect(dd.endDate).toBeInstanceOf(Date);
expect(dd.drawdown).toBeGreaterThan(0);
expect(dd.durationDays).toBeGreaterThanOrEqual(0);
// Recovery date and days might be null for ongoing drawdowns
if (dd.recoveryDate) {
expect(dd.recoveryDate).toBeInstanceOf(Date);
expect(dd.recoveryDays).toBeGreaterThanOrEqual(0);
}
}
// Check sorting by drawdown magnitude
for (let i = 0; i < drawdowns.length - 1; i++) {
expect(drawdowns[i].drawdown).toBeGreaterThanOrEqual(drawdowns[i + 1].drawdown);
}
});
test('should handle empty inputs', () => {
// Act & Assert
expect(() => PerformanceAnalytics.calculateMonthlyReturns([])).not.toThrow();
expect(() => PerformanceAnalytics.analyzeDrawdowns([])).not.toThrow();
const emptyMonthlyReturns = PerformanceAnalytics.calculateMonthlyReturns([]);
const emptyDrawdowns = PerformanceAnalytics.analyzeDrawdowns([]);
expect(emptyMonthlyReturns).toEqual([]);
expect(emptyDrawdowns).toEqual([]);
});
test('should calculate special cases correctly', () => {
// Case with no negative returns
const allPositiveReturns = {
dailyReturns: Array(30).fill(0).map((_, i) => ({
date: new Date(`2023-01-${i + 1}`),
return: 0.01 // Always positive
}))
};
// Case with no recovery from drawdown
const noRecoveryReturns = {
dailyReturns: [
...Array(30).fill(0).map((_, i) => ({
date: new Date(`2023-01-${i + 1}`),
return: 0.01 // Positive returns
})),
...Array(30).fill(0).map((_, i) => ({
date: new Date(`2023-02-${i + 1}`),
return: -0.005 // Negative returns with no recovery
}))
]
};
// Act
const positiveMetrics = PerformanceAnalytics.enhanceResults({
...sampleResult,
dailyReturns: allPositiveReturns.dailyReturns
});
const noRecoveryDrawdowns = PerformanceAnalytics.analyzeDrawdowns(noRecoveryReturns.dailyReturns);
// Assert
expect(positiveMetrics.sortinoRatio).toBe(Infinity); // No downside risk
// Last drawdown should have no recovery
const lastDrawdown = noRecoveryDrawdowns[noRecoveryDrawdowns.length - 1];
expect(lastDrawdown.recoveryDate).toBeNull();
expect(lastDrawdown.recoveryDays).toBeNull();
});
});

View file

@ -0,0 +1,237 @@
import { describe, it, expect, beforeEach, afterEach, jest } from 'bun:test';
import { EventEmitter } from 'events';
import { WebSocket } from 'ws';
import { StrategyExecutionService } from '../../core/execution/StrategyExecutionService';
import { StrategyRegistry } from '../../core/strategies/StrategyRegistry';
import { MarketDataFeed } from '../../core/backtesting/MarketDataFeed';
import { BaseStrategy, BarData, Order } from '../../core/Strategy';
// Mock WebSocket to avoid actual network connections during tests
jest.mock('ws', () => {
const EventEmitter = require('events');
class MockWebSocket extends EventEmitter {
static OPEN = 1;
readyState = 1;
close = jest.fn();
send = jest.fn();
}
class MockServer extends EventEmitter {
clients = new Set();
constructor() {
super();
// Add a mock client to the set
const mockClient = new MockWebSocket();
this.clients.add(mockClient);
}
close(callback) {
callback();
}
}
return {
WebSocket: MockWebSocket,
Server: MockServer
};
});
// Mock MarketDataFeed to avoid actual API calls
jest.mock('../../core/backtesting/MarketDataFeed', () => {
return {
MarketDataFeed: class {
async getHistoricalData(symbol, resolution, startDate, endDate) {
// Return mock data
return [
{
symbol,
timestamp: new Date(),
open: 100,
high: 105,
low: 95,
close: 102,
volume: 1000
}
];
}
}
};
});
// Mock strategy for testing
class MockStrategy extends BaseStrategy {
name = 'MockStrategy';
description = 'A mock strategy for testing';
symbols = ['AAPL', 'MSFT'];
parameters = { param1: 1, param2: 2 };
constructor(id: string) {
super(id);
}
async start(): Promise<void> {}
async stop(): Promise<void> {}
onBar(bar: BarData) {
// Return a mock signal
return {
action: 'BUY',
symbol: bar.symbol,
price: bar.close,
quantity: 10,
metadata: { reason: 'Test signal' }
};
}
async onOrderFilled(order: Order): Promise<void> {}
}
// Mock StrategyRegistry
jest.mock('../../core/strategies/StrategyRegistry', () => {
const mockInstance = {
getStrategyById: jest.fn(),
getStrategyTypes: () => [{ id: 'mock-strategy', name: 'Mock Strategy' }],
getAllStrategies: () => [new MockStrategy('mock-1')]
};
return {
StrategyRegistry: {
getInstance: () => mockInstance
}
};
});
describe('StrategyExecutionService', () => {
let executionService: StrategyExecutionService;
let strategyRegistry: typeof StrategyRegistry.getInstance;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Create a new execution service for each test
executionService = new StrategyExecutionService('http://localhost:3001/api', 8082);
strategyRegistry = StrategyRegistry.getInstance();
// Setup mock strategy
const mockStrategy = new MockStrategy('test-strategy');
(strategyRegistry.getStrategyById as jest.Mock).mockReturnValue(mockStrategy);
});
afterEach(() => {
executionService.shutdown();
});
it('should initialize correctly', () => {
expect(executionService).toBeDefined();
});
it('should start a strategy correctly', () => {
// Arrange & Act
const result = executionService.startStrategy('test-strategy');
// Assert
expect(result).toBe(true);
expect(strategyRegistry.getStrategyById).toHaveBeenCalledWith('test-strategy');
// Check if WebSocket broadcast happened
const ws = executionService['webSocketServer'].clients.values().next().value;
expect(ws.send).toHaveBeenCalled();
// Check the broadcast message contains the correct type
const lastCall = ws.send.mock.calls[0][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_started');
expect(message.data.strategyId).toBe('test-strategy');
});
it('should stop a strategy correctly', () => {
// Arrange
executionService.startStrategy('test-strategy');
// Act
const result = executionService.stopStrategy('test-strategy');
// Assert
expect(result).toBe(true);
// Check if WebSocket broadcast happened
const ws = executionService['webSocketServer'].clients.values().next().value;
const lastCallIndex = ws.send.mock.calls.length - 1;
const lastCall = ws.send.mock.calls[lastCallIndex][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_stopped');
expect(message.data.strategyId).toBe('test-strategy');
});
it('should pause a strategy correctly', () => {
// Arrange
executionService.startStrategy('test-strategy');
// Act
const result = executionService.pauseStrategy('test-strategy');
// Assert
expect(result).toBe(true);
// Check if WebSocket broadcast happened
const ws = executionService['webSocketServer'].clients.values().next().value;
const lastCallIndex = ws.send.mock.calls.length - 1;
const lastCall = ws.send.mock.calls[lastCallIndex][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_paused');
expect(message.data.strategyId).toBe('test-strategy');
});
it('should process market data and generate signals', async () => {
// Arrange
executionService.startStrategy('test-strategy');
// Act - Trigger market data polling manually
await executionService['pollMarketData']('test-strategy');
// Assert - Check if signal was generated and broadcast
const ws = executionService['webSocketServer'].clients.values().next().value;
// Find the strategy_signal message
const signalMessages = ws.send.mock.calls
.map(call => JSON.parse(call[0]))
.filter(msg => msg.type === 'strategy_signal');
expect(signalMessages.length).toBeGreaterThan(0);
expect(signalMessages[0].data.action).toBe('BUY');
expect(signalMessages[0].data.strategyId).toBe('test-strategy');
});
it('should handle WebSocket client connections', () => {
// Arrange
const mockWs = new WebSocket();
const mockMessage = JSON.stringify({ type: 'get_active_strategies' });
// Act - Simulate connection and message
executionService['webSocketServer'].emit('connection', mockWs);
mockWs.emit('message', mockMessage);
// Assert
expect(mockWs.send).toHaveBeenCalled();
// Check that the response is a strategy_status_list message
const lastCall = mockWs.send.mock.calls[0][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_status_list');
});
it('should shut down correctly', () => {
// Act
executionService.shutdown();
// Assert - WebSocket server should be closed
const ws = executionService['webSocketServer'].clients.values().next().value;
expect(ws.close).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,130 @@
import { describe, test, expect, beforeEach, mock } from 'bun:test';
import { MeanReversionStrategy } from '../../core/strategies/MeanReversionStrategy';
import { BarData } from '../../core/Strategy';
describe('MeanReversionStrategy', () => {
let strategy: MeanReversionStrategy;
let mockData: BarData[];
beforeEach(() => {
// Create a strategy instance with test parameters
strategy = new MeanReversionStrategy(
'test_id',
'Test Mean Reversion',
'A test strategy',
['AAPL'],
{
lookback: 20,
entryDeviation: 1.5,
exitDeviation: 0.5,
lookbackPeriod: 100,
positionSize: 0.2,
stopLoss: 0.02,
takeProfit: 0.05,
useBollingerBands: true,
bollingerPeriod: 20,
bollingerDeviation: 2,
rsiPeriod: 14,
rsiOverbought: 70,
rsiOversold: 30,
useRsi: true
}
);
// Create mock price data
const now = new Date();
mockData = [];
// Create 100 bars of data with a mean-reverting pattern
let price = 100;
for (let i = 0; i < 100; i++) {
// Add some mean reversion pattern (oscillating around 100)
price = price + Math.sin(i / 10) * 5 + (Math.random() - 0.5) * 2;
mockData.push({
symbol: 'AAPL',
timestamp: new Date(now.getTime() - (100 - i) * 60000), // 1-minute bars
open: price - 0.5,
high: price + 1,
low: price - 1,
close: price,
volume: 1000 + Math.random() * 1000
});
}
});
test('should initialize with correct parameters', () => {
expect(strategy.id).toBe('test_id');
expect(strategy.name).toBe('Test Mean Reversion');
expect(strategy.description).toBe('A test strategy');
expect(strategy.symbols).toEqual(['AAPL']);
expect(strategy.parameters.lookback).toBe(20);
expect(strategy.parameters.entryDeviation).toBe(1.5);
});
test('should generate signals with vectorized calculation', async () => {
// Arrange a price series with fake mean reversion
const results = await strategy.runVectorized({
symbols: ['AAPL'],
data: { 'AAPL': mockData },
initialCapital: 10000,
startIndex: 20, // Skip the first 20 bars for indicator warmup
endIndex: mockData.length - 1
});
// Assert
expect(results).toBeDefined();
expect(results.positions).toBeDefined();
// Should generate at least one trade in this artificial data
expect(results.trades.length).toBeGreaterThan(0);
expect(results.equityCurve.length).toBeGreaterThan(0);
});
test('should calculate correct entry and exit signals', () => {
// Mock the indicator calculations to test logic directly
// We'll create a simple scenario where price is 2 standard deviations away
const mockBar: BarData = {
symbol: 'AAPL',
timestamp: new Date(),
open: 100,
high: 102,
low: 98,
close: 100,
volume: 1000
};
// Mock the calculation context
const context = {
mean: 100,
stdDev: 5,
upperBand: 110,
lowerBand: 90,
rsi: 25, // Oversold
shouldEnterLong: true,
shouldExitLong: false,
shouldEnterShort: false,
shouldExitShort: false
};
// Call the internal signal generation logic via a protected method
// (For testing purposes, we're accessing a protected method)
const result = (strategy as any).calculateSignals('AAPL', mockBar, context);
// Assert the signals based on our scenario
expect(result).toBeDefined();
expect(result.action).toBe('BUY'); // Should buy in oversold condition
});
test('should handle empty data correctly', async () => {
// Act & Assert
await expect(async () => {
await strategy.runVectorized({
symbols: ['AAPL'],
data: { 'AAPL': [] },
initialCapital: 10000,
startIndex: 0,
endIndex: 0
});
}).not.toThrow();
});
});

View file

@ -0,0 +1,256 @@
import { describe, test, expect, beforeEach } from 'bun:test';
import { BaseStrategy } from '../../core/Strategy';
import { StrategyRegistry, StrategyType } from '../../core/strategies/StrategyRegistry';
import { MovingAverageCrossover } from '../../core/strategies/MovingAverageCrossover';
import { MeanReversionStrategy } from '../../core/strategies/MeanReversionStrategy';
import { VectorizedStrategy } from '../../core/strategies/VectorizedStrategy';
describe('Strategy Registry', () => {
let registry: StrategyRegistry;
beforeEach(() => {
// Reset the singleton for testing
(StrategyRegistry as any).instance = null;
registry = StrategyRegistry.getInstance();
});
test('should create a MovingAverageCrossover strategy', () => {
// Arrange
const id = 'test_id';
const name = 'Test Strategy';
const description = 'A test strategy';
const symbols = ['AAPL', 'MSFT'];
const parameters = { fastPeriod: 10, slowPeriod: 30 };
// Act
const strategy = registry.createStrategy(
'MOVING_AVERAGE_CROSSOVER',
id,
name,
description,
symbols,
parameters
);
// Assert
expect(strategy).toBeInstanceOf(MovingAverageCrossover);
expect(strategy.id).toEqual(id);
expect(strategy.name).toEqual(name);
expect(strategy.description).toEqual(description);
expect(strategy.symbols).toEqual(symbols);
expect(strategy.parameters).toMatchObject(parameters);
});
test('should create a MeanReversion strategy', () => {
// Arrange
const id = 'test_id';
const name = 'Test Strategy';
const description = 'A test strategy';
const symbols = ['AAPL', 'MSFT'];
const parameters = { lookback: 20, entryDeviation: 1.5 };
// Act
const strategy = registry.createStrategy(
'MEAN_REVERSION',
id,
name,
description,
symbols,
parameters
);
// Assert
expect(strategy).toBeInstanceOf(MeanReversionStrategy);
expect(strategy.id).toEqual(id);
expect(strategy.name).toEqual(name);
expect(strategy.description).toEqual(description);
expect(strategy.symbols).toEqual(symbols);
expect(strategy.parameters).toMatchObject(parameters);
});
test('should throw error for invalid strategy type', () => {
// Arrange
const id = 'test_id';
const name = 'Test Strategy';
const description = 'A test strategy';
const symbols = ['AAPL', 'MSFT'];
const parameters = {};
// Act & Assert
expect(() => {
registry.createStrategy(
'INVALID_TYPE' as StrategyType,
id,
name,
description,
symbols,
parameters
);
}).toThrow("Strategy type 'INVALID_TYPE' is not registered");
});
test('should register a custom strategy', () => {
// Arrange
const mockStrategyFactory = (
id: string,
name: string,
description: string,
symbols: string[],
parameters: any
) => {
return new MovingAverageCrossover(id, name, description, symbols, parameters);
};
// Act
registry.registerStrategy('CUSTOM' as StrategyType, mockStrategyFactory);
// Assert
expect(registry.hasStrategyType('CUSTOM')).toBe(true);
const strategy = registry.createStrategy(
'CUSTOM',
'custom_id',
'Custom Strategy',
'A custom strategy',
['BTC/USD'],
{}
);
expect(strategy).toBeInstanceOf(MovingAverageCrossover);
});
test('should get default parameters for a strategy type', () => {
// Act
const macParams = registry.getDefaultParameters('MOVING_AVERAGE_CROSSOVER');
const mrParams = registry.getDefaultParameters('MEAN_REVERSION');
// Assert
expect(macParams).toHaveProperty('fastPeriod');
expect(macParams).toHaveProperty('slowPeriod');
expect(mrParams).toHaveProperty('lookback');
expect(mrParams).toHaveProperty('entryDeviation');
});
test('should return empty object for unknown strategy default parameters', () => {
// Act
const params = registry.getDefaultParameters('CUSTOM' as StrategyType);
// Assert
expect(params).toEqual({});
});
test('should get all registered strategy types', () => {
// Act
const types = registry.getStrategyTypes();
// Assert
expect(types).toContain('MOVING_AVERAGE_CROSSOVER');
expect(types).toContain('MEAN_REVERSION');
});
test('should check if strategy type is registered', () => {
// Act & Assert
expect(registry.hasStrategyType('MOVING_AVERAGE_CROSSOVER')).toBe(true);
expect(registry.hasStrategyType('INVALID_TYPE' as StrategyType)).toBe(false);
});
test('should get all registered strategies', () => {
// Arrange
registry.createStrategy(
'MOVING_AVERAGE_CROSSOVER',
'mac_id',
'MAC Strategy',
'MAC strategy',
['AAPL'],
{}
);
registry.createStrategy(
'MEAN_REVERSION',
'mr_id',
'MR Strategy',
'MR strategy',
['MSFT'],
{}
);
// Act
const strategies = registry.getAllStrategies();
// Assert
expect(strategies).toHaveLength(2);
expect(strategies[0].id).toEqual('mac_id');
expect(strategies[1].id).toEqual('mr_id');
});
test('should get strategy by ID', () => {
// Arrange
registry.createStrategy(
'MOVING_AVERAGE_CROSSOVER',
'mac_id',
'MAC Strategy',
'MAC strategy',
['AAPL'],
{}
);
// Act
const strategy = registry.getStrategyById('mac_id');
const nonExistent = registry.getStrategyById('non_existent');
// Assert
expect(strategy).not.toBeNull();
expect(strategy?.id).toEqual('mac_id');
expect(nonExistent).toBeUndefined();
});
test('should delete strategy by ID', () => {
// Arrange
registry.createStrategy(
'MOVING_AVERAGE_CROSSOVER',
'mac_id',
'MAC Strategy',
'MAC strategy',
['AAPL'],
{}
);
// Act
const result1 = registry.deleteStrategy('mac_id');
const result2 = registry.deleteStrategy('non_existent');
// Assert
expect(result1).toBe(true);
expect(result2).toBe(false);
expect(registry.getStrategyById('mac_id')).toBeUndefined();
});
test('should identify strategy type from instance', () => {
// Arrange
const macStrategy = registry.createStrategy(
'MOVING_AVERAGE_CROSSOVER',
'mac_id',
'MAC Strategy',
'MAC strategy',
['AAPL'],
{}
);
const mrStrategy = registry.createStrategy(
'MEAN_REVERSION',
'mr_id',
'MR Strategy',
'MR strategy',
['MSFT'],
{}
);
// Act
const macType = registry.getStrategyType(macStrategy);
const mrType = registry.getStrategyType(mrStrategy);
// Assert
expect(macType).toEqual('MOVING_AVERAGE_CROSSOVER');
expect(mrType).toEqual('MEAN_REVERSION');
});
});