Overview To fully integrate the enhanced external trade management system, updates are required to 5 out of 7 existing modules. The updates maintain backward compatibility while adding new functionality for external trade handling. Module Update Requirements 🟢 No Updates Required (2 modules) TechnicalAnalysis.mqh - Already provides necessary calculations EntrySystem.mqh - Only handles EA's own entry signals 🟡 Minor Updates (2 modules) DataTypes.mqh - Add external trade structures and fields Utilities.mqh - Enhanced logging for external trades 🟠 Moderate Updates (3 modules) RiskManager.mqh - Enhanced risk enforcement methods TradeManager.mqh - Improved stop management for externals Dashboard.mqh - Display external trade information Integration Steps Phase 1: Data Structures (DataTypes.mqh) Add ENUM_EXTERNAL_STATUS enumeration Extend ManagedTrade structure with external-specific fields Add ExternalTradeStats structure for metrics Update DashboardConfig with show_external flag Key additions: external_status - Track state of external trade source_name - Identify where trade came from stops_modified - Track if we modified the trade original_sl/tp - Store original values for comparison Phase 2: Risk Management (RiskManager.mqh) Add EnforceRiskRulesEnhanced() method Implement GetExternalExposure() for risk aggregation Add UpdateExternalStats() for tracking Enhance ValidateAndAdjustRiskExternal() method Key features: Separate risk calculation for external trades Cache mechanism for performance Statistical tracking of external positions Smart risk adjustment without closing trades Phase 3: Trade Management (TradeManager.mqh) Add ApplyDefaultStopsEnhanced() with better logic Implement OverrideExternalStops() with smart override Create ManageExternalTrade() with different rules Add ApplyBreakevenExternal() with wider triggers Key features: Smart stop override (only improve, never worsen) Different management rules for external trades Respect minimum broker distances Track modification success/failure rates Phase 4: User Interface (Dashboard.mqh) Add CreateExternalSection() for display area Implement UpdateExternalSection() for real-time updates Add SetCustomText() for flexible display Create ShowExternalTrades() toggle method Key features: Real-time external trade count and risk Color-coded risk warnings List of active external positions Modification statistics display Phase 5: Logging (Utilities.mqh) Add LogExternalTrade() for detailed event logging Create separate CSV log for external trades Enhance GenerateReportEnhanced() with external section Add IdentifyTradeSource() for magic number interpretation Key features: Separate CSV log for external trade events Detailed tracking of all modifications Source identification from magic numbers Enhanced reporting with external statistics
963 lines
No EOL
32 KiB
MQL5
963 lines
No EOL
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 |