Files
nt8-sdk/docs/architecture/risk_validation_implementation.md
Billy Valentine 92f3732b3d
Some checks failed
Build and Test / build (push) Has been cancelled
Phase 0 completion: NT8 SDK core framework with risk management and position sizing
2025-09-09 17:06:37 -04:00

17 KiB

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:

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:

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:

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):

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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