SessionVWAPRSIStepped by Naya
575 downloads / 321 views / Created: 11.06.2025 Average Rating: 0
Indicator Description
SessionVWAPRSIStepped is a custom technical indicator designed for Forex Strategy Builder Pro. It combines the power of session-based VWAP (Volume Weighted Average Price) with the Relative Strength Index (RSI) to deliver precise market entry and exit signals. Unlike traditional indicators, it calculates RSI using stepped VWAP values that reset at customizable intervals, anchored to a specific time of day. This approach provides enhanced responsiveness to intraday market structure, enabling advanced filtering based on RSI logic such as rising/falling trends, level crossovers, and threshold comparisons. Ideal for traders seeking adaptive strategies aligned with market sessions and volume-weighted momentum.
Comments
using System;
using System.Drawing;
using System.Collections.Generic;
using ForexStrategyBuilder.Infrastructure.Entities;
using ForexStrategyBuilder.Infrastructure.Enums;
using ForexStrategyBuilder.Infrastructure.Interfaces;
namespace ForexStrategyBuilder.Indicators.Custom
{
public class SessionVWAPRSIStepped : Indicator
{
public SessionVWAPRSIStepped()
{
IndicatorName = "SessionVWAPRSIStepped";
PossibleSlots = SlotTypes.OpenFilter | SlotTypes.CloseFilter;
SeparatedChart = true;
SeparatedChartMinValue = 0;
SeparatedChartMaxValue = 100;
IndicatorAuthor = "NAYA,+237674724684";
IndicatorVersion = "1.0";
IndicatorDescription = "RSI calculated from SessionVWAPStepped price with stepped reset periods.";
}
public override void Initialize(SlotTypes slotType)
{
SlotType = slotType;
// 1) Logic selection
IndParam.ListParam[0].Caption = "Logic";
IndParam.ListParam[0].ItemList = new[]
{
"SessionVWAPRSI rises",
"SessionVWAPRSI falls",
"SessionVWAPRSI is higher than the Level line",
"SessionVWAPRSI is lower than the Level line",
"SessionVWAPRSI crosses the Level line upward",
"SessionVWAPRSI crosses the Level line downward",
"SessionVWAPRSI changes its direction upward",
"SessionVWAPRSI changes its direction downward"
};
IndParam.ListParam[0].Index = 0;
IndParam.ListParam[0].Text = IndParam.ListParam[0].ItemList[0];
IndParam.ListParam[0].Enabled = true;
IndParam.ListParam[0].ToolTip = "Logic of application of the indicator.";
// 2) Anchor Hour (0-23)
IndParam.ListParam[1].Caption = "Anchor Hour (0-23)";
var hourList = new List();
for (int h = 0; h <= 23; h++)
hourList.Add(h.ToString("00"));
IndParam.ListParam[1].ItemList = hourList.ToArray();
IndParam.ListParam[1].Index = 0;
IndParam.ListParam[1].Text = IndParam.ListParam[1].ItemList[0];
IndParam.ListParam[1].Enabled = true;
IndParam.ListParam[1].ToolTip = "Hour component of the anchor time (0-23).";
// 3) Anchor Minute (0-45 by 15)
IndParam.ListParam[2].Caption = "Anchor Minute";
var minuteList = new List();
for (int m = 0; m <= 45; m += 15)
minuteList.Add(m.ToString("00"));
IndParam.ListParam[2].ItemList = minuteList.ToArray();
IndParam.ListParam[2].Index = 0;
IndParam.ListParam[2].Text = IndParam.ListParam[2].ItemList[0];
IndParam.ListParam[2].Enabled = true;
IndParam.ListParam[2].ToolTip = "Minute component of the anchor time (0-45 by 15).";
// 4) Reset Period (minutes) stepping 15-1440 by 15
IndParam.ListParam[3].Caption = "Reset Period (minutes)";
var resetList = new List();
for (int m = 60; m <= 1440; m += 30)
resetList.Add(m.ToString());
IndParam.ListParam[3].ItemList = resetList.ToArray();
int defaultResetIndex = resetList.IndexOf("60");
if (defaultResetIndex < 0) defaultResetIndex = 3;
IndParam.ListParam[3].Index = defaultResetIndex;
IndParam.ListParam[3].Text = resetList[defaultResetIndex];
IndParam.ListParam[3].Enabled = true;
IndParam.ListParam[3].ToolTip = "Select how many minutes before VWAP resets (15-1440 by 15).";
// 5) RSI Period
IndParam.NumParam[0].Caption = "RSI Period";
IndParam.NumParam[0].Value = 14;
IndParam.NumParam[0].Min = 2;
IndParam.NumParam[0].Max = 30;
IndParam.NumParam[0].Enabled = true;
IndParam.NumParam[0].ToolTip = "The period of RSI calculation.";
// 6) Level
IndParam.NumParam[1].Caption = "Level";
IndParam.NumParam[1].Value = 30;
IndParam.NumParam[1].Min = 1;
IndParam.NumParam[1].Max = 100;
IndParam.NumParam[1].Enabled = true;
IndParam.NumParam[1].ToolTip = "A signal level.";
// 7) Use previous bar value
IndParam.CheckParam[0].Caption = "Use previous bar value";
IndParam.CheckParam[0].Enabled = true;
IndParam.CheckParam[0].ToolTip = "Use the indicator value from the previous bar.";
}
public override void Calculate(IDataSet dataSet)
{
DataSet = dataSet;
// Read parameters
int anchorHour = int.Parse(IndParam.ListParam[1].Text);
int anchorMinute = int.Parse(IndParam.ListParam[2].Text);
int resetPeriod = int.Parse(IndParam.ListParam[3].Text);
int rsiPeriod = (int)IndParam.NumParam[0].Value;
double level = IndParam.NumParam[1].Value;
int previous = IndParam.CheckParam[0].Checked ? 1 : 0;
// First calculate SessionVWAPStepped values
double[] vwapPrice = CalculateVWAP(anchorHour, anchorMinute, resetPeriod);
// Then calculate RSI from VWAP prices
double[] rsi = CalculateRSI(vwapPrice, rsiPeriod);
// Prepare components
int firstBar = rsiPeriod + previous + 2;
Component = new IndicatorComp[3];
// Main RSI line
Component[0] = new IndicatorComp
{
CompName = "SessionVWAPRSI",
DataType = IndComponentType.IndicatorValue,
ChartType = IndChartType.Line,
ChartColor = Color.DodgerBlue,
FirstBar = firstBar,
Value = rsi
};
// Filter components
Component[1] = new IndicatorComp
{
ChartType = IndChartType.NoChart,
FirstBar = firstBar,
Value = new double[Bars]
};
Component[2] = new IndicatorComp
{
ChartType = IndChartType.NoChart,
FirstBar = firstBar,
Value = new double[Bars]
};
// Set component types based on slot
if (SlotType == SlotTypes.OpenFilter)
{
Component[1].DataType = IndComponentType.AllowOpenLong;
Component[1].CompName = "Is long entry allowed";
Component[2].DataType = IndComponentType.AllowOpenShort;
Component[2].CompName = "Is short entry allowed";
}
else if (SlotType == SlotTypes.CloseFilter)
{
Component[1].DataType = IndComponentType.ForceCloseLong;
Component[1].CompName = "Close out long position";
Component[2].DataType = IndComponentType.ForceCloseShort;
Component[2].CompName = "Close out short position";
}
// Apply logic
var logicRule = IndicatorLogic.It_does_not_act_as_a_filter;
switch (IndParam.ListParam[0].Text)
{
case "SessionVWAPRSI rises":
logicRule = IndicatorLogic.The_indicator_rises;
break;
case "SessionVWAPRSI falls":
logicRule = IndicatorLogic.The_indicator_falls;
break;
case "SessionVWAPRSI is higher than the Level line":
logicRule = IndicatorLogic.The_indicator_is_higher_than_the_level_line;
break;
case "SessionVWAPRSI is lower than the Level line":
logicRule = IndicatorLogic.The_indicator_is_lower_than_the_level_line;
break;
case "SessionVWAPRSI crosses the Level line upward":
logicRule = IndicatorLogic.The_indicator_crosses_the_level_line_upward;
break;
case "SessionVWAPRSI crosses the Level line downward":
logicRule = IndicatorLogic.The_indicator_crosses_the_level_line_downward;
break;
case "SessionVWAPRSI changes its direction upward":
logicRule = IndicatorLogic.The_indicator_changes_its_direction_upward;
break;
case "SessionVWAPRSI changes its direction downward":
logicRule = IndicatorLogic.The_indicator_changes_its_direction_downward;
break;
}
OscillatorLogic(firstBar, previous, rsi, level, 100 - level, ref Component[1], ref Component[2], logicRule);
}
private double[] CalculateVWAP(int anchorHour, int anchorMinute, int resetPeriod)
{
int firstBar = 3;
int bars = Bars;
double[] vwapBuffer = new double[bars];
double cumPV = 0.0;
double cumV = 0.0;
DateTime currentReset = DateTime.MinValue;
for (int iBar = firstBar; iBar < bars; iBar++)
{
DateTime barTime = Time[iBar]; // CHANGED: Removed AddMinutes(Period) to use raw bar opening time
DateTime lastReset = GetLastResetTime(barTime, anchorHour, anchorMinute, resetPeriod);
if (lastReset != currentReset)
{
cumPV = 0.0;
cumV = 0.0;
currentReset = lastReset;
}
double typicalPrice = (Open[iBar] + High[iBar] + Low[iBar] + Close[iBar]) / 4.0;
double volume = Volume[iBar];
cumPV += typicalPrice * volume;
cumV += volume;
vwapBuffer[iBar] = (cumV > 0.0) ? (cumPV / cumV) : 0.0;
}
return vwapBuffer;
}
private DateTime GetLastResetTime(DateTime currentTime, int anchorHour, int anchorMinute, int resetPeriod)
{
int currentMinutes = currentTime.Hour * 60 + currentTime.Minute;
int anchorMinutes = anchorHour * 60 + anchorMinute;
DateTime lastReset;
if (currentMinutes >= anchorMinutes)
{
int periods = (currentMinutes - anchorMinutes) / resetPeriod;
lastReset = new DateTime(
currentTime.Year, currentTime.Month, currentTime.Day,
anchorHour, anchorMinute, 0
).AddMinutes(periods * resetPeriod);
}
else
{
DateTime prevDayAnchor = new DateTime(
currentTime.Year, currentTime.Month, currentTime.Day,
anchorHour, anchorMinute, 0
).AddDays(-1);
int minutesSincePrev = (1440 - anchorMinutes) + currentMinutes;
int periods = minutesSincePrev / resetPeriod;
lastReset = prevDayAnchor.AddMinutes(periods * resetPeriod);
}
return lastReset;
}
private double[] CalculateRSI(double[] price, int period)
{
int bars = price.Length;
double[] rsi = new double[bars];
double[] pos = new double[bars];
double[] neg = new double[bars];
for (int bar = 1; bar < bars; bar++)
{
if (price[bar] > price[bar - 1] + 0.0000001) // Epsilon
pos[bar] = price[bar] - price[bar - 1];
if (price[bar] < price[bar - 1] - 0.0000001) // Epsilon
neg[bar] = price[bar - 1] - price[bar];
}
// Using standard RSI calculation (Wilder's smoothing)
double[] posMa = new double[bars];
double[] negMa = new double[bars];
// First average
double sumPos = 0;
double sumNeg = 0;
for (int i = 1; i <= period; i++)
{
sumPos += pos[i];
sumNeg += neg[i];
}
posMa[period] = sumPos / period;
negMa[period] = sumNeg / period;
// Subsequent averages
for (int bar = period + 1; bar < bars; bar++)
{
posMa[bar] = (posMa[bar - 1] * (period - 1) + pos[bar]) / period;
negMa[bar] = (negMa[bar - 1] * (period - 1) + neg[bar]) / period;
}
for (int bar = period; bar < bars; bar++)
{
if (Math.Abs(negMa[bar]) > 0.0000001) // Epsilon
rsi[bar] = 100 - (100 / (1 + posMa[bar] / negMa[bar]));
else
rsi[bar] = (Math.Abs(posMa[bar]) > 0.0000001) ? 100 : 50;
}
return rsi;
}
public override void SetDescription()
{
string longLevel = IndParam.NumParam[1].ValueToString;
string shortLevel = IndParam.NumParam[1].AnotherValueToString(100 - IndParam.NumParam[1].Value);
EntryFilterLongDescription = ToString() + " ";
EntryFilterShortDescription = ToString() + " ";
ExitFilterLongDescription = ToString() + " ";
ExitFilterShortDescription = ToString() + " ";
switch (IndParam.ListParam[0].Text)
{
case "SessionVWAPRSI rises":
EntryFilterLongDescription += "rises";
EntryFilterShortDescription += "falls";
ExitFilterLongDescription += "rises";
ExitFilterShortDescription += "falls";
break;
case "SessionVWAPRSI falls":
EntryFilterLongDescription += "falls";
EntryFilterShortDescription += "rises";
ExitFilterLongDescription += "falls";
ExitFilterShortDescription += "rises";
break;
case "SessionVWAPRSI is higher than the Level line":
EntryFilterLongDescription += "is higher than the Level " + longLevel;
EntryFilterShortDescription += "is lower than the Level " + shortLevel;
ExitFilterLongDescription += "is higher than the Level " + longLevel;
ExitFilterShortDescription += "is lower than the Level " + shortLevel;
break;
case "SessionVWAPRSI is lower than the Level line":
EntryFilterLongDescription += "is lower than the Level " + longLevel;
EntryFilterShortDescription += "is higher than the Level " + shortLevel;
ExitFilterLongDescription += "is lower than the Level " + longLevel;
ExitFilterShortDescription += "is higher than the Level " + shortLevel;
break;
case "SessionVWAPRSI crosses the Level line upward":
EntryFilterLongDescription += "crosses the Level " + longLevel + " upward";
EntryFilterShortDescription += "crosses the Level " + shortLevel + " downward";
ExitFilterLongDescription += "crosses the Level " + longLevel + " upward";
ExitFilterShortDescription += "crosses the Level " + shortLevel + " downward";
break;
case "SessionVWAPRSI crosses the Level line downward":
EntryFilterLongDescription += "crosses the Level " + longLevel + " downward";
EntryFilterShortDescription += "crosses the Level " + shortLevel + " upward";
ExitFilterLongDescription += "crosses the Level " + longLevel + " downward";
ExitFilterShortDescription += "crosses the Level " + shortLevel + " upward";
break;
case "SessionVWAPRSI changes its direction upward":
EntryFilterLongDescription += "changes its direction upward";
EntryFilterShortDescription += "changes its direction downward";
ExitFilterLongDescription += "changes its direction upward";
ExitFilterShortDescription += "changes its direction downward";
break;
case "SessionVWAPRSI changes its direction downward":
EntryFilterLongDescription += "changes its direction downward";
EntryFilterShortDescription += "changes its direction upward";
ExitFilterLongDescription += "changes its direction downward";
ExitFilterShortDescription += "changes its direction upward";
break;
}
}
public override string ToString()
{
return IndicatorName +
(IndParam.CheckParam[0].Checked ? "* (" : " (") +
"Anchor: " + IndParam.ListParam[1].Text + ":" + IndParam.ListParam[2].Text + ", " +
"Reset: " + IndParam.ListParam[3].Text + " min, " +
"Period: " + IndParam.NumParam[0].ValueToString + ")";
}
}
}
#property copyright "Copyright (C) 2025 NAYA +237674724684" #property link "https://forexsb.com" #property version "1.0" #property strict #include <Forexsb.com/Indicator.mqh> #include <Forexsb.com/Enumerations.mqh> class SessionVWAPRSIStepped : public Indicator { public: SessionVWAPRSIStepped(SlotTypes slotType) { SlotType = slotType; IndicatorName = "SessionVWAPRSIStepped"; WarningMessage = "RSI calculated from SessionVWAPStepped price with stepped reset periods."; IsAllowLTF = true; ExecTime = ExecutionTime_DuringTheBar; IsSeparateChart = true; IsDiscreteValues = false; IsDefaultGroupAll = false; } virtual void Calculate(DataSet &dataSet); private: void CalculateVWAP(double &vwapBuffer[], int anchorHour, int anchorMinute, int resetPeriod); void CalculateRSI(const double &price[], double &rsi[], int period); datetime GetLastResetTime(datetime currentTime, int anchorHour, int anchorMinute, int resetPeriod); }; //+------------------------------------------------------------------+ void SessionVWAPRSIStepped::Calculate(DataSet &dataSet) { Data = GetPointer(dataSet); // 1) Read parameters int anchorHour = (int)StringToInteger(ListParam[1].Text); int anchorMinute = (int)StringToInteger(ListParam[2].Text); int resetPeriod = (int)StringToInteger(ListParam[3].Text); int rsiPeriod = (int)NumParam[0].Value; double level = NumParam[1].Value; int previous = CheckParam[0].Checked ? 1 : 0; int bars = Data.Bars; int firstBar = rsiPeriod + previous + 2; // 2) Compute stepped VWAP double vwapPrice[]; ArrayResize(vwapPrice, bars); ArrayInitialize(vwapPrice, 0.0); CalculateVWAP(vwapPrice, anchorHour, anchorMinute, resetPeriod); // 3) Compute RSI on VWAP double rsi[]; ArrayResize(rsi, bars); ArrayInitialize(rsi, 0.0); CalculateRSI(vwapPrice, rsi, rsiPeriod); // 4) Prepare components ArrayResize(Component, 3); // Main RSI line ArrayResize(Component[0].Value, bars); Component[0].CompName = "SessionVWAPRSI"; Component[0].DataType = IndComponentType_IndicatorValue; Component[0].FirstBar = firstBar; ArrayCopy(Component[0].Value, rsi); // Two filter buffers (no chart) for(int i=1; i<=2; i++) { ArrayResize(Component[i].Value, bars); ArrayInitialize(Component[i].Value, 0.0); Component[i].FirstBar = firstBar; } // Assign filter types if(SlotType == SlotTypes_OpenFilter) { Component[1].DataType = IndComponentType_AllowOpenLong; Component[1].CompName = "Is long entry allowed"; Component[2].DataType = IndComponentType_AllowOpenShort; Component[2].CompName = "Is short entry allowed"; } else // CloseFilter { Component[1].DataType = IndComponentType_ForceCloseLong; Component[1].CompName = "Close out long position"; Component[2].DataType = IndComponentType_ForceCloseShort; Component[2].CompName = "Close out short position"; } // 5) Apply logic IndicatorLogic logicRule = IndicatorLogic_It_does_not_act_as_a_filter; string logic = ListParam[0].Text; if(logic == "SessionVWAPRSI rises") logicRule = IndicatorLogic_The_indicator_rises; else if(logic == "SessionVWAPRSI falls") logicRule = IndicatorLogic_The_indicator_falls; else if(logic == "SessionVWAPRSI is higher than the Level line") logicRule = IndicatorLogic_The_indicator_is_higher_than_the_level_line; else if(logic == "SessionVWAPRSI is lower than the Level line") logicRule = IndicatorLogic_The_indicator_is_lower_than_the_level_line; else if(logic == "SessionVWAPRSI crosses the Level line upward") logicRule = IndicatorLogic_The_indicator_crosses_the_level_line_upward; else if(logic == "SessionVWAPRSI crosses the Level line downward") logicRule = IndicatorLogic_The_indicator_crosses_the_level_line_downward; else if(logic == "SessionVWAPRSI changes its direction upward") logicRule = IndicatorLogic_The_indicator_changes_its_direction_upward; else if(logic == "SessionVWAPRSI changes its direction downward") logicRule = IndicatorLogic_The_indicator_changes_its_direction_downward; OscillatorLogic(firstBar, previous, rsi, level, 100.0 - level, Component[1], Component[2], logicRule); } //+------------------------------------------------------------------+ void SessionVWAPRSIStepped::CalculateVWAP(double &vwapBuffer[], int anchorHour, int anchorMinute, int resetPeriod) { const int firstStepped = 3; double cumPV = 0.0, cumV = 0.0; datetime currentReset = 0; int bars = Data.Bars; for(int bar = firstStepped; bar < bars; bar++) { // CHANGED: Removed Period()*60 adjustment to use raw bar opening time datetime barTime = Data.Time[bar]; datetime lastReset = GetLastResetTime(barTime, anchorHour, anchorMinute, resetPeriod); if(lastReset != currentReset) { cumPV = 0.0; cumV = 0.0; currentReset = lastReset; } double tp = (Data.Open[bar] + Data.High[bar] + Data.Low[bar] + Data.Close[bar]) / 4.0; double vol = (double)Data.Volume[bar]; cumPV += tp * vol; cumV += vol; vwapBuffer[bar] = (cumV > 0.0) ? (cumPV / cumV) : 0.0; } } //+------------------------------------------------------------------+ void SessionVWAPRSIStepped::CalculateRSI(const double &price[], double &rsi[], int period) { int bars = ArraySize(price); double pos[], neg[], posMa[], negMa[]; ArrayResize(pos, bars); ArrayInitialize(pos, 0.0); ArrayResize(neg, bars); ArrayInitialize(neg, 0.0); ArrayResize(posMa, bars); ArrayInitialize(posMa, 0.0); ArrayResize(negMa, bars); ArrayInitialize(negMa, 0.0); // 1) Gains/Losses for(int bar = 1; bar < bars; bar++) { if(price[bar] > price[bar-1] + 0.0000001) // Epsilon pos[bar] = price[bar] - price[bar-1]; if(price[bar] < price[bar-1] - 0.0000001) // Epsilon neg[bar] = price[bar-1] - price[bar]; } // 2) Wilder's smoothing double sumPos=0.0, sumNeg=0.0; for(int i=1; i<=period; i++) { sumPos += pos[i]; sumNeg += neg[i]; } posMa[period] = sumPos/period; negMa[period] = sumNeg/period; for(int bar = period+1; bar < bars; bar++) { posMa[bar] = (posMa[bar-1]*(period-1) + pos[bar]) / period; negMa[bar] = (negMa[bar-1]*(period-1) + neg[bar]) / period; } // 3) RSI formula ArrayResize(rsi, bars); ArrayInitialize(rsi, 0.0); for(int bar = period; bar < bars; bar++) { if(MathAbs(negMa[bar]) > 0.0000001) // Epsilon rsi[bar] = 100.0 - (100.0 / (1.0 + posMa[bar]/negMa[bar])); else rsi[bar] = (MathAbs(posMa[bar]) > 0.0000001) ? 100.0 : 50.0; } } //+------------------------------------------------------------------+ datetime SessionVWAPRSIStepped::GetLastResetTime(datetime currentTime, int anchorHour, int anchorMinute, int resetPeriod) { MqlDateTime tm; TimeToStruct(currentTime, tm); int currentMinutes = tm.hour*60 + tm.min; int anchorMinutes = anchorHour*60 + anchorMinute; datetime lastReset; if(currentMinutes >= anchorMinutes) { tm.hour = anchorHour; tm.min = anchorMinute; tm.sec = 0; datetime anchorToday = StructToTime(tm); int periods = (currentMinutes - anchorMinutes) / resetPeriod; lastReset = anchorToday + (datetime)(periods * resetPeriod * 60); } else { tm.hour = anchorHour; tm.min = anchorMinute; tm.sec = 0; datetime anchorPrev = StructToTime(tm) - 86400; int minutesSincePrev = (1440 - anchorMinutes) + currentMinutes; int periods = minutesSincePrev / resetPeriod; lastReset = anchorPrev + (datetime)(periods * resetPeriod * 60); } return(lastReset); } //+------------------------------------------------------------------+
Risk warning: Forex, spread bets and CFD are leveraged products. They may not be suitable for you as they carry a high degree of risk to your capital and you can lose more than your initial investment. You should ensure you understand all of the risks.
Copyright © 2006 - 2025, Forex Software Ltd.;
Copyright © 2006 - 2025, Forex Software Ltd.;