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