1261 lines
42 KiB
Markdown
1261 lines
42 KiB
Markdown
# TWAP Algorithm Implementation Design
|
|
|
|
## Overview
|
|
|
|
This document details the implementation of the Time Weighted Average Price (TWAP) algorithm in the Order Management System (OMS), which executes large orders by slicing them into smaller orders over a specified time period to minimize market impact.
|
|
|
|
## TWAP Algorithm Fundamentals
|
|
|
|
### Algorithm Description
|
|
The TWAP algorithm is designed to execute large orders by dividing them into smaller, equal-sized slices and executing these slices at regular intervals throughout a specified time period. The primary goal is to minimize market impact by spreading the execution over time and participating equally in all time periods.
|
|
|
|
### Key Characteristics
|
|
1. **Time-based Slicing**: Orders are divided based on time intervals rather than volume
|
|
2. **Equal Participation**: Equal participation rate in each time interval
|
|
3. **Market Impact Reduction**: Minimizes information leakage by spreading execution
|
|
4. **Predictable Execution**: Provides consistent execution pattern
|
|
|
|
## TWAP Parameters
|
|
|
|
### Core Parameters
|
|
```csharp
|
|
/// <summary>
|
|
/// Parameters for TWAP algorithm execution
|
|
/// </summary>
|
|
public record TwapParameters
|
|
{
|
|
/// <summary>
|
|
/// Trading symbol
|
|
/// </summary>
|
|
public string Symbol { get; set; }
|
|
|
|
/// <summary>
|
|
/// Order side (Buy/Sell)
|
|
/// </summary>
|
|
public OrderSide Side { get; set; }
|
|
|
|
/// <summary>
|
|
/// Total quantity to execute
|
|
/// </summary>
|
|
public int TotalQuantity { get; set; }
|
|
|
|
/// <summary>
|
|
/// Start time for execution
|
|
/// </summary>
|
|
public DateTime StartTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// End time for execution
|
|
/// </summary>
|
|
public DateTime EndTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Interval between order submissions (in seconds)
|
|
/// </summary>
|
|
public int IntervalSeconds { get; set; }
|
|
|
|
/// <summary>
|
|
/// Optional limit price for limit orders
|
|
/// </summary>
|
|
public decimal? LimitPrice { get; set; }
|
|
|
|
/// <summary>
|
|
/// Time in force for orders
|
|
/// </summary>
|
|
public TimeInForce TimeInForce { get; set; } = TimeInForce.Day;
|
|
|
|
/// <summary>
|
|
/// Whether to adjust slice sizes based on remaining time
|
|
/// </summary>
|
|
public bool AdjustForRemainingTime { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Minimum slice size (to avoid very small orders)
|
|
/// </summary>
|
|
public int MinSliceSize { get; set; } = 1;
|
|
|
|
/// <summary>
|
|
/// Maximum slice size (to control individual order impact)
|
|
/// </summary>
|
|
public int? MaxSliceSize { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether to cancel remaining orders at end time
|
|
/// </summary>
|
|
public bool CancelAtEndTime { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Custom metadata for the algorithm
|
|
/// </summary>
|
|
public Dictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
|
}
|
|
```
|
|
|
|
### TWAP Configuration
|
|
```csharp
|
|
/// <summary>
|
|
/// Configuration for TWAP algorithm behavior
|
|
/// </summary>
|
|
public record TwapConfig
|
|
{
|
|
/// <summary>
|
|
/// Default execution duration
|
|
/// </summary>
|
|
public TimeSpan DefaultDuration { get; set; } = TimeSpan.FromMinutes(30);
|
|
|
|
/// <summary>
|
|
/// Default interval between orders
|
|
/// </summary>
|
|
public int DefaultIntervalSeconds { get; set; } = 60;
|
|
|
|
/// <summary>
|
|
/// Whether to enable adaptive slicing based on market conditions
|
|
/// </summary>
|
|
public bool EnableAdaptiveSlicing { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Maximum deviation from scheduled execution time (in seconds)
|
|
/// </summary>
|
|
public int MaxTimeDeviationSeconds { get; set; } = 30;
|
|
|
|
/// <summary>
|
|
/// Whether to prioritize execution speed over impact reduction
|
|
/// </summary>
|
|
public bool PrioritizeSpeed { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Retry count for failed slice executions
|
|
/// </summary>
|
|
public int MaxRetries { get; set; } = 3;
|
|
|
|
/// <summary>
|
|
/// Delay between retries (in seconds)
|
|
/// </summary>
|
|
public int RetryDelaySeconds { get; set; } = 5;
|
|
|
|
/// <summary>
|
|
/// Whether to log detailed execution information
|
|
/// </summary>
|
|
public bool EnableDetailedLogging { get; set; } = false;
|
|
|
|
public static TwapConfig Default => new TwapConfig();
|
|
}
|
|
```
|
|
|
|
## TWAP Execution Models
|
|
|
|
### TWAP Execution State
|
|
```csharp
|
|
/// <summary>
|
|
/// State of a TWAP execution
|
|
/// </summary>
|
|
public record TwapExecutionState
|
|
{
|
|
/// <summary>
|
|
/// Unique identifier for this TWAP execution
|
|
/// </summary>
|
|
public string ExecutionId { get; set; } = Guid.NewGuid().ToString();
|
|
|
|
/// <summary>
|
|
/// Parameters used for this execution
|
|
/// </summary>
|
|
public TwapParameters Parameters { get; set; }
|
|
|
|
/// <summary>
|
|
/// Current execution status
|
|
/// </summary>
|
|
public TwapExecutionStatus Status { get; set; } = TwapExecutionStatus.Pending;
|
|
|
|
/// <summary>
|
|
/// Total quantity to execute
|
|
/// </summary>
|
|
public int TotalQuantity { get; set; }
|
|
|
|
/// <summary>
|
|
/// Quantity already executed
|
|
/// </summary>
|
|
public int ExecutedQuantity { get; set; }
|
|
|
|
/// <summary>
|
|
/// Remaining quantity to execute
|
|
/// </summary>
|
|
public int RemainingQuantity => TotalQuantity - ExecutedQuantity;
|
|
|
|
/// <summary>
|
|
/// Scheduled slice executions
|
|
/// </summary>
|
|
public List<TwapSlice> ScheduledSlices { get; set; } = new List<TwapSlice>();
|
|
|
|
/// <summary>
|
|
/// Completed slice executions
|
|
/// </summary>
|
|
public List<TwapSlice> CompletedSlices { get; set; } = new List<TwapSlice>();
|
|
|
|
/// <summary>
|
|
/// Failed slice executions
|
|
/// </summary>
|
|
public List<TwapSlice> FailedSlices { get; set; } = new List<TwapSlice>();
|
|
|
|
/// <summary>
|
|
/// Start time of execution
|
|
/// </summary>
|
|
public DateTime? StartTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// End time of execution
|
|
/// </summary>
|
|
public DateTime? EndTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// When this execution was created
|
|
/// </summary>
|
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
|
|
/// <summary>
|
|
/// When this execution was last updated
|
|
/// </summary>
|
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
|
|
|
/// <summary>
|
|
/// Error information if execution failed
|
|
/// </summary>
|
|
public string ErrorMessage { get; set; }
|
|
|
|
/// <summary>
|
|
/// Performance metrics for this execution
|
|
/// </summary>
|
|
public TwapPerformanceMetrics PerformanceMetrics { get; set; } = new TwapPerformanceMetrics();
|
|
}
|
|
```
|
|
|
|
### TWAP Slice
|
|
```csharp
|
|
/// <summary>
|
|
/// Represents a single slice of a TWAP execution
|
|
/// </summary>
|
|
public record TwapSlice
|
|
{
|
|
/// <summary>
|
|
/// Unique identifier for this slice
|
|
/// </summary>
|
|
public string SliceId { get; set; } = Guid.NewGuid().ToString();
|
|
|
|
/// <summary>
|
|
/// Reference to parent TWAP execution
|
|
/// </summary>
|
|
public string ExecutionId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Slice number (1-based)
|
|
/// </summary>
|
|
public int SliceNumber { get; set; }
|
|
|
|
/// <summary>
|
|
/// Scheduled execution time
|
|
/// </summary>
|
|
public DateTime ScheduledTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Actual execution time
|
|
/// </summary>
|
|
public DateTime? ActualTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Quantity for this slice
|
|
/// </summary>
|
|
public int Quantity { get; set; }
|
|
|
|
/// <summary>
|
|
/// Status of this slice
|
|
/// </summary>
|
|
public TwapSliceStatus Status { get; set; } = TwapSliceStatus.Pending;
|
|
|
|
/// <summary>
|
|
/// Order ID for this slice (if submitted)
|
|
/// </summary>
|
|
public string OrderId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Fills for this slice
|
|
/// </summary>
|
|
public List<OrderFill> Fills { get; set; } = new List<OrderFill>();
|
|
|
|
/// <summary>
|
|
/// Total filled quantity
|
|
/// </summary>
|
|
public int FilledQuantity => Fills?.Sum(f => f.Quantity) ?? 0;
|
|
|
|
/// <summary>
|
|
/// Average fill price
|
|
/// </summary>
|
|
public decimal AverageFillPrice => Fills?.Any() == true ?
|
|
Fills.Sum(f => f.Quantity * f.FillPrice) / FilledQuantity : 0;
|
|
|
|
/// <summary>
|
|
/// Total commission for this slice
|
|
/// </summary>
|
|
public decimal TotalCommission => Fills?.Sum(f => f.Commission) ?? 0;
|
|
|
|
/// <summary>
|
|
/// Error information if slice failed
|
|
/// </summary>
|
|
public string ErrorMessage { get; set; }
|
|
|
|
/// <summary>
|
|
/// Retry count for this slice
|
|
/// </summary>
|
|
public int RetryCount { get; set; }
|
|
|
|
/// <summary>
|
|
/// When this slice was created
|
|
/// </summary>
|
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
|
|
/// <summary>
|
|
/// When this slice was last updated
|
|
/// </summary>
|
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
|
}
|
|
```
|
|
|
|
### TWAP Performance Metrics
|
|
```csharp
|
|
/// <summary>
|
|
/// Performance metrics for TWAP execution
|
|
/// </summary>
|
|
public record TwapPerformanceMetrics
|
|
{
|
|
/// <summary>
|
|
/// Total execution time
|
|
/// </summary>
|
|
public TimeSpan TotalExecutionTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Average time between slice executions
|
|
/// </summary>
|
|
public TimeSpan AverageIntervalTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Standard deviation of interval times
|
|
/// </summary>
|
|
public TimeSpan IntervalTimeStdDev { get; set; }
|
|
|
|
/// <summary>
|
|
/// Total slippage (percentage)
|
|
/// </summary>
|
|
public decimal TotalSlippage { get; set; }
|
|
|
|
/// <summary>
|
|
/// Average slippage per slice (percentage)
|
|
/// </summary>
|
|
public decimal AverageSlippage { get; set; }
|
|
|
|
/// <summary>
|
|
/// Implementation shortfall (percentage)
|
|
/// </summary>
|
|
public decimal ImplementationShortfall { get; set; }
|
|
|
|
/// <summary>
|
|
/// Volume participation rate
|
|
/// </summary>
|
|
public decimal ParticipationRate { get; set; }
|
|
|
|
/// <summary>
|
|
/// Number of successful slices
|
|
/// </summary>
|
|
public int SuccessfulSlices { get; set; }
|
|
|
|
/// <summary>
|
|
/// Number of failed slices
|
|
/// </summary>
|
|
public int FailedSlices { get; set; }
|
|
|
|
/// <summary>
|
|
/// Number of cancelled slices
|
|
/// </summary>
|
|
public int CancelledSlices { get; set; }
|
|
|
|
/// <summary>
|
|
/// Total commission paid
|
|
/// </summary>
|
|
public decimal TotalCommission { get; set; }
|
|
|
|
/// <summary>
|
|
/// When metrics were last updated
|
|
/// </summary>
|
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
|
}
|
|
```
|
|
|
|
### Enums
|
|
```csharp
|
|
/// <summary>
|
|
/// Status of TWAP execution
|
|
/// </summary>
|
|
public enum TwapExecutionStatus
|
|
{
|
|
Pending,
|
|
Running,
|
|
Completed,
|
|
Cancelled,
|
|
Failed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Status of TWAP slice
|
|
/// </summary>
|
|
public enum TwapSliceStatus
|
|
{
|
|
Pending,
|
|
Scheduled,
|
|
Submitted,
|
|
PartiallyFilled,
|
|
Filled,
|
|
Cancelled,
|
|
Failed
|
|
}
|
|
```
|
|
|
|
## TWAP Algorithm Implementation
|
|
|
|
### TWAP Executor
|
|
```csharp
|
|
/// <summary>
|
|
/// Executes TWAP algorithms
|
|
/// </summary>
|
|
public class TwapExecutor
|
|
{
|
|
private readonly ILogger<TwapExecutor> _logger;
|
|
private readonly IOrderManager _orderManager;
|
|
private readonly TwapConfig _config;
|
|
private readonly Dictionary<string, TwapExecutionState> _executions;
|
|
private readonly Dictionary<string, Timer> _sliceTimers;
|
|
private readonly object _lock = new object();
|
|
|
|
public TwapExecutor(
|
|
ILogger<TwapExecutor> logger,
|
|
IOrderManager orderManager,
|
|
TwapConfig config = null)
|
|
{
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
_orderManager = orderManager ?? throw new ArgumentNullException(nameof(orderManager));
|
|
_config = config ?? TwapConfig.Default;
|
|
_executions = new Dictionary<string, TwapExecutionState>();
|
|
_sliceTimers = new Dictionary<string, Timer>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute a TWAP order
|
|
/// </summary>
|
|
public async Task<TwapExecutionState> ExecuteTwapAsync(TwapParameters parameters, StrategyContext context)
|
|
{
|
|
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
|
|
if (context == null) throw new ArgumentNullException(nameof(context));
|
|
|
|
// Validate parameters
|
|
ValidateParameters(parameters);
|
|
|
|
// Create execution state
|
|
var executionState = CreateExecutionState(parameters);
|
|
|
|
lock (_lock)
|
|
{
|
|
_executions[executionState.ExecutionId] = executionState;
|
|
}
|
|
|
|
_logger.LogInformation("Starting TWAP execution {ExecutionId} for {Symbol} {Side} {Quantity}",
|
|
executionState.ExecutionId, parameters.Symbol, parameters.Side, parameters.TotalQuantity);
|
|
|
|
try
|
|
{
|
|
// Schedule slices
|
|
await ScheduleSlicesAsync(executionState, context);
|
|
|
|
// Start execution
|
|
await StartExecutionAsync(executionState);
|
|
|
|
return executionState;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error starting TWAP execution {ExecutionId}", executionState.ExecutionId);
|
|
|
|
executionState.Status = TwapExecutionStatus.Failed;
|
|
executionState.ErrorMessage = ex.Message;
|
|
executionState.UpdatedAt = DateTime.UtcNow;
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private void ValidateParameters(TwapParameters parameters)
|
|
{
|
|
if (string.IsNullOrEmpty(parameters.Symbol))
|
|
throw new ArgumentException("Symbol is required", nameof(parameters));
|
|
|
|
if (parameters.TotalQuantity <= 0)
|
|
throw new ArgumentException("Total quantity must be positive", nameof(parameters));
|
|
|
|
if (parameters.StartTime >= parameters.EndTime)
|
|
throw new ArgumentException("Start time must be before end time", nameof(parameters));
|
|
|
|
if (parameters.IntervalSeconds <= 0)
|
|
throw new ArgumentException("Interval must be positive", nameof(parameters));
|
|
|
|
var duration = parameters.EndTime - parameters.StartTime;
|
|
var minInterval = TimeSpan.FromSeconds(parameters.IntervalSeconds);
|
|
if (duration < minInterval)
|
|
throw new ArgumentException("Duration must be at least as long as interval", nameof(parameters));
|
|
}
|
|
|
|
private TwapExecutionState CreateExecutionState(TwapParameters parameters)
|
|
{
|
|
var executionState = new TwapExecutionState
|
|
{
|
|
Parameters = parameters,
|
|
TotalQuantity = parameters.TotalQuantity,
|
|
StartTime = parameters.StartTime,
|
|
EndTime = parameters.EndTime
|
|
};
|
|
|
|
// Calculate slices
|
|
var slices = CalculateSlices(parameters);
|
|
executionState.ScheduledSlices.AddRange(slices);
|
|
|
|
return executionState;
|
|
}
|
|
|
|
private List<TwapSlice> CalculateSlices(TwapParameters parameters)
|
|
{
|
|
var slices = new List<TwapSlice>();
|
|
var duration = parameters.EndTime - parameters.StartTime;
|
|
var totalSeconds = duration.TotalSeconds;
|
|
var intervalSeconds = parameters.IntervalSeconds;
|
|
|
|
// Calculate number of slices
|
|
var sliceCount = (int)Math.Floor(totalSeconds / intervalSeconds);
|
|
if (sliceCount <= 0) sliceCount = 1;
|
|
|
|
// Calculate base slice size
|
|
var baseSliceSize = parameters.TotalQuantity / sliceCount;
|
|
var remainder = parameters.TotalQuantity % sliceCount;
|
|
|
|
// Create slices
|
|
for (int i = 0; i < sliceCount; i++)
|
|
{
|
|
var sliceSize = baseSliceSize;
|
|
if (i < remainder) sliceSize++; // Distribute remainder
|
|
|
|
// Apply size limits
|
|
if (sliceSize < parameters.MinSliceSize)
|
|
sliceSize = parameters.MinSliceSize;
|
|
|
|
if (parameters.MaxSliceSize.HasValue && sliceSize > parameters.MaxSliceSize.Value)
|
|
sliceSize = parameters.MaxSliceSize.Value;
|
|
|
|
var slice = new TwapSlice
|
|
{
|
|
ExecutionId = null, // Will be set when execution starts
|
|
SliceNumber = i + 1,
|
|
ScheduledTime = parameters.StartTime.AddSeconds(i * intervalSeconds),
|
|
Quantity = sliceSize
|
|
};
|
|
|
|
slices.Add(slice);
|
|
}
|
|
|
|
return slices;
|
|
}
|
|
|
|
private async Task ScheduleSlicesAsync(TwapExecutionState executionState, StrategyContext context)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
|
|
foreach (var slice in executionState.ScheduledSlices)
|
|
{
|
|
slice.ExecutionId = executionState.ExecutionId;
|
|
|
|
// Calculate delay until scheduled time
|
|
var delay = slice.ScheduledTime - now;
|
|
if (delay.TotalMilliseconds <= 0)
|
|
{
|
|
// Execute immediately
|
|
_ = ExecuteSliceAsync(slice, executionState, context);
|
|
}
|
|
else
|
|
{
|
|
// Schedule for later execution
|
|
var timer = new Timer(async _ =>
|
|
{
|
|
await ExecuteSliceAsync(slice, executionState, context);
|
|
}, null, delay, Timeout.InfiniteTimeSpan);
|
|
|
|
lock (_lock)
|
|
{
|
|
_sliceTimers[slice.SliceId] = timer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task StartExecutionAsync(TwapExecutionState executionState)
|
|
{
|
|
executionState.Status = TwapExecutionStatus.Running;
|
|
executionState.StartTime = DateTime.UtcNow;
|
|
executionState.UpdatedAt = DateTime.UtcNow;
|
|
|
|
_logger.LogInformation("TWAP execution {ExecutionId} started", executionState.ExecutionId);
|
|
}
|
|
|
|
private async Task ExecuteSliceAsync(TwapSlice slice, TwapExecutionState executionState, StrategyContext context)
|
|
{
|
|
try
|
|
{
|
|
// Remove timer if it exists
|
|
lock (_lock)
|
|
{
|
|
if (_sliceTimers.ContainsKey(slice.SliceId))
|
|
{
|
|
_sliceTimers[slice.SliceId].Dispose();
|
|
_sliceTimers.Remove(slice.SliceId);
|
|
}
|
|
}
|
|
|
|
slice.Status = TwapSliceStatus.Submitted;
|
|
slice.ActualTime = DateTime.UtcNow;
|
|
slice.UpdatedAt = DateTime.UtcNow;
|
|
|
|
_logger.LogInformation("Executing TWAP slice {SliceId} ({SliceNumber}/{Total}) for {Quantity}",
|
|
slice.SliceId, slice.SliceNumber, executionState.ScheduledSlices.Count, slice.Quantity);
|
|
|
|
// Create order request
|
|
var orderRequest = new OrderRequest(
|
|
Symbol: executionState.Parameters.Symbol,
|
|
Side: executionState.Parameters.Side,
|
|
Type: executionState.Parameters.LimitPrice.HasValue ? OrderType.Limit : OrderType.Market,
|
|
Quantity: slice.Quantity,
|
|
LimitPrice: executionState.Parameters.LimitPrice,
|
|
StopPrice: null,
|
|
TimeInForce: executionState.Parameters.TimeInForce,
|
|
Algorithm: null, // This is a slice, not an algorithm
|
|
AlgorithmParameters: new Dictionary<string, object>()
|
|
);
|
|
|
|
// Submit order
|
|
var orderResult = await _orderManager.SubmitOrderAsync(orderRequest, context);
|
|
|
|
if (orderResult.Success)
|
|
{
|
|
slice.OrderId = orderResult.OrderId;
|
|
slice.Status = TwapSliceStatus.Filled; // Simplified - in reality would monitor fills
|
|
slice.Fills = orderResult.Status?.Fills ?? new List<OrderFill>();
|
|
slice.UpdatedAt = DateTime.UtcNow;
|
|
|
|
_logger.LogInformation("TWAP slice {SliceId} executed successfully", slice.SliceId);
|
|
}
|
|
else
|
|
{
|
|
slice.Status = TwapSliceStatus.Failed;
|
|
slice.ErrorMessage = orderResult.Message;
|
|
slice.UpdatedAt = DateTime.UtcNow;
|
|
|
|
_logger.LogWarning("TWAP slice {SliceId} failed: {ErrorMessage}", slice.SliceId, orderResult.Message);
|
|
|
|
// Handle retry if configured
|
|
if (slice.RetryCount < _config.MaxRetries)
|
|
{
|
|
slice.RetryCount++;
|
|
_logger.LogInformation("Retrying TWAP slice {SliceId} (attempt {RetryCount})",
|
|
slice.SliceId, slice.RetryCount);
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(_config.RetryDelaySeconds));
|
|
await ExecuteSliceAsync(slice, executionState, context);
|
|
return;
|
|
}
|
|
|
|
// Update execution state
|
|
UpdateExecutionState(executionState, slice);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error executing TWAP slice {SliceId}", slice.SliceId);
|
|
|
|
slice.Status = TwapSliceStatus.Failed;
|
|
slice.ErrorMessage = ex.Message;
|
|
slice.UpdatedAt = DateTime.UtcNow;
|
|
|
|
UpdateExecutionState(executionState, slice);
|
|
}
|
|
}
|
|
|
|
private void UpdateExecutionState(TwapExecutionState executionState, TwapSlice slice)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
// Move slice to appropriate list
|
|
executionState.ScheduledSlices.Remove(slice);
|
|
|
|
if (slice.Status == TwapSliceStatus.Filled || slice.Status == TwapSliceStatus.PartiallyFilled)
|
|
{
|
|
executionState.CompletedSlices.Add(slice);
|
|
executionState.ExecutedQuantity += slice.FilledQuantity;
|
|
}
|
|
else
|
|
{
|
|
executionState.FailedSlices.Add(slice);
|
|
}
|
|
|
|
// Update execution metrics
|
|
UpdatePerformanceMetrics(executionState);
|
|
|
|
// Check if execution is complete
|
|
if (executionState.RemainingQuantity <= 0 ||
|
|
executionState.ScheduledSlices.Count == 0)
|
|
{
|
|
CompleteExecution(executionState);
|
|
}
|
|
|
|
executionState.UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
private void UpdatePerformanceMetrics(TwapExecutionState executionState)
|
|
{
|
|
var metrics = new TwapPerformanceMetrics();
|
|
|
|
// Calculate basic metrics
|
|
var allSlices = executionState.CompletedSlices.Concat(executionState.FailedSlices).ToList();
|
|
if (allSlices.Any())
|
|
{
|
|
metrics.SuccessfulSlices = executionState.CompletedSlices.Count;
|
|
metrics.FailedSlices = executionState.FailedSlices.Count;
|
|
metrics.TotalCommission = allSlices.Sum(s => s.TotalCommission);
|
|
|
|
// Calculate timing metrics
|
|
if (executionState.StartTime.HasValue)
|
|
{
|
|
metrics.TotalExecutionTime = DateTime.UtcNow - executionState.StartTime.Value;
|
|
}
|
|
}
|
|
|
|
executionState.PerformanceMetrics = metrics;
|
|
}
|
|
|
|
private void CompleteExecution(TwapExecutionState executionState)
|
|
{
|
|
if (executionState.RemainingQuantity <= 0)
|
|
{
|
|
executionState.Status = TwapExecutionStatus.Completed;
|
|
_logger.LogInformation("TWAP execution {ExecutionId} completed successfully", executionState.ExecutionId);
|
|
}
|
|
else
|
|
{
|
|
executionState.Status = TwapExecutionStatus.Failed;
|
|
_logger.LogWarning("TWAP execution {ExecutionId} completed with remaining quantity", executionState.ExecutionId);
|
|
}
|
|
|
|
executionState.EndTime = DateTime.UtcNow;
|
|
executionState.UpdatedAt = DateTime.UtcNow;
|
|
|
|
// Clean up timers
|
|
lock (_lock)
|
|
{
|
|
foreach (var timer in _sliceTimers.Values)
|
|
{
|
|
timer.Dispose();
|
|
}
|
|
_sliceTimers.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel a TWAP execution
|
|
/// </summary>
|
|
public async Task<bool> CancelExecutionAsync(string executionId)
|
|
{
|
|
if (string.IsNullOrEmpty(executionId)) throw new ArgumentException("Execution ID required", nameof(executionId));
|
|
|
|
TwapExecutionState executionState;
|
|
lock (_lock)
|
|
{
|
|
if (!_executions.ContainsKey(executionId))
|
|
return false;
|
|
|
|
executionState = _executions[executionId];
|
|
}
|
|
|
|
if (executionState.Status != TwapExecutionStatus.Running)
|
|
return false;
|
|
|
|
try
|
|
{
|
|
// Cancel all pending slices
|
|
foreach (var slice in executionState.ScheduledSlices)
|
|
{
|
|
if (slice.Status == TwapSliceStatus.Pending || slice.Status == TwapSliceStatus.Scheduled)
|
|
{
|
|
slice.Status = TwapSliceStatus.Cancelled;
|
|
slice.UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
// Cancel active orders for submitted slices
|
|
foreach (var slice in executionState.ScheduledSlices
|
|
.Where(s => s.Status == TwapSliceStatus.Submitted))
|
|
{
|
|
if (!string.IsNullOrEmpty(slice.OrderId))
|
|
{
|
|
await _orderManager.CancelOrderAsync(slice.OrderId);
|
|
}
|
|
slice.Status = TwapSliceStatus.Cancelled;
|
|
slice.UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
// Update execution state
|
|
executionState.Status = TwapExecutionStatus.Cancelled;
|
|
executionState.EndTime = DateTime.UtcNow;
|
|
executionState.UpdatedAt = DateTime.UtcNow;
|
|
|
|
// Clean up timers
|
|
lock (_lock)
|
|
{
|
|
foreach (var timer in _sliceTimers.Values)
|
|
{
|
|
timer.Dispose();
|
|
}
|
|
_sliceTimers.Clear();
|
|
}
|
|
|
|
_logger.LogInformation("TWAP execution {ExecutionId} cancelled", executionId);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error cancelling TWAP execution {ExecutionId}", executionId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get execution state
|
|
/// </summary>
|
|
public TwapExecutionState GetExecutionState(string executionId)
|
|
{
|
|
if (string.IsNullOrEmpty(executionId)) return null;
|
|
|
|
lock (_lock)
|
|
{
|
|
return _executions.ContainsKey(executionId) ?
|
|
new TwapExecutionState(_executions[executionId]) : null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all execution states
|
|
/// </summary>
|
|
public List<TwapExecutionState> GetAllExecutionStates()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return _executions.Values.Select(e => new TwapExecutionState(e)).ToList();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Integration with OrderManager
|
|
|
|
### TWAP Integration in OrderManager
|
|
```csharp
|
|
public partial class OrderManager : IOrderManager
|
|
{
|
|
private readonly TwapExecutor _twapExecutor;
|
|
|
|
// Enhanced constructor with TWAP executor
|
|
public OrderManager(
|
|
IRiskManager riskManager,
|
|
IPositionSizer positionSizer,
|
|
ILogger<OrderManager> logger,
|
|
RoutingConfigurationManager configManager,
|
|
RoutingMetricsCollector metricsCollector,
|
|
TwapExecutor twapExecutor) : base(riskManager, positionSizer, logger, configManager, metricsCollector)
|
|
{
|
|
_twapExecutor = twapExecutor ?? throw new ArgumentNullException(nameof(twapExecutor));
|
|
_venueManager = new VenueManager(logger);
|
|
_omsToVenueOrderIdMap = new Dictionary<string, string>();
|
|
_venueToOmsOrderIdMap = new Dictionary<string, string>();
|
|
|
|
// Initialize with configurations
|
|
InitializeWithConfigurationsAsync().Wait();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute a TWAP order
|
|
/// </summary>
|
|
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));
|
|
|
|
try
|
|
{
|
|
_logger.LogInformation("Executing TWAP order for {Symbol} {Side} {Quantity}",
|
|
parameters.Symbol, parameters.Side, parameters.TotalQuantity);
|
|
|
|
// Validate through risk management
|
|
var riskDecision = await ValidateTwapOrderAsync(parameters, context);
|
|
if (!riskDecision.Allow)
|
|
{
|
|
_logger.LogWarning("TWAP order rejected by risk management: {Reason}", riskDecision.RejectReason);
|
|
return new OrderResult(false, null, $"Risk validation failed: {riskDecision.RejectReason}", null);
|
|
}
|
|
|
|
// Execute TWAP
|
|
var executionState = await _twapExecutor.ExecuteTwapAsync(parameters, context);
|
|
|
|
// Create order result
|
|
var orderResult = new OrderResult(
|
|
Success: executionState.Status == TwapExecutionStatus.Completed,
|
|
OrderId: executionState.ExecutionId,
|
|
Message: executionState.Status == TwapExecutionStatus.Completed ?
|
|
"TWAP execution completed successfully" :
|
|
$"TWAP execution failed: {executionState.ErrorMessage}",
|
|
Status: ConvertToOrderStatus(executionState)
|
|
);
|
|
|
|
_logger.LogInformation("TWAP order execution {Result}: {Message}",
|
|
orderResult.Success ? "succeeded" : "failed", orderResult.Message);
|
|
|
|
return orderResult;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error executing TWAP order for {Symbol}", parameters.Symbol);
|
|
return new OrderResult(false, null, $"Error executing TWAP order: {ex.Message}", null);
|
|
}
|
|
}
|
|
|
|
private async Task<RiskDecision> ValidateTwapOrderAsync(TwapParameters parameters, StrategyContext context)
|
|
{
|
|
// Convert TWAP parameters to strategy intent for risk validation
|
|
var intent = new StrategyIntent(
|
|
Symbol: parameters.Symbol,
|
|
Side: ConvertOrderSide(parameters.Side),
|
|
EntryType: parameters.LimitPrice.HasValue ? OrderType.Limit : OrderType.Market,
|
|
LimitPrice: (double?)parameters.LimitPrice,
|
|
StopTicks: 10, // Placeholder - would calculate based on stop price
|
|
TargetTicks: null,
|
|
Confidence: 1.0,
|
|
Reason: "TWAP Algorithm Order",
|
|
Metadata: parameters.Metadata
|
|
);
|
|
|
|
// Get risk configuration
|
|
var config = new RiskConfig(1000, 200, 10, true); // Placeholder - would get from configuration
|
|
|
|
return _riskManager.ValidateOrder(intent, context, config);
|
|
}
|
|
|
|
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,
|
|
_ => throw new ArgumentException($"Unsupported order side: {side}")
|
|
};
|
|
}
|
|
|
|
private OrderStatus ConvertToOrderStatus(TwapExecutionState executionState)
|
|
{
|
|
if (executionState == null) return null;
|
|
|
|
var state = executionState.Status switch
|
|
{
|
|
TwapExecutionStatus.Pending => OrderState.New,
|
|
TwapExecutionStatus.Running => OrderState.Accepted,
|
|
TwapExecutionStatus.Completed => OrderState.Filled,
|
|
TwapExecutionStatus.Cancelled => OrderState.Cancelled,
|
|
TwapExecutionStatus.Failed => OrderState.Rejected,
|
|
_ => OrderState.Unknown
|
|
};
|
|
|
|
// Combine all fills from completed slices
|
|
var allFills = executionState.CompletedSlices.SelectMany(s => s.Fills).ToList();
|
|
|
|
return new OrderStatus(
|
|
OrderId: executionState.ExecutionId,
|
|
Symbol: executionState.Parameters.Symbol,
|
|
Side: executionState.Parameters.Side,
|
|
Type: executionState.Parameters.LimitPrice.HasValue ? OrderType.Limit : OrderType.Market,
|
|
Quantity: executionState.TotalQuantity,
|
|
FilledQuantity: executionState.ExecutedQuantity,
|
|
LimitPrice: executionState.Parameters.LimitPrice,
|
|
StopPrice: null,
|
|
State: state,
|
|
CreatedTime: executionState.CreatedAt,
|
|
FilledTime: executionState.EndTime,
|
|
Fills: allFills
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel a TWAP execution
|
|
/// </summary>
|
|
public async Task<bool> CancelTwapAsync(string executionId)
|
|
{
|
|
if (string.IsNullOrEmpty(executionId)) throw new ArgumentException("Execution ID required", nameof(executionId));
|
|
|
|
return await _twapExecutor.CancelExecutionAsync(executionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get TWAP execution state
|
|
/// </summary>
|
|
public TwapExecutionState GetTwapExecutionState(string executionId)
|
|
{
|
|
if (string.IsNullOrEmpty(executionId)) return null;
|
|
|
|
return _twapExecutor.GetExecutionState(executionId);
|
|
}
|
|
}
|
|
```
|
|
|
|
## TWAP Configuration Management
|
|
|
|
### TWAP Configuration Integration
|
|
```csharp
|
|
public partial class RoutingConfigurationManager
|
|
{
|
|
/// <summary>
|
|
/// Get TWAP configuration
|
|
/// </summary>
|
|
public async Task<TwapConfig> GetTwapConfigAsync()
|
|
{
|
|
var config = await GetConfigurationAsync<TwapConfig>("twap-config");
|
|
return config ?? TwapConfig.Default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update TWAP configuration
|
|
/// </summary>
|
|
public async Task UpdateTwapConfigAsync(TwapConfig config)
|
|
{
|
|
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
|
|
config.Id = "twap-config";
|
|
config.Name = "TWAP Configuration";
|
|
config.Description = "Configuration for TWAP algorithm behavior";
|
|
|
|
await UpdateConfigurationAsync(config);
|
|
_logger.LogInformation("TWAP configuration updated");
|
|
}
|
|
}
|
|
```
|
|
|
|
## Testing Considerations
|
|
|
|
### Unit Tests for TWAP Algorithm
|
|
1. **Slice Calculation**: Test calculation of slices for different parameters
|
|
2. **Parameter Validation**: Test validation of TWAP parameters
|
|
3. **Execution Scheduling**: Test scheduling of slice executions
|
|
4. **Slice Execution**: Test execution of individual slices
|
|
5. **Error Handling**: Test handling of execution errors and retries
|
|
6. **Cancellation**: Test cancellation of TWAP executions
|
|
7. **Metrics Collection**: Test collection of performance metrics
|
|
|
|
### Integration Tests
|
|
1. **End-to-End Execution**: Test complete TWAP execution from start to finish
|
|
2. **Order Manager Integration**: Test integration with OrderManager
|
|
3. **Risk Management Integration**: Test risk validation for TWAP orders
|
|
4. **Performance Testing**: Test performance with large numbers of slices
|
|
5. **Concurrent Executions**: Test multiple concurrent TWAP executions
|
|
|
|
## Performance Considerations
|
|
|
|
### Memory Management
|
|
```csharp
|
|
/// <summary>
|
|
/// Manages memory usage for TWAP executions
|
|
/// </summary>
|
|
public class TwapMemoryManager
|
|
{
|
|
private readonly int _maxExecutions;
|
|
private readonly TimeSpan _executionRetentionTime;
|
|
private readonly Dictionary<string, DateTime> _executionAccessTimes;
|
|
private readonly object _lock = new object();
|
|
|
|
public TwapMemoryManager(int maxExecutions = 1000, TimeSpan retentionTime = default)
|
|
{
|
|
_maxExecutions = maxExecutions;
|
|
_executionRetentionTime = retentionTime == default ?
|
|
TimeSpan.FromHours(24) : retentionTime;
|
|
_executionAccessTimes = new Dictionary<string, DateTime>();
|
|
}
|
|
|
|
public void RecordExecutionAccess(string executionId)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_executionAccessTimes[executionId] = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
public List<string> GetExpiredExecutions()
|
|
{
|
|
var cutoffTime = DateTime.UtcNow.Subtract(_executionRetentionTime);
|
|
|
|
lock (_lock)
|
|
{
|
|
return _executionAccessTimes
|
|
.Where(kvp => kvp.Value < cutoffTime)
|
|
.Select(kvp => kvp.Key)
|
|
.ToList();
|
|
}
|
|
}
|
|
|
|
public bool IsMemoryPressureHigh(int currentExecutionCount)
|
|
{
|
|
return currentExecutionCount > (_maxExecutions * 0.8); // 80% threshold
|
|
}
|
|
}
|
|
```
|
|
|
|
### Adaptive TWAP
|
|
```csharp
|
|
/// <summary>
|
|
/// Adaptive TWAP that adjusts based on market conditions
|
|
/// </summary>
|
|
public class AdaptiveTwapExecutor : TwapExecutor
|
|
{
|
|
private readonly IMarketDataProvider _marketDataProvider;
|
|
|
|
public AdaptiveTwapExecutor(
|
|
ILogger<TwapExecutor> logger,
|
|
IOrderManager orderManager,
|
|
IMarketDataProvider marketDataProvider,
|
|
TwapConfig config = null) : base(logger, orderManager, config)
|
|
{
|
|
_marketDataProvider = marketDataProvider ?? throw new ArgumentNullException(nameof(marketDataProvider));
|
|
}
|
|
|
|
protected override List<TwapSlice> CalculateSlices(TwapParameters parameters)
|
|
{
|
|
// Get current market data
|
|
var marketData = _marketDataProvider.GetCurrentPrice(parameters.Symbol);
|
|
|
|
if (marketData.HasValue && _config.EnableAdaptiveSlicing)
|
|
{
|
|
// Adjust slice sizes based on volatility
|
|
var volatility = CalculateVolatility(parameters.Symbol);
|
|
return CalculateAdaptiveSlices(parameters, volatility);
|
|
}
|
|
|
|
// Fall back to standard calculation
|
|
return base.CalculateSlices(parameters);
|
|
}
|
|
|
|
private decimal CalculateVolatility(string symbol)
|
|
{
|
|
// Simplified volatility calculation
|
|
// In a real implementation, this would use historical price data
|
|
return 0.01m; // 1% volatility
|
|
}
|
|
|
|
private List<TwapSlice> CalculateAdaptiveSlices(TwapParameters parameters, decimal volatility)
|
|
{
|
|
// Adjust slice frequency based on volatility
|
|
// Higher volatility = more frequent, smaller slices
|
|
var adjustedInterval = (int)(parameters.IntervalSeconds * (1 - (double)volatility)));
|
|
if (adjustedInterval < 10) adjustedInterval = 10; // Minimum 10 seconds
|
|
|
|
// Create adjusted parameters
|
|
var adjustedParameters = new TwapParameters
|
|
{
|
|
Symbol = parameters.Symbol,
|
|
Side = parameters.Side,
|
|
TotalQuantity = parameters.TotalQuantity,
|
|
StartTime = parameters.StartTime,
|
|
EndTime = parameters.EndTime,
|
|
IntervalSeconds = adjustedInterval,
|
|
LimitPrice = parameters.LimitPrice,
|
|
TimeInForce = parameters.TimeInForce,
|
|
MinSliceSize = parameters.MinSliceSize,
|
|
MaxSliceSize = parameters.MaxSliceSize
|
|
};
|
|
|
|
return base.CalculateSlices(adjustedParameters);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Monitoring and Alerting
|
|
|
|
### TWAP Metrics Export
|
|
```csharp
|
|
/// <summary>
|
|
/// Exports TWAP metrics for monitoring
|
|
/// </summary>
|
|
public class TwapMetricsExporter
|
|
{
|
|
private readonly TwapExecutor _twapExecutor;
|
|
|
|
public TwapMetricsExporter(TwapExecutor twapExecutor)
|
|
{
|
|
_twapExecutor = twapExecutor ?? throw new ArgumentNullException(nameof(twapExecutor));
|
|
}
|
|
|
|
public string ExportToPrometheus()
|
|
{
|
|
var executions = _twapExecutor.GetAllExecutionStates();
|
|
var sb = new StringBuilder();
|
|
|
|
// Overall TWAP metrics
|
|
var activeExecutions = executions.Count(e => e.Status == TwapExecutionStatus.Running);
|
|
var completedExecutions = executions.Count(e => e.Status == TwapExecutionStatus.Completed);
|
|
var failedExecutions = executions.Count(e => e.Status == TwapExecutionStatus.Failed);
|
|
|
|
sb.AppendLine($"# HELP twap_active_executions Number of active TWAP executions");
|
|
sb.AppendLine($"# TYPE twap_active_executions gauge");
|
|
sb.AppendLine($"twap_active_executions {activeExecutions}");
|
|
|
|
sb.AppendLine($"# HELP twap_completed_executions Total number of completed TWAP executions");
|
|
sb.AppendLine($"# TYPE twap_completed_executions counter");
|
|
sb.AppendLine($"twap_completed_executions {completedExecutions}");
|
|
|
|
sb.AppendLine($"# HELP twap_failed_executions Total number of failed TWAP executions");
|
|
sb.AppendLine($"# TYPE twap_failed_executions counter");
|
|
sb.AppendLine($"twap_failed_executions {failedExecutions}");
|
|
|
|
// Performance metrics for completed executions
|
|
var completed = executions.Where(e => e.Status == TwapExecutionStatus.Completed).ToList();
|
|
if (completed.Any())
|
|
{
|
|
var avgSlippage = completed.Average(e => (double)e.PerformanceMetrics.AverageSlippage);
|
|
var avgCommission = (double)completed.Average(e => e.PerformanceMetrics.TotalCommission);
|
|
|
|
sb.AppendLine($"# HELP twap_average_slippage Average slippage for completed executions");
|
|
sb.AppendLine($"# TYPE twap_average_slippage gauge");
|
|
sb.AppendLine($"twap_average_slippage {avgSlippage:F4}");
|
|
|
|
sb.AppendLine($"# HELP twap_average_commission Average commission for completed executions");
|
|
sb.AppendLine($"# TYPE twap_average_commission gauge");
|
|
sb.AppendLine($"twap_average_commission {avgCommission:F2}");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Machine Learning Optimization**: Use ML to optimize TWAP parameters based on historical performance
|
|
2. **Real-time Market Adaptation**: Adjust TWAP execution based on real-time market conditions
|
|
3. **Cross-Venue TWAP**: Execute TWAP orders across multiple venues simultaneously
|
|
4. **TWAP Analytics**: Advanced analytics and reporting on TWAP performance
|
|
5. **TWAP Strategy Builder**: Visual tools for building and testing TWAP strategies
|
|
6. **TWAP Benchmarking**: Compare TWAP performance against other execution algorithms
|
|
7. **TWAP Risk Controls**: Enhanced risk controls specific to algorithmic execution
|
|
8. **TWAP Compliance**: Ensure TWAP execution complies with regulatory requirements
|