777 lines
25 KiB
MQL5
777 lines
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
|