mql5/Shared Projects/ERMT-ML/Modules-ML8x/OrderFlowAnalyzer.mqh
darashikoh 0fb1bd1b0a Module Integration Summary for External Trade Management
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
2025-08-27 14:21:02 +01:00

777 lines
No EOL
25 KiB
MQL5

//+------------------------------------------------------------------+
//| OrderFlowAnalyzer.mqh |
//| Order Flow and Market Microstructure |
//| Volume Profile, Liquidity Detection, Flow Analysis |
//+------------------------------------------------------------------+
#ifndef ORDER_FLOW_ANALYZER_MQH
#define ORDER_FLOW_ANALYZER_MQH
#include "DataTypes_v71.mqh"
#include <Arrays/ArrayObj.mqh>
//+------------------------------------------------------------------+
//| Volume Profile Level |
//+------------------------------------------------------------------+
struct VolumeProfileLevel
{
double price;
double volume;
double buy_volume;
double sell_volume;
double delta;
int trades;
bool is_poc; // Point of Control
bool is_vah; // Value Area High
bool is_val; // Value Area Low
};
//+------------------------------------------------------------------+
//| Order Flow Imbalance |
//+------------------------------------------------------------------+
struct FlowImbalance
{
datetime time;
double bid_volume;
double ask_volume;
double imbalance_ratio;
double cumulative_delta;
double vpin; // Volume-synchronized PIN
bool absorption_detected;
};
//+------------------------------------------------------------------+
//| Large Order Detection |
//+------------------------------------------------------------------+
struct LargeOrder
{
datetime time;
double price;
double volume;
bool is_buy;
double impact; // Price impact
int execution_time_ms;
bool is_institutional;
};
//+------------------------------------------------------------------+
//| Order Flow Analyzer Class |
//+------------------------------------------------------------------+
class COrderFlowAnalyzer
{
private:
//--- Configuration
int m_profile_period;
double m_liquidity_threshold;
int m_large_order_multiple;
//--- Volume profile data
VolumeProfileLevel m_volume_profile[];
int m_profile_levels;
double m_poc_price;
double m_vah_price;
double m_val_price;
double m_vwap;
//--- Order flow tracking
FlowImbalance m_flow_history[];
int m_flow_buffer_size;
double m_cumulative_delta;
//--- Large order detection
LargeOrder m_large_orders[];
int m_large_order_count;
double m_avg_trade_size;
//--- Tick data buffer
MqlTick m_tick_buffer[];
int m_tick_count;
datetime m_last_update;
//--- Liquidity analysis
double m_bid_liquidity[];
double m_ask_liquidity[];
int m_liquidity_levels;
//--- Helper methods
void BuildVolumeProfile(string symbol);
void CalculateValueArea();
void DetectOrderAbsorption(MqlTick &tick);
void UpdateFlowImbalance(MqlTick &tick);
double CalculateVPIN(int lookback);
void AnalyzeLiquidity(string symbol);
public:
COrderFlowAnalyzer();
~COrderFlowAnalyzer();
//--- Initialization
bool Initialize(int profile_period, double liquidity_threshold);
//--- Real-time processing
void ProcessTick(string symbol, MqlTick &tick);
void UpdateProfile(string symbol);
//--- Volume profile analysis
double GetPOC() { return m_poc_price; }
double GetVAH() { return m_vah_price; }
double GetVAL() { return m_val_price; }
double GetVWAP() { return m_vwap; }
VolumeProfileLevel GetProfileLevel(double price);
//--- Order flow metrics
double GetFlowImbalance(string symbol);
double GetCumulativeDelta() { return m_cumulative_delta; }
double GetVPIN() { return CalculateVPIN(50); }
bool IsAbsorptionDetected();
//--- Large order detection
bool DetectInstitutionalFlow(string symbol);
int GetLargeOrderCount(bool buy_only = false);
double GetAverageTradeSize() { return m_avg_trade_size; }
//--- Liquidity analysis
double GetLiquidityScore(string symbol);
double GetBidDepth(int levels = 5);
double GetAskDepth(int levels = 5);
double GetSpreadQuality(string symbol);
//--- Signal generation
double GetSignalStrength(string symbol);
bool CheckLiquidityConditions(double min_liquidity);
//--- Market microstructure
double GetEffectiveSpread(string symbol);
double GetRealizedSpread(string symbol);
double GetPriceImpact(double volume, bool is_buy);
//--- Export data
void ExportVolumeProfile(string filename);
void ExportFlowData(string filename);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
COrderFlowAnalyzer::COrderFlowAnalyzer()
{
m_profile_period = 20;
m_liquidity_threshold = 1000000;
m_large_order_multiple = 10;
m_profile_levels = 0;
m_poc_price = 0;
m_vah_price = 0;
m_val_price = 0;
m_vwap = 0;
m_flow_buffer_size = 1000;
m_cumulative_delta = 0;
m_large_order_count = 0;
m_avg_trade_size = 0;
m_tick_count = 0;
m_last_update = 0;
m_liquidity_levels = 10;
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
COrderFlowAnalyzer::~COrderFlowAnalyzer()
{
ArrayFree(m_volume_profile);
ArrayFree(m_flow_history);
ArrayFree(m_large_orders);
ArrayFree(m_tick_buffer);
ArrayFree(m_bid_liquidity);
ArrayFree(m_ask_liquidity);
}
//+------------------------------------------------------------------+
//| Initialize analyzer |
//+------------------------------------------------------------------+
bool COrderFlowAnalyzer::Initialize(int profile_period, double liquidity_threshold)
{
m_profile_period = profile_period;
m_liquidity_threshold = liquidity_threshold;
//--- Initialize arrays
ArrayResize(m_flow_history, m_flow_buffer_size);
ArrayResize(m_tick_buffer, 10000);
ArrayResize(m_large_orders, 1000);
ArrayResize(m_bid_liquidity, m_liquidity_levels);
ArrayResize(m_ask_liquidity, m_liquidity_levels);
Print("OrderFlowAnalyzer initialized: Period=", m_profile_period,
", LiquidityThreshold=", m_liquidity_threshold);
return true;
}
//+------------------------------------------------------------------+
//| Process incoming tick |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::ProcessTick(string symbol, MqlTick &tick)
{
//--- Store tick
if(m_tick_count < ArraySize(m_tick_buffer))
{
m_tick_buffer[m_tick_count] = tick;
m_tick_count++;
}
else
{
//--- Shift array and add new tick
ArrayCopy(m_tick_buffer, m_tick_buffer, 0, 1);
m_tick_buffer[ArraySize(m_tick_buffer)-1] = tick;
}
//--- Update flow imbalance
UpdateFlowImbalance(tick);
//--- Detect large orders
if(tick.volume_real > m_avg_trade_size * m_large_order_multiple)
{
LargeOrder order;
order.time = tick.time;
order.price = tick.last;
order.volume = tick.volume_real;
order.is_buy = (tick.last >= tick.ask); // Simplified
order.impact = 0; // Calculate later
order.execution_time_ms = (int)(tick.time_msc % 1000);
order.is_institutional = (tick.volume_real > m_liquidity_threshold / 100);
if(m_large_order_count < ArraySize(m_large_orders))
{
m_large_orders[m_large_order_count] = order;
m_large_order_count++;
}
}
//--- Update average trade size
m_avg_trade_size = m_avg_trade_size * 0.99 + tick.volume_real * 0.01;
//--- Detect absorption
DetectOrderAbsorption(tick);
//--- Update profile periodically
if(TimeCurrent() - m_last_update > 1)
{
UpdateProfile(symbol);
m_last_update = TimeCurrent();
}
}
//+------------------------------------------------------------------+
//| Build volume profile |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::BuildVolumeProfile(string symbol)
{
//--- Get price range
double high = 0, low = DBL_MAX;
double total_volume = 0;
double volume_price_sum = 0;
for(int i = 0; i < m_tick_count; i++)
{
if(m_tick_buffer[i].last > high) high = m_tick_buffer[i].last;
if(m_tick_buffer[i].last < low) low = m_tick_buffer[i].last;
total_volume += m_tick_buffer[i].volume_real;
volume_price_sum += m_tick_buffer[i].volume_real * m_tick_buffer[i].last;
}
if(high == low || total_volume == 0) return;
//--- Calculate VWAP
m_vwap = volume_price_sum / total_volume;
//--- Create price levels
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
int levels = (int)((high - low) / tick_size) + 1;
levels = MathMin(levels, 100); // Limit levels
ArrayResize(m_volume_profile, levels);
m_profile_levels = levels;
//--- Initialize levels
for(int i = 0; i < levels; i++)
{
m_volume_profile[i].price = low + i * tick_size;
m_volume_profile[i].volume = 0;
m_volume_profile[i].buy_volume = 0;
m_volume_profile[i].sell_volume = 0;
m_volume_profile[i].trades = 0;
m_volume_profile[i].is_poc = false;
m_volume_profile[i].is_vah = false;
m_volume_profile[i].is_val = false;
}
//--- Aggregate volume by price
double max_volume = 0;
int poc_index = 0;
for(int i = 0; i < m_tick_count; i++)
{
int level_index = (int)((m_tick_buffer[i].last - low) / tick_size);
if(level_index >= 0 && level_index < levels)
{
m_volume_profile[level_index].volume += m_tick_buffer[i].volume_real;
m_volume_profile[level_index].trades++;
//--- Determine buy/sell
if(m_tick_buffer[i].last >= m_tick_buffer[i].ask)
{
m_volume_profile[level_index].buy_volume += m_tick_buffer[i].volume_real;
}
else
{
m_volume_profile[level_index].sell_volume += m_tick_buffer[i].volume_real;
}
//--- Track POC
if(m_volume_profile[level_index].volume > max_volume)
{
max_volume = m_volume_profile[level_index].volume;
poc_index = level_index;
}
}
}
//--- Mark POC
m_volume_profile[poc_index].is_poc = true;
m_poc_price = m_volume_profile[poc_index].price;
//--- Calculate delta
for(int i = 0; i < levels; i++)
{
m_volume_profile[i].delta = m_volume_profile[i].buy_volume -
m_volume_profile[i].sell_volume;
}
//--- Calculate value area
CalculateValueArea();
}
//+------------------------------------------------------------------+
//| Calculate value area (70% of volume) |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::CalculateValueArea()
{
if(m_profile_levels == 0) return;
//--- Calculate total volume
double total_volume = 0;
for(int i = 0; i < m_profile_levels; i++)
{
total_volume += m_volume_profile[i].volume;
}
double value_area_volume = total_volume * 0.7;
double accumulated_volume = 0;
//--- Find POC index
int poc_index = -1;
for(int i = 0; i < m_profile_levels; i++)
{
if(m_volume_profile[i].is_poc)
{
poc_index = i;
accumulated_volume = m_volume_profile[i].volume;
break;
}
}
if(poc_index < 0) return;
//--- Expand from POC
int upper_index = poc_index;
int lower_index = poc_index;
while(accumulated_volume < value_area_volume &&
(upper_index < m_profile_levels - 1 || lower_index > 0))
{
double upper_volume = (upper_index < m_profile_levels - 1) ?
m_volume_profile[upper_index + 1].volume : 0;
double lower_volume = (lower_index > 0) ?
m_volume_profile[lower_index - 1].volume : 0;
if(upper_volume >= lower_volume && upper_index < m_profile_levels - 1)
{
upper_index++;
accumulated_volume += upper_volume;
}
else if(lower_index > 0)
{
lower_index--;
accumulated_volume += lower_volume;
}
}
//--- Mark value area
m_volume_profile[upper_index].is_vah = true;
m_volume_profile[lower_index].is_val = true;
m_vah_price = m_volume_profile[upper_index].price;
m_val_price = m_volume_profile[lower_index].price;
}
//+------------------------------------------------------------------+
//| Update flow imbalance |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::UpdateFlowImbalance(MqlTick &tick)
{
static int flow_index = 0;
FlowImbalance flow;
flow.time = tick.time;
//--- Classify as buy or sell based on tick
if(tick.last >= tick.ask)
{
flow.ask_volume = tick.volume_real;
flow.bid_volume = 0;
}
else if(tick.last <= tick.bid)
{
flow.bid_volume = tick.volume_real;
flow.ask_volume = 0;
}
else
{
//--- Split volume
double ratio = (tick.last - tick.bid) / (tick.ask - tick.bid);
flow.ask_volume = tick.volume_real * ratio;
flow.bid_volume = tick.volume_real * (1 - ratio);
}
//--- Calculate imbalance
double total = flow.bid_volume + flow.ask_volume;
if(total > 0)
{
flow.imbalance_ratio = (flow.ask_volume - flow.bid_volume) / total;
}
else
{
flow.imbalance_ratio = 0;
}
//--- Update cumulative delta
m_cumulative_delta += (flow.ask_volume - flow.bid_volume);
flow.cumulative_delta = m_cumulative_delta;
//--- Calculate VPIN
flow.vpin = CalculateVPIN(50);
//--- Store in buffer
m_flow_history[flow_index] = flow;
flow_index = (flow_index + 1) % m_flow_buffer_size;
}
//+------------------------------------------------------------------+
//| Calculate Volume-synchronized PIN |
//+------------------------------------------------------------------+
double COrderFlowAnalyzer::CalculateVPIN(int lookback)
{
if(m_tick_count < lookback) return 0;
double total_buy_volume = 0;
double total_sell_volume = 0;
int start_index = MathMax(0, m_tick_count - lookback);
for(int i = start_index; i < m_tick_count; i++)
{
if(m_tick_buffer[i].last >= m_tick_buffer[i].ask)
total_buy_volume += m_tick_buffer[i].volume_real;
else
total_sell_volume += m_tick_buffer[i].volume_real;
}
double total_volume = total_buy_volume + total_sell_volume;
if(total_volume > 0)
{
return MathAbs(total_buy_volume - total_sell_volume) / total_volume;
}
return 0;
}
//+------------------------------------------------------------------+
//| Detect order absorption |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::DetectOrderAbsorption(MqlTick &tick)
{
//--- Check recent flow history
int recent_count = MathMin(10, m_flow_buffer_size);
double recent_imbalance = 0;
double price_change = 0;
for(int i = 0; i < recent_count; i++)
{
int index = (m_flow_buffer_size + flow_index - i - 1) % m_flow_buffer_size;
recent_imbalance += m_flow_history[index].imbalance_ratio;
}
recent_imbalance /= recent_count;
//--- Check price movement
if(m_tick_count > 10)
{
price_change = (tick.last - m_tick_buffer[m_tick_count-10].last) /
m_tick_buffer[m_tick_count-10].last;
}
//--- Absorption detected if high volume but no price movement
bool absorption = (MathAbs(recent_imbalance) > 0.7 && MathAbs(price_change) < 0.0001);
if(absorption)
{
int current_index = (flow_index - 1 + m_flow_buffer_size) % m_flow_buffer_size;
m_flow_history[current_index].absorption_detected = true;
}
}
//+------------------------------------------------------------------+
//| Get flow imbalance |
//+------------------------------------------------------------------+
double COrderFlowAnalyzer::GetFlowImbalance(string symbol)
{
//--- Calculate recent flow imbalance
int lookback = MathMin(20, m_tick_count);
double total_buy = 0;
double total_sell = 0;
for(int i = m_tick_count - lookback; i < m_tick_count; i++)
{
if(i >= 0)
{
if(m_tick_buffer[i].last >= m_tick_buffer[i].ask)
total_buy += m_tick_buffer[i].volume_real;
else if(m_tick_buffer[i].last <= m_tick_buffer[i].bid)
total_sell += m_tick_buffer[i].volume_real;
}
}
double total = total_buy + total_sell;
if(total > 0)
return (total_buy - total_sell) / total;
return 0;
}
//+------------------------------------------------------------------+
//| Detect institutional flow |
//+------------------------------------------------------------------+
bool COrderFlowAnalyzer::DetectInstitutionalFlow(string symbol)
{
//--- Check recent large orders
int recent_large_orders = 0;
datetime current_time = TimeCurrent();
for(int i = 0; i < m_large_order_count; i++)
{
if(current_time - m_large_orders[i].time < 300) // Last 5 minutes
{
if(m_large_orders[i].is_institutional)
recent_large_orders++;
}
}
//--- Check cumulative volume
double recent_volume = 0;
int lookback = MathMin(100, m_tick_count);
for(int i = m_tick_count - lookback; i < m_tick_count; i++)
{
if(i >= 0)
recent_volume += m_tick_buffer[i].volume_real;
}
//--- Institutional flow detected if high volume and large orders
return (recent_large_orders >= 3 || recent_volume > m_liquidity_threshold);
}
//+------------------------------------------------------------------+
//| Get liquidity score |
//+------------------------------------------------------------------+
double COrderFlowAnalyzer::GetLiquidityScore(string symbol)
{
//--- Analyze bid/ask depth
double total_liquidity = 0;
//--- Estimate from recent volume and spread
double avg_spread = 0;
double avg_volume = 0;
int count = MathMin(100, m_tick_count);
for(int i = m_tick_count - count; i < m_tick_count; i++)
{
if(i >= 0)
{
avg_spread += (m_tick_buffer[i].ask - m_tick_buffer[i].bid);
avg_volume += m_tick_buffer[i].volume_real;
}
}
if(count > 0)
{
avg_spread /= count;
avg_volume /= count;
}
//--- Calculate liquidity score (0-100)
double spread_score = MathMax(0, 100 - avg_spread * 10000); // Tighter spread = higher score
double volume_score = MathMin(100, avg_volume / 1000); // Higher volume = higher score
double vpin_score = MathMax(0, 100 - GetVPIN() * 100); // Lower VPIN = higher score
return (spread_score + volume_score + vpin_score) / 3;
}
//+------------------------------------------------------------------+
//| Get signal strength based on order flow |
//+------------------------------------------------------------------+
double COrderFlowAnalyzer::GetSignalStrength(string symbol)
{
//--- Get flow imbalance
double imbalance = GetFlowImbalance(symbol);
//--- Check if at important levels
double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
double level_importance = 0;
if(MathAbs(current_price - m_poc_price) < SymbolInfoDouble(symbol, SYMBOL_POINT) * 10)
level_importance = 0.3; // At POC
else if(current_price >= m_val_price && current_price <= m_vah_price)
level_importance = 0.2; // In value area
else
level_importance = 0.1; // Outside value area
//--- Check for absorption
bool absorption = IsAbsorptionDetected();
double absorption_factor = absorption ? 1.5 : 1.0;
//--- Calculate signal strength
double strength = MathAbs(imbalance) * absorption_factor + level_importance;
//--- Boost for institutional flow
if(DetectInstitutionalFlow(symbol))
strength *= 1.3;
return MathMin(1.0, strength);
}
//+------------------------------------------------------------------+
//| Check if absorption detected |
//+------------------------------------------------------------------+
bool COrderFlowAnalyzer::IsAbsorptionDetected()
{
//--- Check recent flow history for absorption
int recent_count = MathMin(5, m_flow_buffer_size);
for(int i = 0; i < recent_count; i++)
{
int index = (m_flow_buffer_size + flow_index - i - 1) % m_flow_buffer_size;
if(m_flow_history[index].absorption_detected)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Update volume profile |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::UpdateProfile(string symbol)
{
BuildVolumeProfile(symbol);
AnalyzeLiquidity(symbol);
}
//+------------------------------------------------------------------+
//| Analyze market liquidity |
//+------------------------------------------------------------------+
void COrderFlowAnalyzer::AnalyzeLiquidity(string symbol)
{
//--- Simple liquidity estimation from volume profile
for(int i = 0; i < m_liquidity_levels; i++)
{
m_bid_liquidity[i] = 0;
m_ask_liquidity[i] = 0;
//--- Estimate liquidity at each level
if(i < m_profile_levels)
{
m_bid_liquidity[i] = m_volume_profile[i].sell_volume;
m_ask_liquidity[i] = m_volume_profile[i].buy_volume;
}
}
}
//+------------------------------------------------------------------+
//| Get effective spread |
//+------------------------------------------------------------------+
double COrderFlowAnalyzer::GetEffectiveSpread(string symbol)
{
if(m_tick_count < 2) return 0;
//--- Calculate average effective spread
double total_spread = 0;
int count = MathMin(100, m_tick_count - 1);
for(int i = m_tick_count - count; i < m_tick_count; i++)
{
if(i > 0)
{
double mid_price = (m_tick_buffer[i].bid + m_tick_buffer[i].ask) / 2;
double effective_spread = 2 * MathAbs(m_tick_buffer[i].last - mid_price);
total_spread += effective_spread;
}
}
return (count > 0) ? total_spread / count : 0;
}
//+------------------------------------------------------------------+
//| Get price impact estimate |
//+------------------------------------------------------------------+
double COrderFlowAnalyzer::GetPriceImpact(double volume, bool is_buy)
{
//--- Estimate price impact based on volume profile
double cumulative_volume = 0;
double weighted_price = 0;
double current_price = is_buy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);
//--- Walk through profile levels
for(int i = 0; i < m_profile_levels; i++)
{
if(is_buy && m_volume_profile[i].price > current_price)
{
double available = m_volume_profile[i].sell_volume;
double take = MathMin(available, volume - cumulative_volume);
weighted_price += m_volume_profile[i].price * take;
cumulative_volume += take;
if(cumulative_volume >= volume) break;
}
else if(!is_buy && m_volume_profile[i].price < current_price)
{
double available = m_volume_profile[i].buy_volume;
double take = MathMin(available, volume - cumulative_volume);
weighted_price += m_volume_profile[i].price * take;
cumulative_volume += take;
if(cumulative_volume >= volume) break;
}
}
if(cumulative_volume > 0)
{
double avg_price = weighted_price / cumulative_volume;
return MathAbs(avg_price - current_price) / current_price * 100; // Percentage impact
}
return 0;
}
#endif // ORDER_FLOW_ANALYZER_MQH