Phase 0 completion: NT8 SDK core framework with risk management and position sizing
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
This commit is contained in:
562
docs/architecture/risk_validation_implementation.md
Normal file
562
docs/architecture/risk_validation_implementation.md
Normal file
@@ -0,0 +1,562 @@
|
||||
# Risk Validation Logic Implementation Design
|
||||
|
||||
## Overview
|
||||
|
||||
This document details the implementation of risk validation logic in the Order Management System (OMS), which integrates with the existing risk management system to ensure all orders comply with defined risk parameters before submission.
|
||||
|
||||
## Integration Approach
|
||||
|
||||
The OMS integrates with the existing IRiskManager interface to validate all orders before submission. This ensures consistency with the risk management system already implemented in the NT8 SDK.
|
||||
|
||||
## Risk Validation Flow
|
||||
|
||||
### 1. Order Request Conversion
|
||||
Before validation, OMS order requests are converted to StrategyIntent objects that the risk manager can understand:
|
||||
|
||||
```csharp
|
||||
private StrategyIntent ConvertToStrategyIntent(OrderRequest request)
|
||||
{
|
||||
return new StrategyIntent(
|
||||
request.Symbol,
|
||||
ConvertOrderSide(request.Side),
|
||||
ConvertOrderType(request.Type),
|
||||
(double?)request.LimitPrice,
|
||||
GetStopTicks(request),
|
||||
GetTargetTicks(request),
|
||||
1.0, // Confidence - maximum for OMS orders
|
||||
$"OMS {request.Type} Order", // Reason
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["OrderId"] = Guid.NewGuid().ToString(),
|
||||
["Algorithm"] = request.Algorithm,
|
||||
["TimeInForce"] = request.TimeInForce.ToString()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private OrderSide ConvertOrderSide(NT8.Core.Orders.OrderSide side)
|
||||
{
|
||||
return side switch
|
||||
{
|
||||
NT8.Core.Orders.OrderSide.Buy => OrderSide.Buy,
|
||||
NT8.Core.Orders.OrderSide.Sell => OrderSide.Sell,
|
||||
_ => OrderSide.Flat
|
||||
};
|
||||
}
|
||||
|
||||
private NT8.Core.Common.Models.OrderType ConvertOrderType(NT8.Core.Orders.OrderType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
NT8.Core.Orders.OrderType.Market => NT8.Core.Common.Models.OrderType.Market,
|
||||
NT8.Core.Orders.OrderType.Limit => NT8.Core.Common.Models.OrderType.Limit,
|
||||
NT8.Core.Orders.OrderType.StopMarket => NT8.Core.Common.Models.OrderType.StopMarket,
|
||||
NT8.Core.Orders.OrderType.StopLimit => NT8.Core.Common.Models.OrderType.StopLimit,
|
||||
_ => NT8.Core.Common.Models.OrderType.Market
|
||||
};
|
||||
}
|
||||
|
||||
private int GetStopTicks(OrderRequest request)
|
||||
{
|
||||
// Calculate stop ticks based on stop price and current market price
|
||||
// This is a simplified implementation
|
||||
if (request.StopPrice.HasValue)
|
||||
{
|
||||
// In a real implementation, this would calculate the actual tick difference
|
||||
return 10; // Placeholder value
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int? GetTargetTicks(OrderRequest request)
|
||||
{
|
||||
// Calculate target ticks for profit targets if applicable
|
||||
// This would be used for strategies with predefined profit targets
|
||||
return null; // Not applicable for OMS orders
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Risk Configuration
|
||||
The risk validation uses a configuration that aligns with the existing risk management system:
|
||||
|
||||
```csharp
|
||||
private RiskConfig GetRiskConfig()
|
||||
{
|
||||
// In a real implementation, this would be configurable
|
||||
// For now, using values consistent with the existing risk management system
|
||||
return new RiskConfig(
|
||||
DailyLossLimit: 1000, // $1000 daily loss limit
|
||||
MaxTradeRisk: 200, // $200 maximum per-trade risk
|
||||
MaxOpenPositions: 10, // Maximum 10 open positions
|
||||
EmergencyFlattenEnabled: true // Emergency flatten functionality enabled
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Risk Validation Implementation
|
||||
The core risk validation logic that integrates with the existing risk manager:
|
||||
|
||||
```csharp
|
||||
public async Task<RiskDecision> ValidateOrderAsync(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Validating order for {Symbol} {Side} {Quantity}",
|
||||
request.Symbol, request.Side, request.Quantity);
|
||||
|
||||
// Convert order request to strategy intent
|
||||
var intent = ConvertToStrategyIntent(request);
|
||||
|
||||
// Validate intent
|
||||
if (!intent.IsValid())
|
||||
{
|
||||
_logger.LogWarning("Invalid strategy intent generated from order request");
|
||||
return new RiskDecision(
|
||||
Allow: false,
|
||||
RejectReason: "Invalid order parameters",
|
||||
ModifiedIntent: null,
|
||||
RiskLevel: RiskLevel.Critical,
|
||||
RiskMetrics: new Dictionary<string, object> { ["error"] = "Invalid intent" }
|
||||
);
|
||||
}
|
||||
|
||||
// Get risk configuration
|
||||
var config = GetRiskConfig();
|
||||
|
||||
// Validate with risk manager
|
||||
var decision = _riskManager.ValidateOrder(intent, context, config);
|
||||
|
||||
if (decision.Allow)
|
||||
{
|
||||
_logger.LogInformation("Order validation passed for {Symbol}", request.Symbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Order validation failed for {Symbol}: {Reason}",
|
||||
request.Symbol, decision.RejectReason);
|
||||
}
|
||||
|
||||
return decision;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error validating order for {Symbol}", request.Symbol);
|
||||
return new RiskDecision(
|
||||
Allow: false,
|
||||
RejectReason: $"Risk validation error: {ex.Message}",
|
||||
ModifiedIntent: null,
|
||||
RiskLevel: RiskLevel.Critical,
|
||||
RiskMetrics: new Dictionary<string, object> { ["error"] = ex.Message }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Enhanced Validation for Algorithmic Orders
|
||||
Special validation logic for algorithmic orders (TWAP, VWAP, Iceberg):
|
||||
|
||||
```csharp
|
||||
private RiskDecision ValidateAlgorithmicOrder(OrderRequest request, StrategyContext context, RiskConfig config)
|
||||
{
|
||||
// For algorithmic orders, we need to validate the total risk of the entire order
|
||||
// rather than just individual slices
|
||||
|
||||
var intent = ConvertToStrategyIntent(request);
|
||||
|
||||
// Adjust risk calculation for algorithmic orders
|
||||
if (!string.IsNullOrEmpty(request.Algorithm))
|
||||
{
|
||||
// Modify intent to reflect total order size for risk calculation
|
||||
var modifiedIntent = ModifyIntentForAlgorithmicRisk(intent, request);
|
||||
return _riskManager.ValidateOrder(modifiedIntent, context, config);
|
||||
}
|
||||
|
||||
// Standard validation for regular orders
|
||||
return _riskManager.ValidateOrder(intent, context, config);
|
||||
}
|
||||
|
||||
private StrategyIntent ModifyIntentForAlgorithmicRisk(StrategyIntent intent, OrderRequest request)
|
||||
{
|
||||
// For algorithmic orders, the risk manager needs to understand that this is
|
||||
// part of a larger strategy and may need to adjust risk calculations
|
||||
|
||||
var metadata = new Dictionary<string, object>(intent.Metadata ?? new Dictionary<string, object>())
|
||||
{
|
||||
["IsAlgorithmic"] = true,
|
||||
["AlgorithmType"] = request.Algorithm,
|
||||
["TotalQuantity"] = request.Quantity
|
||||
};
|
||||
|
||||
// If this is a slice of a larger order, we might need to adjust the risk calculation
|
||||
// to account for the total order size rather than just this slice
|
||||
|
||||
return new StrategyIntent(
|
||||
intent.Symbol,
|
||||
intent.Side,
|
||||
intent.EntryType,
|
||||
intent.LimitPrice,
|
||||
intent.StopTicks,
|
||||
intent.TargetTicks,
|
||||
intent.Confidence,
|
||||
intent.Reason,
|
||||
metadata
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Risk Integration with Order Processing
|
||||
|
||||
### Pre-Submission Validation
|
||||
All order submission methods validate orders through the risk management system:
|
||||
|
||||
```csharp
|
||||
public async Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
// Validate request parameters
|
||||
if (!request.IsValid(out var errors))
|
||||
{
|
||||
return new OrderResult(false, null, string.Join("; ", errors), null);
|
||||
}
|
||||
|
||||
// Validate through risk management
|
||||
var riskDecision = await ValidateOrderAsync(request, context);
|
||||
if (!riskDecision.Allow)
|
||||
{
|
||||
_logger.LogWarning("Order rejected by risk management: {Reason}", riskDecision.RejectReason);
|
||||
return new OrderResult(false, null, $"Risk validation failed: {riskDecision.RejectReason}", null);
|
||||
}
|
||||
|
||||
// Continue with order processing if validation passes
|
||||
// ... (rest of order processing logic)
|
||||
}
|
||||
```
|
||||
|
||||
### Real-time Risk Updates
|
||||
The OMS also updates the risk manager with real-time information about order fills:
|
||||
|
||||
```csharp
|
||||
private async Task NotifyRiskManagerOfFillAsync(OrderFill fill)
|
||||
{
|
||||
try
|
||||
{
|
||||
_riskManager.OnFill(fill);
|
||||
_logger.LogInformation("Risk manager notified of fill for order {OrderId}", fill.OrderId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error notifying risk manager of fill for order {OrderId}", fill.OrderId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NotifyRiskManagerOfPnLAsync(double netPnL, double dayPnL)
|
||||
{
|
||||
try
|
||||
{
|
||||
_riskManager.OnPnLUpdate(netPnL, dayPnL);
|
||||
_logger.LogInformation("Risk manager updated with P&L: Net={NetPnL}, Day={DayPnL}", netPnL, dayPnL);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating risk manager with P&L");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Emergency Handling
|
||||
|
||||
### Emergency Flatten Integration
|
||||
The OMS can trigger emergency flattening through the risk management system:
|
||||
|
||||
```csharp
|
||||
public async Task<bool> EmergencyFlattenAsync(string reason)
|
||||
{
|
||||
if (string.IsNullOrEmpty(reason)) throw new ArgumentException("Reason required", nameof(reason));
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogCritical("Emergency flatten triggered: {Reason}", reason);
|
||||
|
||||
// Cancel all active orders
|
||||
var activeOrders = await GetActiveOrdersAsync();
|
||||
foreach (var order in activeOrders)
|
||||
{
|
||||
await CancelOrderAsync(order.OrderId);
|
||||
}
|
||||
|
||||
// Trigger emergency flatten in risk manager
|
||||
var result = await _riskManager.EmergencyFlatten(reason);
|
||||
|
||||
_logger.LogInformation("Emergency flatten completed: {Result}", result);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during emergency flatten: {Reason}", reason);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Circuit Breaker Integration
|
||||
The OMS respects circuit breaker status from the risk management system:
|
||||
|
||||
```csharp
|
||||
private bool IsTradingHalted()
|
||||
{
|
||||
try
|
||||
{
|
||||
var riskStatus = _riskManager.GetRiskStatus();
|
||||
return !riskStatus.TradingEnabled;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking trading halt status");
|
||||
// Err on the side of caution - assume trading is halted if we can't determine status
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
// Check if trading is halted
|
||||
if (IsTradingHalted())
|
||||
{
|
||||
_logger.LogWarning("Order submission blocked - trading is currently halted");
|
||||
return new OrderResult(false, null, "Trading is currently halted by risk management", null);
|
||||
}
|
||||
|
||||
// Continue with normal order processing
|
||||
// ... (rest of order processing logic)
|
||||
}
|
||||
```
|
||||
|
||||
## Risk Metrics Collection
|
||||
|
||||
### Order-Level Risk Metrics
|
||||
Collect risk metrics for each order:
|
||||
|
||||
```csharp
|
||||
private Dictionary<string, object> CollectRiskMetrics(OrderRequest request, RiskDecision decision)
|
||||
{
|
||||
var metrics = new Dictionary<string, object>
|
||||
{
|
||||
["Symbol"] = request.Symbol,
|
||||
["OrderType"] = request.Type.ToString(),
|
||||
["Quantity"] = request.Quantity,
|
||||
["RiskLevel"] = decision.RiskLevel.ToString(),
|
||||
["ValidationTime"] = DateTime.UtcNow
|
||||
};
|
||||
|
||||
if (decision.RiskMetrics != null)
|
||||
{
|
||||
foreach (var kvp in decision.RiskMetrics)
|
||||
{
|
||||
metrics[$"Risk_{kvp.Key}"] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
```
|
||||
|
||||
### Aggregated Risk Metrics
|
||||
Maintain aggregated risk metrics for monitoring:
|
||||
|
||||
```csharp
|
||||
private void UpdateAggregatedRiskMetrics(RiskDecision decision)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Update counters based on risk decision
|
||||
switch (decision.RiskLevel)
|
||||
{
|
||||
case RiskLevel.Low:
|
||||
_riskMetrics.LowRiskOrders++;
|
||||
break;
|
||||
case RiskLevel.Medium:
|
||||
_riskMetrics.MediumRiskOrders++;
|
||||
break;
|
||||
case RiskLevel.High:
|
||||
_riskMetrics.HighRiskOrders++;
|
||||
break;
|
||||
case RiskLevel.Critical:
|
||||
_riskMetrics.CriticalRiskOrders++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Track rejected orders
|
||||
if (!decision.Allow)
|
||||
{
|
||||
_riskMetrics.RejectedOrders++;
|
||||
}
|
||||
|
||||
_riskMetrics.LastUpdated = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Fallbacks
|
||||
|
||||
### Graceful Degradation
|
||||
If the risk manager is unavailable, the system can implement fallback behavior:
|
||||
|
||||
```csharp
|
||||
public async Task<RiskDecision> ValidateOrderAsync(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Normal risk validation
|
||||
var intent = ConvertToStrategyIntent(request);
|
||||
var config = GetRiskConfig();
|
||||
return _riskManager.ValidateOrder(intent, context, config);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Risk manager unavailable, applying fallback validation for {Symbol}", request.Symbol);
|
||||
|
||||
// Fallback validation - more conservative approach
|
||||
return ApplyFallbackValidation(request, context);
|
||||
}
|
||||
}
|
||||
|
||||
private RiskDecision ApplyFallbackValidation(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
// Conservative fallback validation
|
||||
// Reject orders that are clearly problematic
|
||||
|
||||
// Reject if order size is too large
|
||||
if (request.Quantity > 100) // Arbitrary large size
|
||||
{
|
||||
return new RiskDecision(
|
||||
Allow: false,
|
||||
RejectReason: "Order size exceeds fallback limit",
|
||||
ModifiedIntent: null,
|
||||
RiskLevel: RiskLevel.Critical,
|
||||
RiskMetrics: new Dictionary<string, object> { ["fallback_reject"] = "size" }
|
||||
);
|
||||
}
|
||||
|
||||
// Allow other orders with high risk level
|
||||
return new RiskDecision(
|
||||
Allow: true,
|
||||
RejectReason: null,
|
||||
ModifiedIntent: null,
|
||||
RiskLevel: RiskLevel.High, // Conservative risk level
|
||||
RiskMetrics: new Dictionary<string, object> { ["fallback_allow"] = true }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
### Unit Tests for Risk Validation
|
||||
1. **Valid Orders**: Orders that should pass risk validation
|
||||
2. **Invalid Orders**: Orders that should be rejected by risk management
|
||||
3. **Edge Cases**: Boundary conditions for risk limits
|
||||
4. **Algorithmic Orders**: Special handling for TWAP, VWAP, Iceberg orders
|
||||
5. **Emergency Scenarios**: Trading halt and emergency flatten scenarios
|
||||
|
||||
### Integration Tests
|
||||
1. **Risk Manager Integration**: Verify proper integration with IRiskManager
|
||||
2. **Configuration Changes**: Test behavior with different risk configurations
|
||||
3. **State Management**: Verify risk state is properly maintained
|
||||
4. **Error Handling**: Test fallback behavior when risk manager is unavailable
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Validation Caching
|
||||
Cache risk validation results for identical orders within a short time window:
|
||||
|
||||
```csharp
|
||||
private readonly Dictionary<string, (RiskDecision decision, DateTime timestamp)> _validationCache
|
||||
= new Dictionary<string, (RiskDecision, DateTime)>();
|
||||
|
||||
private string GenerateValidationCacheKey(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
// Generate a cache key based on order parameters and context
|
||||
return $"{request.Symbol}_{request.Side}_{request.Quantity}_{request.Type}_{context?.CurrentTime:yyyyMMdd}";
|
||||
}
|
||||
|
||||
private RiskDecision GetCachedValidationResult(string cacheKey)
|
||||
{
|
||||
if (_validationCache.ContainsKey(cacheKey))
|
||||
{
|
||||
var (decision, timestamp) = _validationCache[cacheKey];
|
||||
// Expire cache after 1 second
|
||||
if (DateTime.UtcNow.Subtract(timestamp).TotalSeconds < 1)
|
||||
{
|
||||
return decision;
|
||||
}
|
||||
else
|
||||
{
|
||||
_validationCache.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### Asynchronous Validation
|
||||
Perform risk validation asynchronously to avoid blocking order submission:
|
||||
|
||||
```csharp
|
||||
public async Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context)
|
||||
{
|
||||
// Start risk validation in background
|
||||
var validationTask = ValidateOrderAsync(request, context);
|
||||
|
||||
// Continue with other order processing steps
|
||||
|
||||
// Wait for validation result
|
||||
var riskDecision = await validationTask;
|
||||
|
||||
// Process validation result
|
||||
// ... (rest of logic)
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring and Alerting
|
||||
|
||||
### Risk Alerts
|
||||
Generate alerts for high-risk orders or risk limit breaches:
|
||||
|
||||
```csharp
|
||||
private void GenerateRiskAlerts(RiskDecision decision, OrderRequest request)
|
||||
{
|
||||
if (decision.RiskLevel == RiskLevel.High || decision.RiskLevel == RiskLevel.Critical)
|
||||
{
|
||||
_logger.LogWarning("High-risk order detected: {Symbol} {Side} {Quantity} - Risk Level: {RiskLevel}",
|
||||
request.Symbol, request.Side, request.Quantity, decision.RiskLevel);
|
||||
|
||||
// In a real implementation, this might trigger:
|
||||
// - Email alerts to risk managers
|
||||
// - Slack notifications
|
||||
// - Dashboard warnings
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Risk Dashboard Integration
|
||||
Provide metrics for risk dashboard integration:
|
||||
|
||||
```csharp
|
||||
public RiskMetrics GetRiskMetrics()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _riskMetrics;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Dynamic Risk Limits**: Adjust risk limits based on market conditions
|
||||
2. **Machine Learning**: Use ML models to predict risk levels
|
||||
3. **Real-time Market Data**: Integrate real-time volatility data into risk calculations
|
||||
4. **Cross-Asset Risk**: Calculate risk across multiple asset classes
|
||||
5. **Scenario Analysis**: Simulate risk under different market conditions
|
||||
Reference in New Issue
Block a user