//+------------------------------------------------------------------+ //| WhaleBubbles.mq5 | //| Copyright 2026, Antigravity AI | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Antigravity AI" #property link "https://www.mql5.com" #property version "1.21" #property indicator_chart_window #property indicator_plots 0 //--- Input Parameters input double InpMinRatio = 3.0; // Min Intensity Ratio (x Avg) input int InpBurstWindowMs = 500; // Burst Window (ms) input int InpAvgWindowSec = 20; // Average Window (sec) input double InpAbsorptionPips = 1.5; // Max Pips for Absorption input color InpBuyColor = clrAqua; input color InpSellColor = clrMagenta; input color InpAbsorbColor = clrGold; input int InpMaxBubbles = 300; //--- Global Variables long m_tick_history_ms[]; int m_history_size = 5000; int m_history_ptr = 0; double m_prev_price = 0; double m_prev_bid = 0; double m_prev_ask = 0; int m_bubble_count = 0; long m_unique_id = 0; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ArrayResize(m_tick_history_ms, m_history_size); ArrayInitialize(m_tick_history_ms, 0); IndicatorSetString(INDICATOR_SHORTNAME, "WhaleBubbles v1.21"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "WB_"); Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return(rates_total); // 1. Log intensity long now_ms = GetMicrosecondCount() / 1000; m_tick_history_ms[m_history_ptr] = now_ms; m_history_ptr = (m_history_ptr + 1) % m_history_size; // 2. Calculate Intensity Ratio int burst_ticks = 0; int long_ticks = 0; for(int i=0; i= (long)(now_ms - InpBurstWindowMs)) burst_ticks++; if(m_tick_history_ms[i] >= (long)(now_ms - InpAvgWindowSec * 1000)) long_ticks++; } double avg_per_burst = (double)long_ticks * (InpBurstWindowMs / (InpAvgWindowSec * 1000.0)); if(avg_per_burst < 0.5) avg_per_burst = 0.5; double ratio = (double)burst_ticks / avg_per_burst; // 3. Direction Detection string direction = "UNKNOWN"; double price = (tick.last != 0) ? tick.last : ((tick.bid + tick.ask) / 2.0); if(tick.flags & TICK_FLAG_BUY) direction = "BUY"; else if(tick.flags & TICK_FLAG_SELL) direction = "SELL"; else if(tick.last != 0) { if(tick.last >= tick.ask) direction = "BUY"; else if(tick.last <= tick.bid) direction = "SELL"; } else { if(tick.ask > m_prev_ask && m_prev_ask > 0) direction = "BUY"; else if(tick.bid < m_prev_bid && m_prev_bid > 0) direction = "SELL"; else if(tick.flags & TICK_FLAG_ASK) direction = "BUY"; else if(tick.flags & TICK_FLAG_BID) direction = "SELL"; else direction = (price > m_prev_price) ? "BUY" : (price < m_prev_price ? "SELL" : "UNKNOWN"); } // 4. Signal and Drawing double delta = MathAbs(price - m_prev_price); bool is_whale = (ratio >= InpMinRatio && burst_ticks > 2); bool is_absorbed = (is_whale && m_prev_price > 0 && delta <= InpAbsorptionPips * _Point * 10); if(is_whale && direction != "UNKNOWN") { DrawBubble(tick, ratio, price, is_absorbed, burst_ticks, direction); ChartRedraw(); } m_prev_price = price; m_prev_bid = tick.bid; m_prev_ask = tick.ask; Comment(StringFormat("WhaleBubbles v1.21\nRatio: %.2fx | Burst: %d\nLast Dir: %s", ratio, burst_ticks, direction)); return(rates_total); } //+------------------------------------------------------------------+ //| Drawing the Bubble | //+------------------------------------------------------------------+ void DrawBubble(MqlTick &tick, double ratio, double price, bool absorbed, int burst_count, string direction) { m_unique_id++; string name = StringFormat("WB_%d_%d", TimeCurrent(), m_unique_id); if(m_bubble_count >= InpMaxBubbles) { ObjectsDeleteAll(0, "WB_"); m_bubble_count = 0; } color clr = clrGray; if(absorbed) clr = InpAbsorbColor; else if(direction == "BUY") clr = InpBuyColor; else if(direction == "SELL") clr = InpSellColor; int width = (int)MathMin(10, MathMax(2, ratio)); if(ObjectCreate(0, name, OBJ_ARROW, 0, tick.time, price)) { ObjectSetInteger(0, name, OBJPROP_ARROWCODE, 159); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetString(0, name, OBJPROP_TOOLTIP, StringFormat("Dir: %s\nRatio: %.1fx\nBurst: %d ticks", direction, ratio, burst_count)); m_bubble_count++; } } //+------------------------------------------------------------------+