//+------------------------------------------------------------------+ //| CustomRectangles.mq5 | //| Copyright 2025, Rahul Dhangar | //| | //+------------------------------------------------------------------+ #property copyright "Copyright 2025" #property link "https://www.rahuldhangar.com" #property version "1.00" #property indicator_chart_window #property indicator_plots 0 // Input parameters input int DaysToShow = 20; // Number of Periods to Show input ENUM_TIMEFRAMES AggTFInput = PERIOD_D1; // Aggregation timeframe input int ShiftBars = 0; // Shift (bars) input int ATRLen = 14; // ATR Length // Color inputs input color ColText = clrBlack; input color ColRed = clrRed; input color ColGreen = clrLime; input color ColBodyBull = clrWhite; input color ColBodyBear = clrBlack; // Structure to store rectangle data struct DayRectangles { string redRect; string greenRect; string bodyRect; string infoLabel; // text above red rectangle string atrLabel; // text below green rectangle }; // Array to store rectangle names DayRectangles rectangles[]; int rectangleCount = 0; // Compute ATR at a given index on series arrays (index 0 = most recent) double CalcATRAt(const double &h[], const double &l[], const double &c[], int idx, int len) { int sz = ArraySize(h); if(idx + len >= sz) return(0.0); double sum = 0.0; for(int j = idx; j < idx + len; j++) { double tr1 = h[j] - l[j]; double tr2 = MathAbs(h[j] - c[j+1]); double tr3 = MathAbs(l[j] - c[j+1]); double tr = MathMax(tr1, MathMax(tr2, tr3)); sum += tr; } return(sum / len); } //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ArrayResize(rectangles, DaysToShow); rectangleCount = 0; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Delete all rectangles for(int i = 0; i < rectangleCount; i++) { if(rectangles[i].redRect != "") ObjectDelete(0, rectangles[i].redRect); if(rectangles[i].greenRect != "") ObjectDelete(0, rectangles[i].greenRect); if(rectangles[i].bodyRect != "") ObjectDelete(0, rectangles[i].bodyRect); if(rectangles[i].infoLabel != "") ObjectDelete(0, rectangles[i].infoLabel); if(rectangles[i].atrLabel != "") ObjectDelete(0, rectangles[i].atrLabel); } } //+------------------------------------------------------------------+ //| 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[]) { // Remove previously drawn rectangles so we can re-draw the last N days // Clear existing rectangles for(int i = 0; i < rectangleCount; i++) { if(rectangles[i].redRect != "") ObjectDelete(0, rectangles[i].redRect); if(rectangles[i].greenRect != "") ObjectDelete(0, rectangles[i].greenRect); if(rectangles[i].bodyRect != "") ObjectDelete(0, rectangles[i].bodyRect); } rectangleCount = 0; // Ensure storage can hold current count ArrayResize(rectangles, DaysToShow); // Determine effective aggregation timeframe (use input only if higher than chart TF) ENUM_TIMEFRAMES chartTF = (ENUM_TIMEFRAMES)Period(); ENUM_TIMEFRAMES aggTF = (PeriodSeconds(AggTFInput) > PeriodSeconds(chartTF)) ? AggTFInput : chartTF; // Fetch aggregated timeframe data (extra bars for ATR calculation) int barsToFetch = DaysToShow + ShiftBars + ATRLen + 2; double o[], h[], l[], c[]; datetime t[]; if(CopyTime(_Symbol, aggTF, 0, barsToFetch, t) <= 0) return(0); if(CopyOpen(_Symbol, aggTF, 0, barsToFetch, o) <= 0) return(0); if(CopyHigh(_Symbol, aggTF, 0, barsToFetch, h) <= 0) return(0); if(CopyLow(_Symbol, aggTF, 0, barsToFetch, l) <= 0) return(0); if(CopyClose(_Symbol, aggTF, 0, barsToFetch, c) <= 0) return(0); ArraySetAsSeries(t, true); ArraySetAsSeries(o, true); ArraySetAsSeries(h, true); ArraySetAsSeries(l, true); ArraySetAsSeries(c, true); // Create rectangles for each aggregated bar int built = 0; for(int i = 1 + ShiftBars; built < DaysToShow && i < ArraySize(t); i++) { if(i - 1 >= ArraySize(t)) break; datetime dayStart = t[i]; datetime dayEnd = t[i-1]; double dOpen = o[i]; double dHigh = h[i]; double dLow = l[i]; double dClose = c[i]; bool isBullish = (dClose > dOpen); // Create unique names for rectangles string idxStr = IntegerToString(i); string prefix = "DayRect_" + idxStr + "_"; if(isBullish) { // Bullish day: Close > Open // Red rectangle: Closing price to High string redName = prefix + "Red"; ObjectCreate(0, redName, OBJ_RECTANGLE, 0, dayStart, dClose, dayEnd, dHigh); ObjectSetInteger(0, redName, OBJPROP_COLOR, ColRed); ObjectSetInteger(0, redName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, redName, OBJPROP_WIDTH, 1); ObjectSetInteger(0, redName, OBJPROP_BACK, true); ObjectSetInteger(0, redName, OBJPROP_FILL, true); ObjectSetInteger(0, redName, OBJPROP_BGCOLOR, ColorToARGB(ColRed, 25)); // ~90% transparent rectangles[rectangleCount].redRect = redName; // Green rectangle: Opening price to Low string greenName = prefix + "Green"; ObjectCreate(0, greenName, OBJ_RECTANGLE, 0, dayStart, dLow, dayEnd, dOpen); ObjectSetInteger(0, greenName, OBJPROP_COLOR, ColGreen); ObjectSetInteger(0, greenName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, greenName, OBJPROP_WIDTH, 1); ObjectSetInteger(0, greenName, OBJPROP_BACK, true); ObjectSetInteger(0, greenName, OBJPROP_FILL, true); ObjectSetInteger(0, greenName, OBJPROP_BGCOLOR, ColorToARGB(ColGreen, 25)); rectangles[rectangleCount].greenRect = greenName; // White rectangle: Opening to Closing price (body) string bodyName = prefix + "Body"; ObjectCreate(0, bodyName, OBJ_RECTANGLE, 0, dayStart, dOpen, dayEnd, dClose); ObjectSetInteger(0, bodyName, OBJPROP_COLOR, ColBodyBull); ObjectSetInteger(0, bodyName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, bodyName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, bodyName, OBJPROP_BACK, true); ObjectSetInteger(0, bodyName, OBJPROP_FILL, true); ObjectSetInteger(0, bodyName, OBJPROP_BGCOLOR, ColorToARGB(ColBodyBull, 25)); rectangles[rectangleCount].bodyRect = bodyName; } else { // Bearish day: Close <= Open // Red rectangle: Opening price to High string redName = prefix + "Red"; ObjectCreate(0, redName, OBJ_RECTANGLE, 0, dayStart, dOpen, dayEnd, dHigh); ObjectSetInteger(0, redName, OBJPROP_COLOR, ColRed); ObjectSetInteger(0, redName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, redName, OBJPROP_WIDTH, 1); ObjectSetInteger(0, redName, OBJPROP_BACK, true); ObjectSetInteger(0, redName, OBJPROP_FILL, true); ObjectSetInteger(0, redName, OBJPROP_BGCOLOR, ColorToARGB(ColRed, 25)); rectangles[rectangleCount].redRect = redName; // Green rectangle: Closing price to Low string greenName = prefix + "Green"; ObjectCreate(0, greenName, OBJ_RECTANGLE, 0, dayStart, dLow, dayEnd, dClose); ObjectSetInteger(0, greenName, OBJPROP_COLOR, ColGreen); ObjectSetInteger(0, greenName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, greenName, OBJPROP_WIDTH, 1); ObjectSetInteger(0, greenName, OBJPROP_BACK, true); ObjectSetInteger(0, greenName, OBJPROP_FILL, true); ObjectSetInteger(0, greenName, OBJPROP_BGCOLOR, ColorToARGB(ColGreen, 25)); rectangles[rectangleCount].greenRect = greenName; // Black rectangle: Closing to Opening price (body) string bodyName = prefix + "Body"; ObjectCreate(0, bodyName, OBJ_RECTANGLE, 0, dayStart, dClose, dayEnd, dOpen); ObjectSetInteger(0, bodyName, OBJPROP_COLOR, ColBodyBear); ObjectSetInteger(0, bodyName, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, bodyName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, bodyName, OBJPROP_BACK, true); ObjectSetInteger(0, bodyName, OBJPROP_FILL, true); ObjectSetInteger(0, bodyName, OBJPROP_BGCOLOR, ColorToARGB(ColBodyBear, 25)); rectangles[rectangleCount].bodyRect = bodyName; } // Labels: sizes in pips (points) and ATR comparison double pip = _Point; int redPips = (int)MathRound(MathAbs(dHigh - (isBullish ? dClose : dOpen)) / pip); int greenPips = (int)MathRound(MathAbs((isBullish ? dOpen : dClose) - dLow) / pip); int bodyPips = (int)MathRound(MathAbs(dClose - dOpen) / pip); int candlePips = (int)MathRound(MathAbs(dHigh - dLow) / pip); double atrVal = CalcATRAt(h, l, c, i, ATRLen); int atrPips = (int)MathRound(atrVal / pip); datetime midTime = dayStart; // + (dayEnd - dayStart) / 2; string infoName = "DayInfo_" + idxStr; string infoText = "R:" + IntegerToString(redPips) + " G:" + IntegerToString(greenPips) + " B:" + IntegerToString(bodyPips); ObjectCreate(0, infoName, OBJ_TEXT, 0, midTime, dHigh + 40 * pip); ObjectSetString(0, infoName, OBJPROP_TEXT, infoText); ObjectSetInteger(0, infoName, OBJPROP_COLOR, ColText); rectangles[rectangleCount].infoLabel = infoName; string atrName = "DayATR_" + idxStr; string cmp = (candlePips > atrPips) ? ">" : (candlePips < atrPips) ? "<" : "="; string atrText = "ATR" + IntegerToString(ATRLen) + ": " + IntegerToString(atrPips) + " | " + candlePips + " " + cmp + " ATR"; ObjectCreate(0, atrName, OBJ_TEXT, 0, midTime, dLow - 10 * pip); ObjectSetString(0, atrName, OBJPROP_TEXT, atrText); ObjectSetInteger(0, atrName, OBJPROP_COLOR, ColText); rectangles[rectangleCount].atrLabel = atrName; rectangleCount++; if(rectangleCount >= DaysToShow) break; built++; } ChartRedraw(); return(rates_total); } //+------------------------------------------------------------------+