//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| 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