22 KiB
22 KiB
OrderManager Class Implementation Design
Overview
The OrderManager class is the primary implementation of the IOrderManager interface, providing core order management functionality, integration with risk management, and support for algorithmic execution strategies.
Class Structure
using NT8.Core.Common.Models;
using NT8.Core.Risk;
using NT8.Core.Sizing;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NT8.Core.Orders
{
/// <summary>
/// Order manager implementation with smart routing and algorithmic execution
/// </summary>
public class OrderManager : IOrderManager
{
private readonly IRiskManager _riskManager;
private readonly IPositionSizer _positionSizer;
private readonly ILogger<OrderManager> _logger;
private readonly object _lock = new object();
// Configuration
private RoutingConfig _routingConfig;
private AlgorithmParameters _algorithmParameters;
// State
private readonly Dictionary<string, OrderStatus> _orders;
private readonly Dictionary<string, ExecutionVenue> _venues;
private readonly RoutingMetrics _routingMetrics;
private readonly OmsMetrics _omsMetrics;
public OrderManager(
IRiskManager riskManager,
IPositionSizer positionSizer,
ILogger<OrderManager> logger)
{
_riskManager = riskManager ?? throw new ArgumentNullException(nameof(riskManager));
_positionSizer = positionSizer ?? throw new ArgumentNullException(nameof(positionSizer));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orders = new Dictionary<string, OrderStatus>();
_venues = new Dictionary<string, ExecutionVenue>();
_routingMetrics = new RoutingMetrics(new Dictionary<string, VenueMetrics>(), 0, 0.0, DateTime.UtcNow);
_omsMetrics = new OmsMetrics(0, 0, 0.0, 0.0, 0.0, 0, DateTime.UtcNow);
InitializeDefaultConfig();
InitializeVenues();
}
private void InitializeDefaultConfig()
{
_routingConfig = new RoutingConfig(
SmartRoutingEnabled: true,
DefaultVenue: "Primary",
VenuePreferences: new Dictionary<string, double> { ["Primary"] = 1.0, ["Secondary"] = 0.8 },
MaxSlippagePercent: 0.5,
MaxRoutingTime: TimeSpan.FromSeconds(30),
RouteByCost: true,
RouteBySpeed: true,
RouteByReliability: true
);
_algorithmParameters = new AlgorithmParameters(
new TwapConfig(TimeSpan.FromMinutes(15), 30, true),
new VwapConfig(0.1, true),
new IcebergConfig(0.1, true)
);
}
private void InitializeVenues()
{
_venues.Add("Primary", new ExecutionVenue(
"Primary", "Primary execution venue", true, 1.0, 1.0, 0.99));
_venues.Add("Secondary", new ExecutionVenue(
"Secondary", "Backup execution venue", true, 1.2, 0.9, 0.95));
}
}
}
Core Order Management Implementation
Order Submission
public async Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (context == null) throw new ArgumentNullException(nameof(context));
try
{
_logger.LogInformation("Submitting order for {Symbol} {Side} {Quantity}",
request.Symbol, request.Side, request.Quantity);
// 1. Validate order 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, riskDecision.RejectReason, null);
}
// 2. Route order based on smart routing logic
var routingResult = await RouteOrderAsync(request, context);
if (!routingResult.Success)
{
_logger.LogError("Order routing failed: {Message}", routingResult.Message);
return new OrderResult(false, null, routingResult.Message, null);
}
// 3. Submit to selected venue (simulated)
var orderId = Guid.NewGuid().ToString();
var orderStatus = new OrderStatus(
orderId, request.Symbol, request.Side, request.Type, request.Quantity, 0,
request.LimitPrice, request.StopPrice, OrderState.Submitted, DateTime.UtcNow, null,
new List<OrderFill>());
lock (_lock)
{
_orders[orderId] = orderStatus;
UpdateOmsMetrics();
}
_logger.LogInformation("Order {OrderId} submitted to {Venue}", orderId, routingResult.SelectedVenue.Name);
return new OrderResult(true, orderId, "Order submitted successfully", orderStatus);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error submitting order for {Symbol}", request.Symbol);
return new OrderResult(false, null, $"Error submitting order: {ex.Message}", null);
}
}
Order Cancellation
public async Task<bool> CancelOrderAsync(string orderId)
{
if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", nameof(orderId));
try
{
lock (_lock)
{
if (!_orders.ContainsKey(orderId))
{
_logger.LogWarning("Cannot cancel order {OrderId} - not found", orderId);
return false;
}
var order = _orders[orderId];
if (order.State == OrderState.Filled || order.State == OrderState.Cancelled)
{
_logger.LogWarning("Cannot cancel order {OrderId} - already {State}", orderId, order.State);
return false;
}
// Update order state to cancelled
var updatedOrder = order with { State = OrderState.Cancelled, FilledTime = DateTime.UtcNow };
_orders[orderId] = updatedOrder;
UpdateOmsMetrics();
}
_logger.LogInformation("Order {OrderId} cancelled successfully", orderId);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error cancelling order {OrderId}", orderId);
return false;
}
}
Risk Integration Implementation
public async Task<RiskDecision> ValidateOrderAsync(OrderRequest request, StrategyContext context)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (context == null) throw new ArgumentNullException(nameof(context));
// Convert OrderRequest to StrategyIntent for risk validation
var intent = new StrategyIntent(
request.Symbol,
request.Side,
ConvertOrderType(request.Type),
(double?)request.LimitPrice,
GetStopTicks(request),
null, // TargetTicks
1.0, // Confidence
"OMS Order Submission",
new Dictionary<string, object>()
);
// Create a mock risk config for validation
var riskConfig = new RiskConfig(1000, 200, 10, true);
return _riskManager.ValidateOrder(intent, context, riskConfig);
}
private OrderType ConvertOrderType(NT8.Core.Orders.OrderType orderType)
{
return orderType switch
{
NT8.Core.Orders.OrderType.Market => OrderType.Market,
NT8.Core.Orders.OrderType.Limit => OrderType.Limit,
NT8.Core.Orders.OrderType.StopMarket => OrderType.StopMarket,
NT8.Core.Orders.OrderType.StopLimit => OrderType.StopLimit,
_ => OrderType.Market
};
}
private int GetStopTicks(OrderRequest request)
{
// Simplified stop ticks calculation
return 10;
}
Smart Order Routing Implementation
public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (context == null) throw new ArgumentNullException(nameof(context));
if (!_routingConfig.SmartRoutingEnabled)
{
var defaultVenue = _venues[_routingConfig.DefaultVenue];
return new RoutingResult(true, null, defaultVenue, "Routing disabled, using default venue",
new Dictionary<string, object> { ["venue"] = defaultVenue.Name });
}
// Select best venue based on configuration
var selectedVenue = SelectBestVenue(request, context);
// Update routing metrics
UpdateRoutingMetrics(selectedVenue);
return new RoutingResult(true, null, selectedVenue, "Order routed successfully",
new Dictionary<string, object>
{
["venue"] = selectedVenue.Name,
["cost_factor"] = selectedVenue.CostFactor,
["speed_factor"] = selectedVenue.SpeedFactor
});
}
private ExecutionVenue SelectBestVenue(OrderRequest request, StrategyContext context)
{
ExecutionVenue bestVenue = null;
double bestScore = double.MinValue;
foreach (var venue in _venues.Values)
{
if (!venue.IsActive) continue;
double score = 0;
// Factor in venue preferences
if (_routingConfig.VenuePreferences.ContainsKey(venue.Name))
{
score += _routingConfig.VenuePreferences[venue.Name] * 100;
}
// Factor in cost if enabled
if (_routingConfig.RouteByCost)
{
score -= venue.CostFactor * 50; // Lower cost is better
}
// Factor in speed if enabled
if (_routingConfig.RouteBySpeed)
{
score += venue.SpeedFactor * 30; // Higher speed is better
}
// Factor in reliability
if (_routingConfig.RouteByReliability)
{
score += venue.ReliabilityFactor * 20; // Higher reliability is better
}
if (score > bestScore)
{
bestScore = score;
bestVenue = venue;
}
return bestVenue ?? _venues[_routingConfig.DefaultVenue];
}
private void UpdateRoutingMetrics(ExecutionVenue venue)
{
lock (_lock)
{
var venueMetrics = _routingMetrics.VenuePerformance.ContainsKey(venue.Name) ?
_routingMetrics.VenuePerformance[venue.Name] :
new VenueMetrics(venue.Name, 0, 0.0, 0.0, 0.0, 0);
var updatedMetrics = venueMetrics with
{
OrdersRouted = venueMetrics.OrdersRouted + 1
};
_routingMetrics.VenuePerformance[venue.Name] = updatedMetrics;
_routingMetrics.TotalRoutedOrders++;
_routingMetrics.LastUpdated = DateTime.UtcNow;
}
}
Algorithmic Execution Support
TWAP Algorithm Integration
public async Task<OrderResult> ExecuteTwapAsync(TwapParameters parameters, StrategyContext context)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (context == null) throw new ArgumentNullException(nameof(context));
_logger.LogInformation("Executing TWAP order for {Symbol} {Side} {Quantity} over {Duration}",
parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.Duration);
// Create a parent order for tracking
var parentOrderId = Guid.NewGuid().ToString();
// Calculate slice parameters
var sliceCount = (int)(parameters.Duration.TotalSeconds / parameters.IntervalSeconds);
var sliceQuantity = parameters.TotalQuantity / sliceCount;
// Execute slices
for (int i = 0; i < sliceCount; i++)
{
// Create slice order
var sliceRequest = new OrderRequest(
parameters.Symbol,
parameters.Side,
OrderType.Market, // Simplified to market orders
sliceQuantity,
parameters.LimitPrice,
null, // StopPrice
TimeInForce.Day,
null, // No algorithm for slices
new Dictionary<string, object>()
);
// Submit slice order
var result = await SubmitOrderAsync(sliceRequest, context);
if (!result.Success)
{
_logger.LogWarning("TWAP slice {Slice}/{Total} failed: {Message}",
i + 1, sliceCount, result.Message);
}
else
{
_logger.LogInformation("TWAP slice {Slice}/{Total} submitted: {OrderId}",
i + 1, sliceCount, result.OrderId);
}
// Wait for next interval (except for last slice)
if (i < sliceCount - 1)
{
await Task.Delay(TimeSpan.FromSeconds(parameters.IntervalSeconds));
}
}
return new OrderResult(true, parentOrderId, "TWAP execution completed", null);
}
VWAP Algorithm Integration
public async Task<OrderResult> ExecuteVwapAsync(VwapParameters parameters, StrategyContext context)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (context == null) throw new ArgumentNullException(nameof(context));
_logger.LogInformation("Executing VWAP order for {Symbol} {Side} {Quantity} from {Start} to {End}",
parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.StartTime, parameters.EndTime);
// Create a parent order for tracking
var parentOrderId = Guid.NewGuid().ToString();
// Simplified VWAP implementation - in a real system, this would:
// 1. Monitor market volume throughout the execution period
// 2. Calculate participation rate based on target participation
// 3. Execute orders in proportion to volume
// For now, we'll execute the order as a single market order
var request = new OrderRequest(
parameters.Symbol,
parameters.Side,
OrderType.Market,
parameters.TotalQuantity,
parameters.LimitPrice,
null, // StopPrice
TimeInForce.Day,
null, // No algorithm for this simplified version
new Dictionary<string, object>()
);
var result = await SubmitOrderAsync(request, context);
return new OrderResult(result.Success, parentOrderId,
result.Success ? "VWAP execution completed" : $"VWAP execution failed: {result.Message}",
result.Status);
}
Iceberg Algorithm Integration
public async Task<OrderResult> ExecuteIcebergAsync(IcebergParameters parameters, StrategyContext context)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (context == null) throw new ArgumentNullException(nameof(context));
_logger.LogInformation("Executing Iceberg order for {Symbol} {Side} {TotalQuantity} (visible: {VisibleQuantity})",
parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.VisibleQuantity);
// Create a parent order for tracking
var parentOrderId = Guid.NewGuid().ToString();
var remainingQuantity = parameters.TotalQuantity;
while (remainingQuantity > 0)
{
// Determine visible quantity for this slice
var visibleQuantity = Math.Min(parameters.VisibleQuantity, remainingQuantity);
// Create slice order
var sliceRequest = new OrderRequest(
parameters.Symbol,
parameters.Side,
parameters.LimitPrice.HasValue ? OrderType.Limit : OrderType.Market,
visibleQuantity,
parameters.LimitPrice,
null, // StopPrice
TimeInForce.Day,
null, // No algorithm for slices
new Dictionary<string, object>()
);
// Submit slice order
var result = await SubmitOrderAsync(sliceRequest, context);
if (!result.Success)
{
_logger.LogWarning("Iceberg slice failed with {Remaining} qty remaining: {Message}",
remainingQuantity, result.Message);
break;
}
// Update remaining quantity
remainingQuantity -= visibleQuantity;
_logger.LogInformation("Iceberg slice submitted, {Remaining} qty remaining", remainingQuantity);
// In a real implementation, we would wait for the order to fill
// before submitting the next slice. For this design, we'll add a delay.
if (remainingQuantity > 0)
{
await Task.Delay(TimeSpan.FromSeconds(5)); // Simulate time between slices
}
return new OrderResult(true, parentOrderId, "Iceberg execution completed", null);
}
Configuration Management
public void UpdateRoutingConfig(RoutingConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
lock (_lock)
{
_routingConfig = config;
}
_logger.LogInformation("Routing configuration updated");
}
public RoutingConfig GetRoutingConfig()
{
lock (_lock)
{
return _routingConfig;
}
}
public void UpdateAlgorithmParameters(AlgorithmParameters parameters)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
lock (_lock)
{
_algorithmParameters = parameters;
}
_logger.LogInformation("Algorithm parameters updated");
}
public AlgorithmParameters GetAlgorithmParameters()
{
lock (_lock)
{
return _algorithmParameters;
}
}
Metrics and Monitoring
private void UpdateOmsMetrics()
{
lock (_lock)
{
var activeOrders = 0;
var filledOrders = 0;
var failedOrders = 0;
var totalQuantity = 0;
foreach (var order in _orders.Values)
{
switch (order.State)
{
case OrderState.Submitted:
case OrderState.Accepted:
case OrderState.PartiallyFilled:
activeOrders++;
break;
case OrderState.Filled:
filledOrders++;
break;
case OrderState.Rejected:
case OrderState.Expired:
case OrderState.Cancelled:
failedOrders++;
break;
}
totalQuantity += order.Quantity;
}
var totalOrders = _orders.Count;
var fillRate = totalOrders > 0 ? (double)filledOrders / totalOrders : 0.0;
_omsMetrics = _omsMetrics with
{
TotalOrders = totalOrders,
ActiveOrders = activeOrders,
FailedOrders = failedOrders,
FillRate = fillRate,
TotalValueTraded = totalQuantity, // Simplified
LastUpdated = DateTime.UtcNow
};
}
}
public OmsMetrics GetMetrics()
{
lock (_lock)
{
return _omsMetrics;
}
}
public RoutingMetrics GetRoutingMetrics()
{
lock (_lock)
{
return _routingMetrics;
}
}
public bool IsHealthy()
{
// Simple health check - in a real implementation, this would check:
// - Connection to execution venues
// - Risk management system availability
// - Position sizing system availability
// - Internal state consistency
return true;
}
Order Status Management
public async Task<OrderStatus> GetOrderStatusAsync(string orderId)
{
if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", nameof(orderId));
lock (_lock)
{
return _orders.ContainsKey(orderId) ? _orders[orderId] : null;
}
}
public async Task<List<OrderStatus>> GetOrdersBySymbolAsync(string symbol)
{
if (string.IsNullOrEmpty(symbol)) throw new ArgumentException("Symbol required", nameof(symbol));
var result = new List<OrderStatus>();
lock (_lock)
{
foreach (var order in _orders.Values)
{
if (order.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase))
{
result.Add(order);
}
}
}
return result;
}
public async Task<List<OrderStatus>> GetActiveOrdersAsync()
{
var result = new List<OrderStatus>();
lock (_lock)
{
foreach (var order in _orders.Values)
{
if (order.State == OrderState.Submitted ||
order.State == OrderState.Accepted ||
order.State == OrderState.PartiallyFilled)
{
result.Add(order);
}
}
}
return result;
}
Error Handling and Validation
The OrderManager implements comprehensive error handling:
- Input Validation: All public methods validate their parameters
- Exception Handling: Try-catch blocks around critical operations
- Logging: Detailed logging of all operations and errors
- Graceful Degradation: When possible, the system continues operating even when some components fail
Thread Safety
The OrderManager uses a lock-based approach to ensure thread safety:
- State Protection: All shared state is protected by a single lock
- Atomic Operations: Complex state updates are performed atomically
- Immutable Data: Where possible, immutable data structures are used
Integration Points
Risk Management
- All orders pass through the IRiskManager.ValidateOrder method
- Risk decisions are respected before order submission
Position Sizing
- Future enhancement could integrate with IPositionSizer for dynamic quantity adjustments
Execution Venues
- Orders are routed to configured execution venues
- Routing decisions are based on configurable criteria
Testing Considerations
The OrderManager is designed to be testable:
- Dependency Injection: All dependencies are injected through the constructor
- Interface-Based: Depends on interfaces rather than concrete implementations
- State Access: Provides methods to access internal state for verification
- Configuration: All behavior can be controlled through configuration
Performance Considerations
- Lock Contention: The single lock could become a bottleneck under high load
- Memory Usage: Order state is maintained in memory
- Latency: Order routing adds minimal latency
- Scalability: Design supports horizontal scaling through instance isolation