963 lines
32 KiB
MQL5
963 lines
32 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| ExecutionOptimizer.mqh |
|
||
|
//| Smart Order Routing and Execution |
|
||
|
//| Optimal Execution Algorithms & Strategies |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#ifndef EXECUTION_OPTIMIZER_MQH
|
||
|
#define EXECUTION_OPTIMIZER_MQH
|
||
|
|
||
|
#include "DataTypes_v71.mqh"
|
||
|
#include <Trade/Trade.mqh>
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execution Venue |
|
||
|
//+------------------------------------------------------------------+
|
||
|
struct ExecutionVenue
|
||
|
{
|
||
|
string name;
|
||
|
double liquidity_score;
|
||
|
double avg_spread;
|
||
|
double avg_latency_ms;
|
||
|
double fill_rate;
|
||
|
double price_improvement_rate;
|
||
|
bool is_available;
|
||
|
int priority;
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execution Schedule (for TWAP/VWAP) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
struct ExecutionSchedule
|
||
|
{
|
||
|
datetime start_time;
|
||
|
datetime end_time;
|
||
|
int slices;
|
||
|
double slice_sizes[];
|
||
|
datetime slice_times[];
|
||
|
bool is_adaptive;
|
||
|
double urgency_factor;
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execution Analytics |
|
||
|
//+------------------------------------------------------------------+
|
||
|
struct ExecutionAnalytics
|
||
|
{
|
||
|
double implementation_shortfall;
|
||
|
double arrival_price;
|
||
|
double avg_fill_price;
|
||
|
double vwap_tracking_error;
|
||
|
double timing_risk;
|
||
|
double market_impact;
|
||
|
double opportunity_cost;
|
||
|
int fills_count;
|
||
|
double fill_rate;
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execution Optimizer Class |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CExecutionOptimizer
|
||
|
{
|
||
|
private:
|
||
|
//--- Configuration
|
||
|
ENUM_EXECUTION_ALGO m_default_algo;
|
||
|
double m_max_slippage_pips;
|
||
|
double m_urgency_threshold;
|
||
|
bool m_use_dark_pools;
|
||
|
|
||
|
//--- Execution venues
|
||
|
ExecutionVenue m_venues[];
|
||
|
int m_venue_count;
|
||
|
|
||
|
//--- Active executions
|
||
|
struct ActiveExecution
|
||
|
{
|
||
|
ulong order_id;
|
||
|
ExecutionPlan plan;
|
||
|
ExecutionSchedule schedule;
|
||
|
double filled_volume;
|
||
|
double avg_fill_price;
|
||
|
datetime start_time;
|
||
|
int retry_count;
|
||
|
bool is_complete;
|
||
|
};
|
||
|
ActiveExecution m_active_executions[];
|
||
|
|
||
|
//--- Analytics tracking
|
||
|
ExecutionAnalytics m_analytics;
|
||
|
double m_total_volume_executed;
|
||
|
|
||
|
//--- Market impact model
|
||
|
double m_impact_coefficient;
|
||
|
double m_temporary_impact;
|
||
|
double m_permanent_impact;
|
||
|
|
||
|
//--- Helper methods
|
||
|
double EstimateMarketImpact(string symbol, double volume, double urgency);
|
||
|
double CalculateOptimalSliceSize(string symbol, double total_volume, double time_horizon);
|
||
|
ExecutionVenue SelectBestVenue(string symbol, double volume);
|
||
|
void UpdateVenueMetrics(ExecutionVenue &venue, double latency, bool success);
|
||
|
double GetMarketVolatility(string symbol);
|
||
|
double GetMarketDepth(string symbol, bool is_buy);
|
||
|
|
||
|
//--- Execution algorithms
|
||
|
bool ExecuteWithTWAP(ExecutionPlan &plan);
|
||
|
bool ExecuteWithVWAP(ExecutionPlan &plan);
|
||
|
bool ExecuteWithAdaptive(ExecutionPlan &plan);
|
||
|
bool ExecuteWithSniper(ExecutionPlan &plan);
|
||
|
bool ExecuteWithIceberg(ExecutionPlan &plan);
|
||
|
|
||
|
public:
|
||
|
CExecutionOptimizer();
|
||
|
~CExecutionOptimizer();
|
||
|
|
||
|
//--- Initialization
|
||
|
bool Initialize(ENUM_EXECUTION_ALGO default_algo, double max_slippage);
|
||
|
void AddVenue(string name, double liquidity, double spread);
|
||
|
|
||
|
//--- Execution planning
|
||
|
ExecutionPlan CreatePlan(string symbol, ENUM_ORDER_TYPE direction,
|
||
|
double volume, double urgency = 0.5);
|
||
|
ExecutionSchedule CreateSchedule(ExecutionPlan &plan, int slices);
|
||
|
|
||
|
//--- Smart execution
|
||
|
bool ExecuteOrder(ExecutionPlan &plan);
|
||
|
bool ExecuteSlice(ActiveExecution &execution);
|
||
|
void ProcessActiveExecutions();
|
||
|
|
||
|
//--- Emergency operations
|
||
|
bool ExecuteEmergencyClose(ulong ticket);
|
||
|
void CancelAllPendingExecutions();
|
||
|
|
||
|
//--- Analytics
|
||
|
double GetImplementationShortfall() { return m_analytics.implementation_shortfall; }
|
||
|
double GetVWAPTrackingError() { return m_analytics.vwap_tracking_error; }
|
||
|
double GetAverageFillPrice() { return m_analytics.avg_fill_price; }
|
||
|
ExecutionAnalytics GetAnalytics() { return m_analytics; }
|
||
|
|
||
|
//--- Optimization
|
||
|
ENUM_EXECUTION_ALGO SelectOptimalAlgorithm(string symbol, double volume,
|
||
|
double urgency, MarketConditionsV71 &conditions);
|
||
|
double OptimizeSlicing(string symbol, double volume, double volatility);
|
||
|
|
||
|
//--- Market impact
|
||
|
double PredictMarketImpact(string symbol, double volume, double participation_rate);
|
||
|
double GetOptimalParticipationRate(string symbol, double urgency);
|
||
|
|
||
|
//--- Venue management
|
||
|
void UpdateVenueAvailability(string venue_name, bool available);
|
||
|
void RebalanceVenuePriorities();
|
||
|
};
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CExecutionOptimizer::CExecutionOptimizer()
|
||
|
{
|
||
|
m_default_algo = EXEC_ADAPTIVE;
|
||
|
m_max_slippage_pips = 2.0;
|
||
|
m_urgency_threshold = 0.7;
|
||
|
m_use_dark_pools = true;
|
||
|
m_venue_count = 0;
|
||
|
m_total_volume_executed = 0;
|
||
|
m_impact_coefficient = 0.1;
|
||
|
m_temporary_impact = 0.5;
|
||
|
m_permanent_impact = 0.3;
|
||
|
|
||
|
//--- Initialize analytics
|
||
|
m_analytics.implementation_shortfall = 0;
|
||
|
m_analytics.arrival_price = 0;
|
||
|
m_analytics.avg_fill_price = 0;
|
||
|
m_analytics.vwap_tracking_error = 0;
|
||
|
m_analytics.timing_risk = 0;
|
||
|
m_analytics.market_impact = 0;
|
||
|
m_analytics.opportunity_cost = 0;
|
||
|
m_analytics.fills_count = 0;
|
||
|
m_analytics.fill_rate = 0;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CExecutionOptimizer::~CExecutionOptimizer()
|
||
|
{
|
||
|
ArrayFree(m_venues);
|
||
|
ArrayFree(m_active_executions);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Initialize optimizer |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::Initialize(ENUM_EXECUTION_ALGO default_algo, double max_slippage)
|
||
|
{
|
||
|
m_default_algo = default_algo;
|
||
|
m_max_slippage_pips = max_slippage;
|
||
|
|
||
|
//--- Add default venues
|
||
|
AddVenue("Primary", 0.8, 0.1);
|
||
|
AddVenue("ECN", 0.9, 0.05);
|
||
|
AddVenue("Dark", 0.6, 0.02);
|
||
|
|
||
|
//--- Initialize active executions array
|
||
|
ArrayResize(m_active_executions, 100);
|
||
|
|
||
|
Print("ExecutionOptimizer initialized: Algorithm=", EnumToString(m_default_algo),
|
||
|
", MaxSlippage=", m_max_slippage_pips);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Add execution venue |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CExecutionOptimizer::AddVenue(string name, double liquidity, double spread)
|
||
|
{
|
||
|
if(m_venue_count >= ArraySize(m_venues))
|
||
|
ArrayResize(m_venues, m_venue_count + 10);
|
||
|
|
||
|
m_venues[m_venue_count].name = name;
|
||
|
m_venues[m_venue_count].liquidity_score = liquidity;
|
||
|
m_venues[m_venue_count].avg_spread = spread;
|
||
|
m_venues[m_venue_count].avg_latency_ms = 10; // Default
|
||
|
m_venues[m_venue_count].fill_rate = 0.95;
|
||
|
m_venues[m_venue_count].price_improvement_rate = 0.3;
|
||
|
m_venues[m_venue_count].is_available = true;
|
||
|
m_venues[m_venue_count].priority = m_venue_count + 1;
|
||
|
|
||
|
m_venue_count++;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Create execution plan |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionPlan CExecutionOptimizer::CreatePlan(string symbol, ENUM_ORDER_TYPE direction,
|
||
|
double volume, double urgency)
|
||
|
{
|
||
|
ExecutionPlan plan;
|
||
|
plan.symbol = symbol;
|
||
|
plan.direction = direction;
|
||
|
plan.total_volume = volume;
|
||
|
plan.urgency = urgency;
|
||
|
|
||
|
//--- Determine execution algorithm
|
||
|
if(urgency > m_urgency_threshold)
|
||
|
{
|
||
|
plan.algorithm = EXEC_SNIPER; // Aggressive for urgent orders
|
||
|
}
|
||
|
else if(volume > SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN) * 50)
|
||
|
{
|
||
|
plan.algorithm = EXEC_ICEBERG; // Hide large orders
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plan.algorithm = m_default_algo;
|
||
|
}
|
||
|
|
||
|
//--- Calculate display volume for iceberg
|
||
|
if(plan.algorithm == EXEC_ICEBERG)
|
||
|
{
|
||
|
plan.display_volume = volume * 0.2; // Show 20%
|
||
|
plan.reserve_volume = volume * 0.8;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plan.display_volume = volume;
|
||
|
plan.reserve_volume = 0;
|
||
|
}
|
||
|
|
||
|
//--- Set price limits
|
||
|
double current_price = (direction == ORDER_TYPE_BUY) ?
|
||
|
SymbolInfoDouble(symbol, SYMBOL_ASK) :
|
||
|
SymbolInfoDouble(symbol, SYMBOL_BID);
|
||
|
|
||
|
double slippage = m_max_slippage_pips * SymbolInfoDouble(symbol, SYMBOL_POINT) * 10;
|
||
|
|
||
|
if(direction == ORDER_TYPE_BUY)
|
||
|
{
|
||
|
plan.limit_price = current_price + slippage;
|
||
|
plan.stop_price = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plan.limit_price = current_price - slippage;
|
||
|
plan.stop_price = 0;
|
||
|
}
|
||
|
|
||
|
//--- Calculate time horizon based on urgency
|
||
|
plan.time_horizon = (int)((1 - urgency) * 300 + 60); // 1-6 minutes
|
||
|
|
||
|
//--- Dark pool usage
|
||
|
plan.use_dark_pool = m_use_dark_pools && (volume > SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN) * 20);
|
||
|
|
||
|
//--- Maximum spread
|
||
|
plan.max_spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;
|
||
|
|
||
|
return plan;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute order with smart routing |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteOrder(ExecutionPlan &plan)
|
||
|
{
|
||
|
//--- Route to appropriate algorithm
|
||
|
switch(plan.algorithm)
|
||
|
{
|
||
|
case EXEC_MARKET:
|
||
|
{
|
||
|
CTrade trade;
|
||
|
trade.SetDeviationInPoints((int)(m_max_slippage_pips * 10));
|
||
|
|
||
|
if(plan.direction == ORDER_TYPE_BUY)
|
||
|
return trade.Buy(plan.total_volume, plan.symbol);
|
||
|
else
|
||
|
return trade.Sell(plan.total_volume, plan.symbol);
|
||
|
}
|
||
|
|
||
|
case EXEC_TWAP:
|
||
|
return ExecuteWithTWAP(plan);
|
||
|
|
||
|
case EXEC_VWAP:
|
||
|
return ExecuteWithVWAP(plan);
|
||
|
|
||
|
case EXEC_ADAPTIVE:
|
||
|
return ExecuteWithAdaptive(plan);
|
||
|
|
||
|
case EXEC_ICEBERG:
|
||
|
return ExecuteWithIceberg(plan);
|
||
|
|
||
|
case EXEC_SNIPER:
|
||
|
return ExecuteWithSniper(plan);
|
||
|
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute with adaptive algorithm |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteWithAdaptive(ExecutionPlan &plan)
|
||
|
{
|
||
|
//--- Analyze current market conditions
|
||
|
double volatility = GetMarketVolatility(plan.symbol);
|
||
|
double depth = GetMarketDepth(plan.symbol, plan.direction == ORDER_TYPE_BUY);
|
||
|
|
||
|
//--- Create adaptive execution
|
||
|
ActiveExecution exec;
|
||
|
exec.order_id = 0;
|
||
|
exec.plan = plan;
|
||
|
exec.filled_volume = 0;
|
||
|
exec.avg_fill_price = 0;
|
||
|
exec.start_time = TimeCurrent();
|
||
|
exec.retry_count = 0;
|
||
|
exec.is_complete = false;
|
||
|
|
||
|
//--- Determine optimal slice size
|
||
|
double optimal_slice = CalculateOptimalSliceSize(plan.symbol, plan.total_volume, plan.time_horizon);
|
||
|
|
||
|
//--- Create adaptive schedule
|
||
|
int slices = (int)(plan.total_volume / optimal_slice) + 1;
|
||
|
exec.schedule = CreateSchedule(plan, slices);
|
||
|
exec.schedule.is_adaptive = true;
|
||
|
|
||
|
//--- Adjust for market conditions
|
||
|
if(volatility > 0.02) // High volatility
|
||
|
{
|
||
|
exec.schedule.urgency_factor = MathMin(1.0, plan.urgency * 1.2);
|
||
|
exec.schedule.slices = MathMax(3, slices / 2); // Fewer, larger slices
|
||
|
}
|
||
|
else if(depth < 10000) // Low liquidity
|
||
|
{
|
||
|
exec.schedule.urgency_factor = plan.urgency * 0.8;
|
||
|
exec.schedule.slices = slices * 2; // More, smaller slices
|
||
|
}
|
||
|
|
||
|
//--- Store active execution
|
||
|
for(int i = 0; i < ArraySize(m_active_executions); i++)
|
||
|
{
|
||
|
if(!m_active_executions[i].is_complete)
|
||
|
{
|
||
|
m_active_executions[i] = exec;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Execute first slice immediately
|
||
|
return ExecuteSlice(exec);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute with TWAP algorithm |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteWithTWAP(ExecutionPlan &plan)
|
||
|
{
|
||
|
//--- Create TWAP execution
|
||
|
ActiveExecution exec;
|
||
|
exec.order_id = 0;
|
||
|
exec.plan = plan;
|
||
|
exec.filled_volume = 0;
|
||
|
exec.avg_fill_price = 0;
|
||
|
exec.start_time = TimeCurrent();
|
||
|
exec.retry_count = 0;
|
||
|
exec.is_complete = false;
|
||
|
|
||
|
//--- Create time-weighted schedule
|
||
|
int slices = MathMax(5, plan.time_horizon / 30); // One slice per 30 seconds
|
||
|
exec.schedule = CreateSchedule(plan, slices);
|
||
|
|
||
|
//--- Equal-sized slices for TWAP
|
||
|
double slice_size = plan.total_volume / slices;
|
||
|
ArrayResize(exec.schedule.slice_sizes, slices);
|
||
|
ArrayResize(exec.schedule.slice_times, slices);
|
||
|
|
||
|
for(int i = 0; i < slices; i++)
|
||
|
{
|
||
|
exec.schedule.slice_sizes[i] = slice_size;
|
||
|
exec.schedule.slice_times[i] = exec.schedule.start_time +
|
||
|
(i * plan.time_horizon / slices);
|
||
|
}
|
||
|
|
||
|
//--- Store and execute
|
||
|
for(int i = 0; i < ArraySize(m_active_executions); i++)
|
||
|
{
|
||
|
if(!m_active_executions[i].is_complete)
|
||
|
{
|
||
|
m_active_executions[i] = exec;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ExecuteSlice(exec);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute with VWAP algorithm |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteWithVWAP(ExecutionPlan &plan)
|
||
|
{
|
||
|
//--- Get historical volume profile
|
||
|
long volumes[24];
|
||
|
datetime current = TimeCurrent();
|
||
|
MqlDateTime time_struct;
|
||
|
TimeToStruct(current, time_struct);
|
||
|
|
||
|
//--- Estimate intraday volume distribution
|
||
|
double total_hist_volume = 0;
|
||
|
for(int i = 0; i < 24; i++)
|
||
|
{
|
||
|
// Simplified U-shaped volume profile
|
||
|
if(i >= 8 && i <= 16) // Market hours
|
||
|
{
|
||
|
volumes[i] = (long)(100 * (1 + MathSin((i - 12) * M_PI / 8)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
volumes[i] = 10;
|
||
|
}
|
||
|
total_hist_volume += volumes[i];
|
||
|
}
|
||
|
|
||
|
//--- Create VWAP execution
|
||
|
ActiveExecution exec;
|
||
|
exec.order_id = 0;
|
||
|
exec.plan = plan;
|
||
|
exec.filled_volume = 0;
|
||
|
exec.avg_fill_price = 0;
|
||
|
exec.start_time = TimeCurrent();
|
||
|
exec.retry_count = 0;
|
||
|
exec.is_complete = false;
|
||
|
|
||
|
//--- Create volume-weighted schedule
|
||
|
int slices = MathMin(plan.time_horizon / 60, 10); // Max 10 slices
|
||
|
exec.schedule = CreateSchedule(plan, slices);
|
||
|
|
||
|
ArrayResize(exec.schedule.slice_sizes, slices);
|
||
|
ArrayResize(exec.schedule.slice_times, slices);
|
||
|
|
||
|
//--- Distribute volume according to historical pattern
|
||
|
for(int i = 0; i < slices; i++)
|
||
|
{
|
||
|
int hour = (time_struct.hour + i * plan.time_horizon / slices / 3600) % 24;
|
||
|
double weight = volumes[hour] / total_hist_volume;
|
||
|
exec.schedule.slice_sizes[i] = plan.total_volume * weight / slices;
|
||
|
exec.schedule.slice_times[i] = exec.schedule.start_time +
|
||
|
(i * plan.time_horizon / slices);
|
||
|
}
|
||
|
|
||
|
//--- Store and execute
|
||
|
for(int i = 0; i < ArraySize(m_active_executions); i++)
|
||
|
{
|
||
|
if(!m_active_executions[i].is_complete)
|
||
|
{
|
||
|
m_active_executions[i] = exec;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ExecuteSlice(exec);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute with iceberg algorithm |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteWithIceberg(ExecutionPlan &plan)
|
||
|
{
|
||
|
//--- Create iceberg execution
|
||
|
ActiveExecution exec;
|
||
|
exec.order_id = 0;
|
||
|
exec.plan = plan;
|
||
|
exec.filled_volume = 0;
|
||
|
exec.avg_fill_price = 0;
|
||
|
exec.start_time = TimeCurrent();
|
||
|
exec.retry_count = 0;
|
||
|
exec.is_complete = false;
|
||
|
|
||
|
//--- Calculate visible and hidden portions
|
||
|
double visible_size = plan.display_volume;
|
||
|
int total_slices = (int)(plan.total_volume / visible_size) + 1;
|
||
|
|
||
|
exec.schedule = CreateSchedule(plan, total_slices);
|
||
|
exec.schedule.is_adaptive = true; // Adaptive timing between slices
|
||
|
|
||
|
//--- Store and execute first visible slice
|
||
|
for(int i = 0; i < ArraySize(m_active_executions); i++)
|
||
|
{
|
||
|
if(!m_active_executions[i].is_complete)
|
||
|
{
|
||
|
m_active_executions[i] = exec;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ExecuteSlice(exec);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute with sniper algorithm (aggressive) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteWithSniper(ExecutionPlan &plan)
|
||
|
{
|
||
|
//--- Sniper executes immediately with wide slippage tolerance
|
||
|
CTrade trade;
|
||
|
trade.SetDeviationInPoints((int)(m_max_slippage_pips * 20)); // 10x normal slippage
|
||
|
trade.SetTypeFilling(ORDER_FILLING_IOC); // Immediate or cancel
|
||
|
|
||
|
//--- Try to execute full size
|
||
|
bool result = false;
|
||
|
|
||
|
if(plan.direction == ORDER_TYPE_BUY)
|
||
|
{
|
||
|
result = trade.Buy(plan.total_volume, plan.symbol);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = trade.Sell(plan.total_volume, plan.symbol);
|
||
|
}
|
||
|
|
||
|
//--- If partial fill, try again with remaining
|
||
|
if(result && trade.ResultVolume() < plan.total_volume)
|
||
|
{
|
||
|
double remaining = plan.total_volume - trade.ResultVolume();
|
||
|
|
||
|
//--- Second attempt
|
||
|
if(plan.direction == ORDER_TYPE_BUY)
|
||
|
trade.Buy(remaining, plan.symbol);
|
||
|
else
|
||
|
trade.Sell(remaining, plan.symbol);
|
||
|
}
|
||
|
|
||
|
//--- Update analytics
|
||
|
if(result)
|
||
|
{
|
||
|
m_analytics.fills_count++;
|
||
|
m_analytics.avg_fill_price = trade.ResultPrice();
|
||
|
m_total_volume_executed += trade.ResultVolume();
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute a single slice |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteSlice(ActiveExecution &execution)
|
||
|
{
|
||
|
//--- Check if execution is complete
|
||
|
if(execution.filled_volume >= execution.plan.total_volume * 0.99)
|
||
|
{
|
||
|
execution.is_complete = true;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//--- Select best venue
|
||
|
ExecutionVenue venue = SelectBestVenue(execution.plan.symbol,
|
||
|
execution.plan.total_volume - execution.filled_volume);
|
||
|
|
||
|
//--- Calculate slice size
|
||
|
double slice_size = execution.plan.display_volume;
|
||
|
if(execution.schedule.slices > 0 && execution.schedule.slice_sizes != NULL)
|
||
|
{
|
||
|
int current_slice = (int)(execution.filled_volume / (execution.plan.total_volume / execution.schedule.slices));
|
||
|
if(current_slice < ArraySize(execution.schedule.slice_sizes))
|
||
|
slice_size = execution.schedule.slice_sizes[current_slice];
|
||
|
}
|
||
|
|
||
|
//--- Ensure we don't over-execute
|
||
|
slice_size = MathMin(slice_size, execution.plan.total_volume - execution.filled_volume);
|
||
|
|
||
|
//--- Normalize to lot step
|
||
|
double lot_step = SymbolInfoDouble(execution.plan.symbol, SYMBOL_VOLUME_STEP);
|
||
|
slice_size = MathRound(slice_size / lot_step) * lot_step;
|
||
|
|
||
|
//--- Execute slice
|
||
|
CTrade trade;
|
||
|
trade.SetDeviationInPoints((int)(m_max_slippage_pips * 10));
|
||
|
|
||
|
ulong start_time = GetMicrosecondCount();
|
||
|
bool result = false;
|
||
|
|
||
|
if(execution.plan.direction == ORDER_TYPE_BUY)
|
||
|
result = trade.Buy(slice_size, execution.plan.symbol);
|
||
|
else
|
||
|
result = trade.Sell(slice_size, execution.plan.symbol);
|
||
|
|
||
|
ulong execution_time = GetMicrosecondCount() - start_time;
|
||
|
|
||
|
//--- Update execution tracking
|
||
|
if(result)
|
||
|
{
|
||
|
double fill_price = trade.ResultPrice();
|
||
|
double fill_volume = trade.ResultVolume();
|
||
|
|
||
|
//--- Update average fill price
|
||
|
execution.avg_fill_price = (execution.avg_fill_price * execution.filled_volume +
|
||
|
fill_price * fill_volume) /
|
||
|
(execution.filled_volume + fill_volume);
|
||
|
|
||
|
execution.filled_volume += fill_volume;
|
||
|
|
||
|
//--- Update venue metrics
|
||
|
UpdateVenueMetrics(venue, execution_time / 1000.0, true);
|
||
|
|
||
|
//--- Update analytics
|
||
|
m_analytics.fills_count++;
|
||
|
m_total_volume_executed += fill_volume;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
execution.retry_count++;
|
||
|
UpdateVenueMetrics(venue, execution_time / 1000.0, false);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Select best execution venue |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionVenue CExecutionOptimizer::SelectBestVenue(string symbol, double volume)
|
||
|
{
|
||
|
//--- Score each venue
|
||
|
double best_score = -1;
|
||
|
int best_index = 0;
|
||
|
|
||
|
for(int i = 0; i < m_venue_count; i++)
|
||
|
{
|
||
|
if(!m_venues[i].is_available)
|
||
|
continue;
|
||
|
|
||
|
//--- Calculate venue score
|
||
|
double score = 0;
|
||
|
|
||
|
//--- Liquidity component (40%)
|
||
|
score += m_venues[i].liquidity_score * 0.4;
|
||
|
|
||
|
//--- Spread component (30%)
|
||
|
double spread_score = 1.0 - m_venues[i].avg_spread;
|
||
|
score += spread_score * 0.3;
|
||
|
|
||
|
//--- Latency component (20%)
|
||
|
double latency_score = 1.0 - (m_venues[i].avg_latency_ms / 100.0);
|
||
|
score += MathMax(0, latency_score) * 0.2;
|
||
|
|
||
|
//--- Fill rate component (10%)
|
||
|
score += m_venues[i].fill_rate * 0.1;
|
||
|
|
||
|
//--- Adjust for volume
|
||
|
if(volume > 10000) // Large order
|
||
|
{
|
||
|
score *= m_venues[i].liquidity_score; // Favor high liquidity venues
|
||
|
}
|
||
|
|
||
|
if(score > best_score)
|
||
|
{
|
||
|
best_score = score;
|
||
|
best_index = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return m_venues[best_index];
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Update venue metrics after execution |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CExecutionOptimizer::UpdateVenueMetrics(ExecutionVenue &venue, double latency, bool success)
|
||
|
{
|
||
|
//--- Update latency with exponential moving average
|
||
|
venue.avg_latency_ms = venue.avg_latency_ms * 0.9 + latency * 0.1;
|
||
|
|
||
|
//--- Update fill rate
|
||
|
venue.fill_rate = venue.fill_rate * 0.95 + (success ? 1.0 : 0.0) * 0.05;
|
||
|
|
||
|
//--- Adjust priority based on performance
|
||
|
if(venue.fill_rate < 0.8)
|
||
|
venue.priority = MathMin(10, venue.priority + 1);
|
||
|
else if(venue.fill_rate > 0.95)
|
||
|
venue.priority = MathMax(1, venue.priority - 1);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Calculate optimal slice size |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CExecutionOptimizer::CalculateOptimalSliceSize(string symbol, double total_volume, double time_horizon)
|
||
|
{
|
||
|
//--- Get market metrics
|
||
|
double avg_volume = 0;
|
||
|
long volume_array[];
|
||
|
if(CopyTickVolume(symbol, PERIOD_M1, 0, 60, volume_array) > 0)
|
||
|
{
|
||
|
for(int i = 0; i < ArraySize(volume_array); i++)
|
||
|
avg_volume += volume_array[i];
|
||
|
avg_volume /= ArraySize(volume_array);
|
||
|
}
|
||
|
|
||
|
//--- Target participation rate (% of market volume)
|
||
|
double target_participation = 0.1; // 10% default
|
||
|
|
||
|
//--- Adjust for urgency
|
||
|
if(time_horizon < 120) // Less than 2 minutes
|
||
|
target_participation = 0.2; // More aggressive
|
||
|
else if(time_horizon > 600) // More than 10 minutes
|
||
|
target_participation = 0.05; // More passive
|
||
|
|
||
|
//--- Calculate slice size
|
||
|
double slices_per_minute = 60.0 / (time_horizon / (time_horizon / 60));
|
||
|
double slice_size = avg_volume * target_participation / slices_per_minute;
|
||
|
|
||
|
//--- Apply bounds
|
||
|
double min_size = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
double max_size = total_volume / 3; // At least 3 slices
|
||
|
|
||
|
slice_size = MathMax(min_size, MathMin(max_size, slice_size));
|
||
|
|
||
|
//--- Normalize to lot step
|
||
|
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
slice_size = MathRound(slice_size / lot_step) * lot_step;
|
||
|
|
||
|
return slice_size;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Create execution schedule |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ExecutionSchedule CExecutionOptimizer::CreateSchedule(ExecutionPlan &plan, int slices)
|
||
|
{
|
||
|
ExecutionSchedule schedule;
|
||
|
schedule.start_time = TimeCurrent();
|
||
|
schedule.end_time = schedule.start_time + plan.time_horizon;
|
||
|
schedule.slices = slices;
|
||
|
schedule.is_adaptive = false;
|
||
|
schedule.urgency_factor = plan.urgency;
|
||
|
|
||
|
//--- Initialize slice arrays
|
||
|
ArrayResize(schedule.slice_sizes, slices);
|
||
|
ArrayResize(schedule.slice_times, slices);
|
||
|
|
||
|
//--- Default equal distribution
|
||
|
double slice_size = plan.total_volume / slices;
|
||
|
int time_interval = plan.time_horizon / slices;
|
||
|
|
||
|
for(int i = 0; i < slices; i++)
|
||
|
{
|
||
|
schedule.slice_sizes[i] = slice_size;
|
||
|
schedule.slice_times[i] = schedule.start_time + i * time_interval;
|
||
|
}
|
||
|
|
||
|
return schedule;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get market volatility |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CExecutionOptimizer::GetMarketVolatility(string symbol)
|
||
|
{
|
||
|
//--- Calculate realized volatility
|
||
|
double returns[];
|
||
|
ArrayResize(returns, 20);
|
||
|
|
||
|
for(int i = 1; i <= 20; i++)
|
||
|
{
|
||
|
double close1 = iClose(symbol, PERIOD_M1, i);
|
||
|
double close2 = iClose(symbol, PERIOD_M1, i-1);
|
||
|
if(close2 > 0)
|
||
|
returns[i-1] = (close1 - close2) / close2;
|
||
|
}
|
||
|
|
||
|
//--- Calculate standard deviation
|
||
|
double mean = 0;
|
||
|
for(int i = 0; i < 20; i++)
|
||
|
mean += returns[i];
|
||
|
mean /= 20;
|
||
|
|
||
|
double variance = 0;
|
||
|
for(int i = 0; i < 20; i++)
|
||
|
variance += MathPow(returns[i] - mean, 2);
|
||
|
variance /= 20;
|
||
|
|
||
|
return MathSqrt(variance);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Get market depth |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CExecutionOptimizer::GetMarketDepth(string symbol, bool is_buy)
|
||
|
{
|
||
|
//--- Simplified depth calculation from recent volume
|
||
|
long volumes[];
|
||
|
if(CopyTickVolume(symbol, PERIOD_M1, 0, 10, volumes) > 0)
|
||
|
{
|
||
|
double total_volume = 0;
|
||
|
for(int i = 0; i < ArraySize(volumes); i++)
|
||
|
total_volume += volumes[i];
|
||
|
|
||
|
return total_volume / ArraySize(volumes);
|
||
|
}
|
||
|
|
||
|
return 1000; // Default depth
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Process active executions |
|
||
|
//+------------------------------------------------------------------+
|
||
|
void CExecutionOptimizer::ProcessActiveExecutions()
|
||
|
{
|
||
|
datetime current_time = TimeCurrent();
|
||
|
|
||
|
for(int i = 0; i < ArraySize(m_active_executions); i++)
|
||
|
{
|
||
|
if(m_active_executions[i].is_complete || m_active_executions[i].plan.symbol == "")
|
||
|
continue;
|
||
|
|
||
|
//--- Check if it's time for next slice
|
||
|
bool execute_now = false;
|
||
|
|
||
|
if(m_active_executions[i].schedule.is_adaptive)
|
||
|
{
|
||
|
//--- Adaptive timing based on market conditions
|
||
|
double filled_pct = m_active_executions[i].filled_volume /
|
||
|
m_active_executions[i].plan.total_volume;
|
||
|
double time_pct = (double)(current_time - m_active_executions[i].start_time) /
|
||
|
m_active_executions[i].plan.time_horizon;
|
||
|
|
||
|
if(filled_pct < time_pct - 0.1) // Behind schedule
|
||
|
execute_now = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//--- Fixed schedule
|
||
|
int current_slice = (int)(m_active_executions[i].filled_volume /
|
||
|
(m_active_executions[i].plan.total_volume /
|
||
|
m_active_executions[i].schedule.slices));
|
||
|
|
||
|
if(current_slice < m_active_executions[i].schedule.slices &&
|
||
|
current_time >= m_active_executions[i].schedule.slice_times[current_slice])
|
||
|
{
|
||
|
execute_now = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--- Execute if needed
|
||
|
if(execute_now)
|
||
|
{
|
||
|
ExecuteSlice(m_active_executions[i]);
|
||
|
}
|
||
|
|
||
|
//--- Check for timeout
|
||
|
if(current_time > m_active_executions[i].schedule.end_time)
|
||
|
{
|
||
|
//--- Execute remaining volume urgently
|
||
|
if(m_active_executions[i].filled_volume < m_active_executions[i].plan.total_volume)
|
||
|
{
|
||
|
m_active_executions[i].plan.urgency = 1.0; // Maximum urgency
|
||
|
ExecuteSlice(m_active_executions[i]);
|
||
|
}
|
||
|
|
||
|
m_active_executions[i].is_complete = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Predict market impact |
|
||
|
//+------------------------------------------------------------------+
|
||
|
double CExecutionOptimizer::PredictMarketImpact(string symbol, double volume, double participation_rate)
|
||
|
{
|
||
|
//--- Linear impact model: Impact = α * Volume^β * Volatility^γ * ParticipationRate^δ
|
||
|
double volatility = GetMarketVolatility(symbol);
|
||
|
double avg_daily_volume = GetMarketDepth(symbol, true) * 60 * 8; // Rough estimate
|
||
|
|
||
|
double volume_ratio = volume / avg_daily_volume;
|
||
|
|
||
|
//--- Temporary impact
|
||
|
double temp_impact = m_temporary_impact * MathSqrt(volume_ratio) *
|
||
|
volatility * MathSqrt(participation_rate);
|
||
|
|
||
|
//--- Permanent impact
|
||
|
double perm_impact = m_permanent_impact * volume_ratio * participation_rate;
|
||
|
|
||
|
return (temp_impact + perm_impact) * 10000; // Convert to basis points
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Select optimal algorithm based on conditions |
|
||
|
//+------------------------------------------------------------------+
|
||
|
ENUM_EXECUTION_ALGO CExecutionOptimizer::SelectOptimalAlgorithm(string symbol, double volume,
|
||
|
double urgency, MarketConditionsV71 &conditions)
|
||
|
{
|
||
|
//--- High urgency
|
||
|
if(urgency > 0.8)
|
||
|
return EXEC_SNIPER;
|
||
|
|
||
|
//--- Large order in low liquidity
|
||
|
if(volume > GetMarketDepth(symbol, true) * 0.1 && conditions.liquidity_depth < 0.5)
|
||
|
return EXEC_ICEBERG;
|
||
|
|
||
|
//--- Volatile market
|
||
|
if(conditions.volatility > 0.02)
|
||
|
return EXEC_ADAPTIVE;
|
||
|
|
||
|
//--- Trending market
|
||
|
if(conditions.regime == REGIME_TRENDING_UP || conditions.regime == REGIME_TRENDING_DOWN)
|
||
|
return EXEC_TWAP; // Spread risk over time
|
||
|
|
||
|
//--- Normal conditions
|
||
|
return EXEC_VWAP;
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Execute emergency close |
|
||
|
//+------------------------------------------------------------------+
|
||
|
bool CExecutionOptimizer::ExecuteEmergencyClose(ulong ticket)
|
||
|
{
|
||
|
//--- Use most aggressive settings
|
||
|
CTrade trade;
|
||
|
trade.SetDeviationInPoints(1000); // 100 pips slippage
|
||
|
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||
|
|
||
|
return trade.PositionClose(ticket);
|
||
|
}
|
||
|
|
||
|
#endif // EXECUTION_OPTIMIZER_MQH
|