2703 lines
90 KiB
Markdown
2703 lines
90 KiB
Markdown
# Unit Test Plan for OMS Components
|
|
|
|
## Overview
|
|
|
|
This document outlines a comprehensive plan for writing unit tests for all Order Management System (OMS) components, ensuring proper functionality, reliability, and maintainability of the system.
|
|
|
|
## Test Strategy
|
|
|
|
### Testing Principles
|
|
1. **Comprehensive Coverage**: Aim for >90% code coverage for all critical components
|
|
2. **Isolation**: Each test should be independent and not rely on external state
|
|
3. **Repeatability**: Tests should produce consistent results across runs
|
|
4. **Speed**: Tests should execute quickly to enable rapid development cycles
|
|
5. **Readability**: Tests should be clear and self-documenting
|
|
6. **Maintainability**: Tests should be easy to update when implementation changes
|
|
|
|
### Testing Framework
|
|
- **Framework**: xUnit.net
|
|
- **Mocking**: Moq
|
|
- **Assertion Library**: FluentAssertions
|
|
- **Test Runner**: dotnet test
|
|
|
|
### Test Categories
|
|
1. **Unit Tests**: Test individual components in isolation
|
|
2. **Integration Tests**: Test interactions between components
|
|
3. **Performance Tests**: Test system performance under load
|
|
4. **Regression Tests**: Ensure previously fixed bugs don't reappear
|
|
|
|
## Component Test Plans
|
|
|
|
### 1. OrderManager Tests
|
|
|
|
#### Core Functionality Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for OrderManager core functionality
|
|
/// </summary>
|
|
public class OrderManagerTests
|
|
{
|
|
private Mock<IRiskManager> _mockRiskManager;
|
|
private Mock<IPositionSizer> _mockPositionSizer;
|
|
private Mock<ILogger<OrderManager>> _mockLogger;
|
|
private Mock<RoutingConfigurationManager> _mockConfigManager;
|
|
private Mock<RoutingMetricsCollector> _mockMetricsCollector;
|
|
private Mock<TwapExecutor> _mockTwapExecutor;
|
|
private Mock<VwapExecutor> _mockVwapExecutor;
|
|
private Mock<IcebergExecutor> _mockIcebergExecutor;
|
|
private Mock<AlgorithmParameterProvider> _mockParameterProvider;
|
|
private Mock<RateLimiter> _mockRateLimiter;
|
|
private Mock<ValueLimiter> _mockValueLimiter;
|
|
private Mock<CircuitBreaker> _mockCircuitBreaker;
|
|
private OrderManager _orderManager;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidDependencies_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
|
|
// Act
|
|
var orderManager = new OrderManager(
|
|
_mockRiskManager.Object,
|
|
_mockPositionSizer.Object,
|
|
_mockLogger.Object,
|
|
_mockConfigManager.Object,
|
|
_mockMetricsCollector.Object,
|
|
_mockTwapExecutor.Object,
|
|
_mockVwapExecutor.Object,
|
|
_mockIcebergExecutor.Object,
|
|
_mockParameterProvider.Object,
|
|
_mockRateLimiter.Object,
|
|
_mockValueLimiter.Object,
|
|
_mockCircuitBreaker.Object);
|
|
|
|
// Assert
|
|
orderManager.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitOrderAsync_WithValidMarketOrder_ShouldSubmitSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var request = new OrderRequest(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var riskDecision = new RiskDecision(
|
|
Allow: true,
|
|
RejectReason: null,
|
|
ModifiedIntent: null,
|
|
RiskLevel: RiskLevel.Low,
|
|
RiskMetrics: new Dictionary<string, object>()
|
|
);
|
|
|
|
_mockRiskManager.Setup(rm => rm.ValidateOrder(It.IsAny<StrategyIntent>(), It.IsAny<StrategyContext>(), It.IsAny<RiskConfig>()))
|
|
.Returns(riskDecision);
|
|
|
|
// Act
|
|
var result = await _orderManager.SubmitOrderAsync(request, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Success.Should().BeTrue();
|
|
result.OrderId.Should().NotBeNullOrEmpty();
|
|
result.Message.Should().Contain("submitted successfully");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitOrderAsync_WithRiskRejection_ShouldRejectOrder()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var request = new OrderRequest(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var riskDecision = new RiskDecision(
|
|
Allow: false,
|
|
RejectReason: "Daily loss limit exceeded",
|
|
ModifiedIntent: null,
|
|
RiskLevel: RiskLevel.Critical,
|
|
RiskMetrics: new Dictionary<string, object>()
|
|
);
|
|
|
|
_mockRiskManager.Setup(rm => rm.ValidateOrder(It.IsAny<StrategyIntent>(), It.IsAny<StrategyContext>(), It.IsAny<RiskConfig>()))
|
|
.Returns(riskDecision);
|
|
|
|
// Act
|
|
var result = await _orderManager.SubmitOrderAsync(request, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Success.Should().BeFalse();
|
|
result.Message.Should().Contain("Risk validation failed");
|
|
result.Message.Should().Contain("Daily loss limit exceeded");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitOrderAsync_WithInvalidParameters_ShouldReturnValidationError()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var request = new OrderRequest(
|
|
Symbol: "", // Invalid - empty symbol
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 0, // Invalid - zero quantity
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act
|
|
var result = await _orderManager.SubmitOrderAsync(request, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Success.Should().BeFalse();
|
|
result.Message.Should().Contain("validation");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelOrderAsync_WithValidOrderId_ShouldCancelSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var orderId = Guid.NewGuid().ToString();
|
|
|
|
// Act
|
|
var result = await _orderManager.CancelOrderAsync(orderId);
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
// Note: In a real implementation, we would verify the order was actually cancelled
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelOrderAsync_WithInvalidOrderId_ShouldReturnFalse()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
string orderId = null; // Invalid - null order ID
|
|
|
|
// Act
|
|
var result = await _orderManager.CancelOrderAsync(orderId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetOrderStatusAsync_WithValidOrderId_ShouldReturnStatus()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var orderId = Guid.NewGuid().ToString();
|
|
|
|
// Act
|
|
var status = await _orderManager.GetOrderStatusAsync(orderId);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the status was retrieved correctly
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetActiveOrdersAsync_ShouldReturnActiveOrders()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
// Act
|
|
var orders = await _orderManager.GetActiveOrdersAsync();
|
|
|
|
// Assert
|
|
orders.Should().NotBeNull();
|
|
// In a real implementation, we would verify the active orders were retrieved correctly
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockRiskManager = new Mock<IRiskManager>();
|
|
_mockPositionSizer = new Mock<IPositionSizer>();
|
|
_mockLogger = new Mock<ILogger<OrderManager>>();
|
|
_mockConfigManager = new Mock<RoutingConfigurationManager>();
|
|
_mockMetricsCollector = new Mock<RoutingMetricsCollector>();
|
|
_mockTwapExecutor = new Mock<TwapExecutor>();
|
|
_mockVwapExecutor = new Mock<VwapExecutor>();
|
|
_mockIcebergExecutor = new Mock<IcebergExecutor>();
|
|
_mockParameterProvider = new Mock<AlgorithmParameterProvider>();
|
|
_mockRateLimiter = new Mock<RateLimiter>();
|
|
_mockValueLimiter = new Mock<ValueLimiter>();
|
|
_mockCircuitBreaker = new Mock<CircuitBreaker>();
|
|
}
|
|
|
|
private OrderManager CreateOrderManager()
|
|
{
|
|
return new OrderManager(
|
|
_mockRiskManager.Object,
|
|
_mockPositionSizer.Object,
|
|
_mockLogger.Object,
|
|
_mockConfigManager.Object,
|
|
_mockMetricsCollector.Object,
|
|
_mockTwapExecutor.Object,
|
|
_mockVwapExecutor.Object,
|
|
_mockIcebergExecutor.Object,
|
|
_mockParameterProvider.Object,
|
|
_mockRateLimiter.Object,
|
|
_mockValueLimiter.Object,
|
|
_mockCircuitBreaker.Object);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Algorithmic Order Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for OrderManager algorithmic order functionality
|
|
/// </summary>
|
|
public class OrderManagerAlgorithmicTests
|
|
{
|
|
private Mock<IRiskManager> _mockRiskManager;
|
|
private Mock<IPositionSizer> _mockPositionSizer;
|
|
private Mock<ILogger<OrderManager>> _mockLogger;
|
|
private Mock<RoutingConfigurationManager> _mockConfigManager;
|
|
private Mock<RoutingMetricsCollector> _mockMetricsCollector;
|
|
private Mock<TwapExecutor> _mockTwapExecutor;
|
|
private Mock<VwapExecutor> _mockVwapExecutor;
|
|
private Mock<IcebergExecutor> _mockIcebergExecutor;
|
|
private Mock<AlgorithmParameterProvider> _mockParameterProvider;
|
|
private Mock<RateLimiter> _mockRateLimiter;
|
|
private Mock<ValueLimiter> _mockValueLimiter;
|
|
private Mock<CircuitBreaker> _mockCircuitBreaker;
|
|
private OrderManager _orderManager;
|
|
|
|
[Fact]
|
|
public async Task ExecuteTwapAsync_WithValidParameters_ShouldExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var parameters = new TwapParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 10,
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
IntervalSeconds: 60,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinSliceSize: 1,
|
|
MaxSliceSize: null,
|
|
AdjustForRemainingTime: true,
|
|
CancelAtEndTime: true,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var riskDecision = new RiskDecision(
|
|
Allow: true,
|
|
RejectReason: null,
|
|
ModifiedIntent: null,
|
|
RiskLevel: RiskLevel.Low,
|
|
RiskMetrics: new Dictionary<string, object>()
|
|
);
|
|
|
|
_mockRiskManager.Setup(rm => rm.ValidateOrder(It.IsAny<StrategyIntent>(), It.IsAny<StrategyContext>(), It.IsAny<RiskConfig>()))
|
|
.Returns(riskDecision);
|
|
|
|
var executionState = new TwapExecutionState
|
|
{
|
|
ExecutionId = Guid.NewGuid().ToString(),
|
|
Parameters = parameters,
|
|
Status = TwapExecutionStatus.Completed,
|
|
TotalQuantity = 10,
|
|
ExecutedQuantity = 10,
|
|
StartTime = DateTime.UtcNow,
|
|
EndTime = DateTime.UtcNow.AddMinutes(30),
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_mockTwapExecutor.Setup(te => te.ExecuteTwapAsync(It.IsAny<TwapParameters>(), It.IsAny<StrategyContext>()))
|
|
.ReturnsAsync(executionState);
|
|
|
|
// Act
|
|
var result = await _orderManager.ExecuteTwapAsync(parameters, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Success.Should().BeTrue();
|
|
result.OrderId.Should().NotBeNullOrEmpty();
|
|
result.Message.Should().Contain("completed successfully");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteVwapAsync_WithValidParameters_ShouldExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var parameters = new VwapParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 10,
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
ParticipationRate: 0.1,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinOrderSize: 1,
|
|
MaxOrderSize: null,
|
|
CheckIntervalSeconds: 30,
|
|
CancelAtEndTime: true,
|
|
Aggressiveness: 0.5,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var riskDecision = new RiskDecision(
|
|
Allow: true,
|
|
RejectReason: null,
|
|
ModifiedIntent: null,
|
|
RiskLevel: RiskLevel.Low,
|
|
RiskMetrics: new Dictionary<string, object>()
|
|
);
|
|
|
|
_mockRiskManager.Setup(rm => rm.ValidateOrder(It.IsAny<StrategyIntent>(), It.IsAny<StrategyContext>(), It.IsAny<RiskConfig>()))
|
|
.Returns(riskDecision);
|
|
|
|
var executionState = new VwapExecutionState
|
|
{
|
|
ExecutionId = Guid.NewGuid().ToString(),
|
|
Parameters = parameters,
|
|
Status = VwapExecutionStatus.Completed,
|
|
TotalQuantity = 10,
|
|
ExecutedQuantity = 10,
|
|
StartTime = DateTime.UtcNow,
|
|
EndTime = DateTime.UtcNow.AddMinutes(30),
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_mockVwapExecutor.Setup(ve => ve.ExecuteVwapAsync(It.IsAny<VwapParameters>(), It.IsAny<StrategyContext>()))
|
|
.ReturnsAsync(executionState);
|
|
|
|
// Act
|
|
var result = await _orderManager.ExecuteVwapAsync(parameters, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Success.Should().BeTrue();
|
|
result.OrderId.Should().NotBeNullOrEmpty();
|
|
result.Message.Should().Contain("completed successfully");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteIcebergAsync_WithValidParameters_ShouldExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_orderManager = CreateOrderManager();
|
|
|
|
var parameters = new IcebergParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
VisibleQuantity: 10,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
AutoReplenish: true,
|
|
MinVisibleQuantity: 1,
|
|
MaxVisibleQuantity: null,
|
|
PlacementDelayMs: 1000,
|
|
CancelAtEnd: true,
|
|
Aggressiveness: 0.5,
|
|
AdaptiveVisibility: false,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var riskDecision = new RiskDecision(
|
|
Allow: true,
|
|
RejectReason: null,
|
|
ModifiedIntent: null,
|
|
RiskLevel: RiskLevel.Low,
|
|
RiskMetrics: new Dictionary<string, object>()
|
|
);
|
|
|
|
_mockRiskManager.Setup(rm => rm.ValidateOrder(It.IsAny<StrategyIntent>(), It.IsAny<StrategyContext>(), It.IsAny<RiskConfig>()))
|
|
.Returns(riskDecision);
|
|
|
|
var executionState = new IcebergExecutionState
|
|
{
|
|
ExecutionId = Guid.NewGuid().ToString(),
|
|
Parameters = parameters,
|
|
Status = IcebergExecutionStatus.Completed,
|
|
TotalQuantity = 100,
|
|
ExecutedQuantity = 100,
|
|
VisibleQuantity = 10,
|
|
StartTime = DateTime.UtcNow,
|
|
EndTime = DateTime.UtcNow.AddMinutes(30),
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
_mockIcebergExecutor.Setup(ie => ie.ExecuteIcebergAsync(It.IsAny<IcebergParameters>(), It.IsAny<StrategyContext>()))
|
|
.ReturnsAsync(executionState);
|
|
|
|
// Act
|
|
var result = await _orderManager.ExecuteIcebergAsync(parameters, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Success.Should().BeTrue();
|
|
result.OrderId.Should().NotBeNullOrEmpty();
|
|
result.Message.Should().Contain("completed successfully");
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockRiskManager = new Mock<IRiskManager>();
|
|
_mockPositionSizer = new Mock<IPositionSizer>();
|
|
_mockLogger = new Mock<ILogger<OrderManager>>();
|
|
_mockConfigManager = new Mock<RoutingConfigurationManager>();
|
|
_mockMetricsCollector = new Mock<RoutingMetricsCollector>();
|
|
_mockTwapExecutor = new Mock<TwapExecutor>();
|
|
_mockVwapExecutor = new Mock<VwapExecutor>();
|
|
_mockIcebergExecutor = new Mock<IcebergExecutor>();
|
|
_mockParameterProvider = new Mock<AlgorithmParameterProvider>();
|
|
_mockRateLimiter = new Mock<RateLimiter>();
|
|
_mockValueLimiter = new Mock<ValueLimiter>();
|
|
_mockCircuitBreaker = new Mock<CircuitBreaker>();
|
|
}
|
|
|
|
private OrderManager CreateOrderManager()
|
|
{
|
|
return new OrderManager(
|
|
_mockRiskManager.Object,
|
|
_mockPositionSizer.Object,
|
|
_mockLogger.Object,
|
|
_mockConfigManager.Object,
|
|
_mockMetricsCollector.Object,
|
|
_mockTwapExecutor.Object,
|
|
_mockVwapExecutor.Object,
|
|
_mockIcebergExecutor.Object,
|
|
_mockParameterProvider.Object,
|
|
_mockRateLimiter.Object,
|
|
_mockValueLimiter.Object,
|
|
_mockCircuitBreaker.Object);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. RiskManager Tests
|
|
|
|
#### Core Risk Management Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for RiskManager core functionality
|
|
/// </summary>
|
|
public class RiskManagerTests
|
|
{
|
|
private Mock<ILogger<BasicRiskManager>> _mockLogger;
|
|
private BasicRiskManager _riskManager;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidLogger_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
|
|
// Act
|
|
var riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
// Assert
|
|
riskManager.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateOrder_WithValidIntentAndWithinLimits_ShouldAllow()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 10,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var config = new RiskConfig(
|
|
DailyLossLimit: 1000,
|
|
MaxTradeRisk: 200,
|
|
MaxOpenPositions: 5,
|
|
EmergencyFlattenEnabled: true
|
|
);
|
|
|
|
// Act
|
|
var decision = _riskManager.ValidateOrder(intent, context, config);
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision.Allow.Should().BeTrue();
|
|
decision.RejectReason.Should().BeNull();
|
|
decision.RiskLevel.Should().Be(RiskLevel.Low);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateOrder_WithNullIntent_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
StrategyIntent intent = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var config = new RiskConfig(
|
|
DailyLossLimit: 1000,
|
|
MaxTradeRisk: 200,
|
|
MaxOpenPositions: 5,
|
|
EmergencyFlattenEnabled: true
|
|
);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _riskManager.ValidateOrder(intent, context, config));
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateOrder_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 10,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
var config = new RiskConfig(
|
|
DailyLossLimit: 1000,
|
|
MaxTradeRisk: 200,
|
|
MaxOpenPositions: 5,
|
|
EmergencyFlattenEnabled: true
|
|
);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _riskManager.ValidateOrder(intent, context, config));
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateOrder_WithNullConfig_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 10,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
RiskConfig config = null;
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _riskManager.ValidateOrder(intent, context, config));
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateOrder_WithDailyLossLimitExceeded_ShouldReject()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
// Simulate daily loss exceeding limit
|
|
_riskManager.OnPnLUpdate(-1500, -1500); // $1500 loss, exceeds $1000 limit
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 10,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var config = new RiskConfig(
|
|
DailyLossLimit: 1000,
|
|
MaxTradeRisk: 200,
|
|
MaxOpenPositions: 5,
|
|
EmergencyFlattenEnabled: true
|
|
);
|
|
|
|
// Act
|
|
var decision = _riskManager.ValidateOrder(intent, context, config);
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision.Allow.Should().BeFalse();
|
|
decision.RejectReason.Should().Contain("Daily loss limit breached");
|
|
decision.RiskLevel.Should().Be(RiskLevel.Critical);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnFill_WithValidFill_ShouldUpdateRiskState()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
var fill = new OrderFill(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Quantity: 2,
|
|
FillPrice: 4200,
|
|
FillTime: DateTime.UtcNow,
|
|
Commission: 4.50m,
|
|
ExecutionId: Guid.NewGuid().ToString()
|
|
);
|
|
|
|
// Act
|
|
_riskManager.OnFill(fill);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the risk state was updated correctly
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public void OnPnLUpdate_WithValidPnL_ShouldUpdateRiskState()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
var netPnL = -500.0;
|
|
var dayPnL = -500.0;
|
|
|
|
// Act
|
|
_riskManager.OnPnLUpdate(netPnL, dayPnL);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the risk state was updated correctly
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EmergencyFlattenAsync_WithValidReason_ShouldFlattenPositions()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
var reason = "Manual emergency flatten";
|
|
|
|
// Act
|
|
var result = await _riskManager.EmergencyFlatten(reason);
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
// In a real implementation, we would verify positions were actually flattened
|
|
}
|
|
|
|
[Fact]
|
|
public void GetRiskStatus_ShouldReturnCurrentRiskStatus()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicRiskManager>>();
|
|
_riskManager = new BasicRiskManager(_mockLogger.Object);
|
|
|
|
// Act
|
|
var status = _riskManager.GetRiskStatus();
|
|
|
|
// Assert
|
|
status.Should().NotBeNull();
|
|
status.TradingEnabled.Should().BeTrue();
|
|
status.DailyPnL.Should().Be(0);
|
|
status.MaxDrawdown.Should().Be(0);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. PositionSizer Tests
|
|
|
|
#### Core Position Sizing Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for PositionSizer core functionality
|
|
/// </summary>
|
|
public class PositionSizerTests
|
|
{
|
|
private Mock<ILogger<BasicPositionSizer>> _mockLogger;
|
|
private BasicPositionSizer _positionSizer;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidLogger_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
|
|
// Act
|
|
var positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
// Assert
|
|
positionSizer.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculateSize_WithFixedContractsMethod_ShouldReturnCorrectSize()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
_positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 8,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var config = new SizingConfig(
|
|
Method: SizingMethod.FixedContracts,
|
|
MinContracts: 1,
|
|
MaxContracts: 10,
|
|
RiskPerTrade: 200,
|
|
MethodParameters: new Dictionary<string, object> { ["contracts"] = 3 }
|
|
);
|
|
|
|
// Act
|
|
var result = _positionSizer.CalculateSize(intent, context, config);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Contracts.Should().Be(3);
|
|
result.Method.Should().Be(SizingMethod.FixedContracts);
|
|
result.RiskAmount.Should().Be(300.0); // 3 contracts * 8 ticks * $12.50
|
|
result.Calculations.Should().ContainKey("target_contracts");
|
|
result.Calculations.Should().ContainKey("clamped_contracts");
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculateSize_WithFixedDollarRiskMethod_ShouldReturnCorrectSize()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
_positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 10,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var config = new SizingConfig(
|
|
Method: SizingMethod.FixedDollarRisk,
|
|
MinContracts: 1,
|
|
MaxContracts: 10,
|
|
RiskPerTrade: 250.0, // Target $250 risk
|
|
MethodParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act
|
|
var result = _positionSizer.CalculateSize(intent, context, config);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Contracts.Should().Be(2); // $250 / (10 ticks * $12.50) = 2 contracts
|
|
result.Method.Should().Be(SizingMethod.FixedDollarRisk);
|
|
result.RiskAmount.Should().Be(250.0); // 2 * 10 * $12.50
|
|
result.Calculations.Should().ContainKey("target_risk");
|
|
result.Calculations.Should().ContainKey("optimal_contracts");
|
|
result.Calculations.Should().ContainKey("actual_risk");
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculateSize_WithNullIntent_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
_positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
StrategyIntent intent = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var config = new SizingConfig(
|
|
Method: SizingMethod.FixedContracts,
|
|
MinContracts: 1,
|
|
MaxContracts: 10,
|
|
RiskPerTrade: 200,
|
|
MethodParameters: new Dictionary<string, object> { ["contracts"] = 3 }
|
|
);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _positionSizer.CalculateSize(intent, context, config));
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculateSize_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
_positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 8,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
var config = new SizingConfig(
|
|
Method: SizingMethod.FixedContracts,
|
|
MinContracts: 1,
|
|
MaxContracts: 10,
|
|
RiskPerTrade: 200,
|
|
MethodParameters: new Dictionary<string, object> { ["contracts"] = 3 }
|
|
);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _positionSizer.CalculateSize(intent, context, config));
|
|
}
|
|
|
|
[Fact]
|
|
public void CalculateSize_WithNullConfig_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
_positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
var intent = new StrategyIntent(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
EntryType: OrderType.Market,
|
|
LimitPrice: null,
|
|
StopTicks: 8,
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "Test order",
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
SizingConfig config = null;
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _positionSizer.CalculateSize(intent, context, config));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetMetadata_ShouldReturnCorrectMetadata()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<BasicPositionSizer>>();
|
|
_positionSizer = new BasicPositionSizer(_mockLogger.Object);
|
|
|
|
// Act
|
|
var metadata = _positionSizer.GetMetadata();
|
|
|
|
// Assert
|
|
metadata.Should().NotBeNull();
|
|
metadata.Name.Should().Be("Basic Position Sizer");
|
|
metadata.Description.Should().Contain("Fixed contracts");
|
|
metadata.Description.Should().Contain("fixed dollar risk");
|
|
metadata.RequiredParameters.Should().Contain("method");
|
|
metadata.RequiredParameters.Should().Contain("risk_per_trade");
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateConfig_WithValidConfig_ShouldReturnTrue()
|
|
{
|
|
// Arrange
|
|
var config = new SizingConfig(
|
|
Method: SizingMethod.FixedContracts,
|
|
MinContracts: 1,
|
|
MaxContracts: 10,
|
|
RiskPerTrade: 200,
|
|
MethodParameters: new Dictionary<string, object> { ["contracts"] = 2 }
|
|
);
|
|
|
|
// Act
|
|
var isValid = BasicPositionSizer.ValidateConfig(config, out var errors);
|
|
|
|
// Assert
|
|
isValid.Should().BeTrue();
|
|
errors.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateConfig_WithInvalidConfig_ShouldReturnFalse()
|
|
{
|
|
// Arrange
|
|
var config = new SizingConfig(
|
|
Method: SizingMethod.FixedContracts,
|
|
MinContracts: 5,
|
|
MaxContracts: 2, // Invalid: min > max
|
|
RiskPerTrade: -100, // Invalid: negative risk
|
|
MethodParameters: new Dictionary<string, object>() // Missing required parameter
|
|
);
|
|
|
|
// Act
|
|
var isValid = BasicPositionSizer.ValidateConfig(config, out var errors);
|
|
|
|
// Assert
|
|
isValid.Should().BeFalse();
|
|
errors.Should().Contain("MinContracts must be <= MaxContracts");
|
|
errors.Should().Contain("RiskPerTrade must be > 0");
|
|
errors.Should().Contain("FixedContracts method requires 'contracts' parameter");
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. TWAP Algorithm Tests
|
|
|
|
#### TWAP Execution Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for TWAP algorithm execution
|
|
/// </summary>
|
|
public class TwapExecutorTests
|
|
{
|
|
private Mock<ILogger<TwapExecutor>> _mockLogger;
|
|
private Mock<IOrderManager> _mockOrderManager;
|
|
private Mock<IMarketDataProvider> _mockMarketDataProvider;
|
|
private TwapExecutor _twapExecutor;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidDependencies_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
|
|
// Act
|
|
var twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
// Assert
|
|
twapExecutor.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteTwapAsync_WithValidParameters_ShouldExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new TwapParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
IntervalSeconds: 60,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinSliceSize: 1,
|
|
MaxSliceSize: null,
|
|
AdjustForRemainingTime: true,
|
|
CancelAtEndTime: true,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var orderResult = new OrderResult(
|
|
Success: true,
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Message: "Order submitted successfully",
|
|
Status: new OrderStatus(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 10,
|
|
FilledQuantity: 10,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
State: OrderState.Filled,
|
|
CreatedTime: DateTime.UtcNow,
|
|
FilledTime: DateTime.UtcNow,
|
|
Fills: new List<OrderFill>
|
|
{
|
|
new OrderFill(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Quantity: 10,
|
|
FillPrice: 4200,
|
|
FillTime: DateTime.UtcNow,
|
|
Commission: 4.50m,
|
|
ExecutionId: Guid.NewGuid().ToString()
|
|
)
|
|
}
|
|
)
|
|
);
|
|
|
|
_mockOrderManager.Setup(om => om.SubmitOrderAsync(It.IsAny<OrderRequest>(), It.IsAny<StrategyContext>()))
|
|
.ReturnsAsync(orderResult);
|
|
|
|
// Act
|
|
var executionState = await _twapExecutor.ExecuteTwapAsync(parameters, context);
|
|
|
|
// Assert
|
|
executionState.Should().NotBeNull();
|
|
executionState.Status.Should().Be(TwapExecutionStatus.Running); // Initially running
|
|
executionState.TotalQuantity.Should().Be(100);
|
|
executionState.ExecutedQuantity.Should().Be(0); // Initially 0
|
|
executionState.Parameters.Should().BeEquivalentTo(parameters);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteTwapAsync_WithInvalidParameters_ShouldThrowArgumentException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new TwapParameters(
|
|
Symbol: "", // Invalid - empty symbol
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 0, // Invalid - zero quantity
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
IntervalSeconds: 60,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinSliceSize: 1,
|
|
MaxSliceSize: null,
|
|
AdjustForRemainingTime: true,
|
|
CancelAtEndTime: true,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(() => _twapExecutor.ExecuteTwapAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteTwapAsync_WithNullParameters_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
TwapParameters parameters = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _twapExecutor.ExecuteTwapAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteTwapAsync_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new TwapParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
IntervalSeconds: 60,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinSliceSize: 1,
|
|
MaxSliceSize: null,
|
|
AdjustForRemainingTime: true,
|
|
CancelAtEndTime: true,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _twapExecutor.ExecuteTwapAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelExecutionAsync_WithValidExecutionId_ShouldCancelSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var executionId = Guid.NewGuid().ToString();
|
|
|
|
// Act
|
|
var result = await _twapExecutor.CancelExecutionAsync(executionId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse(); // False because execution doesn't exist
|
|
// In a real implementation, we would test cancelling an actual execution
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelExecutionAsync_WithInvalidExecutionId_ShouldReturnFalse()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_twapExecutor = new TwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
string executionId = null; // Invalid - null execution ID
|
|
|
|
// Act
|
|
var result = await _twapExecutor.CancelExecutionAsync(executionId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse(); // False because execution ID is invalid
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockLogger = new Mock<ILogger<TwapExecutor>>();
|
|
_mockOrderManager = new Mock<IOrderManager>();
|
|
_mockMarketDataProvider = new Mock<IMarketDataProvider>();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. VWAP Algorithm Tests
|
|
|
|
#### VWAP Execution Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for VWAP algorithm execution
|
|
/// </summary>
|
|
public class VwapExecutorTests
|
|
{
|
|
private Mock<ILogger<VwapExecutor>> _mockLogger;
|
|
private Mock<IOrderManager> _mockOrderManager;
|
|
private Mock<IMarketDataProvider> _mockMarketDataProvider;
|
|
private VwapExecutor _vwapExecutor;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidDependencies_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
|
|
// Act
|
|
var vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
// Assert
|
|
vwapExecutor.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteVwapAsync_WithValidParameters_ShouldExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new VwapParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
ParticipationRate: 0.1,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinOrderSize: 1,
|
|
MaxOrderSize: null,
|
|
CheckIntervalSeconds: 30,
|
|
CancelAtEndTime: true,
|
|
Aggressiveness: 0.5,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var orderResult = new OrderResult(
|
|
Success: true,
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Message: "Order submitted successfully",
|
|
Status: new OrderStatus(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 10,
|
|
FilledQuantity: 10,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
State: OrderState.Filled,
|
|
CreatedTime: DateTime.UtcNow,
|
|
FilledTime: DateTime.UtcNow,
|
|
Fills: new List<OrderFill>
|
|
{
|
|
new OrderFill(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Quantity: 10,
|
|
FillPrice: 4200,
|
|
FillTime: DateTime.UtcNow,
|
|
Commission: 4.50m,
|
|
ExecutionId: Guid.NewGuid().ToString()
|
|
)
|
|
}
|
|
)
|
|
);
|
|
|
|
_mockOrderManager.Setup(om => om.SubmitOrderAsync(It.IsAny<OrderRequest>(), It.IsAny<StrategyContext>()))
|
|
.ReturnsAsync(orderResult);
|
|
|
|
// Act
|
|
var executionState = await _vwapExecutor.ExecuteVwapAsync(parameters, context);
|
|
|
|
// Assert
|
|
executionState.Should().NotBeNull();
|
|
executionState.Status.Should().Be(VwapExecutionStatus.Running); // Initially running
|
|
executionState.TotalQuantity.Should().Be(100);
|
|
executionState.ExecutedQuantity.Should().Be(0); // Initially 0
|
|
executionState.Parameters.Should().BeEquivalentTo(parameters);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteVwapAsync_WithInvalidParameters_ShouldThrowArgumentException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new VwapParameters(
|
|
Symbol: "", // Invalid - empty symbol
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 0, // Invalid - zero quantity
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
ParticipationRate: 0.1,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinOrderSize: 1,
|
|
MaxOrderSize: null,
|
|
CheckIntervalSeconds: 30,
|
|
CancelAtEndTime: true,
|
|
Aggressiveness: 0.5,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(() => _vwapExecutor.ExecuteVwapAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteVwapAsync_WithNullParameters_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
VwapParameters parameters = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _vwapExecutor.ExecuteVwapAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteVwapAsync_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new VwapParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
StartTime: DateTime.UtcNow,
|
|
EndTime: DateTime.UtcNow.AddMinutes(30),
|
|
ParticipationRate: 0.1,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
MinOrderSize: 1,
|
|
MaxOrderSize: null,
|
|
CheckIntervalSeconds: 30,
|
|
CancelAtEndTime: true,
|
|
Aggressiveness: 0.5,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _vwapExecutor.ExecuteVwapAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelExecutionAsync_WithValidExecutionId_ShouldCancelSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var executionId = Guid.NewGuid().ToString();
|
|
|
|
// Act
|
|
var result = await _vwapExecutor.CancelExecutionAsync(executionId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse(); // False because execution doesn't exist
|
|
// In a real implementation, we would test cancelling an actual execution
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelExecutionAsync_WithInvalidExecutionId_ShouldReturnFalse()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_vwapExecutor = new VwapExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
string executionId = null; // Invalid - null execution ID
|
|
|
|
// Act
|
|
var result = await _vwapExecutor.CancelExecutionAsync(executionId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse(); // False because execution ID is invalid
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockLogger = new Mock<ILogger<VwapExecutor>>();
|
|
_mockOrderManager = new Mock<IOrderManager>();
|
|
_mockMarketDataProvider = new Mock<IMarketDataProvider>();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. Iceberg Algorithm Tests
|
|
|
|
#### Iceberg Execution Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for Iceberg algorithm execution
|
|
/// </summary>
|
|
public class IcebergExecutorTests
|
|
{
|
|
private Mock<ILogger<IcebergExecutor>> _mockLogger;
|
|
private Mock<IOrderManager> _mockOrderManager;
|
|
private Mock<IMarketDataProvider> _mockMarketDataProvider;
|
|
private IcebergExecutor _icebergExecutor;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidDependencies_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
|
|
// Act
|
|
var icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
// Assert
|
|
icebergExecutor.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteIcebergAsync_WithValidParameters_ShouldExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new IcebergParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
VisibleQuantity: 10,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
AutoReplenish: true,
|
|
MinVisibleQuantity: 1,
|
|
MaxVisibleQuantity: null,
|
|
PlacementDelayMs: 1000,
|
|
CancelAtEnd: true,
|
|
Aggressiveness: 0.5,
|
|
AdaptiveVisibility: false,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
var orderResult = new OrderResult(
|
|
Success: true,
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Message: "Order submitted successfully",
|
|
Status: new OrderStatus(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 10,
|
|
FilledQuantity: 10,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
State: OrderState.Filled,
|
|
CreatedTime: DateTime.UtcNow,
|
|
FilledTime: DateTime.UtcNow,
|
|
Fills: new List<OrderFill>
|
|
{
|
|
new OrderFill(
|
|
OrderId: Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Quantity: 10,
|
|
FillPrice: 4200,
|
|
FillTime: DateTime.UtcNow,
|
|
Commission: 4.50m,
|
|
ExecutionId: Guid.NewGuid().ToString()
|
|
)
|
|
}
|
|
)
|
|
);
|
|
|
|
_mockOrderManager.Setup(om => om.SubmitOrderAsync(It.IsAny<OrderRequest>(), It.IsAny<StrategyContext>()))
|
|
.ReturnsAsync(orderResult);
|
|
|
|
// Act
|
|
var executionState = await _icebergExecutor.ExecuteIcebergAsync(parameters, context);
|
|
|
|
// Assert
|
|
executionState.Should().NotBeNull();
|
|
executionState.Status.Should().Be(IcebergExecutionStatus.Running); // Initially running
|
|
executionState.TotalQuantity.Should().Be(100);
|
|
executionState.ExecutedQuantity.Should().Be(0); // Initially 0
|
|
executionState.VisibleQuantity.Should().Be(10);
|
|
executionState.Parameters.Should().BeEquivalentTo(parameters);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteIcebergAsync_WithInvalidParameters_ShouldThrowArgumentException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new IcebergParameters(
|
|
Symbol: "", // Invalid - empty symbol
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 0, // Invalid - zero quantity
|
|
VisibleQuantity: 0, // Invalid - zero visible quantity
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
AutoReplenish: true,
|
|
MinVisibleQuantity: 1,
|
|
MaxVisibleQuantity: null,
|
|
PlacementDelayMs: 1000,
|
|
CancelAtEnd: true,
|
|
Aggressiveness: 0.5,
|
|
AdaptiveVisibility: false,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(() => _icebergExecutor.ExecuteIcebergAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteIcebergAsync_WithNullParameters_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
IcebergParameters parameters = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _icebergExecutor.ExecuteIcebergAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteIcebergAsync_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var parameters = new IcebergParameters(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
TotalQuantity: 100,
|
|
VisibleQuantity: 10,
|
|
LimitPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
AutoReplenish: true,
|
|
MinVisibleQuantity: 1,
|
|
MaxVisibleQuantity: null,
|
|
PlacementDelayMs: 1000,
|
|
CancelAtEnd: true,
|
|
Aggressiveness: 0.5,
|
|
AdaptiveVisibility: false,
|
|
Metadata: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _icebergExecutor.ExecuteIcebergAsync(parameters, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelExecutionAsync_WithValidExecutionId_ShouldCancelSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
var executionId = Guid.NewGuid().ToString();
|
|
|
|
// Act
|
|
var result = await _icebergExecutor.CancelExecutionAsync(executionId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse(); // False because execution doesn't exist
|
|
// In a real implementation, we would test cancelling an actual execution
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelExecutionAsync_WithInvalidExecutionId_ShouldReturnFalse()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_icebergExecutor = new IcebergExecutor(
|
|
_mockLogger.Object,
|
|
_mockOrderManager.Object,
|
|
_mockMarketDataProvider.Object);
|
|
|
|
string executionId = null; // Invalid - null execution ID
|
|
|
|
// Act
|
|
var result = await _icebergExecutor.CancelExecutionAsync(executionId);
|
|
|
|
// Assert
|
|
result.Should().BeFalse(); // False because execution ID is invalid
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockLogger = new Mock<ILogger<IcebergExecutor>>();
|
|
_mockOrderManager = new Mock<IOrderManager>();
|
|
_mockMarketDataProvider = new Mock<IMarketDataProvider>();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7. Rate Limiter Tests
|
|
|
|
#### Rate Limiting Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for RateLimiter functionality
|
|
/// </summary>
|
|
public class RateLimiterTests
|
|
{
|
|
private Mock<ILogger<RateLimiter>> _mockLogger;
|
|
private RateLimiter _rateLimiter;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidLogger_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
|
|
// Act
|
|
var rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
// Assert
|
|
rateLimiter.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void CheckRateLimit_WithValidOrderAndWithinLimits_ShouldAllow()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
var order = new OrderRequest(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act
|
|
var result = _rateLimiter.CheckRateLimit(order, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Action.Should().Be(RateLimitAction.Allow);
|
|
result.Violations.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void CheckRateLimit_WithNullOrder_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
OrderRequest order = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _rateLimiter.CheckRateLimit(order, context));
|
|
}
|
|
|
|
[Fact]
|
|
public void CheckRateLimit_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
var order = new OrderRequest(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => _rateLimiter.CheckRateLimit(order, context));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetMetrics_ShouldReturnCurrentMetrics()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
// Act
|
|
var metrics = _rateLimiter.GetMetrics();
|
|
|
|
// Assert
|
|
metrics.Should().NotBeNull();
|
|
metrics.GlobalPerSecondRate.Should().BeGreaterOrEqualTo(0);
|
|
metrics.GlobalPerMinuteRate.Should().BeGreaterOrEqualTo(0);
|
|
metrics.GlobalPerHourRate.Should().BeGreaterOrEqualTo(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetUserState_WithValidUserId_ShouldResetUserState()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
var userId = "test-user";
|
|
|
|
// Act
|
|
_rateLimiter.ResetUserState(userId);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the user state was reset
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetSymbolState_WithValidSymbol_ShouldResetSymbolState()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
var symbol = "ES";
|
|
|
|
// Act
|
|
_rateLimiter.ResetSymbolState(symbol);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the symbol state was reset
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetAllState_ShouldResetAllState()
|
|
{
|
|
// Arrange
|
|
_mockLogger = new Mock<ILogger<RateLimiter>>();
|
|
_rateLimiter = new RateLimiter(_mockLogger.Object);
|
|
|
|
// Act
|
|
_rateLimiter.ResetAllState();
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify all state was reset
|
|
// This is a simplified test
|
|
}
|
|
}
|
|
```
|
|
|
|
### 8. Value Limiter Tests
|
|
|
|
#### Value Limiting Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for ValueLimiter functionality
|
|
/// </summary>
|
|
public class ValueLimiterTests
|
|
{
|
|
private Mock<ILogger<ValueLimiter>> _mockLogger;
|
|
private Mock<ICurrencyConverter> _mockCurrencyConverter;
|
|
private ValueLimiter _valueLimiter;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidDependencies_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
|
|
// Act
|
|
var valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
// Assert
|
|
valueLimiter.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CheckValueLimitAsync_WithValidOrderAndWithinLimits_ShouldAllow()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
var order = new OrderRequest(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act
|
|
var result = await _valueLimiter.CheckValueLimitAsync(order, context);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Action.Should().Be(ValueLimitAction.Allow);
|
|
result.Violations.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CheckValueLimitAsync_WithNullOrder_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
OrderRequest order = null;
|
|
var context = new StrategyContext(
|
|
Symbol: "ES",
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _valueLimiter.CheckValueLimitAsync(order, context));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CheckValueLimitAsync_WithNullContext_ShouldThrowArgumentNullException()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
var order = new OrderRequest(
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
StrategyContext context = null;
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(() => _valueLimiter.CheckValueLimitAsync(order, context));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetMetrics_ShouldReturnCurrentMetrics()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
// Act
|
|
var metrics = _valueLimiter.GetMetrics();
|
|
|
|
// Assert
|
|
metrics.Should().NotBeNull();
|
|
metrics.GlobalTotalValueToday.Should().BeGreaterOrEqualTo(0);
|
|
metrics.GlobalAverageOrderValue.Should().BeGreaterOrEqualTo(0);
|
|
metrics.GlobalMaxOrderValue.Should().BeGreaterOrEqualTo(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetUserState_WithValidUserId_ShouldResetUserState()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
var userId = "test-user";
|
|
|
|
// Act
|
|
_valueLimiter.ResetUserState(userId);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the user state was reset
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetSymbolState_WithValidSymbol_ShouldResetSymbolState()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
var symbol = "ES";
|
|
|
|
// Act
|
|
_valueLimiter.ResetSymbolState(symbol);
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify the symbol state was reset
|
|
// This is a simplified test
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetAllState_ShouldResetAllState()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_valueLimiter = new ValueLimiter(
|
|
_mockLogger.Object,
|
|
_mockCurrencyConverter.Object);
|
|
|
|
// Act
|
|
_valueLimiter.ResetAllState();
|
|
|
|
// Assert
|
|
// In a real implementation, we would verify all state was reset
|
|
// This is a simplified test
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockLogger = new Mock<ILogger<ValueLimiter>>();
|
|
_mockCurrencyConverter = new Mock<ICurrencyConverter>();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 9. Circuit Breaker Tests
|
|
|
|
#### Circuit Breaking Tests
|
|
```csharp
|
|
/// <summary>
|
|
/// Tests for CircuitBreaker functionality
|
|
/// </summary>
|
|
public class CircuitBreakerTests
|
|
{
|
|
private Mock<ILogger<CircuitBreaker>> _mockLogger;
|
|
private Mock<IHealthChecker> _mockHealthChecker;
|
|
private CircuitBreaker _circuitBreaker;
|
|
|
|
[Fact]
|
|
public void Constructor_WithValidDependencies_ShouldInitializeSuccessfully()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
|
|
// Act
|
|
var circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
// Assert
|
|
circuitBreaker.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void CheckCircuit_WhenClosed_ShouldAllow()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
// Act
|
|
var result = _circuitBreaker.CheckCircuit();
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Action.Should().Be(CircuitBreakerAction.Allow);
|
|
result.State.Should().Be(CircuitBreakerState.Closed);
|
|
}
|
|
|
|
[Fact]
|
|
public void CheckCircuit_WhenOpen_ShouldReject()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
// Manually open circuit
|
|
_circuitBreaker.OpenCircuit("Test open");
|
|
|
|
// Act
|
|
var result = _circuitBreaker.CheckCircuit();
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Action.Should().Be(CircuitBreakerAction.Reject);
|
|
result.State.Should().Be(CircuitBreakerState.Open);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordFailure_WhenThresholdExceeded_ShouldOpenCircuit()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
var config = new CircuitBreakerConfig
|
|
{
|
|
FailureThreshold = 2,
|
|
TimeoutSeconds = 60,
|
|
WindowSizeSeconds = 300
|
|
};
|
|
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object,
|
|
config);
|
|
|
|
var failure1 = new FailureRecord
|
|
{
|
|
Timestamp = DateTime.UtcNow,
|
|
Type = FailureType.OrderRejection,
|
|
Message = "Test failure 1"
|
|
};
|
|
|
|
var failure2 = new FailureRecord
|
|
{
|
|
Timestamp = DateTime.UtcNow,
|
|
Type = FailureType.OrderRejection,
|
|
Message = "Test failure 2"
|
|
};
|
|
|
|
// Act
|
|
_circuitBreaker.RecordFailure(failure1);
|
|
_circuitBreaker.RecordFailure(failure2);
|
|
|
|
// Assert
|
|
_circuitBreaker.GetState().Should().Be(CircuitBreakerState.Open);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordSuccess_WhenInHalfOpenAndThresholdExceeded_ShouldCloseCircuit()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
var config = new CircuitBreakerConfig
|
|
{
|
|
FailureThreshold = 1,
|
|
SuccessThreshold = 2,
|
|
TimeoutSeconds = 1,
|
|
WindowSizeSeconds = 300,
|
|
EnableHalfOpenState = true,
|
|
EnableAutomaticReset = true
|
|
};
|
|
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object,
|
|
config);
|
|
|
|
// Open circuit
|
|
var failure = new FailureRecord
|
|
{
|
|
Timestamp = DateTime.UtcNow,
|
|
Type = FailureType.OrderRejection,
|
|
Message = "Test failure"
|
|
};
|
|
|
|
_circuitBreaker.RecordFailure(failure);
|
|
|
|
// Wait for timeout to transition to half-open
|
|
Thread.Sleep(TimeSpan.FromSeconds(2));
|
|
|
|
// Act
|
|
_circuitBreaker.RecordSuccess(DateTime.UtcNow);
|
|
_circuitBreaker.RecordSuccess(DateTime.UtcNow);
|
|
|
|
// Assert
|
|
_circuitBreaker.GetState().Should().Be(CircuitBreakerState.Closed);
|
|
}
|
|
|
|
[Fact]
|
|
public void OpenCircuit_WithValidReason_ShouldOpenCircuit()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
var reason = "Manual open";
|
|
|
|
// Act
|
|
_circuitBreaker.OpenCircuit(reason);
|
|
|
|
// Assert
|
|
_circuitBreaker.GetState().Should().Be(CircuitBreakerState.Open);
|
|
}
|
|
|
|
[Fact]
|
|
public void CloseCircuit_WithValidReason_ShouldCloseCircuit()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
// Open circuit first
|
|
_circuitBreaker.OpenCircuit("Test open");
|
|
|
|
var reason = "Manual close";
|
|
|
|
// Act
|
|
_circuitBreaker.CloseCircuit(reason);
|
|
|
|
// Assert
|
|
_circuitBreaker.GetState().Should().Be(CircuitBreakerState.Closed);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetMetrics_ShouldReturnCurrentMetrics()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
// Act
|
|
var metrics = _circuitBreaker.GetMetrics();
|
|
|
|
// Assert
|
|
metrics.Should().NotBeNull();
|
|
metrics.State.Should().Be(CircuitBreakerState.Closed);
|
|
metrics.FailureCount.Should().Be(0);
|
|
metrics.SuccessCount.Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Reset_ShouldResetCircuitState()
|
|
{
|
|
// Arrange
|
|
SetupMocks();
|
|
_circuitBreaker = new CircuitBreaker(
|
|
_mockLogger.Object,
|
|
_mockHealthChecker.Object);
|
|
|
|
// Open circuit
|
|
_circuitBreaker.OpenCircuit("Test open");
|
|
|
|
// Act
|
|
_circuitBreaker.Reset();
|
|
|
|
// Assert
|
|
_circuitBreaker.GetState().Should().Be(CircuitBreakerState.Closed);
|
|
}
|
|
|
|
private void SetupMocks()
|
|
{
|
|
_mockLogger = new Mock<ILogger<CircuitBreaker>>();
|
|
_mockHealthChecker = new Mock<IHealthChecker>();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Test Coverage Goals
|
|
|
|
### Component Coverage Targets
|
|
| Component | Coverage Target | Notes |
|
|
|-----------|-----------------|-------|
|
|
| OrderManager | 95% | Critical component, extensive testing required |
|
|
| RiskManager | 95% | Core risk management, must be thoroughly tested |
|
|
| PositionSizer | 90% | Important for position sizing accuracy |
|
|
| TWAP Executor | 90% | Algorithmic execution, complex logic |
|
|
| VWAP Executor | 90% | Algorithmic execution, complex logic |
|
|
| Iceberg Executor | 90% | Algorithmic execution, complex logic |
|
|
| Rate Limiter | 85% | Important for system stability |
|
|
| Value Limiter | 85% | Important for risk management |
|
|
| Circuit Breaker | 90% | Critical for system resilience |
|
|
|
|
### Edge Case Testing
|
|
1. **Boundary Conditions**: Test at the limits of all numeric parameters
|
|
2. **Race Conditions**: Test concurrent access to shared resources
|
|
3. **Network Failures**: Test handling of network interruptions
|
|
4. **System Overload**: Test behavior under high load conditions
|
|
5. **Invalid Input**: Test handling of malformed or malicious input
|
|
6. **Time Zone Issues**: Test behavior across different time zones
|
|
7. **Memory Pressure**: Test behavior under memory constraints
|
|
8. **Disk Space**: Test behavior when disk space is low
|
|
|
|
## Test Data Management
|
|
|
|
### Test Data Generation
|
|
```csharp
|
|
/// <summary>
|
|
/// Helper class for generating test data
|
|
/// </summary>
|
|
public static class TestDataGenerator
|
|
{
|
|
/// <summary>
|
|
/// Generate a valid order request for testing
|
|
/// </summary>
|
|
public static OrderRequest GenerateValidOrderRequest(
|
|
string symbol = "ES",
|
|
OrderSide side = OrderSide.Buy,
|
|
OrderType type = OrderType.Market,
|
|
int quantity = 1)
|
|
{
|
|
return new OrderRequest(
|
|
Symbol: symbol,
|
|
Side: side,
|
|
Type: type,
|
|
Quantity: quantity,
|
|
LimitPrice: type == OrderType.Limit || type == OrderType.StopLimit ? 4200m : (decimal?)null,
|
|
StopPrice: type == OrderType.StopMarket || type == OrderType.StopLimit ? 4190m : (decimal?)null,
|
|
TimeInForce: TimeInForce.Day,
|
|
Algorithm: null,
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a valid strategy context for testing
|
|
/// </summary>
|
|
public static StrategyContext GenerateValidStrategyContext(string symbol = "ES")
|
|
{
|
|
return new StrategyContext(
|
|
Symbol: symbol,
|
|
CurrentTime: DateTime.UtcNow,
|
|
CurrentPosition: new Position(symbol, 0, 0, 0, 0, DateTime.UtcNow),
|
|
Account: new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
|
|
Session: new MarketSession(DateTime.UtcNow.Date, DateTime.UtcNow.Date.AddDays(1), true, "RTH"),
|
|
CustomData: new Dictionary<string, object>()
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a valid risk config for testing
|
|
/// </summary>
|
|
public static RiskConfig GenerateValidRiskConfig()
|
|
{
|
|
return new RiskConfig(
|
|
DailyLossLimit: 1000,
|
|
MaxTradeRisk: 200,
|
|
MaxOpenPositions: 5,
|
|
EmergencyFlattenEnabled: true
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a valid sizing config for testing
|
|
/// </summary>
|
|
public static SizingConfig GenerateValidSizingConfig()
|
|
{
|
|
return new SizingConfig(
|
|
Method: SizingMethod.FixedContracts,
|
|
MinContracts: 1,
|
|
MaxContracts: 10,
|
|
RiskPerTrade: 200,
|
|
MethodParameters: new Dictionary<string, object> { ["contracts"] = 2 }
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate a valid order result for testing
|
|
/// </summary>
|
|
public static OrderResult GenerateValidOrderResult(string orderId = null)
|
|
{
|
|
return new OrderResult(
|
|
Success: true,
|
|
OrderId: orderId ?? Guid.NewGuid().ToString(),
|
|
Message: "Order submitted successfully",
|
|
Status: new OrderStatus(
|
|
OrderId: orderId ?? Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Side: OrderSide.Buy,
|
|
Type: OrderType.Market,
|
|
Quantity: 1,
|
|
FilledQuantity: 1,
|
|
LimitPrice: null,
|
|
StopPrice: null,
|
|
State: OrderState.Filled,
|
|
CreatedTime: DateTime.UtcNow,
|
|
FilledTime: DateTime.UtcNow,
|
|
Fills: new List<OrderFill>
|
|
{
|
|
new OrderFill(
|
|
OrderId: orderId ?? Guid.NewGuid().ToString(),
|
|
Symbol: "ES",
|
|
Quantity: 1,
|
|
FillPrice: 4200,
|
|
FillTime: DateTime.UtcNow,
|
|
Commission: 4.50m,
|
|
ExecutionId: Guid.NewGuid().ToString()
|
|
)
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Test Execution Strategy
|
|
|
|
### Continuous Integration Testing
|
|
1. **Build Verification**: Run critical tests on every commit
|
|
2. **Nightly Builds**: Run full test suite nightly
|
|
3. **Release Testing**: Run extended test suite before releases
|
|
4. **Performance Testing**: Run performance tests weekly
|
|
|
|
### Test Parallelization
|
|
1. **Component Isolation**: Run component tests in parallel
|
|
2. **Database Isolation**: Use separate test databases for each test
|
|
3. **Resource Management**: Ensure tests don't interfere with each other
|
|
|
|
### Test Reporting
|
|
1. **Real-time Feedback**: Provide immediate feedback on test results
|
|
2. **Historical Trends**: Track test results over time
|
|
3. **Coverage Reports**: Generate code coverage reports
|
|
4. **Performance Metrics**: Track test execution performance
|
|
|
|
## Test Maintenance
|
|
|
|
### Test Refactoring
|
|
1. **Regular Reviews**: Review and refactor tests quarterly
|
|
2. **Dead Code Removal**: Remove obsolete tests
|
|
3. **Duplication Elimination**: Consolidate duplicate test logic
|
|
4. **Performance Optimization**: Optimize slow tests
|
|
|
|
### Test Documentation
|
|
1. **Inline Comments**: Document complex test logic
|
|
2. **Test Descriptions**: Provide clear test descriptions
|
|
3. **Failure Analysis**: Document common test failures and resolutions
|
|
4. **Best Practices**: Maintain test best practices documentation
|
|
|
|
## Conclusion
|
|
|
|
This comprehensive unit test plan ensures that all OMS components are thoroughly tested with >90% code coverage. The plan covers all critical functionality, edge cases, and integration points, providing confidence in the reliability and correctness of the system.
|
|
|
|
The test suite will be implemented incrementally, with priority given to the most critical components (OrderManager, RiskManager, PositionSizer) followed by algorithmic execution components (TWAP, VWAP, Iceberg) and finally auxiliary components (Rate Limiter, Value Limiter, Circuit Breaker).
|
|
|
|
Regular test reviews and maintenance will ensure the test suite remains effective as the system evolves.
|