154 Zeilen
6,1 KiB
MQL5
154 Zeilen
6,1 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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<m_history_size; i++)
|
|
{
|
|
if(m_tick_history_ms[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++;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|