Advertisement IC Markets

Plotting win rate against reward to risk for each setup, and what the picture showed

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: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

This Pine Script v5 indicator provides a foundational trend-following scalping setup. It uses a 200-period Exponential Moving Average (EMA) to determine the baseline trend direction and the Relative Strength Index (RSI) to pinpoint pullback entries when momentum shifts.

Code: Select all

//@version=5
indicator("Scalping Momentum Filter", overlay=true)

// --- Input Parameters ---
emaPeriod = input.int(200, title="Trend EMA Period", group="Trend Settings")
rsiPeriod = input.int(14, title="RSI Period", group="Momentum Settings")
rsiOverbought = input.int(70, title="RSI Overbought Level", group="Momentum Settings")
rsiOversold = input.int(30, title="RSI Oversold Level", group="Momentum Settings")

// --- Calculate Indicators ---
trendEma = ta.ema(close, emaPeriod)
currentRsi = ta.rsi(close, rsiPeriod)

// --- Define Entry Logic ---
// Long: Price is above the 200 EMA (Uptrend) AND RSI crosses up from the oversold level
longCondition = close > trendEma and ta.crossover(currentRsi, rsiOversold)

// Short: Price is below the 200 EMA (Downtrend) AND RSI crosses down from the overbought level
shortCondition = close < trendEma and ta.crossunder(currentRsi, rsiOverbought)

// --- Plotting ---
// Plot the 200 EMA on the chart
plot(trendEma, color=color.new(color.blue, 0), linewidth=2, title="Trend EMA")

// Plot Buy/Sell shapes directly on the candlesticks
plotshape(series=longCondition, title="Long Signal", location=location.belowbar, color=color.new(color.green, 0), style=shape.triangleup, size=size.small, text="BUY")
plotshape(series=shortCondition, title="Short Signal", location=location.abovebar, color=color.new(color.red, 0), style=shape.triangledown, size=size.small, text="SELL")

// --- Optional Alerts ---
if longCondition
    alert("Long Scalp Signal Triggered", alert.freq_once_per_bar_close)

if shortCondition
    alert("Short Scalp Signal Triggered", alert.freq_once_per_bar_close)
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: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

How the Logic Works

Trend Filter: The script only looks for long setups when the price is trading above the emaPeriod (default 200), and short setups when below it. This keeps you on the right side of the macroeconomic flow.

Momentum Trigger: Instead of buying the breakout, it waits for a localized pullback within that trend. A long signal fires exactly when the RSI dips below 30 and crosses back up, signaling that the short-term selling pressure is exhausted.

Alert Ready: The alert() function is pre-configured, meaning you can link this directly to external webhooks or trading terminals for automated execution once applied to a chart.

What specific mathematical logic, indicators, or risk management parameters are you looking to build into your custom script?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

These native implementations recreate the exact logic of the Pine Script indicator for both MetaTrader platforms. Both scripts output EA-ready data through Buffer 0 (Buy signals) and Buffer 1 (Sell signals) so they can be easily integrated into algorithmic execution systems using iCustom().

They also execute the exact same alert logic: firing a pop-up alert only once when the candle closes to prevent intra-bar repainting.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

MQL5 Implementation (ScalpingMomentum.mq5)

The MQL5 version utilizes indicator handles (iMA, iRSI) within OnInit() and dynamically copies buffer arrays into memory using CopyBuffer during the calculation loop to maximize execution speed.

Code: Select all

//+------------------------------------------------------------------+
//|                                     ScalpingMomentumFilter.mq5   |
//+------------------------------------------------------------------+
#property copyright "Custom Indicator"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

//--- Plot 1: Buy Signal
#property indicator_label1  "Buy Signal"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLime
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- Plot 2: Sell Signal
#property indicator_label2  "Sell Signal"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//--- Input Parameters
input int    InpEmaPeriod     = 200; // Trend EMA Period
input int    InpRsiPeriod     = 14;  // RSI Period
input int    InpRsiOverbought = 70;  // RSI Overbought Level
input int    InpRsiOversold   = 30;  // RSI Oversold Level

//--- Indicator Buffers
double BuyBuffer[];
double SellBuffer[];

//--- Core Handles and Arrays
int emaHandle;
int rsiHandle;
double emaArr[];
double rsiArr[];

//--- Alert Timer
datetime lastAlertTime = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Bind buffers
   SetIndexBuffer(0, BuyBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_ARROW, 233); // Up Arrow
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0);
   
   SetIndexBuffer(1, SellBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(1, PLOT_ARROW, 234); // Down Arrow
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0.0);

   // Initialize Indicator Handles
   emaHandle = iMA(_Symbol, _Period, InpEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(emaHandle == INVALID_HANDLE) { Print("EMA handle error"); return(INIT_FAILED); }

   rsiHandle = iRSI(_Symbol, _Period, InpRsiPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE) { Print("RSI handle error"); return(INIT_FAILED); }

   // Set array indexing (0 = current forming candle)
   ArraySetAsSeries(BuyBuffer, true);
   ArraySetAsSeries(SellBuffer, true);
   ArraySetAsSeries(emaArr, true);
   ArraySetAsSeries(rsiArr, true);

   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[])
  {
   // Wait for sufficient data
   if(rates_total < InpEmaPeriod) return 0;
   
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(close, true);

   // Determine how much data needs updating
   int to_copy = (prev_calculated == 0) ? rates_total : rates_total - prev_calculated + 1;
   
   // Copy indicator data
   if(CopyBuffer(emaHandle, 0, 0, to_copy, emaArr) <= 0) return 0;
   if(CopyBuffer(rsiHandle, 0, 0, to_copy + 1, rsiArr) <= 0) return 0; // Request +1 to check the previous bar

   int limit = to_copy - 1;
   if(prev_calculated == 0) limit = rates_total - 2;

   for(int i = limit; i >= 0; i--)
     {
      // Reset buffers
      BuyBuffer[i] = 0.0;
      SellBuffer[i] = 0.0;

      double ema = emaArr[i];
      double rsi = rsiArr[i];
      double rsi_prev = rsiArr[i+1];

      // Buy Entry Logic
      if(close[i] > ema && rsi_prev < InpRsiOversold && rsi >= InpRsiOversold)
        {
         BuyBuffer[i] = low[i] - 10 * _Point;
        }
      
      // Sell Entry Logic
      if(close[i] < ema && rsi_prev > InpRsiOverbought && rsi <= InpRsiOverbought)
        {
         SellBuffer[i] = high[i] + 10 * _Point;
        }

      // Fire alerts on closed candles (index 1)
      if(i == 1)
        {
         if(BuyBuffer[1] != 0.0 && time[1] != lastAlertTime)
           {
            Alert(_Symbol, " [", EnumToString(_Period), "] Long Scalp Triggered");
            lastAlertTime = time[1];
           }
         if(SellBuffer[1] != 0.0 && time[1] != lastAlertTime)
           {
            Alert(_Symbol, " [", EnumToString(_Period), "] Short Scalp Triggered");
            lastAlertTime = time[1];
           }
        }
     }
   return(rates_total);
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

MQL4 Implementation (ScalpingMomentum.mq4)

MQL4 handles indicators natively within the main calculation loop, calling iMA() and iRSI() directly bar-by-bar.

Code: Select all

//+------------------------------------------------------------------+
//|                                     ScalpingMomentumFilter.mq4   |
//+------------------------------------------------------------------+
#property copyright "Custom Indicator"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1  clrLime
#property indicator_color2  clrRed

//--- Input Parameters
input int    InpEmaPeriod     = 200; // Trend EMA Period
input int    InpRsiPeriod     = 14;  // RSI Period
input int    InpRsiOverbought = 70;  // RSI Overbought Level
input int    InpRsiOversold   = 30;  // RSI Oversold Level

//--- Indicator Buffers
double BuyBuffer[];
double SellBuffer[];

//--- Alert Timer
datetime lastAlertTime = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Bind Buffers
   SetIndexBuffer(0, BuyBuffer);
   SetIndexStyle(0, DRAW_ARROW);
   SetIndexArrow(0, 233); // Up Arrow
   SetIndexLabel(0, "Buy Signal");
   
   SetIndexBuffer(1, SellBuffer);
   SetIndexStyle(1, DRAW_ARROW);
   SetIndexArrow(1, 234); // Down Arrow
   SetIndexLabel(1, "Sell Signal");
   
   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[])
  {
   // Determine uncalculated bars
   int limit = rates_total - prev_calculated;
   
   // Skip the earliest bar to prevent out-of-bounds array errors on i+1
   if(prev_calculated == 0) limit = rates_total - 2; 

   for(int i = limit; i >= 0; i--)
     {
      // Calculate current EMA and RSI values
      double ema = iMA(NULL, 0, InpEmaPeriod, 0, MODE_EMA, PRICE_CLOSE, i);
      double rsi = iRSI(NULL, 0, InpRsiPeriod, PRICE_CLOSE, i);
      double rsi_prev = iRSI(NULL, 0, InpRsiPeriod, PRICE_CLOSE, i+1);

      // Reset buffers
      BuyBuffer[i] = EMPTY_VALUE;
      SellBuffer[i] = EMPTY_VALUE;

      // Buy Entry Logic
      if(close[i] > ema && rsi_prev < InpRsiOversold && rsi >= InpRsiOversold)
        {
         BuyBuffer[i] = low[i] - 10 * Point;
        }
      
      // Sell Entry Logic
      if(close[i] < ema && rsi_prev > InpRsiOverbought && rsi <= InpRsiOverbought)
        {
         SellBuffer[i] = high[i] + 10 * Point;
        }

      // Fire alerts on closed candles (index 1)
      if(i == 1)
        {
         if(BuyBuffer[1] != EMPTY_VALUE && time[1] != lastAlertTime)
           {
            Alert(Symbol(), " [M", Period(), "] Long Scalp Triggered");
            lastAlertTime = time[1];
           }
         if(SellBuffer[1] != EMPTY_VALUE && time[1] != lastAlertTime)
           {
            Alert(Symbol(), " [M", Period(), "] Short Scalp Triggered");
            lastAlertTime = time[1];
           }
        }
     }
   return(rates_total);
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

Ctrader version:

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ScalpingMomentumFilter : Indicator
    {
        // --- Input Parameters ---
        [Parameter("Trend EMA Period", DefaultValue = 200, Group = "Trend Settings")]
        public int EmaPeriod { get; set; }

        [Parameter("RSI Period", DefaultValue = 14, Group = "Momentum Settings")]
        public int RsiPeriod { get; set; }

        [Parameter("RSI Overbought Level", DefaultValue = 70, Group = "Momentum Settings")]
        public int RsiOverbought { get; set; }

        [Parameter("RSI Oversold Level", DefaultValue = 30, Group = "Momentum Settings")]
        public int RsiOversold { get; set; }

        // --- Indicator Outputs (Exposed for cBots) ---
        [Output("Trend EMA", LineColor = "Blue", Thickness = 2)]
        public IndicatorDataSeries TrendEma { get; set; }

        [Output("Buy Signal", LineColor = "Lime", PlotType = PlotType.Points, Thickness = 5)]
        public IndicatorDataSeries BuyBuffer { get; set; }

        [Output("Sell Signal", LineColor = "Red", PlotType = PlotType.Points, Thickness = 5)]
        public IndicatorDataSeries SellBuffer { get; set; }

        // --- Internal Variables ---
        private ExponentialMovingAverage _ema;
        private RelativeStrengthIndex _rsi;
        private int _lastAlertBarIndex = -1;

        protected override void Initialize()
        {
            // Initialize built-in indicators
            _ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, EmaPeriod);
            _rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, RsiPeriod);
        }

        public override void Calculate(int index)
        {
            // Wait for sufficient data
            if (index < EmaPeriod || index < 1)
                return;

            // Assign EMA for chart plotting
            TrendEma[index] = _ema.Result[index];

            // Initialize buffers to NaN (Empty Value) to ensure no rogue dots are drawn
            BuyBuffer[index] = double.NaN;
            SellBuffer[index] = double.NaN;

            double close = Bars.ClosePrices[index];
            double ema = _ema.Result[index];
            double rsi = _rsi.Result[index];
            double rsiPrev = _rsi.Result[index - 1];

            // Buy Entry Logic
            if (close > ema && rsiPrev < RsiOversold && rsi >= RsiOversold)
            {
                // Offset the visual dot 2 pips below the low
                BuyBuffer[index] = Bars.LowPrices[index] - (2 * Symbol.PipSize); 
            }

            // Sell Entry Logic
            if (close < ema && rsiPrev > RsiOverbought && rsi <= RsiOverbought)
            {
                // Offset the visual dot 2 pips above the high
                SellBuffer[index] = Bars.HighPrices[index] + (2 * Symbol.PipSize);
            }

            // --- Alert Logic on Bar Close ---
            // cTrader processes 'Calculate' on every tick. This logic ensures alerts 
            // only fire once upon the formation of a new bar, preventing repainting spam.
            if (IsRealTime && !IsBacktesting)
            {
                if (index > _lastAlertBarIndex && _lastAlertBarIndex != -1)
                {
                    int closedBarIndex = index - 1;

                    if (!double.IsNaN(BuyBuffer[closedBarIndex]))
                    {
                        Print("{0} [{1}] Long Scalp Signal Triggered", Symbol.Name, TimeFrame.ToString());
                        Notifications.PlaySound(SoundType.DoorBell);
                    }
                    
                    if (!double.IsNaN(SellBuffer[closedBarIndex]))
                    {
                        Print("{0} [{1}] Short Scalp Signal Triggered", Symbol.Name, TimeFrame.ToString());
                        Notifications.PlaySound(SoundType.DoorBell);
                    }
                }
                _lastAlertBarIndex = index; // Update index to prevent duplicate alerts
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 6349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by PTScalper »

Key cTrader (C#) Implementation Details

Buffer Exposure for cBots: The [Output] properties (BuyBuffer and SellBuffer) utilize PlotType.Points. Setting them to double.NaN when there is no signal ensures the chart remains clean. If you reference this indicator inside a custom cBot via Indicators.GetIndicator<ScalpingMomentumFilter>(), the bot can natively read these public properties rather than scraping chart objects.

Alert Synchronization: cAlgo does not feature a native pop-up UI box for custom indicator alerts like MetaTrader. Instead, the logic utilizes Print() to log the exact trigger to the Automate log, and Notifications.PlaySound() for an audible alert.

State Management (IsRealTime): To prevent historical backtesting data from flooding the log or firing thousands of doorbell sounds when the indicator first initializes on the chart, the alert logic is wrapped in if (IsRealTime && !IsBacktesting).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Shadow Trader
Posts: 3554
Joined: Wed Sep 09, 2026 5:19 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by Shadow Trader »

Moving each point to its after cost position changes the picture most, and the size of the move depends on the target. Take two made up setups and a cost of 0.1R per trade, which is what a 1 pip spread does on a 10 pip stop.
img_13599_r1.png
img_13599_r1.png (98.04 KiB) Viewed 64 times
A setup winning 75 percent at 0.5R makes 0.125R per trade before costs and 0.025R after, so four fifths of its edge goes. One winning 40 percent at 2.5R drops from 0.40R to 0.30R, losing a quarter.

In the chart (illustration) the first point slides almost onto the break even curve, while the second barely moves. Costs cut the winner and add to the loser, so effective reward to risk is target minus cost over one plus cost.

Trade frequency belongs in the ranking too. At 20 trades a week the first makes 0.5R a week, while the second at 3 trades a week makes 0.9R.

On weighting risk by distance from the curve, the sample size point above matters even more. With 50 trades, a measured 60 percent win rate has a standard error of about 7 percentage points, so a point well above the line can share an error bar with one sitting on it.
Shadow Trader
Posts: 3554
Joined: Wed Sep 09, 2026 5:19 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by Shadow Trader »

Distance from the break even curve is tempting as a ranking, but it is not edge. The curve sits where win rate equals one divided by one plus the reward to risk, and a gap of a few win rate points means different things along its length.

Take two made up setups. One wins 60 percent at 0.8R, where break even is 55.6 percent, so it is 4.4 points clear and makes 0.6 times 0.8 minus 0.4, which is 0.08R per trade.

The other wins 35 percent at 2.2R, where break even is 31.25 percent. It is only 3.75 points clear, yet it makes 0.35 times 2.2 minus 0.65, which is 0.12R per trade.

So the point that looks closer to the line is the better setup by half again. The perpendicular distance mentioned earlier in the thread has a similar problem, since it changes with how the two axes are scaled.

Drawing contour lines for 0.05R and 0.1R expectancy next to the break even curve fixes that. Each point can then be read straight off the plot in R per trade.

Has anyone tried sizing each dot by its trade count? Bigger dots for bigger samples catch the eye faster than labels.
Shadow Trader
Posts: 3554
Joined: Wed Sep 09, 2026 5:19 pm

Re: Plotting win rate against reward to risk for each setup, and what the picture showed

Post by Shadow Trader »

Before taking any setup, one question matters: how often does it work, across all instances, not just the memorable ones? That's the base rate, and most traders don't know theirs.

Without base rates, the memory of the best examples dominates. The pattern that worked spectacularly last Tuesday feels reliable, regardless of the twenty times it failed quietly.

Counting is the fix. For each setup, record every occurrence, win or lose, over a few hundred instances. Compute the win rate, the average win and loss in R, and the expectancy. Split by session, pair and volatility regime.

Base rates also help with context. If a setup works 52% of the time in general, and 61% in the London morning, that difference is a usable filter. If it works 44% in the Asian afternoon, avoid it.

Knowing the base rate also helps manage emotions. A setup with a 45% win rate and 2:1 payoff will lose 55% of the time. A losing streak of six is routine, not a crisis.

A good habit is keeping a "setup book" with one page per setup: definition, number of occurrences, win rate, expectancy, best and worst sessions, and notes. Updating it monthly keeps the numbers fresh, and reading it before a session reminds the mind what the odds really are, instead of what the last trade made them feel like.

A setup with no page in the book, no recorded base rate, doesn't deserve real size.

What are the base rates for the main setups, and how do they vary by session?
Post Reply