CustomRectangles/Indicators/_Thivyam/CustomRectangles.mq5

264 lines
12 KiB
MQL5

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