Page 2 of 2

Re: Rolling Expectancy: Watching Your Edge Over Time

Posted: Tue Oct 06, 2026 12:01 pm
by PTScalper
MQL4: Rolling Expectancy Indicator

This script scans your MT4 OrdersHistoryTotal(), filters out deposits/withdrawals, converts the profits to R-Multiples, and plots the rolling average over the specified window.

Code: Select all

//+------------------------------------------------------------------+
//|                                           RollingExpectancy.mq4 |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Expectancy Monitor"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2

//--- Inputs
input int    RollingWindow = 50;      // Rolling Window (Trades)
input double RiskPerTrade  = 100.0;   // Assumed Risk per Trade ($)

//--- Buffers
double ExpectancyBuffer[];

//--- Trade Structure to hold history
struct ClosedTrade {
   datetime time;
   double   profit;
};
ClosedTrade tradeHistory[];
int totalTradesCount = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, ExpectancyBuffer);
   SetIndexStyle(0, DRAW_LINE);
   IndicatorShortName("Rolling Expectancy (" + IntegerToString(RollingWindow) + ")");
   
   // Create a zero line
   SetLevelValue(0, 0.0);
   SetLevelStyle(STYLE_DASH, 1, clrGray);
   
   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[])
  {
   // 1. Refresh Trade History Array on first load or when a new trade closes
   int currentHistoryTotal = OrdersHistoryTotal();
   static int lastHistoryTotal = -1;
   
   if(lastHistoryTotal != currentHistoryTotal)
     {
      totalTradesCount = 0;
      ArrayResize(tradeHistory, currentHistoryTotal);
      
      for(int i = 0; i < currentHistoryTotal; i++)
        {
         if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
           {
            // Filter out deposits, withdrawals, and cancelled orders
            if(OrderType() <= OP_SELL && OrderSymbol() == Symbol()) 
              {
               tradeHistory[totalTradesCount].time = OrderCloseTime();
               tradeHistory[totalTradesCount].profit = OrderProfit() + OrderSwap() + OrderCommission();
               totalTradesCount++;
              }
           }
        }
      ArrayResize(tradeHistory, totalTradesCount);
      lastHistoryTotal = currentHistoryTotal;
     }

   // 2. Map Rolling Expectancy to Chart Bars
   int limit = rates_total - prev_calculated;
   if(prev_calculated == 0) limit = rates_total - 1;

   for(int i = limit; i >= 0; i--)
     {
      double sumR = 0;
      int tradesFound = 0;
      
      // Look back through our trade history array chronologically
      for(int t = totalTradesCount - 1; t >= 0; t--)
        {
         // Find trades that closed before or exactly on this bar's time
         if(tradeHistory[t].time <= time[i])
           {
            double rMultiple = tradeHistory[t].profit / RiskPerTrade;
            sumR += rMultiple;
            tradesFound++;
            
            if(tradesFound == RollingWindow) break;
           }
        }
      
      if(tradesFound == RollingWindow)
         ExpectancyBuffer[i] = sumR / RollingWindow;
      else
         ExpectancyBuffer[i] = EMPTY_VALUE; // Not enough trades yet
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

Re: Rolling Expectancy: Watching Your Edge Over Time

Posted: Tue Oct 06, 2026 12:02 pm
by PTScalper
MQL5: Rolling Expectancy Indicator

MT5 handles history differently using Deals. This script accesses HistorySelect() to evaluate actual DEAL_ENTRY_OUT (trade closures), making it highly accurate for complex MT5 position accounting (like partial closures or hedging).

Code: Select all

//+------------------------------------------------------------------+
//|                                           RollingExpectancy.mq5 |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Expectancy Monitor"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1

#property indicator_label1  "Rolling Expectancy"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- Inputs
input int    RollingWindow = 50;      // Rolling Window (Trades)
input double RiskPerTrade  = 100.0;   // Assumed Risk per Trade ($)

//--- Buffers
double ExpectancyBuffer[];

//--- Trade Structure
struct ClosedDeal {
   datetime time;
   double   profit;
};
ClosedDeal dealHistory[];
int totalDealsCount = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, ExpectancyBuffer, INDICATOR_DATA);
   IndicatorSetString(INDICATOR_SHORTNAME, "Rolling Expectancy (" + IntegerToString(RollingWindow) + ")");
   
   // Zero level line
   IndicatorSetInteger(INDICATOR_LEVELS, 1);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 0.0);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR, 0, clrGray);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE, 0, STYLE_DASH);
   
   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[])
  {
   // 1. Load entire history cache
   if(!HistorySelect(0, TimeCurrent())) return(0);
   
   int currentHistoryTotal = HistoryDealsTotal();
   static int lastHistoryTotal = -1;
   
   // 2. Update Deal History Array if new deals occurred
   if(lastHistoryTotal != currentHistoryTotal)
     {
      totalDealsCount = 0;
      ArrayResize(dealHistory, currentHistoryTotal);
      
      for(int i = 0; i < currentHistoryTotal; i++)
        {
         ulong ticket = HistoryDealGetTicket(i);
         if(ticket > 0)
           {
            long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
            long dealType  = HistoryDealGetInteger(ticket, DEAL_TYPE);
            string symbol  = HistoryDealGetString(ticket, DEAL_SYMBOL);
            
            // Look for closed trades (OUT or INOUT) on the current chart's symbol
            if((entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT) && 
               (dealType == DEAL_TYPE_BUY || dealType == DEAL_TYPE_SELL) &&
               symbol == Symbol())
              {
               dealHistory[totalDealsCount].time = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
               dealHistory[totalDealsCount].profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + 
                                                     HistoryDealGetDouble(ticket, DEAL_SWAP) + 
                                                     HistoryDealGetDouble(ticket, DEAL_COMMISSION);
               totalDealsCount++;
              }
           }
        }
      ArrayResize(dealHistory, totalDealsCount);
      lastHistoryTotal = currentHistoryTotal;
     }

   // 3. Map Rolling Expectancy to Chart Bars
   int limit = rates_total - prev_calculated;
   if(prev_calculated == 0) limit = rates_total - 1;

   for(int i = limit; i >= 0 && !IsStopped(); i--)
     {
      double sumR = 0;
      int tradesFound = 0;
      
      for(int t = totalDealsCount - 1; t >= 0; t--)
        {
         if(dealHistory[t].time <= time[i])
           {
            double rMultiple = dealHistory[t].profit / RiskPerTrade;
            sumR += rMultiple;
            tradesFound++;
            
            if(tradesFound == RollingWindow) break;
           }
        }
      
      if(tradesFound == RollingWindow)
         ExpectancyBuffer[i] = sumR / (double)RollingWindow;
      else
         ExpectancyBuffer[i] = EMPTY_VALUE;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

Re: Rolling Expectancy: Watching Your Edge Over Time

Posted: Tue Oct 06, 2026 12:02 pm
by PTScalper
Installation & Usage

1.) Open MetaTrader and press F4 to launch the MetaEditor.

2.) In the Navigator panel on the left, right-click on the Indicators folder and select New File.

3.) Choose Custom Indicator, name it RollingExpectancy, and click through to finish (leave the default settings).

4.) Delete all the auto-generated code, paste the respective MQL4 or MQL5 code above, and click Compile at the top.

5.) In your MetaTrader terminal, drag the RollingExpectancy indicator from the Navigator onto your chart.

Note: By default, the script filters trades by the current chart's symbol (OrderSymbol() == Symbol()). If you want the rolling expectancy of your entire account across all pairs, simply remove the && OrderSymbol() == Symbol() condition in the MQL4 version, or && symbol == Symbol() in the MQL5 version.

Re: Rolling Expectancy: Watching Your Edge Over Time

Posted: Tue Oct 06, 2026 12:02 pm
by PTScalper
In cTrader, custom indicators cannot directly query account history (History is restricted to cBots). To solve this, we can build a cTrader Automate cBot (C#) that runs in the background.

This cBot reads your historical account trades, listens for newly closed positions in real-time, calculates your rolling 50-trade expectancy (R-multiple), logs threshold warnings, and outputs a dynamic visual dashboard directly onto your chart.

Re: Rolling Expectancy: Watching Your Edge Over Time

Posted: Tue Oct 06, 2026 12:03 pm
by PTScalper
cTrader C# cBot Code (RollingExpectancyBot.cs)

Create a new cBot in cTrader Automate, name it RollingExpectancyBot, and paste the following code:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None)]
    public class RollingExpectancyBot : Robot
    {
        [Parameter("Rolling Window (Trades)", DefaultValue = 50, MinValue = 10, MaxValue = 200)]
        public int RollingWindow { get; set; }

        [Parameter("Risk Per Trade ($)", DefaultValue = 100.0, MinValue = 1.0)]
        public double RiskPerTrade { get; set; }

        [Parameter("Filter By Current Symbol Only", DefaultValue = true)]
        public bool FilterBySymbol { get; set; }

        private readonly Queue<double> _recentRMultiples = new Queue<double>();

        protected override void OnStart()
        {
            // 1. Load initial historical trades
            var closedDeals = History.Where(d => d.PositionImpact == PositionImpact.Closing);
            
            if (FilterBySymbol)
            {
                closedDeals = closedDeals.Where(d => d.SymbolName == SymbolName);
            }

            var orderedDeals = closedDeals.OrderBy(d => d.ClosingTime);

            foreach (var deal in orderedDeals)
            {
                // NetProfit in cTrader history deals automatically accounts for swaps and commissions
                double netProfit = deal.NetProfit; 
                double rMultiple = netProfit / RiskPerTrade;
                
                _recentRMultiples.Enqueue(rMultiple);
                if (_recentRMultiples.Count > RollingWindow)
                {
                    _recentRMultiples.Dequeue();
                }
            }

            EvaluateExpectancy();

            // 2. Subscribe to live position closure events
            Positions.Closed += OnPositionClosed;
        }

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            var position = args.Position;

            if (FilterBySymbol && position.SymbolName != SymbolName)
                return;

            // Fetch closing deals associated with the closed position ID
            var closingDeals = History.FindByPositionId(position.Id)
                .Where(d => d.PositionImpact == PositionImpact.Closing);

            double totalProfit = closingDeals.Sum(d => d.NetProfit);
            double rMultiple = totalProfit / RiskPerTrade;

            _recentRMultiples.Enqueue(rMultiple);
            if (_recentRMultiples.Count > RollingWindow)
            {
                _recentRMultiples.Dequeue();
            }

            EvaluateExpectancy();
        }

        private void EvaluateExpectancy()
        {
            if (_recentRMultiples.Count < RollingWindow)
            {
                string accumMsg = $"Accumulating Trades: {_recentRMultiples.Count}/{RollingWindow}";
                Print(accumMsg);
                Chart.DrawStaticText("RollingExpectancyDisplay", accumMsg, VerticalAlignment.Top, HorizontalAlignment.Right, Color.Orange);
                return;
            }

            double rollingExpectancy = _recentRMultiples.Average();
            string statusMsg = $"Rolling Expectancy ({RollingWindow} Trades): {rollingExpectancy:F2}R";

            // Threshold checks & color styling
            if (rollingExpectancy < -0.2)
            {
                Print("🚨 CRITICAL WARNING: Rolling Expectancy is negative ({0:F2}R)! Edge is failing.", rollingExpectancy);
                Chart.DrawStaticText("RollingExpectancyDisplay", statusMsg, VerticalAlignment.Top, HorizontalAlignment.Right, Color.Red);
            }
            else if (rollingExpectancy < 0.0)
            {
                Print("⚠️ CAUTION: Rolling Expectancy dropped below breakeven ({0:F2}R). Review rules.", rollingExpectancy);
                Chart.DrawStaticText("RollingExpectancyDisplay", statusMsg, VerticalAlignment.Top, HorizontalAlignment.Right, Color.Yellow);
            }
            else
            {
                Print("✅ Rolling Expectancy is stable: {0:F2}R over the last {1} trades.", rollingExpectancy, RollingWindow);
                Chart.DrawStaticText("RollingExpectancyDisplay", statusMsg, VerticalAlignment.Top, HorizontalAlignment.Right, Color.DodgerBlue);
            }
        }

        protected override void OnStop()
        {
            Positions.Closed -= OnPositionClosed;
        }
    }
}

Re: Rolling Expectancy: Watching Your Edge Over Time

Posted: Tue Oct 06, 2026 12:03 pm
by PTScalper
How to Install and Run in cTrader:

1.) Open cTrader and click on the Automate tab on the left sidebar (or press F10).

2.) Click New -> cBot, name it RollingExpectancyBot, and click Create.

3.) Clear the default code template, paste the C# code provided above into the editor, and click the Build button at the top.

4.) On the left side of your Automate window, locate your newly compiled cBot, click Instance, and select the chart symbol/timeframe you want to overlay it on.

5.) In the parameters panel, adjust your Rolling Window (default 50) and your Risk Per Trade ($) to match your fixed dollar risk profile.

6.) Click the Play button (Start cBot). A real-time data box will appear in the top-right corner of your chart, and log updates will stream into the Automate console window at the bottom.