//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+