40 KiB
40 KiB
Multiple Execution Venues Implementation Design
Overview
This document details the implementation of support for multiple execution venues in the Order Management System (OMS), allowing orders to be routed to different execution destinations based on routing logic and configuration.
Venue Architecture
The multiple execution venues system is designed to be extensible and configurable, supporting various types of execution destinations including brokers, exchanges, dark pools, and alternative trading systems.
Execution Venue Types
Base Venue Interface
/// <summary>
/// Base interface for all execution venues
/// </summary>
public interface IExecutionVenue
{
/// <summary>
/// Unique identifier for the venue
/// </summary>
string Id { get; }
/// <summary>
/// Human-readable name of the venue
/// </summary>
string Name { get; }
/// <summary>
/// Description of the venue
/// </summary>
string Description { get; }
/// <summary>
/// Whether the venue is currently active and available for routing
/// </summary>
bool IsActive { get; }
/// <summary>
/// Type of venue (broker, exchange, dark pool, etc.)
/// </summary>
VenueType Type { get; }
/// <summary>
/// Submit an order to this venue
/// </summary>
Task<VenueOrderResult> SubmitOrderAsync(VenueOrderRequest request);
/// <summary>
/// Cancel an order at this venue
/// </summary>
Task<bool> CancelOrderAsync(string venueOrderId);
/// <summary>
/// Modify an order at this venue
/// </summary>
Task<VenueOrderResult> ModifyOrderAsync(string venueOrderId, VenueOrderModification modification);
/// <summary>
/// Get the status of an order at this venue
/// </summary>
Task<VenueOrderStatus> GetOrderStatusAsync(string venueOrderId);
/// <summary>
/// Get real-time market data from this venue
/// </summary>
Task<MarketDataSnapshot> GetMarketDataAsync(string symbol);
/// <summary>
/// Check if this venue is healthy and responsive
/// </summary>
Task<bool> IsHealthyAsync();
}
Venue Order Models
/// <summary>
/// Order request specific to a venue
/// </summary>
public record VenueOrderRequest(
string Symbol,
OrderSide Side,
VenueOrderType Type,
int Quantity,
decimal? LimitPrice,
decimal? StopPrice,
TimeInForce TimeInForce,
string OmsOrderId, // Reference to OMS order ID
Dictionary<string, object> Metadata
);
/// <summary>
/// Result from venue order submission
/// </summary>
public record VenueOrderResult(
bool Success,
string VenueOrderId, // Order ID assigned by the venue
string Message,
VenueOrderStatus Status,
Dictionary<string, object> Metadata
);
/// <summary>
/// Status of an order at a venue
/// </summary>
public record VenueOrderStatus(
string VenueOrderId,
string Symbol,
OrderSide Side,
VenueOrderType Type,
int Quantity,
int FilledQuantity,
decimal? LimitPrice,
decimal? StopPrice,
VenueOrderState State,
DateTime CreatedTime,
DateTime? FilledTime,
List<VenueOrderFill> Fills,
Dictionary<string, object> Metadata
);
/// <summary>
/// Fill information from a venue
/// </summary>
public record VenueOrderFill(
string VenueOrderId,
string Symbol,
int Quantity,
decimal FillPrice,
DateTime FillTime,
decimal Commission,
string ExecutionId,
Dictionary<string, object> Metadata
);
Venue Types
/// <summary>
/// Types of execution venues
/// </summary>
public enum VenueType
{
Broker,
Exchange,
DarkPool,
AlternativeTradingSystem,
Internalizer,
MarketMaker
}
/// <summary>
/// Types of orders supported by venues
/// </summary>
public enum VenueOrderType
{
Market,
Limit,
StopMarket,
StopLimit,
MarketToLimit,
Pegged
}
/// <summary>
/// States of orders at venues
/// </summary>
public enum VenueOrderState
{
New,
Submitted,
Accepted,
PartiallyFilled,
Filled,
Cancelled,
Rejected,
Expired,
Suspended
}
Venue Implementations
Base Venue Implementation
/// <summary>
/// Base implementation for execution venues
/// </summary>
public abstract class BaseExecutionVenue : IExecutionVenue
{
protected readonly ILogger _logger;
protected readonly VenueConfig _config;
public string Id { get; }
public string Name { get; }
public string Description { get; }
public bool IsActive { get; protected set; }
public VenueType Type { get; }
protected BaseExecutionVenue(
string id,
string name,
string description,
VenueType type,
VenueConfig config,
ILogger logger)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
Name = name ?? throw new ArgumentNullException(nameof(name));
Description = description ?? throw new ArgumentNullException(nameof(description));
Type = type;
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
IsActive = true;
}
public abstract Task<VenueOrderResult> SubmitOrderAsync(VenueOrderRequest request);
public abstract Task<bool> CancelOrderAsync(string venueOrderId);
public abstract Task<VenueOrderResult> ModifyOrderAsync(string venueOrderId, VenueOrderModification modification);
public abstract Task<VenueOrderStatus> GetOrderStatusAsync(string venueOrderId);
public abstract Task<MarketDataSnapshot> GetMarketDataAsync(string symbol);
public abstract Task<bool> IsHealthyAsync();
protected virtual void ValidateOrderRequest(VenueOrderRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (string.IsNullOrEmpty(request.Symbol))
throw new ArgumentException("Symbol is required", nameof(request));
if (request.Quantity <= 0)
throw new ArgumentException("Quantity must be positive", nameof(request));
if ((request.Type == VenueOrderType.Limit || request.Type == VenueOrderType.StopLimit)
&& !request.LimitPrice.HasValue)
throw new ArgumentException("Limit price is required for limit orders", nameof(request));
if ((request.Type == VenueOrderType.StopMarket || request.Type == VenueOrderType.StopLimit)
&& !request.StopPrice.HasValue)
throw new ArgumentException("Stop price is required for stop orders", nameof(request));
}
}
Broker Venue Implementation
/// <summary>
/// Implementation for broker execution venues
/// </summary>
public class BrokerExecutionVenue : BaseExecutionVenue
{
private readonly IBrokerApi _brokerApi;
private readonly Dictionary<string, string> _omsToVenueOrderIdMap;
private readonly Dictionary<string, string> _venueToOmsOrderIdMap;
public BrokerExecutionVenue(
string id,
string name,
string description,
VenueConfig config,
IBrokerApi brokerApi,
ILogger<BrokerExecutionVenue> logger)
: base(id, name, description, VenueType.Broker, config, logger)
{
_brokerApi = brokerApi ?? throw new ArgumentNullException(nameof(brokerApi));
_omsToVenueOrderIdMap = new Dictionary<string, string>();
_venueToOmsOrderIdMap = new Dictionary<string, string>();
}
public override async Task<VenueOrderResult> SubmitOrderAsync(VenueOrderRequest request)
{
try
{
ValidateOrderRequest(request);
_logger.LogInformation("Submitting order to broker venue {Venue}: {Symbol} {Side} {Quantity}",
Name, request.Symbol, request.Side, request.Quantity);
// Convert to broker-specific order format
var brokerOrder = ConvertToBrokerOrder(request);
// Submit to broker
var brokerResult = await _brokerApi.SubmitOrderAsync(brokerOrder);
if (brokerResult.Success)
{
// Map order IDs
lock (_omsToVenueOrderIdMap)
{
_omsToVenueOrderIdMap[request.OmsOrderId] = brokerResult.OrderId;
_venueToOmsOrderIdMap[brokerResult.OrderId] = request.OmsOrderId;
}
var status = ConvertToVenueOrderStatus(brokerResult.OrderStatus);
_logger.LogInformation("Order submitted to broker venue {Venue}: {VenueOrderId}",
Name, brokerResult.OrderId);
return new VenueOrderResult(true, brokerResult.OrderId, "Order submitted successfully", status,
new Dictionary<string, object> { ["broker_response"] = brokerResult.RawResponse });
}
else
{
_logger.LogWarning("Order submission failed at broker venue {Venue}: {Message}",
Name, brokerResult.Message);
return new VenueOrderResult(false, null, brokerResult.Message, null,
new Dictionary<string, object> { ["broker_error"] = brokerResult.RawResponse });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error submitting order to broker venue {Venue}", Name);
return new VenueOrderResult(false, null, $"Error submitting order: {ex.Message}", null,
new Dictionary<string, object> { ["error"] = ex.Message });
}
}
public override async Task<bool> CancelOrderAsync(string venueOrderId)
{
try
{
if (string.IsNullOrEmpty(venueOrderId))
throw new ArgumentException("Venue order ID required", nameof(venueOrderId));
_logger.LogInformation("Cancelling order at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
var result = await _brokerApi.CancelOrderAsync(venueOrderId);
if (result)
{
_logger.LogInformation("Order cancelled at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
}
else
{
_logger.LogWarning("Order cancellation failed at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error cancelling order at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
return false;
}
}
public override async Task<VenueOrderResult> ModifyOrderAsync(string venueOrderId, VenueOrderModification modification)
{
try
{
if (string.IsNullOrEmpty(venueOrderId))
throw new ArgumentException("Venue order ID required", nameof(venueOrderId));
if (modification == null)
throw new ArgumentNullException(nameof(modification));
_logger.LogInformation("Modifying order at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
var brokerModification = ConvertToBrokerModification(modification);
var brokerResult = await _brokerApi.ModifyOrderAsync(venueOrderId, brokerModification);
if (brokerResult.Success)
{
var status = ConvertToVenueOrderStatus(brokerResult.OrderStatus);
_logger.LogInformation("Order modified at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
return new VenueOrderResult(true, venueOrderId, "Order modified successfully", status,
new Dictionary<string, object> { ["broker_response"] = brokerResult.RawResponse });
}
else
{
_logger.LogWarning("Order modification failed at broker venue {Venue}: {VenueOrderId}: {Message}",
Name, venueOrderId, brokerResult.Message);
return new VenueOrderResult(false, venueOrderId, brokerResult.Message, null,
new Dictionary<string, object> { ["broker_error"] = brokerResult.RawResponse });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error modifying order at broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
return new VenueOrderResult(false, venueOrderId, $"Error modifying order: {ex.Message}", null,
new Dictionary<string, object> { ["error"] = ex.Message });
}
}
public override async Task<VenueOrderStatus> GetOrderStatusAsync(string venueOrderId)
{
try
{
if (string.IsNullOrEmpty(venueOrderId))
throw new ArgumentException("Venue order ID required", nameof(venueOrderId));
var brokerStatus = await _brokerApi.GetOrderStatusAsync(venueOrderId);
return ConvertToVenueOrderStatus(brokerStatus);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting order status from broker venue {Venue}: {VenueOrderId}",
Name, venueOrderId);
throw;
}
}
public override async Task<MarketDataSnapshot> GetMarketDataAsync(string symbol)
{
try
{
if (string.IsNullOrEmpty(symbol))
throw new ArgumentException("Symbol required", nameof(symbol));
var brokerData = await _brokerApi.GetMarketDataAsync(symbol);
return ConvertToMarketDataSnapshot(brokerData);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting market data from broker venue {Venue}: {Symbol}",
Name, symbol);
throw;
}
}
public override async Task<bool> IsHealthyAsync()
{
try
{
return await _brokerApi.IsHealthyAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking health of broker venue {Venue}", Name);
return false;
}
}
private BrokerOrder ConvertToBrokerOrder(VenueOrderRequest request)
{
return new BrokerOrder(
Symbol: request.Symbol,
Side: ConvertOrderSide(request.Side),
Type: ConvertOrderType(request.Type),
Quantity: request.Quantity,
LimitPrice: request.LimitPrice,
StopPrice: request.StopPrice,
TimeInForce: ConvertTimeInForce(request.TimeInForce),
Metadata: request.Metadata
);
}
private BrokerOrderSide ConvertOrderSide(OrderSide side)
{
return side switch
{
OrderSide.Buy => BrokerOrderSide.Buy,
OrderSide.Sell => BrokerOrderSide.Sell,
_ => throw new ArgumentException($"Unsupported order side: {side}")
};
}
private BrokerOrderType ConvertOrderType(VenueOrderType type)
{
return type switch
{
VenueOrderType.Market => BrokerOrderType.Market,
VenueOrderType.Limit => BrokerOrderType.Limit,
VenueOrderType.StopMarket => BrokerOrderType.StopMarket,
VenueOrderType.StopLimit => BrokerOrderType.StopLimit,
VenueOrderType.MarketToLimit => BrokerOrderType.MarketToLimit,
VenueOrderType.Pegged => BrokerOrderType.Pegged,
_ => throw new ArgumentException($"Unsupported order type: {type}")
};
}
private BrokerTimeInForce ConvertTimeInForce(TimeInForce tif)
{
return tif switch
{
TimeInForce.Day => BrokerTimeInForce.Day,
TimeInForce.Gtc => BrokerTimeInForce.Gtc,
TimeInForce.Ioc => BrokerTimeInForce.Ioc,
TimeInForce.Fok => BrokerTimeInForce.Fok,
_ => throw new ArgumentException($"Unsupported time in force: {tif}")
};
}
private VenueOrderStatus ConvertToVenueOrderStatus(BrokerOrderStatus brokerStatus)
{
if (brokerStatus == null) return null;
return new VenueOrderStatus(
VenueOrderId: brokerStatus.OrderId,
Symbol: brokerStatus.Symbol,
Side: ConvertBrokerOrderSide(brokerStatus.Side),
Type: ConvertBrokerOrderType(brokerStatus.Type),
Quantity: brokerStatus.Quantity,
FilledQuantity: brokerStatus.FilledQuantity,
LimitPrice: brokerStatus.LimitPrice,
StopPrice: brokerStatus.StopPrice,
State: ConvertBrokerOrderState(brokerStatus.State),
CreatedTime: brokerStatus.CreatedTime,
FilledTime: brokerStatus.FilledTime,
Fills: brokerStatus.Fills?.Select(ConvertToVenueOrderFill).ToList() ?? new List<VenueOrderFill>(),
Metadata: brokerStatus.Metadata
);
}
private OrderSide ConvertBrokerOrderSide(BrokerOrderSide side)
{
return side switch
{
BrokerOrderSide.Buy => OrderSide.Buy,
BrokerOrderSide.Sell => OrderSide.Sell,
_ => throw new ArgumentException($"Unsupported broker order side: {side}")
};
}
private VenueOrderType ConvertBrokerOrderType(BrokerOrderType type)
{
return type switch
{
BrokerOrderType.Market => VenueOrderType.Market,
BrokerOrderType.Limit => VenueOrderType.Limit,
BrokerOrderType.StopMarket => VenueOrderType.StopMarket,
BrokerOrderType.StopLimit => VenueOrderType.StopLimit,
BrokerOrderType.MarketToLimit => VenueOrderType.MarketToLimit,
BrokerOrderType.Pegged => VenueOrderType.Pegged,
_ => throw new ArgumentException($"Unsupported broker order type: {type}")
};
}
private VenueOrderState ConvertBrokerOrderState(BrokerOrderState state)
{
return state switch
{
BrokerOrderState.New => VenueOrderState.New,
BrokerOrderState.Submitted => VenueOrderState.Submitted,
BrokerOrderState.Accepted => VenueOrderState.Accepted,
BrokerOrderState.PartiallyFilled => VenueOrderState.PartiallyFilled,
BrokerOrderState.Filled => VenueOrderState.Filled,
BrokerOrderState.Cancelled => VenueOrderState.Cancelled,
BrokerOrderState.Rejected => VenueOrderState.Rejected,
BrokerOrderState.Expired => VenueOrderState.Expired,
BrokerOrderState.Suspended => VenueOrderState.Suspended,
_ => throw new ArgumentException($"Unsupported broker order state: {state}")
};
}
private VenueOrderFill ConvertToVenueOrderFill(BrokerOrderFill brokerFill)
{
if (brokerFill == null) return null;
return new VenueOrderFill(
VenueOrderId: brokerFill.OrderId,
Symbol: brokerFill.Symbol,
Quantity: brokerFill.Quantity,
FillPrice: brokerFill.FillPrice,
FillTime: brokerFill.FillTime,
Commission: brokerFill.Commission,
ExecutionId: brokerFill.ExecutionId,
Metadata: brokerFill.Metadata
);
}
private MarketDataSnapshot ConvertToMarketDataSnapshot(BrokerMarketData brokerData)
{
if (brokerData == null) return null;
return new MarketDataSnapshot(
Symbol: brokerData.Symbol,
BidPrice: brokerData.BidPrice,
BidSize: brokerData.BidSize,
AskPrice: brokerData.AskPrice,
AskSize: brokerData.AskSize,
LastPrice: brokerData.LastPrice,
LastSize: brokerData.LastSize,
Volume: brokerData.Volume,
Timestamp: brokerData.Timestamp,
Metadata: brokerData.Metadata
);
}
private BrokerOrderModification ConvertToBrokerModification(VenueOrderModification modification)
{
return new BrokerOrderModification(
Quantity: modification.Quantity,
LimitPrice: modification.LimitPrice,
StopPrice: modification.StopPrice,
TimeInForce: ConvertTimeInForce(modification.TimeInForce),
Metadata: modification.Metadata
);
}
}
Venue Management System
Venue Manager
/// <summary>
/// Manages multiple execution venues
/// </summary>
public class VenueManager
{
private readonly Dictionary<string, IExecutionVenue> _venues;
private readonly ILogger<VenueManager> _logger;
private readonly object _lock = new object();
public VenueManager(ILogger<VenueManager> logger)
{
_venues = new Dictionary<string, IExecutionVenue>();
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Add a new execution venue
/// </summary>
public void AddVenue(IExecutionVenue venue)
{
if (venue == null) throw new ArgumentNullException(nameof(venue));
lock (_lock)
{
_venues[venue.Id] = venue;
}
_logger.LogInformation("Venue added: {VenueId} ({VenueName})", venue.Id, venue.Name);
}
/// <summary>
/// Remove an execution venue
/// </summary>
public void RemoveVenue(string venueId)
{
if (string.IsNullOrEmpty(venueId)) throw new ArgumentException("Venue ID required", nameof(venueId));
lock (_lock)
{
if (_venues.ContainsKey(venueId))
{
_venues.Remove(venueId);
}
}
_logger.LogInformation("Venue removed: {VenueId}", venueId);
}
/// <summary>
/// Get a specific venue by ID
/// </summary>
public IExecutionVenue GetVenue(string venueId)
{
if (string.IsNullOrEmpty(venueId)) return null;
lock (_lock)
{
return _venues.ContainsKey(venueId) ? _venues[venueId] : null;
}
}
/// <summary>
/// Get all active venues
/// </summary>
public List<IExecutionVenue> GetActiveVenues()
{
lock (_lock)
{
return _venues.Values.Where(v => v.IsActive).ToList();
}
}
/// <summary>
/// Get venues by type
/// </summary>
public List<IExecutionVenue> GetVenuesByType(VenueType type)
{
lock (_lock)
{
return _venues.Values.Where(v => v.Type == type && v.IsActive).ToList();
}
}
/// <summary>
/// Update venue status
/// </summary>
public void UpdateVenueStatus(string venueId, bool isActive)
{
var venue = GetVenue(venueId);
if (venue != null)
{
// Note: This would require the venue to have a mutable IsActive property
// or we would need to replace the venue instance
_logger.LogInformation("Venue {VenueId} status updated to {IsActive}", venueId, isActive);
}
}
/// <summary>
/// Check health of all venues
/// </summary>
public async Task<Dictionary<string, bool>> CheckVenueHealthAsync()
{
var healthStatus = new Dictionary<string, bool>();
var venues = GetActiveVenues();
var healthTasks = venues.Select(async venue =>
{
try
{
var isHealthy = await venue.IsHealthyAsync();
return new { VenueId = venue.Id, IsHealthy = isHealthy };
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking health of venue {VenueId}", venue.Id);
return new { VenueId = venue.Id, IsHealthy = false };
}
}).ToList();
var results = await Task.WhenAll(healthTasks);
foreach (var result in results)
{
healthStatus[result.VenueId] = result.IsHealthy;
}
return healthStatus;
}
}
Integration with OrderManager
Venue Integration in OrderManager
public partial class OrderManager : IOrderManager
{
private readonly VenueManager _venueManager;
private readonly Dictionary<string, string> _omsToVenueOrderIdMap;
private readonly Dictionary<string, string> _venueToOmsOrderIdMap;
// Initialize venue manager in constructor
public OrderManager(
IRiskManager riskManager,
IPositionSizer positionSizer,
ILogger<OrderManager> logger) : base(riskManager, positionSizer, logger)
{
_venueManager = new VenueManager(logger);
_omsToVenueOrderIdMap = new Dictionary<string, string>();
_venueToOmsOrderIdMap = new Dictionary<string, string>();
// Initialize default venues
InitializeDefaultVenues();
}
private void InitializeDefaultVenues()
{
// Add primary broker venue
var primaryBrokerConfig = new VenueConfig(
ApiKey: "primary-broker-key",
ApiSecret: "primary-broker-secret",
BaseUrl: "https://api.primary-broker.com",
RateLimit: 100 // requests per minute
);
var primaryBrokerApi = new PrimaryBrokerApi(primaryBrokerConfig);
var primaryVenue = new BrokerExecutionVenue(
id: "primary-broker",
name: "Primary Broker",
description: "Primary execution broker",
config: primaryBrokerConfig,
brokerApi: primaryBrokerApi,
logger: _logger
);
_venueManager.AddVenue(primaryVenue);
// Add secondary broker venue
var secondaryBrokerConfig = new VenueConfig(
ApiKey: "secondary-broker-key",
ApiSecret: "secondary-broker-secret",
BaseUrl: "https://api.secondary-broker.com",
RateLimit: 50 // requests per minute
);
var secondaryBrokerApi = new SecondaryBrokerApi(secondaryBrokerConfig);
var secondaryVenue = new BrokerExecutionVenue(
id: "secondary-broker",
name: "Secondary Broker",
description: "Backup execution broker",
config: secondaryBrokerConfig,
brokerApi: secondaryBrokerApi,
logger: _logger
);
_venueManager.AddVenue(secondaryVenue);
}
// Enhanced order submission with venue routing
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);
}
try
{
// Route order to appropriate venue
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);
}
// Submit to selected venue
var venueOrderRequest = ConvertToVenueOrderRequest(request);
var venueResult = await routingResult.SelectedVenue.SubmitOrderAsync(venueOrderRequest);
if (venueResult.Success)
{
// Map order IDs
lock (_lock)
{
_omsToVenueOrderIdMap[venueResult.VenueOrderId] = venueResult.VenueOrderId;
_venueToOmsOrderIdMap[venueResult.VenueOrderId] = venueResult.VenueOrderId;
}
// Create order status
var orderStatus = ConvertToOrderStatus(venueResult.Status, request);
// Store order status
lock (_lock)
{
_orders[venueResult.VenueOrderId] = orderStatus; // Using venue order ID as key
}
_logger.LogInformation("Order {OrderId} submitted to venue {Venue}",
venueResult.VenueOrderId, routingResult.SelectedVenue.Name);
return new OrderResult(true, venueResult.VenueOrderId, "Order submitted successfully", orderStatus);
}
else
{
_logger.LogError("Order submission failed at venue {Venue}: {Message}",
routingResult.SelectedVenue.Name, venueResult.Message);
return new OrderResult(false, null, $"Venue submission failed: {venueResult.Message}", null);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error submitting order for {Symbol}", request.Symbol);
return new OrderResult(false, null, $"Error submitting order: {ex.Message}", null);
}
}
// Enhanced order cancellation with venue integration
public async Task<bool> CancelOrderAsync(string orderId)
{
if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", nameof(orderId));
try
{
// Get venue order ID
string venueOrderId;
lock (_lock)
{
if (!_omsToVenueOrderIdMap.ContainsKey(orderId))
{
_logger.LogWarning("Cannot cancel order {OrderId} - not found", orderId);
return false;
}
venueOrderId = _omsToVenueOrderIdMap[orderId];
}
// Get venue for this order
var venue = await GetVenueForOrderAsync(orderId);
if (venue == null)
{
_logger.LogWarning("Cannot cancel order {OrderId} - venue not found", orderId);
return false;
}
// Cancel at venue
var result = await venue.CancelOrderAsync(venueOrderId);
if (result)
{
// Update order status
lock (_lock)
{
if (_orders.ContainsKey(orderId))
{
var order = _orders[orderId];
var updatedOrder = order with { State = OrderState.Cancelled, FilledTime = DateTime.UtcNow };
_orders[orderId] = updatedOrder;
}
}
_logger.LogInformation("Order {OrderId} cancelled successfully at venue {Venue}",
orderId, venue.Name);
}
else
{
_logger.LogWarning("Order {OrderId} cancellation failed at venue {Venue}",
orderId, venue.Name);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error cancelling order {OrderId}", orderId);
return false;
}
}
private async Task<IExecutionVenue> GetVenueForOrderAsync(string orderId)
{
// In a real implementation, this would lookup which venue was used for the order
// For now, we'll return the primary venue
return _venueManager.GetVenue("primary-broker");
}
private VenueOrderRequest ConvertToVenueOrderRequest(OrderRequest request)
{
return new VenueOrderRequest(
Symbol: request.Symbol,
Side: ConvertOrderSide(request.Side),
Type: ConvertOrderType(request.Type),
Quantity: request.Quantity,
LimitPrice: request.LimitPrice,
StopPrice: request.StopPrice,
TimeInForce: request.TimeInForce,
OmsOrderId: Guid.NewGuid().ToString(), // This would be the actual OMS order ID
Metadata: new Dictionary<string, object>
{
["Algorithm"] = request.Algorithm,
["AlgorithmParameters"] = request.AlgorithmParameters
}
);
}
private OrderStatus ConvertToOrderStatus(VenueOrderStatus venueStatus, OrderRequest request)
{
if (venueStatus == null) return null;
return new OrderStatus(
OrderId: venueStatus.VenueOrderId,
Symbol: venueStatus.Symbol,
Side: ConvertVenueOrderSide(venueStatus.Side),
Type: ConvertVenueOrderType(venueStatus.Type),
Quantity: venueStatus.Quantity,
FilledQuantity: venueStatus.FilledQuantity,
LimitPrice: venueStatus.LimitPrice,
StopPrice: venueStatus.StopPrice,
State: ConvertVenueOrderState(venueStatus.State),
CreatedTime: venueStatus.CreatedTime,
FilledTime: venueStatus.FilledTime,
Fills: venueStatus.Fills?.Select(ConvertToOrderFill).ToList() ?? new List<OrderFill>()
);
}
private OrderSide ConvertVenueOrderSide(VenueOrderSide side)
{
return side switch
{
VenueOrderSide.Buy => OrderSide.Buy,
VenueOrderSide.Sell => OrderSide.Sell,
_ => throw new ArgumentException($"Unsupported venue order side: {side}")
};
}
private OrderType ConvertVenueOrderType(VenueOrderType type)
{
return type switch
{
VenueOrderType.Market => OrderType.Market,
VenueOrderType.Limit => OrderType.Limit,
VenueOrderType.StopMarket => OrderType.StopMarket,
VenueOrderType.StopLimit => OrderType.StopLimit,
_ => throw new ArgumentException($"Unsupported venue order type: {type}")
};
}
private OrderState ConvertVenueOrderState(VenueOrderState state)
{
return state switch
{
VenueOrderState.New => OrderState.New,
VenueOrderState.Submitted => OrderState.Submitted,
VenueOrderState.Accepted => OrderState.Accepted,
VenueOrderState.PartiallyFilled => OrderState.PartiallyFilled,
VenueOrderState.Filled => OrderState.Filled,
VenueOrderState.Cancelled => OrderState.Cancelled,
VenueOrderState.Rejected => OrderState.Rejected,
VenueOrderState.Expired => OrderState.Expired,
VenueOrderState.Suspended => OrderState.Suspended,
_ => throw new ArgumentException($"Unsupported venue order state: {state}")
};
}
private OrderFill ConvertToOrderFill(VenueOrderFill venueFill)
{
if (venueFill == null) return null;
return new OrderFill(
OrderId: venueFill.VenueOrderId,
Symbol: venueFill.Symbol,
Quantity: venueFill.Quantity,
FillPrice: venueFill.FillPrice,
FillTime: venueFill.FillTime,
Commission: venueFill.Commission,
ExecutionId: venueFill.ExecutionId
);
}
}
Venue Configuration
Venue Configuration Model
/// <summary>
/// Configuration for execution venues
/// </summary>
public record VenueConfig(
string ApiKey,
string ApiSecret,
string BaseUrl,
int RateLimit, // Requests per minute
Dictionary<string, object> AdditionalSettings = null
);
Testing Considerations
Unit Tests for Venue Management
- Venue Addition/Removal: Test adding and removing venues
- Venue Selection: Test getting venues by ID and type
- Health Checks: Test venue health monitoring
- Order Mapping: Test mapping between OMS and venue order IDs
Integration Tests
- Venue Integration: Test integration with different venue types
- Order Lifecycle: Test complete order lifecycle across venues
- Error Handling: Test error handling for venue connectivity issues
- Performance: Test performance with multiple venues
Performance Considerations
Connection Management
/// <summary>
/// Connection pool for venue connections
/// </summary>
public class VenueConnectionPool
{
private readonly Dictionary<string, SemaphoreSlim> _rateLimiters;
private readonly Dictionary<string, HttpClient> _httpClients;
public VenueConnectionPool()
{
_rateLimiters = new Dictionary<string, SemaphoreSlim>();
_httpClients = new Dictionary<string, HttpClient>();
}
public SemaphoreSlim GetRateLimiter(string venueId, int maxRequestsPerMinute)
{
if (!_rateLimiters.ContainsKey(venueId))
{
_rateLimiters[venueId] = new SemaphoreSlim(maxRequestsPerMinute, maxRequestsPerMinute);
}
return _rateLimiters[venueId];
}
public HttpClient GetHttpClient(string venueId, string baseUrl)
{
if (!_httpClients.ContainsKey(venueId))
{
var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var client = new HttpClient(handler)
{
BaseAddress = new Uri(baseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
client.DefaultRequestHeaders.Add("User-Agent", "NT8-OMS/1.0");
_httpClients[venueId] = client;
}
return _httpClients[venueId];
}
}
Caching and Rate Limiting
/// <summary>
/// Rate limiter for venue API calls
/// </summary>
public class ApiRateLimiter
{
private readonly SemaphoreSlim _semaphore;
private readonly TimeSpan _timeWindow;
private readonly int _maxRequests;
private readonly Queue<DateTime> _requestTimes;
public ApiRateLimiter(int maxRequests, TimeSpan timeWindow)
{
_semaphore = new SemaphoreSlim(maxRequests, maxRequests);
_timeWindow = timeWindow;
_maxRequests = maxRequests;
_requestTimes = new Queue<DateTime>();
}
public async Task<IDisposable> AcquireAsync(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
lock (_requestTimes)
{
var now = DateTime.UtcNow;
_requestTimes.Enqueue(now);
// Remove old requests outside the time window
while (_requestTimes.Count > 0 && _requestTimes.Peek() < now.Subtract(_timeWindow))
{
_requestTimes.Dequeue();
}
}
return new RateLimitLease(_semaphore);
}
private class RateLimitLease : IDisposable
{
private readonly SemaphoreSlim _semaphore;
public RateLimitLease(SemaphoreSlim semaphore)
{
_semaphore = semaphore;
}
public void Dispose()
{
_semaphore.Release();
}
}
}
Monitoring and Alerting
Venue Metrics
/// <summary>
/// Metrics for execution venues
/// </summary>
public class VenueMetrics
{
public string VenueId { get; set; }
public string VenueName { get; set; }
public int TotalOrders { get; set; }
public int SuccessfulOrders { get; set; }
public int FailedOrders { get; set; }
public double SuccessRate => TotalOrders > 0 ? (double)SuccessfulOrders / TotalOrders : 0;
public double AverageLatencyMs { get; set; }
public double AverageSlippage { get; set; }
public DateTime LastUpdated { get; set; }
}
Future Enhancements
- Cross-Venue Order Management: Coordinate orders across multiple venues
- Smart Order Routing: Advanced routing based on real-time venue performance
- Venue Failover: Automatic failover to backup venues when primary venues fail
- Regulatory Compliance: Ensure venue selection complies with regulatory requirements
- Cost Analysis: Detailed cost analysis including rebates and fees across venues
- Liquidity Aggregation: Aggregate liquidity from multiple venues for better execution