Files

269 lines
9.3 KiB
Plaintext

//+------------------------------------------------------------------+
//| VWAP_History_Levels.mq5 |
//| Copyright 2026, xxxxxxxx|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, xxxxxxxx"
#property version "2.11" // BUGFIX: Prevent array out of range on TF switch
#property description "Draws Historical VWAP Close Levels as Support/Resistance."
#property indicator_chart_window
#property indicator_plots 0 // No buffers shown
#include <MyIncludes\VWAP_Calculator.mqh>
//--- Input Parameters
input group "Daily Levels"
input bool InpShowDaily = true;
input int InpDailyCount = 3; // Keep last N Levels
input color InpDailyColor = clrDeepPink;
input group "Weekly Levels"
input bool InpShowWeekly = true;
input int InpWeeklyCount = 3;
input color InpWeeklyColor = clrDodgerBlue;
input group "Monthly Levels"
input bool InpShowMonthly = true;
input int InpMonthlyCount = 2;
input color InpMonthlyColor = clrMediumTurquoise;
//--- Internal Calculators & Buffers (Hidden)
CVWAPCalculator *g_vwap_d;
CVWAPCalculator *g_vwap_w;
CVWAPCalculator *g_vwap_m;
double calc_daily[], calc_weekly[], calc_monthly[];
//+------------------------------------------------------------------+
//| Init |
//+------------------------------------------------------------------+
int OnInit()
{
// Init Calculators
g_vwap_d = new CVWAPCalculator();
g_vwap_d.Init(PERIOD_SESSION, VOLUME_TICK, 0, InpShowDaily);
g_vwap_w = new CVWAPCalculator();
g_vwap_w.Init(PERIOD_WEEK, VOLUME_TICK, 0, InpShowWeekly);
g_vwap_m = new CVWAPCalculator();
g_vwap_m.Init(PERIOD_MONTH, VOLUME_TICK, 0, InpShowMonthly);
IndicatorSetString(INDICATOR_SHORTNAME, "VWAP Hist Levels");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Deinit |
//+------------------------------------------------------------------+
void OnDeinit(const int r)
{
ObjectsDeleteAll(0, "VLevel_");
delete g_vwap_d;
delete g_vwap_w;
delete g_vwap_m;
}
//+------------------------------------------------------------------+
//| Calculate |
//+------------------------------------------------------------------+
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[])
{
if(rates_total < 2)
return 0;
// OPTIMIZATION: Limit Lookback
// We only show last N days/weeks. We don't need to calc VWAP from 1990.
// Let's safe-limit to last ~5000 bars (usually enough for monthly VWAP on M5).
// Or dynamically based on Period.
int limit_bars = 5000;
if(InpShowMonthly)
limit_bars = 40000; // Need more history for months on M1
int start_calc = rates_total - limit_bars;
if(start_calc < 0)
start_calc = 0;
// Respect prev_calculated but apply floor limit
int start_loop = (prev_calculated > 0) ? prev_calculated - 1 : start_calc;
if(start_loop < start_calc)
start_loop = start_calc; // Force start if full recalc requested but limit history
// Resize buffers
if(ArraySize(calc_daily) != rates_total)
{
ArrayResize(calc_daily, rates_total);
ArrayResize(calc_weekly, rates_total);
ArrayResize(calc_monthly, rates_total);
}
// 1. Calculate VWAP History
// Note: The Calculator Engine iterates safely from 0 if required.
double odd[], even[];
if(InpShowDaily)
{
g_vwap_d.Calculate(rates_total, start_loop, time, open, high, low, close, tick_volume, volume, odd, even);
MergeBuffers(rates_total, odd, even, calc_daily, start_loop);
}
if(InpShowWeekly)
{
g_vwap_w.Calculate(rates_total, start_loop, time, open, high, low, close, tick_volume, volume, odd, even);
MergeBuffers(rates_total, odd, even, calc_weekly, start_loop);
}
if(InpShowMonthly)
{
g_vwap_m.Calculate(rates_total, start_loop, time, open, high, low, close, tick_volume, volume, odd, even);
MergeBuffers(rates_total, odd, even, calc_monthly, start_loop);
}
// 2. Manage Objects
// OPTIMIZATION: Don't check objects on every tick if not needed.
// Only check if new candle or full recalc.
// CRITICAL FIX: To prevent "Array out of range" when querying [i-1],
// the object scanner must start at minimum index 1.
int start_obj = (start_loop == 0) ? 1 : start_loop;
for(int i = start_obj; i < rates_total; i++)
{
MqlDateTime dt, dt_prev;
TimeToStruct(time[i], dt);
TimeToStruct(time[i-1], dt_prev);
// Daily
if(InpShowDaily && dt.day_of_year != dt_prev.day_of_year)
{
CreateLevelObject(time[i-1], calc_daily[i-1], "D", InpDailyColor, InpDailyCount);
}
// Weekly
datetime t_week = iTime(_Symbol, PERIOD_W1, iBarShift(_Symbol, PERIOD_W1, time[i]));
datetime t_p_week = iTime(_Symbol, PERIOD_W1, iBarShift(_Symbol, PERIOD_W1, time[i-1]));
if(InpShowWeekly && t_week != t_p_week)
{
CreateLevelObject(time[i-1], calc_weekly[i-1], "W", InpWeeklyColor, InpWeeklyCount);
}
// Monthly
if(InpShowMonthly && dt.mon != dt_prev.mon)
{
CreateLevelObject(time[i-1], calc_monthly[i-1], "M", InpMonthlyColor, InpMonthlyCount);
}
}
return(rates_total);
}
//+------------------------------------------------------------------+
//| Helpers |
//+------------------------------------------------------------------+
// Updated MergeBuffer to respect start index
void MergeBuffers(int total, const double &src1[], const double &src2[], double &dst[], int start)
{
for(int i=start; i<total; i++)
{
if(src1[i] != EMPTY_VALUE && src1[i] != 0)
dst[i] = src1[i];
else
dst[i] = src2[i];
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void MergeBuffers(int total, const double &src1[], const double &src2[], double &dst[])
{
for(int i=0; i<total; i++)
{
if(src1[i] != EMPTY_VALUE && src1[i] != 0)
dst[i] = src1[i];
else
dst[i] = src2[i];
}
}
//+------------------------------------------------------------------+
//| Object Creation Logic |
//+------------------------------------------------------------------+
void CreateLevelObject(datetime time_start, double price, string suffix, color clr, int limit_count)
{
if(price == 0 || price == EMPTY_VALUE)
return;
// Name includes specific Date to be unique
string name_prefix = "VLevel_" + suffix + "_";
string name = name_prefix + TimeToString(time_start, TIME_DATE); // Unique per day/week
if(ObjectFind(0, name) >= 0)
return; // Already exists
// Cleanup before creating new
CleanOldObjects(name_prefix, limit_count);
ObjectCreate(0, name, OBJ_TREND, 0, time_start, price, TimeCurrent()+PeriodSeconds()*100, price);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
// Label
string text_name = name + "_txt";
ObjectCreate(0, text_name, OBJ_TEXT, 0, time_start, price);
ObjectSetString(0, text_name, OBJPROP_TEXT, " " + suffix + " " + DoubleToString(price, _Digits));
ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
ObjectSetInteger(0, text_name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 8);
}
//+------------------------------------------------------------------+
//| Garbage Collection (Clears old lines to keep chart clean) |
//+------------------------------------------------------------------+
void CleanOldObjects(string prefix, int max_count)
{
string objects[];
int count = 0;
int total = ObjectsTotal(0);
// Collect
for(int i=0; i<total; i++)
{
string n = ObjectName(0, i);
if(StringFind(n, prefix) == 0 && StringFind(n, "_txt") == -1)
{
ArrayResize(objects, count+1);
objects[count] = n;
count++;
}
}
// Delete Oldest
if(count >= max_count)
{
ArraySort(objects); // String sort works for YYYY.MM.DD
int to_delete = count - max_count + 1;
for(int i=0; i<to_delete; i++)
{
ObjectDelete(0, objects[i]);
ObjectDelete(0, objects[i] + "_txt");
}
}
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+