Advertisement IC Markets

Rolling Expectancy: Watching Your Edge Over Time

Document your personal trading journey. Track daily equity curves, review winning and losing streaks, share trade screenshots, and get constructive feedback.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Rolling Expectancy: Watching Your Edge Over Time

Post 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);
  }
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Rolling Expectancy: Watching Your Edge Over Time

Post 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);
  }
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Rolling Expectancy: Watching Your Edge Over Time

Post 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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Rolling Expectancy: Watching Your Edge Over Time

Post 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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Rolling Expectancy: Watching Your Edge Over Time

Post 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;
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Rolling Expectancy: Watching Your Edge Over Time

Post 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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply