Advertisement IC Markets

Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Share, develop, and backtest custom MQL4/MQL5 Expert Advisors, Python data-scraping scripts, trading bots, and automated market alert systems.
FTtrader
Posts: 908
Joined: Mon Aug 03, 2026 2:43 pm

Re: Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Post by FTtrader »

Code: Select all

Version for MT5:

Code: Select all

//+------------------------------------------------------------------+
//|                                        Session_VWAP_SD_Bands.mq5 |
//|                                                      Pavel Tuček |
//+------------------------------------------------------------------+
#property copyright "Pavel Tuček"
#property link      ""
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 7
#property indicator_plots   7

//--- plot properties
#property indicator_label1  "VWAP"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrYellow
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "Upper 2.0 SD"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDodgerBlue
#property indicator_style2  STYLE_DASH

#property indicator_label3  "Lower 2.0 SD"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrDodgerBlue
#property indicator_style3  STYLE_DASH

#property indicator_label4  "Upper 2.5 SD"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrOrange
#property indicator_style4  STYLE_SOLID

#property indicator_label5  "Lower 2.5 SD"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrOrange
#property indicator_style5  STYLE_SOLID

#property indicator_label6  "Upper 3.0 SD"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrRed
#property indicator_style6  STYLE_SOLID
#property indicator_width6  2

#property indicator_label7  "Lower 3.0 SD"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrRed
#property indicator_style7  STYLE_SOLID
#property indicator_width7  2

//--- indicator buffers
double         VWAPBuffer[];
double         Upper20Buffer[];
double         Lower20Buffer[];
double         Upper25Buffer[];
double         Lower25Buffer[];
double         Upper30Buffer[];
double         Lower30Buffer[];

//--- global variables for Welford's algorithm
double         g_sum_vol = 0;
double         g_vwap = 0;
double         g_S = 0;
int            g_last_day = -1;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, VWAPBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, Upper20Buffer, INDICATOR_DATA);
   SetIndexBuffer(2, Lower20Buffer, INDICATOR_DATA);
   SetIndexBuffer(3, Upper25Buffer, INDICATOR_DATA);
   SetIndexBuffer(4, Lower25Buffer, INDICATOR_DATA);
   SetIndexBuffer(5, Upper30Buffer, INDICATOR_DATA);
   SetIndexBuffer(6, Lower30Buffer, INDICATOR_DATA);
   
   IndicatorSetString(INDICATOR_SHORTNAME, "Session VWAP + SD Bands");

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| 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[])
  {
   if(rates_total < 1) return(0);

   int start = prev_calculated - 1;
   if(start < 0) 
     {
      start = 0;
      g_sum_vol = 0;
      g_vwap = 0;
      g_S = 0;
      g_last_day = -1;
     }

   for(int i = start; i < rates_total; i++)
     {
      datetime current_time = time[i];
      MqlDateTime dt;
      TimeToStruct(current_time, dt);
      int current_day = dt.day_of_year;

      double typ_price = (high[i] + low[i] + close[i]) / 3.0;
      double vol = (double)tick_volume[i];

      // Reset at the start of a new session (midnight server time)
      if(current_day != g_last_day)
        {
         g_sum_vol = vol;
         g_vwap = typ_price;
         g_S = 0;
         g_last_day = current_day;
        }
      else
        {
         g_sum_vol += vol;
         if(g_sum_vol > 0)
           {
            double old_vwap = g_vwap;
            g_vwap = old_vwap + (vol / g_sum_vol) * (typ_price - old_vwap);
            g_S = g_S + vol * (typ_price - old_vwap) * (typ_price - g_vwap);
           }
        }

      double variance = (g_sum_vol > 0) ? (g_S / g_sum_vol) : 0;
      double sd = MathSqrt(variance);

      // Populate buffers
      VWAPBuffer[i]    = g_vwap;
      
      Upper20Buffer[i] = g_vwap + (2.0 * sd);
      Lower20Buffer[i] = g_vwap - (2.0 * sd);
      
      Upper25Buffer[i] = g_vwap + (2.5 * sd);
      Lower25Buffer[i] = g_vwap - (2.5 * sd);
      
      Upper30Buffer[i] = g_vwap + (3.0 * sd);
      Lower30Buffer[i] = g_vwap - (3.0 * sd);
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 908
Joined: Mon Aug 03, 2026 2:43 pm

Re: Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Post by FTtrader »

Implementation Notes

Server Time vs. Local Time: This calculates the session from your broker's midnight (server time). If your broker operates on a standard New York close schedule (EET/EST structure), this aligns perfectly out of the box.

Tick Volume Integration: It uses tick_volume[] naturally. Because it resets strictly at the day flip, it prevents historical tick data from skewing the active session valuation.

Array Formatting: The prev_calculated logic handles tick-by-tick updates efficiently. During active market hours, the for loop only executes on the current index i, updating the running Welford totals in milliseconds.
FTtrader
Posts: 908
Joined: Mon Aug 03, 2026 2:43 pm

Re: Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Post by FTtrader »

Translating this to cTrader requires a fundamental architectural shift. Because cTrader’s Calculate(int index) method executes on every single tick for the active bar, using global variables for Welford’s running totals (like we did in MQL5) will double-count the current bar's volume thousands of times.

To make the calculation idempotent, this C# implementation stores the cumulative running totals inside IndicatorDataSeries. This ensures that tick-by-tick updates recalculate safely from the previous bar's finalized state without corrupting the math.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class SessionVWAP_SDBands : Indicator
    {
        [Output("VWAP", LineColor = "Yellow", Thickness = 2)]
        public IndicatorDataSeries Vwap { get; set; }

        [Output("Upper 2.0 SD", LineColor = "DodgerBlue", LineStyle = LineStyle.Lines)]
        public IndicatorDataSeries Upper20 { get; set; }

        [Output("Lower 2.0 SD", LineColor = "DodgerBlue", LineStyle = LineStyle.Lines)]
        public IndicatorDataSeries Lower20 { get; set; }

        [Output("Upper 2.5 SD", LineColor = "Orange", Thickness = 1)]
        public IndicatorDataSeries Upper25 { get; set; }

        [Output("Lower 2.5 SD", LineColor = "Orange", Thickness = 1)]
        public IndicatorDataSeries Lower25 { get; set; }

        [Output("Upper 3.0 SD", LineColor = "Red", Thickness = 2)]
        public IndicatorDataSeries Upper30 { get; set; }

        [Output("Lower 3.0 SD", LineColor = "Red", Thickness = 2)]
        public IndicatorDataSeries Lower30 { get; set; }

        // DataSeries used to hold Welford's running totals safely across tick updates
        private IndicatorDataSeries _sumVol;
        private IndicatorDataSeries _S;

        protected override void Initialize()
        {
            _sumVol = CreateDataSeries();
            _S = CreateDataSeries();
        }

        public override void Calculate(int index)
        {
            double typPrice = (Bars.HighPrices[index] + Bars.LowPrices[index] + Bars.ClosePrices[index]) / 3.0;
            double vol = Bars.TickVolumes[index];

            // Reset condition based on calendar day rollover
            bool isNewDay = index == 0 || Bars.OpenTimes[index].Date != Bars.OpenTimes[index - 1].Date;

            if (isNewDay)
            {
                _sumVol[index] = vol;
                Vwap[index] = typPrice;
                _S[index] = 0;
            }
            else
            {
                // Pull the finalized values from the previous bar
                double prevSumVol = _sumVol[index - 1];
                double prevVwap = Vwap[index - 1];
                double prevS = _S[index - 1];

                _sumVol[index] = prevSumVol + vol;

                if (_sumVol[index] > 0)
                {
                    Vwap[index] = prevVwap + (vol / _sumVol[index]) * (typPrice - prevVwap);
                    _S[index] = prevS + vol * (typPrice - prevVwap) * (typPrice - Vwap[index]);
                }
                else
                {
                    Vwap[index] = prevVwap;
                    _S[index] = prevS;
                }
            }

            double variance = _sumVol[index] > 0 ? _S[index] / _sumVol[index] : 0;
            double sd = Math.Sqrt(variance);

            Upper20[index] = Vwap[index] + (2.0 * sd);
            Lower20[index] = Vwap[index] - (2.0 * sd);
            
            Upper25[index] = Vwap[index] + (2.5 * sd);
            Lower25[index] = Vwap[index] - (2.5 * sd);
            
            Upper30[index] = Vwap[index] + (3.0 * sd);
            Lower30[index] = Vwap[index] - (3.0 * sd);
        }
    }
}
FTtrader
Posts: 908
Joined: Mon Aug 03, 2026 2:43 pm

Re: Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Post by FTtrader »

cAlgo Implementation Notes

Idempotent Ticks: Because _sumVol[index] and _S[index] fetch their baseline from index - 1, the current bar can update 10,000 times during an active 1-minute candle without breaking the cumulative session variance.

Time Zones: The indicator is decorated with TimeZone = TimeZones.UTC. This ensures the .Date rollover behaves consistently across different brokers. If you prefer the bands to reset exactly at the 5:00 PM EST New York close rather than midnight UTC, you can switch the timezone property to TimeZones.EasternStandardTime.
FTtrader
Posts: 908
Joined: Mon Aug 03, 2026 2:43 pm

Re: Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Post by FTtrader »

To automate this in cTrader, you build a cBot that hooks into the custom SessionVWAP_SDBands indicator we just wrote.

Because you want to trade a structural rejection rather than a blind touch, the logic must execute inside the OnBar method. This guarantees the 1-minute or 5-minute candle has officially closed, confirming the liquidity sweep (the wick) before committing capital.

Here is the C# implementation for the automated fade.

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class VWAPMeanReversionBot : Robot
    {
        [Parameter("Trade Volume (Lots)", DefaultValue = 0.1, MinValue = 0.01)]
        public double TradeVolume { get; set; }

        [Parameter("Stop Loss Buffer (Pips)", DefaultValue = 1.0, MinValue = 0.0)]
        public double SlBufferPips { get; set; }

        // Reference to the custom indicator
        private SessionVWAP_SDBands _vwapIndicator;
        private string _label = "VWAP_Fade_Bot";

        protected override void Initialize()
        {
            // Load the custom indicator we built previously
            // Note: You must add the indicator as a reference in the cBot manager
            _vwapIndicator = Indicators.GetIndicator<SessionVWAP_SDBands>();
        }

        protected override void OnBar()
        {
            // Prevent pyramiding; only take one setup at a time
            if (Positions.Count(p => p.Label == _label && p.SymbolName == SymbolName) > 0)
                return;

            // Analyze the previously closed candle (index 1)
            int index = Bars.Count - 2;
            
            double high = Bars.HighPrices[index];
            double low = Bars.LowPrices[index];
            double close = Bars.ClosePrices[index];
            double open = Bars.OpenPrices[index];

            double upper25 = _vwapIndicator.Upper25[index];
            double lower25 = _vwapIndicator.Lower25[index];
            double vwap = _vwapIndicator.Vwap[index];

            // ---------------------------------------------------------
            // SHORT SETUP (Fade the Upper Band)
            // Condition 1: Wick sweeps above the 2.5 SD band
            // Condition 2: Candle closes bearish (Close < Open)
            // ---------------------------------------------------------
            if (high >= upper25 && close < open && close < upper25)
            {
                // Dynamic SL: Sweep High + Buffer
                double stopLossPrice = high + (SlBufferPips * Symbol.PipSize);
                double slDistancePips = (stopLossPrice - close) / Symbol.PipSize;

                // Dynamic TP: Return to VWAP
                double tpDistancePips = (close - vwap) / Symbol.PipSize;

                // Only take the trade if the risk-to-reward makes sense (e.g., TP > SL)
                if (tpDistancePips > slDistancePips)
                {
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, Symbol.QuantityToVolumeInUnits(TradeVolume), _label, slDistancePips, tpDistancePips);
                }
            }

            // ---------------------------------------------------------
            // LONG SETUP (Fade the Lower Band)
            // Condition 1: Wick sweeps below the 2.5 SD band
            // Condition 2: Candle closes bullish (Close > Open)
            // ---------------------------------------------------------
            if (low <= lower25 && close > open && close > lower25)
            {
                // Dynamic SL: Sweep Low - Buffer
                double stopLossPrice = low - (SlBufferPips * Symbol.PipSize);
                double slDistancePips = (close - stopLossPrice) / Symbol.PipSize;

                // Dynamic TP: Return to VWAP
                double tpDistancePips = (vwap - close) / Symbol.PipSize;

                if (tpDistancePips > slDistancePips)
                {
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(TradeVolume), _label, slDistancePips, tpDistancePips);
                }
            }
        }
    }
}
FTtrader
Posts: 908
Joined: Mon Aug 03, 2026 2:43 pm

Re: Free Tool: VWAP + MACD Momentum Scalping Strategy & Custom Indicators (MT4 / MT5 / cTrader)

Post by FTtrader »

Architectural Details for cTrader

Indicator Referencing: Because SessionVWAP_SDBands is a custom indicator, the cBot cannot natively see it. In cTrader Automate, you must right-click your cBot, select Manage References, navigate to the Indicators tab, and check the box next to your VWAP indicator before this will compile.

The "Index 1" Shift: The OnBar event fires the millisecond a new candle opens. Therefore, the setup is evaluated on Bars.Count - 2 (the candle that just closed). If you evaluate Bars.Count - 1, you are reading the open price of the brand-new, empty candle.

Dynamic Targeting: The ExecuteMarketOrder method in cAlgo requires Stop Loss and Take Profit to be passed as pip distances, not absolute price levels. The code dynamically calculates the pip distance from the entry (the close of the rejection candle) to the sweep wick (SL) and the VWAP line (TP).

Built-in R:R Filter: The logic includes a hard filter if (tpDistancePips > slDistancePips). If the 2.5 SD band is sitting too close to the VWAP, or if the rejection wick is unusually massive, the risk-to-reward ratio inverts. The bot will automatically pass on the trade rather than risking 15 pips to make 4.
Post Reply