223 lines
7.6 KiB
MQL5
223 lines
7.6 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Article-22742-EquiVolume-Indicator-In-MQL5.mq5 |
|
|
//| Abioye Israel Pelumi |
|
|
//| https://Algoyin.com |
|
|
//+------------------------------------------------------------------+
|
|
#property indicator_separate_window
|
|
#property indicator_plots 0
|
|
|
|
//--- Input parameters
|
|
input int InpLookback = 100; // number of bars for EquiVolume
|
|
input color InpBullColor = clrDodgerBlue; // bullish bar color (close >= open)
|
|
input color InpBearColor = clrTomato; // bearish bar color (close < open)
|
|
input color InpOutlineColor = clrGray; // rectangle outline color
|
|
input double InpMaxWidthPct = 10; // maximum box width scaling factor
|
|
|
|
//--- Global variables
|
|
int indicator_window; // indicator subwindow index
|
|
datetime lastTradeBarTime = 0; // ensures logic runs once per new candle
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Returns unique empty box object name |
|
|
//+------------------------------------------------------------------+
|
|
string BoxEmptyName(datetime t)
|
|
{
|
|
return StringFormat("Box%d", (int)t);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Returns unique filled box object name |
|
|
//+------------------------------------------------------------------+
|
|
string BoxFillName(datetime t)
|
|
{
|
|
return StringFormat("Box Fill%d", (int)t);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Create or update volume box |
|
|
//+------------------------------------------------------------------+
|
|
bool DrawBox(const string name,
|
|
datetime x1, double yTop,
|
|
datetime x2, double yBot, bool is_fill,
|
|
color Col, int in_window)
|
|
{
|
|
bool created = false;
|
|
|
|
//--- create rectangle object only if it does not exist
|
|
if(ObjectFind(0, name) < 0)
|
|
{
|
|
ObjectCreate(0, name, OBJ_RECTANGLE, in_window, x1, yTop, x2, yBot);
|
|
|
|
//--- configure rectangle properties
|
|
ObjectSetInteger(0, name, OBJPROP_FILL, is_fill);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, Col);
|
|
|
|
created = true;
|
|
}
|
|
|
|
//--- continuously update rectangle coordinates
|
|
ObjectMove(0, name, 0, x1, yTop);
|
|
ObjectMove(0, name, 1, x2, yBot);
|
|
|
|
return created;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Custom indicator initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
//--- set indicator short name
|
|
IndicatorSetString(INDICATOR_SHORTNAME, "EquiVolume");
|
|
|
|
//--- locate indicator subwindow
|
|
indicator_window = ChartWindowFind(ChartID(), "EquiVolume");
|
|
|
|
//--- remove old box objects during initialization
|
|
ObjectsDeleteAll(0, "Box", indicator_window);
|
|
|
|
//---
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Deinitialization |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
//--- delete all indicator objects during cleanup
|
|
ObjectsDeleteAll(0, "Box", indicator_window);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Custom indicator iteration function |
|
|
//+------------------------------------------------------------------+
|
|
int OnCalculate(const int32_t rates_total,
|
|
const int32_t 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 int32_t &spread[])
|
|
{
|
|
//--- limit processing to selected lookback range
|
|
int bars = MathMin(rates_total, InpLookback);
|
|
|
|
//--- ensure enough bars are available
|
|
if(bars < 2)
|
|
return rates_total;
|
|
|
|
bool need_redraw = false;
|
|
|
|
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
//--- execute logic only once per new candle
|
|
if(currentBarTime != lastTradeBarTime)
|
|
{
|
|
//--- check whether broker provides real volume
|
|
bool useRealVol = (volume[rates_total - 1] > 0);
|
|
int startIdx = rates_total - bars;
|
|
|
|
long maxVol = 0; // highest volume in range
|
|
double maxHigh = 0; // highest price in range
|
|
double minLow = low[startIdx]; // lowest price in range
|
|
|
|
//--- scan candles to determine volume and price boundaries
|
|
for(int i = startIdx; i < rates_total; i++)
|
|
{
|
|
|
|
long vol = useRealVol ? volume[i] : tick_volume[i];
|
|
|
|
//--- track maximum volume
|
|
if(vol > maxVol)
|
|
maxVol = vol;
|
|
|
|
//--- track highest price
|
|
double h = high[i];
|
|
if(h > maxHigh)
|
|
maxHigh = high[i];
|
|
|
|
//--- track lowest price
|
|
double l = low[i];
|
|
if(l < minLow)
|
|
minLow = low[i];
|
|
}
|
|
|
|
//--- adjust indicator scale dynamically
|
|
IndicatorSetDouble(INDICATOR_MAXIMUM, maxHigh);
|
|
IndicatorSetDouble(INDICATOR_MINIMUM, minLow);
|
|
|
|
//--- prevent division by zero
|
|
if(maxVol == 0)
|
|
return rates_total;
|
|
|
|
//--- retrieve timeframe duration in seconds
|
|
datetime periodSec = (datetime)PeriodSeconds();
|
|
|
|
datetime start_time = time[rates_total - 1]; // starting point of current box
|
|
datetime end_time = 0;
|
|
|
|
//--- process candles backward
|
|
for(int i = rates_total - 2; i >= startIdx; i--)
|
|
{
|
|
long vol = useRealVol ? volume[i] : tick_volume[i];
|
|
|
|
//--- skip candles with zero volume
|
|
if(vol == 0)
|
|
continue;
|
|
|
|
//--- normalize width relative to highest volume
|
|
int val = (int)((double)vol / maxVol * InpMaxWidthPct);
|
|
|
|
//--- calculate horizontal width endpoint
|
|
end_time = start_time - (periodSec * val);
|
|
|
|
//--- draw candle outline rectangle
|
|
if(DrawBox(BoxEmptyName(time[i]), start_time, high[i], end_time, low[i], false, InpOutlineColor, indicator_window))
|
|
{
|
|
need_redraw = true;
|
|
}
|
|
|
|
//--- determine candle direction
|
|
bool bullish = (close[i] >= open[i]);
|
|
|
|
//--- select fill color based on candle direction
|
|
color fillCol = bullish ? InpBullColor : InpBearColor;
|
|
|
|
//--- draw candle body rectangle
|
|
if(DrawBox(BoxFillName(time[i]), start_time, open[i], end_time, close[i], true, fillCol, indicator_window))
|
|
{
|
|
need_redraw = true;
|
|
}
|
|
|
|
//--- shift next rectangle starting point
|
|
start_time = end_time;
|
|
|
|
//--- remove outdated objects outside visible range
|
|
for(int i = ObjectsTotal(0, indicator_window)-1; i >= 0; i--)
|
|
{
|
|
string obj_name = ObjectName(0, i);
|
|
|
|
if((datetime)ObjectGetInteger(0, obj_name, OBJPROP_TIME, 0) < end_time)
|
|
{
|
|
ObjectDelete(0, obj_name);
|
|
|
|
need_redraw = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--- update last processed candle time
|
|
lastTradeBarTime = currentBarTime;
|
|
}
|
|
//--- redraw chart only when changes occur
|
|
if(need_redraw)
|
|
ChartRedraw(0);
|
|
|
|
//--- return value of prev_calculated for next call
|
|
return(rates_total);
|
|
}
|
|
//+------------------------------------------------------------------+
|