//+------------------------------------------------------------------+
//| Multi-Time_Period_Charts.mq5 |
//| TradingView|
//| Converted from Pine Script |
//+------------------------------------------------------------------+
#property copyright "Converted from TradingView"
#property indicator_chart_window
#property description "Multi-Time Period Charts (MTPC)"
#property indicator_buffers 20
//+------------------------------------------------------------------+
//| Constants |
//+------------------------------------------------------------------+
#define MAX_BOXES 500
// Color constants
color UP_COLOR = C'0,150,136'; // #009688
color DN_COLOR = C'244,67,70'; // #F44336
color UPBD_COLOR = C'0,150,136'; // with transparency 70/255 ≈ 27%
color DNBD_COLOR = C'244,67,70'; // with transparency 70/255 ≈ 27%
// Calculation types enumeration
enum ENUM_CALC_TYPE
{
CALC_HL = 0, // High/Low Range
CALC_OC = 1, // Open/Close Range
CALC_OHLC = 2, // OHLC
CALC_TR = 3 // True Range
};
//+------------------------------------------------------------------+
//| Input parameters |
//+------------------------------------------------------------------+
input bool AutoTF = true; // Auto-timeframe
input ENUM_TIMEFRAMES TF = PERIOD_CURRENT; // Timeframe (when AutoTF is false)
input ENUM_CALC_TYPE CalcType = CALC_HL; // Calculation (0=High/Low, 1=Open/Close, 2=OHLC, 3=True Range)
input bool UseHaInput = false; // Display Heikin Ashi values
input bool UseDailyInput = false; // Use daily-based values
// Border and fill colors
input color upBorderColorInput = UP_COLOR; // Up bars border
input color upBodyColorInput = UPBD_COLOR; // Up bars body
input color dnBorderColorInput = DN_COLOR; // Down bars border
input color dnBodyColorInput = DNBD_COLOR; // Down bars body
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
string g_timeframe;
string g_ticker;
int g_calcType;
bool g_useHa;
bool g_useDaily;
datetime g_prevBarTime = 0;
int g_barIndex = 0;
int g_boxCounter = 0;
//+------------------------------------------------------------------+
//| Structure for OHLC data |
//+------------------------------------------------------------------+
struct SOHLC
{
double open;
double high;
double low;
double close;
double prevClose;
};
//+------------------------------------------------------------------+
//| Select automatic higher timeframe based on current chart TF |
//+------------------------------------------------------------------+
string SelectAutoTimeframe()
{
ENUM_TIMEFRAMES curTF = (ENUM_TIMEFRAMES)_Period;
int curSeconds = (int)Period();
// Get seconds for current timeframe
switch(curTF)
{
case PERIOD_M1: curSeconds = 60; break;
case PERIOD_M2: curSeconds = 120; break;
case PERIOD_M3: curSeconds = 180; break;
case PERIOD_M4: curSeconds = 240; break;
case PERIOD_M5: curSeconds = 300; break;
case PERIOD_M6: curSeconds = 360; break;
case PERIOD_M10: curSeconds = 600; break;
case PERIOD_M12: curSeconds = 720; break;
case PERIOD_M15: curSeconds = 900; break;
case PERIOD_M20: curSeconds = 1200; break;
case PERIOD_M30: curSeconds = 1800; break;
case PERIOD_H1: curSeconds = 3600; break;
case PERIOD_H2: curSeconds = 7200; break;
case PERIOD_H3: curSeconds = 10800; break;
case PERIOD_H4: curSeconds = 14400; break;
case PERIOD_H6: curSeconds = 21600; break;
case PERIOD_H8: curSeconds = 28800; break;
case PERIOD_H12: curSeconds = 43200; break;
case PERIOD_D1: curSeconds = 86400; break;
case PERIOD_W1: curSeconds = 604800; break;
case PERIOD_MN1: curSeconds = 2592000; break;
}
// Determine higher timeframe string
if(curSeconds <= 60)
return "120"; // 2 hours for seconds-based
else if(curSeconds < 86400)
return "1440"; // 1 day
else if(curSeconds < 604800)
return "1W"; // 1 week
else if(curSeconds < 2592000)
return "1M"; // 1 month
else if(curSeconds < 7776000)
return "3M"; // 3 months
else
return "12M"; // 12 months
}
//+------------------------------------------------------------------+
//| Convert timeframe string to ENUM_TIMEFRAMES |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES StringToTimeframe(string tf)
{
if(tf == "120") return PERIOD_H2;
if(tf == "1440") return PERIOD_D1;
if(tf == "1W") return PERIOD_W1;
if(tf == "1M") return PERIOD_MN1;
if(tf == "3M") return PERIOD_MN1;
if(tf == "12M") return PERIOD_MN1;
return StringToTimeframe(_Symbol, tf);
}
//+------------------------------------------------------------------+
//| Get adjusted timeframe (consider extended sessions) |
//+------------------------------------------------------------------+
string SelectTimeframeFromInput(string tf)
{
// For MQL5, we handle extended sessions differently
// In MT5, we check if current symbol has extended hours
bool isExtended = (bool)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_EXISIBLE);
if(isExtended && tf == "1D")
return "1440";
return tf;
}
//+------------------------------------------------------------------+
//| Calculate Heikin Ashi values |
//+------------------------------------------------------------------+
void CalculateHeikinAshi(double o, double h, double l, double c,
double prevHO, double prevHC,
double &haO, double &haH, double &haL, double &haC)
{
haC = (o + h + l + c) / 4.0;
if(prevHO == 0 && prevHC == 0)
haO = (o + c) / 2.0;
else
haO = (prevHO + prevHC) / 2.0;
haH = MathMax(h, MathMax(haO, haC));
haL = MathMin(l, MathMin(haO, haC));
}
//+------------------------------------------------------------------+
//| Create OHLC object from current bar data |
//+------------------------------------------------------------------+
SOHLC MakeOHLC(int offset = 0)
{
SOHLC ohlc;
// Get offset bar data
if(offset == 0)
{
ohlc.open = iOpen(_Symbol, PERIOD_CURRENT, offset);
ohlc.high = iHigh(_Symbol, PERIOD_CURRENT, offset);
ohlc.low = iLow(_Symbol, PERIOD_CURRENT, offset);
ohlc.close = iClose(_Symbol, PERIOD_CURRENT, offset);
ohlc.prevClose = iClose(_Symbol, PERIOD_CURRENT, 1);
}
else
{
ohlc.open = iOpen(_Symbol, PERIOD_CURRENT, offset);
ohlc.high = iHigh(_Symbol, PERIOD_CURRENT, offset);
ohlc.low = iLow(_Symbol, PERIOD_CURRENT, offset);
ohlc.close = iClose(_Symbol, PERIOD_CURRENT, offset);
ohlc.prevClose = iClose(_Symbol, PERIOD_CURRENT, offset + 1);
}
return ohlc;
}
//+------------------------------------------------------------------+
//| Get HTF data from chart aggregation |
//+------------------------------------------------------------------+
SOHLC ChartOHLC(ENUM_TIMEFRAMES tf, bool useHA, int idx)
{
static SOHLC lastHTFBar;
static bool initialized = false;
// Get current HTF bar data
double htfOpen = iOpen(_Symbol, tf, idx);
double htfHigh = iHigh(_Symbol, tf, idx);
double htfLow = iLow(_Symbol, tf, idx);
double htfClose = iClose(_Symbol, tf, idx);
double prevClose = iClose(_Symbol, tf, idx + 1);
// For Heikin Ashi
static double prevHaO = 0;
static double prevHaC = 0;
double haOpen = 0, haHigh = 0, haLow = 0, haClose = 0;
if(useHA)
{
CalculateHeikinAshi(htfOpen, htfHigh, htfLow, htfClose,
prevHaO, prevHaC, haOpen, haHigh, haLow, haClose);
prevHaO = haOpen;
prevHaC = haClose;
SOHLC result;
result.open = haOpen;
result.high = haHigh;
result.low = haLow;
result.close = haClose;
result.prevClose = prevHaC;
return result;
}
else
{
SOHLC result;
result.open = htfOpen;
result.high = htfHigh;
result.low = htfLow;
result.close = htfClose;
result.prevClose = prevClose;
return result;
}
}
//+------------------------------------------------------------------+
//| Get box colors based on direction |
//+------------------------------------------------------------------+
void GetBoxColors(double diff, color &bodyColor, color &borderColor)
{
if(diff < 0)
{
bodyColor = dnBodyColorInput;
borderColor = dnBorderColorInput;
}
else
{
bodyColor = upBodyColorInput;
borderColor = upBorderColorInput;
}
}
//+------------------------------------------------------------------+
//| Draw HTF box on chart |
//+------------------------------------------------------------------+
void SetBox(string name, bool hide, datetime left, datetime right,
double top, double bottom, double diff)
{
color bodyColor, borderColor;
GetBoxColors(diff, bodyColor, borderColor);
// Create or update box
if(!ObjectCreate(0, name, OBJ_RECTANGLE, 0, left, top, right, bottom))
{
// If creation fails (already exists), try to update
ObjectMove(0, name, 0, left, top);
ObjectMove(0, name, 1, right, bottom);
}
if(hide)
{
ObjectSetInteger(0, name, OBJPROP_COLOR, clrNONE);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrNONE);
}
else
{
ObjectSetInteger(0, name, OBJPROP_COLOR, borderColor);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bodyColor);
}
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, name, OBJPROP_FILL, true);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//+------------------------------------------------------------------+
//| Delete old boxes |
//+------------------------------------------------------------------+
void CleanupOldBoxes(int maxBoxes)
{
// Count existing MTPC boxes
int count = 0;
string prefix = "MTPC_Box_";
for(int i = ObjectsTotal(0, -1, OBJ_RECTANGLE) - 1; i >= 0; i--)
{
string name = ObjectName(0, i, OBJ_RECTANGLE);
if(StringFind(name, prefix) == 0)
{
count++;
if(count > maxBoxes)
ObjectDelete(0, name);
}
}
}
//+------------------------------------------------------------------+
//| Initialize indicator |
//+------------------------------------------------------------------+
int OnInit()
{
// Determine higher timeframe
if(AutoTF)
g_timeframe = SelectAutoTimeframe();
else
g_timeframe = TF;
g_ticker = _Symbol;
g_calcType = (int)CalcType;
g_useHa = UseHaInput;
g_useDaily = UseDailyInput;
// Clean up any existing boxes
string prefix = "MTPC_Box_";
for(int i = ObjectsTotal(0, -1, OBJ_RECTANGLE) - 1; i >= 0; i--)
{
string name = ObjectName(0, i, OBJ_RECTANGLE);
if(StringFind(name, prefix) == 0)
ObjectDelete(0, name);
}
prefix = "MTPC_Body_";
for(int i = ObjectsTotal(0, -1, OBJ_RECTANGLE) - 1; i >= 0; i--)
{
string name = ObjectName(0, i, OBJ_RECTANGLE);
if(StringFind(name, prefix) == 0)
ObjectDelete(0, name);
}
Print("MTPC initialized with timeframe: ", g_timeframe);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Deinitialize indicator |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Clean up all MTPC boxes
string prefix = "MTPC_Box_";
for(int i = ObjectsTotal(0, -1, OBJ_RECTANGLE) - 1; i >= 0; i--)
{
string name = ObjectName(0, i, OBJ_RECTANGLE);
if(StringFind(name, prefix) == 0)
ObjectDelete(0, name);
}
prefix = "MTPC_Body_";
for(int i = ObjectsTotal(0, -1, OBJ_RECTANGLE) - 1; i >= 0; i--)
{
string name = ObjectName(0, i, OBJ_RECTANGLE);
if(StringFind(name, prefix) == 0)
ObjectDelete(0, name);
}
}
//+------------------------------------------------------------------+
//| Main indicator calculation |
//+------------------------------------------------------------------+
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[])
{
// Get current bar index
int currentBar = rates_total - 1;
datetime currentTime = time[currentBar];
// Convert timeframe string to ENUM_TIMEFRAMES
ENUM_TIMEFRAMES htf = StringToTimeframe(g_timeframe);
// Get HTF bar data
SOHLC bar0, bar1;
if(g_useDaily)
{
// Use daily-based values via security request
bar0.open = iOpen(_Symbol, PERIOD_D1, 0);
bar0.high = iHigh(_Symbol, PERIOD_D1, 0);
bar0.low = iLow(_Symbol, PERIOD_D1, 0);
bar0.close = iClose(_Symbol, PERIOD_D1, 0);
bar0.prevClose = iClose(_Symbol, PERIOD_D1, 1);
bar1.open = iOpen(_Symbol, PERIOD_D1, 1);
bar1.high = iHigh(_Symbol, PERIOD_D1, 1);
bar1.low = iLow(_Symbol, PERIOD_D1, 1);
bar1.close = iClose(_Symbol, PERIOD_D1, 1);
bar1.prevClose = iClose(_Symbol, PERIOD_D1, 2);
}
else
{
// Use chart-based aggregation
bar0 = ChartOHLC(htf, g_useHa, 0);
bar1 = ChartOHLC(htf, g_useHa, 1);
}
// Check if new HTF bar started
datetime htfBarTime = iTime(_Symbol, htf, 0);
bool isNewPeriod = (g_prevBarTime != 0 && htfBarTime != g_prevBarTime);
g_prevBarTime = htfBarTime;
// Determine current vs completed bar
bool isLastBar = (currentBar == rates_total - 1);
bool drawCurrent = isLastBar && !isNewPeriod;
// Get the appropriate bar data
SOHLC bar;
if(drawCurrent)
bar = bar0;
else
bar = bar1;
// Handle NaN values
if(bar.high == 0 && bar.low == 0)
{
bar.open = bar.close;
bar.high = bar.open;
bar.low = bar.open;
}
// Calculate difference and coordinates
double diff = bar.close - bar.open;
datetime left = time[currentBar - 1];
datetime right = drawCurrent ? currentTime : time[currentBar - 1];
double top, bottom;
switch(g_calcType)
{
case CALC_OC:
top = bar.close;
bottom = bar.open;
break;
case CALC_TR:
top = MathMax(bar.high, bar.prevClose);
bottom = MathMin(bar.low, bar.prevClose);
break;
case CALC_HL:
case CALC_OHLC:
default:
top = bar.high;
bottom = bar.low;
break;
}
// Limit boxes to prevent performance issues
if(g_boxCounter > MAX_BOXES)
{
// Clean up oldest boxes
string oldBoxName = "MTPC_Box_" + IntegerToString(g_boxCounter - MAX_BOXES);
ObjectDelete(0, oldBoxName);
oldBoxName = "MTPC_Body_" + IntegerToString(g_boxCounter - MAX_BOXES);
ObjectDelete(0, oldBoxName);
}
// Draw HTF range box
string boxName = "MTPC_Box_" + IntegerToString(g_boxCounter);
SetBox(boxName, isNewPeriod, left, right, top, bottom, diff);
// Draw body box for OHLC mode
if(g_calcType == CALC_OHLC && !isNewPeriod)
{
string bodyName = "MTPC_Body_" + IntegerToString(g_boxCounter);
double bodyTop = bar.open > bar.close ? bar.open : bar.close;
double bodyBottom = bar.open > bar.close ? bar.close : bar.open;
SetBox(bodyName, false, left, right, bodyTop, bodyBottom, diff);
}
// Update counter on new period
if(isNewPeriod)
{
g_barIndex = currentBar;
g_boxCounter++;
}
// Draw developing box for current realtime bar
if(isLastBar)
{
string rtBoxName = "MTPC_RT_Box";
SetBox(rtBoxName, isNewPeriod, left, currentTime, top, bottom, diff);
if(g_calcType == CALC_OHLC)
{
string rtBodyName = "MTPC_RT_Body";
double bodyTop = bar.open > bar.close ? bar.open : bar.close;
double bodyBottom = bar.open > bar.close ? bar.close : bar.open;
SetBox(rtBodyName, isNewPeriod, left, currentTime, bodyTop, bodyBottom, diff);
}
}
// Return bars processed
return rates_total;
}
//+------------------------------------------------------------------+