Advertisement IC Markets

How many pips a day is realistic for scalping

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

To bring these up to an institutional standard—especially if you are providing these as tools on a community discussion forum or relying on them for your own tight M15 scalping setups—the code needs three major upgrades:

Institutional GUI: Floating text bleeds into chart candles. A true "pro" indicator uses a self-contained, semi-transparent background panel (OBJ_RECTANGLE_LABEL) with fixed dimensions.

Defensive Architecture: Added zero-divide protection (pipSize == 0) and optimized memory-safe object cleanup to prevent chart ghosting when removing the indicator.

Monospaced Rendering: Values now use Consolas font so the decimals align perfectly on every tick.
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

Here are the professional-grade versions for both platforms.

MQL4: Institutional Net Expectancy Model

Code: Select all

//+------------------------------------------------------------------+
//|                                    Pro_NetExpectancyFriction.mq4 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property strict

// =========================================================================
// INPUTS
// =========================================================================
input string   sep1 = "--- Strategy Metrics ---";
input double   WinRate = 55.0;          // Historical Win Rate (%)
input double   MeanGrossWin = 4.0;      // Mean Gross Win (Pips)
input double   MeanGrossLoss = 3.0;     // Mean Gross Loss (Pips)

input string   sep2 = "--- Execution Friction ---";
input double   CommPerLot = 7.0;        // Round Turn Comm ($/Lot)
input double   PipValue = 10.0;         // Pip Value ($/Lot)
input double   EstSlippage = 0.2;       // Mean Slippage (Pips)

// Globals
double pipSize;
string prefix = "ProExpDash_";

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit() {
    pipSize = Point;
    if (Digits == 3 || Digits == 5) pipSize *= 10.0;
    
    if (pipSize == 0) {
        Print("Error: Zero pip size detected.");
        return(INIT_FAILED);
    }
    
    DrawBackground();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    ObjectsDeleteAll(0, prefix);
    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Main Calculation Iteration                                       |
//+------------------------------------------------------------------+
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[]) 
{
    // Execution Math
    double liveSpreadPips = (Ask - Bid) / pipSize;
    double wr = WinRate / 100.0;
    double commInPips = CommPerLot / PipValue;
    double totalFriction = commInPips + liveSpreadPips + EstSlippage;
    
    // Expectancy Math
    double netWinMean = MeanGrossWin - totalFriction;
    double netLossMean = MeanGrossLoss + totalFriction;
    
    double grossExp = (MeanGrossWin * wr) - (MeanGrossLoss * (1.0 - wr));
    double netExp = (netWinMean * wr) - (netLossMean * (1.0 - wr));
    
    // UI Rendering
    color netColor = (netExp > 0) ? clrLime : clrRed;
    
    DrawRow(1, "Live Spread", DoubleToStr(liveSpreadPips, 2), clrAqua);
    DrawRow(2, "Execution Drag", DoubleToStr(totalFriction, 2), clrRed);
    DrawRow(3, "Gross Alpha", DoubleToStr(grossExp, 2), clrSilver);
    DrawRow(4, "Net Yield (1x)", DoubleToStr(netExp, 2), netColor);
    DrawRow(5, "Net Yield (100x)", DoubleToStr(netExp * 100.0, 2), netColor);

    return(rates_total);
}

//+------------------------------------------------------------------+
//| GUI: Draw Background Panel                                       |
//+------------------------------------------------------------------+
void DrawBackground() {
    string bgName = prefix + "BG";
    if(ObjectFind(0, bgName) < 0) {
        ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
        ObjectSetInteger(0, bgName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, 20);
        ObjectSetInteger(0, bgName, OBJPROP_XSIZE, 240);
        ObjectSetInteger(0, bgName, OBJPROP_YSIZE, 135);
        ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, clrBlack);
        ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
        ObjectSetInteger(0, bgName, OBJPROP_COLOR, clrDimGray); // Border color
        ObjectSetInteger(0, bgName, OBJPROP_BACK, true);
    }
}

//+------------------------------------------------------------------+
//| GUI: Draw Data Row                                               |
//+------------------------------------------------------------------+
void DrawRow(int row, string label, string val, color valColor) {
    int yOffset = (row * 20) + 15;
    string lblName = prefix + "lbl_" + IntegerToString(row);
    string valName = prefix + "val_" + IntegerToString(row);
    
    // Label (Left Aligned)
    if(ObjectFind(0, lblName) < 0) {
        ObjectCreate(0, lblName, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, lblName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, lblName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
        ObjectSetInteger(0, lblName, OBJPROP_XDISTANCE, 230);
        ObjectSetString(0, lblName, OBJPROP_FONT, "Arial");
        ObjectSetInteger(0, lblName, OBJPROP_FONTSIZE, 9);
        ObjectSetInteger(0, lblName, OBJPROP_COLOR, clrLightGray);
    }
    ObjectSetInteger(0, lblName, OBJPROP_YDISTANCE, yOffset);
    ObjectSetString(0, lblName, OBJPROP_TEXT, label + ":");

    // Value (Right Aligned, Monospaced)
    if(ObjectFind(0, valName) < 0) {
        ObjectCreate(0, valName, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, valName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, valName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
        ObjectSetInteger(0, valName, OBJPROP_XDISTANCE, 20);
        ObjectSetString(0, valName, OBJPROP_FONT, "Consolas");
        ObjectSetInteger(0, valName, OBJPROP_FONTSIZE, 9);
    }
    ObjectSetInteger(0, valName, OBJPROP_YDISTANCE, yOffset);
    ObjectSetInteger(0, valName, OBJPROP_COLOR, valColor);
    ObjectSetString(0, valName, OBJPROP_TEXT, val);
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

MQL5: Institutional Net Expectancy Model

MQL5 allows for cleaner input groupings. This version utilizes input group for a sleek properties menu and fetches ticks robustly using SymbolInfoDouble().

Code: Select all

//+------------------------------------------------------------------+
//|                                    Pro_NetExpectancyFriction.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_plots 0

// =========================================================================
// INPUTS
// =========================================================================
input group "--- Strategy Metrics ---";
input double   InpWinRate = 55.0;          // Historical Win Rate (%)
input double   InpMeanGrossWin = 4.0;      // Mean Gross Win (Pips)
input double   InpMeanGrossLoss = 3.0;     // Mean Gross Loss (Pips)

input group "--- Execution Friction ---";
input double   InpCommPerLot = 7.0;        // Round Turn Comm ($/Lot)
input double   InpPipValue = 10.0;         // Pip Value ($/Lot)
input double   InpEstSlippage = 0.2;       // Mean Slippage (Pips)

// Globals
double pipSize;
string prefix = "ProExpDash_";

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit() {
    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    
    pipSize = point;
    if (digits == 3 || digits == 5) pipSize *= 10.0;
    
    if (pipSize == 0.0) {
        Print("Initialization Error: Division by zero risk (pipSize = 0).");
        return(INIT_FAILED);
    }
    
    DrawBackground();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    ObjectsDeleteAll(0, prefix);
    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Main Calculation Iteration                                       |
//+------------------------------------------------------------------+
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[]) 
{
    // Fetch live tick data safely
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    
    // Execution Math
    double liveSpreadPips = (ask - bid) / pipSize;
    double wr = InpWinRate / 100.0;
    double commInPips = InpCommPerLot / InpPipValue;
    double totalFriction = commInPips + liveSpreadPips + InpEstSlippage;
    
    // Expectancy Math
    double netWinMean = InpMeanGrossWin - totalFriction;
    double netLossMean = InpMeanGrossLoss + totalFriction;
    
    double grossExp = (InpMeanGrossWin * wr) - (InpMeanGrossLoss * (1.0 - wr));
    double netExp = (netWinMean * wr) - (netLossMean * (1.0 - wr));
    
    // UI Rendering
    color netColor = (netExp > 0) ? clrLime : clrRed;
    
    DrawRow(1, "Live Spread", DoubleToString(liveSpreadPips, 2), clrAqua);
    DrawRow(2, "Execution Drag", DoubleToString(totalFriction, 2), clrRed);
    DrawRow(3, "Gross Alpha", DoubleToString(grossExp, 2), clrSilver);
    DrawRow(4, "Net Yield (1x)", DoubleToString(netExp, 2), netColor);
    DrawRow(5, "Net Yield (100x)", DoubleToString(netExp * 100.0, 2), netColor);

    return(rates_total);
}

//+------------------------------------------------------------------+
//| GUI: Draw Background Panel                                       |
//+------------------------------------------------------------------+
void DrawBackground() {
    string bgName = prefix + "BG";
    if(ObjectFind(0, bgName) < 0) {
        ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
        ObjectSetInteger(0, bgName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, 20);
        ObjectSetInteger(0, bgName, OBJPROP_XSIZE, 240);
        ObjectSetInteger(0, bgName, OBJPROP_YSIZE, 135);
        ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, clrBlack);
        ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
        ObjectSetInteger(0, bgName, OBJPROP_COLOR, clrDimGray);
        ObjectSetInteger(0, bgName, OBJPROP_BACK, true);
        ObjectSetInteger(0, bgName, OBJPROP_SELECTABLE, false);
    }
}

//+------------------------------------------------------------------+
//| GUI: Draw Data Row                                               |
//+------------------------------------------------------------------+
void DrawRow(int row, string label, string val, color valColor) {
    int yOffset = (row * 20) + 15;
    string lblName = prefix + "lbl_" + IntegerToString(row);
    string valName = prefix + "val_" + IntegerToString(row);
    
    // Label (Left Aligned)
    if(ObjectFind(0, lblName) < 0) {
        ObjectCreate(0, lblName, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, lblName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, lblName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
        ObjectSetInteger(0, lblName, OBJPROP_XDISTANCE, 230);
        ObjectSetString(0, lblName, OBJPROP_FONT, "Arial");
        ObjectSetInteger(0, lblName, OBJPROP_FONTSIZE, 9);
        ObjectSetInteger(0, lblName, OBJPROP_COLOR, clrLightGray);
        ObjectSetInteger(0, lblName, OBJPROP_SELECTABLE, false);
    }
    ObjectSetInteger(0, lblName, OBJPROP_YDISTANCE, yOffset);
    ObjectSetString(0, lblName, OBJPROP_TEXT, label + ":");

    // Value (Right Aligned, Monospaced)
    if(ObjectFind(0, valName) < 0) {
        ObjectCreate(0, valName, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(0, valName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
        ObjectSetInteger(0, valName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
        ObjectSetInteger(0, valName, OBJPROP_XDISTANCE, 20);
        ObjectSetString(0, valName, OBJPROP_FONT, "Consolas");
        ObjectSetInteger(0, valName, OBJPROP_FONTSIZE, 9);
        ObjectSetInteger(0, valName, OBJPROP_SELECTABLE, false);
    }
    ObjectSetInteger(0, valName, OBJPROP_YDISTANCE, yOffset);
    ObjectSetInteger(0, valName, OBJPROP_COLOR, valColor);
    ObjectSetString(0, valName, OBJPROP_TEXT, val);
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

Because cTrader uses C# and its native cAlgo.API UI framework, we don't have to rely on messy chart objects like we do in MetaTrader. We can build a true, WPF-style front-end dashboard that is memory-safe, resizes beautifully, and anchors directly to the chart canvas without ghosting.

This institutional-grade C# implementation uses a semi-transparent StackPanel wrapped in a custom Border. It isolates the math to real-time ticks (IsLastBar) so it won't consume resources iterating over historical price data.

cTrader: Institutional Net Expectancy Model

Save this in the Automate tab of cTrader as a new Indicator.

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 ProNetExpectancyFriction : Indicator
    {
        // =========================================================================
        // INPUTS: Strategy Metrics
        // =========================================================================
        [Parameter("Historical Win Rate (%)", DefaultValue = 55.0, Group = "Strategy Metrics")]
        public double WinRate { get; set; }

        [Parameter("Mean Gross Win (Pips)", DefaultValue = 4.0, Group = "Strategy Metrics")]
        public double MeanGrossWin { get; set; }

        [Parameter("Mean Gross Loss (Pips)", DefaultValue = 3.0, Group = "Strategy Metrics")]
        public double MeanGrossLoss { get; set; }

        // =========================================================================
        // INPUTS: Execution Friction
        // =========================================================================
        [Parameter("Round Turn Comm ($/Lot)", DefaultValue = 7.0, Group = "Execution Friction")]
        public double CommPerLot { get; set; }

        [Parameter("Pip Value ($/Lot)", DefaultValue = 10.0, Group = "Execution Friction")]
        public double PipValue { get; set; }

        [Parameter("Mean Slippage (Pips)", DefaultValue = 0.2, Group = "Execution Friction")]
        public double EstSlippage { get; set; }

        // UI Elements
        private TextBlock _spreadVal;
        private TextBlock _dragVal;
        private TextBlock _alphaVal;
        private TextBlock _netYield1xVal;
        private TextBlock _netYield100xVal;

        protected override void Initialize()
        {
            if (Symbol.PipSize == 0)
            {
                Print("Initialization Error: Division by zero risk (PipSize = 0).");
                return;
            }

            BuildDashboardUI();
        }

        public override void Calculate(int index)
        {
            // Only calculate on the live edge to save CPU cycles on historical bars
            if (!IsLastBar) return;

            // Fetch live spread dynamically
            double liveSpreadPips = Symbol.Spread / Symbol.PipSize;
            
            // Execution Math
            double wr = WinRate / 100.0;
            double commInPips = CommPerLot / PipValue;
            double totalFriction = commInPips + liveSpreadPips + EstSlippage;
            
            // Expectancy Math
            double netWinMean = MeanGrossWin - totalFriction;
            double netLossMean = MeanGrossLoss + totalFriction;
            
            double grossExp = (MeanGrossWin * wr) - (MeanGrossLoss * (1.0 - wr));
            double netExp = (netWinMean * wr) - (netLossMean * (1.0 - wr));

            // Update UI dynamically
            _spreadVal.Text = liveSpreadPips.ToString("F2");
            _dragVal.Text = totalFriction.ToString("F2");
            _alphaVal.Text = grossExp.ToString("F2");
            
            _netYield1xVal.Text = netExp.ToString("F2");
            _netYield1xVal.ForegroundColor = netExp > 0 ? Color.Lime : Color.Red;

            _netYield100xVal.Text = (netExp * 100.0).ToString("F2");
            _netYield100xVal.ForegroundColor = netExp > 0 ? Color.Lime : Color.Red;
        }

        // =========================================================================
        // GUI: Build Institutional Panel
        // =========================================================================
        private void BuildDashboardUI()
        {
            var mainContainer = new StackPanel
            {
                BackgroundColor = Color.FromArgb(230, 12, 12, 12),
                Width = 260,
                Margin = new Thickness(0)
            };

            // Add Header
            var header = new TextBlock
            {
                Text = "NET EXPECTANCY MODEL",
                ForegroundColor = Color.Gray,
                FontFamily = "Arial",
                FontSize = 10,
                FontWeight = FontWeight.Bold,
                Margin = new Thickness(10, 10, 10, 15),
                TextAlignment = TextAlignment.Center
            };
            mainContainer.AddChild(header);

            // Add Data Rows
            mainContainer.AddChild(CreateRow("Live Spread", out _spreadVal, Color.Aqua));
            mainContainer.AddChild(CreateRow("Execution Drag", out _dragVal, Color.Red));
            mainContainer.AddChild(CreateRow("Gross Alpha", out _alphaVal, Color.Silver));
            mainContainer.AddChild(CreateRow("Net Yield (1x)", out _netYield1xVal, Color.Lime));
            mainContainer.AddChild(CreateRow("Net Yield (100x)", out _netYield100xVal, Color.Lime));

            // Wrap in Border and add to Chart Canvas
            var border = new Border
            {
                Child = mainContainer,
                BorderColor = Color.FromArgb(255, 60, 60, 60),
                BorderThickness = new Thickness(1),
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                Margin = new Thickness(0, 0, 15, 25)
            };

            Chart.AddControl(border);
        }

        private StackPanel CreateRow(string labelText, out TextBlock valueTextBlock, Color defaultColor)
        {
            var row = new StackPanel
            {
                Orientation = Orientation.Horizontal,
                Margin = new Thickness(15, 0, 15, 8)
            };

            var label = new TextBlock
            {
                Text = labelText + ":",
                ForegroundColor = Color.LightGray,
                FontFamily = "Arial",
                FontSize = 11,
                Width = 150
            };

            valueTextBlock = new TextBlock
            {
                Text = "0.00",
                ForegroundColor = defaultColor,
                FontFamily = "Consolas", // Monospaced for alignment
                FontSize = 12,
                FontWeight = FontWeight.Bold,
                Width = 70,
                TextAlignment = TextAlignment.Right
            };

            row.AddChild(label);
            row.AddChild(valueTextBlock);

            return row;
        }
    }
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

To elevate this to an enterprise C# standard, we need to move away from procedural UI scripting and treat the indicator like a proper .NET application.

When you are pulling live tick data, updating the UI thread on every single Calculate() iteration creates unnecessary CPU drag. A professional architecture separates the UI composition from the business logic, utilizes object styling to keep the code DRY, and implements a state-caching mechanism so the WPF thread only repaints when the actual pip values change.

Here is the refactored, performance-optimized C# architecture for cTrader.

cTrader: Enterprise Net Expectancy Model

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 InstitutionalNetExpectancy : Indicator
    {
        #region Parameters
        [Parameter("Historical Win Rate (%)", DefaultValue = 55.0, Group = "Strategy Metrics")]
        public double WinRate { get; set; }

        [Parameter("Mean Gross Win (Pips)", DefaultValue = 4.0, Group = "Strategy Metrics")]
        public double MeanGrossWin { get; set; }

        [Parameter("Mean Gross Loss (Pips)", DefaultValue = 3.0, Group = "Strategy Metrics")]
        public double MeanGrossLoss { get; set; }

        [Parameter("Round Turn Comm ($/Lot)", DefaultValue = 7.0, Group = "Execution Friction")]
        public double CommPerLot { get; set; }

        [Parameter("Pip Value ($/Lot)", DefaultValue = 10.0, Group = "Execution Friction")]
        public double PipValue { get; set; }

        [Parameter("Mean Slippage (Pips)", DefaultValue = 0.2, Group = "Execution Friction")]
        public double EstSlippage { get; set; }
        #endregion

        #region Fields & State Caching
        private TextBlock _spreadVal, _dragVal, _alphaVal, _netYield1xVal, _netYield100xVal;
        
        // Cache to prevent excessive UI thread repaints
        private double _lastSpreadPips = -1;
        #endregion

        #region Initialization
        protected override void Initialize()
        {
            if (Symbol.PipSize == 0)
            {
                Print("FATAL: Symbol.PipSize evaluated to zero. Indicator aborted.");
                return;
            }

            BuildDashboardUI();
        }
        #endregion

        #region Core Calculation Logic
        public override void Calculate(int index)
        {
            // Restrict execution to the live tick edge
            if (!IsLastBar) return;

            double currentSpreadPips = Math.Round(Symbol.Spread / Symbol.PipSize, 2);

            // CPU Optimization: Only push updates to the UI thread if the spread has actually changed
            if (Math.Abs(currentSpreadPips - _lastSpreadPips) < 0.01) return;
            
            _lastSpreadPips = currentSpreadPips;
            UpdateDashboardEngine(currentSpreadPips);
        }

        private void UpdateDashboardEngine(double spreadPips)
        {
            // Math routines
            double wr = WinRate / 100.0;
            double commInPips = CommPerLot / PipValue;
            double totalFriction = commInPips + spreadPips + EstSlippage;
            
            double netWinMean = MeanGrossWin - totalFriction;
            double netLossMean = MeanGrossLoss + totalFriction;
            
            double grossExp = (MeanGrossWin * wr) - (MeanGrossLoss * (1.0 - wr));
            double netExp = (netWinMean * wr) - (netLossMean * (1.0 - wr));

            // State mutations (UI formatting)
            _spreadVal.Text = $"{spreadPips:F2}";
            _dragVal.Text = $"{totalFriction:F2}";
            _alphaVal.Text = $"{grossExp:F2}";
            
            _netYield1xVal.Text = $"{netExp:F2}";
            _netYield1xVal.ForegroundColor = netExp > 0 ? Color.Lime : Color.Red;

            _netYield100xVal.Text = $"{(netExp * 100.0):F2}";
            _netYield100xVal.ForegroundColor = netExp > 0 ? Color.Lime : Color.Red;
        }
        #endregion

        #region UI Composition
        private void BuildDashboardUI()
        {
            var mainContainer = new StackPanel
            {
                BackgroundColor = Color.FromArgb(230, 15, 15, 15),
                Width = 260,
                Margin = new Thickness(0)
            };

            mainContainer.AddChild(new TextBlock
            {
                Text = "NET EXPECTANCY MODEL",
                ForegroundColor = Color.Gray,
                FontFamily = "Arial",
                FontSize = 10,
                FontWeight = FontWeight.Bold,
                Margin = new Thickness(10, 10, 10, 15),
                TextAlignment = TextAlignment.Center
            });

            // Compose rows utilizing the helper factory
            mainContainer.AddChild(CreateRow("Live Spread", out _spreadVal, Color.Aqua));
            mainContainer.AddChild(CreateRow("Execution Drag", out _dragVal, Color.Red));
            mainContainer.AddChild(CreateRow("Gross Alpha", out _alphaVal, Color.Silver));
            mainContainer.AddChild(CreateRow("Net Yield (1x)", out _netYield1xVal, Color.Lime));
            mainContainer.AddChild(CreateRow("Net Yield (100x)", out _netYield100xVal, Color.Lime));

            Chart.AddControl(new Border
            {
                Child = mainContainer,
                BorderColor = Color.FromArgb(255, 60, 60, 60),
                BorderThickness = new Thickness(1),
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                Margin = new Thickness(0, 0, 15, 25)
            });
        }

        private StackPanel CreateRow(string labelText, out TextBlock valueTextBlock, Color defaultColor)
        {
            var row = new StackPanel
            {
                Orientation = Orientation.Horizontal,
                Margin = new Thickness(15, 0, 15, 8)
            };

            row.AddChild(new TextBlock
            {
                Text = $"{labelText}:",
                ForegroundColor = Color.LightGray,
                FontFamily = "Arial",
                FontSize = 11,
                Width = 150
            });

            valueTextBlock = new TextBlock
            {
                Text = "0.00",
                ForegroundColor = defaultColor,
                FontFamily = "Consolas", // Critical for decimal alignment
                FontSize = 12,
                FontWeight = FontWeight.Bold,
                Width = 70,
                TextAlignment = TextAlignment.Right
            };

            row.AddChild(valueTextBlock);
            return row;
        }
        #endregion
    }
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

Key Architectural Improvements

Thread Safety & State Caching: The UI relies on WPF rendering. The _lastSpreadPips cache ensures the script bypasses the UpdateDashboardEngine method entirely unless the price change actually translates into a visible pip variance. This prevents the indicator from wasting CPU cycles repainting identical numbers during high-frequency micro-ticks.

Separation of Concerns: The structure is divided into distinct #region blocks, isolating the math engine (UpdateDashboardEngine) from the WPF control factory (BuildDashboardUI).

String Interpolation: Replaced clunky .ToString("F2") calls with modern C# string interpolation ($"{value:F2}"), reducing garbage collection overhead.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

To convert this logic into a cBot that physically prevents trades in negative expectancy environments, we have to address a fundamental limitation in the cTrader API: a cBot cannot intercept or block a manual click on cTrader’s native UI buttons before the order is sent to the server.

If you use the Positions.Opened event to instantly close a bad trade, you will still pay the spread and commission for that split-second execution, which defeats the purpose of the safeguard.

The institutional solution is to bypass the native cTrader interface entirely and build a Smart Execution Panel directly into the cBot. This panel dynamically disables its own execution buttons the millisecond the spread widens enough to push your net expectancy below zero.

Here is the enterprise C# architecture for the ExpectancyGuard cBot. It combines the real-time statistical dashboard with an execution module and built-in rejection logging.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

cTrader: Expectancy Guard & Execution cBot

Save this in the Automate tab as a new cBot (not an Indicator).

Code: Select all

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ExpectancyGuardBot : Robot
    {
        #region Parameters
        [Parameter("Lot Size", DefaultValue = 1.0, Group = "Execution")]
        public double VolumeInLots { get; set; }

        [Parameter("Historical Win Rate (%)", DefaultValue = 55.0, Group = "Strategy Metrics")]
        public double WinRate { get; set; }

        [Parameter("Mean Gross Win (Pips)", DefaultValue = 4.0, Group = "Strategy Metrics")]
        public double MeanGrossWin { get; set; }

        [Parameter("Mean Gross Loss (Pips)", DefaultValue = 3.0, Group = "Strategy Metrics")]
        public double MeanGrossLoss { get; set; }

        [Parameter("Round Turn Comm ($/Lot)", DefaultValue = 7.0, Group = "Friction")]
        public double CommPerLot { get; set; }

        [Parameter("Pip Value ($/Lot)", DefaultValue = 10.0, Group = "Friction")]
        public double PipValue { get; set; }

        [Parameter("Mean Slippage (Pips)", DefaultValue = 0.2, Group = "Friction")]
        public double EstSlippage { get; set; }
        #endregion

        #region Fields & State
        private TextBlock _spreadVal, _dragVal, _netYieldVal;
        private Button _buyButton, _sellButton;
        private double _currentNetExpectancy = 0;
        private double _lastSpreadPips = -1;
        #endregion

        #region Initialization
        protected override void OnStart()
        {
            if (Symbol.PipSize == 0)
            {
                Print("FATAL: Symbol.PipSize evaluated to zero. Robot aborted.");
                Stop();
                return;
            }

            BuildExecutionPanel();
        }
        #endregion

        #region Core Event Loop
        protected override void OnTick()
        {
            double currentSpreadPips = Math.Round(Symbol.Spread / Symbol.PipSize, 2);

            // CPU Optimization: Only update UI and State if spread changes
            if (Math.Abs(currentSpreadPips - _lastSpreadPips) < 0.01) return;
            
            _lastSpreadPips = currentSpreadPips;
            UpdateEngine(currentSpreadPips);
        }
        #endregion

        #region Math & State Engine
        private void UpdateEngine(double spreadPips)
        {
            double wr = WinRate / 100.0;
            double totalFriction = (CommPerLot / PipValue) + spreadPips + EstSlippage;
            
            double netWinMean = MeanGrossWin - totalFriction;
            double netLossMean = MeanGrossLoss + totalFriction;
            
            _currentNetExpectancy = (netWinMean * wr) - (netLossMean * (1.0 - wr));

            // Update Dashboard Metrics
            _spreadVal.Text = $"{spreadPips:F2}";
            _dragVal.Text = $"{totalFriction:F2}";
            
            _netYieldVal.Text = $"{_currentNetExpectancy:F2}";
            _netYieldVal.ForegroundColor = _currentNetExpectancy > 0 ? Color.Lime : Color.Red;

            // Trigger UI Guard
            UpdateGuardState();
        }

        private void UpdateGuardState()
        {
            bool isEdgePresent = _currentNetExpectancy > 0;

            // Dynamically lock/unlock execution buttons
            _buyButton.IsEnabled = isEdgePresent;
            _sellButton.IsEnabled = isEdgePresent;

            // Visual feedback for locked state
            _buyButton.BackgroundColor = isEdgePresent ? Color.FromArgb(255, 0, 120, 215) : Color.FromArgb(100, 50, 50, 50);
            _sellButton.BackgroundColor = isEdgePresent ? Color.FromArgb(255, 215, 60, 50) : Color.FromArgb(100, 50, 50, 50);
        }
        #endregion

        #region Execution Handling
        private void OnExecuteClick(TradeType type)
        {
            // Double-check the expectancy at the exact millisecond of the click
            if (_currentNetExpectancy <= 0)
            {
                Print($"[GUARD REJECTION] {type} order blocked. Net Expectancy is negative ({_currentNetExpectancy:F2} pips). Spread spiked to {_lastSpreadPips:F2}.");
                return;
            }

            double volumeInUnits = Symbol.QuantityToVolumeInUnits(VolumeInLots);
            ExecuteMarketOrderAsync(type, SymbolName, volumeInUnits, "ExpectancyGuard", 0, 0);
            
            Print($"[EXECUTION] {type} order sent. Verified Net Expectancy: {_currentNetExpectancy:F2} pips.");
        }
        #endregion

        #region UI Composition
        private void BuildExecutionPanel()
        {
            var mainContainer = new StackPanel
            {
                BackgroundColor = Color.FromArgb(230, 15, 15, 15),
                Width = 240,
                Margin = new Thickness(0)
            };

            // Metrics Section
            mainContainer.AddChild(new TextBlock
            {
                Text = "NET EXPECTANCY GUARD",
                ForegroundColor = Color.Gray,
                FontFamily = "Arial",
                FontSize = 10,
                FontWeight = FontWeight.Bold,
                Margin = new Thickness(10, 10, 10, 10),
                TextAlignment = TextAlignment.Center
            });

            mainContainer.AddChild(CreateRow("Live Spread", out _spreadVal, Color.Aqua));
            mainContainer.AddChild(CreateRow("Execution Drag", out _dragVal, Color.Red));
            mainContainer.AddChild(CreateRow("Net Yield (1x)", out _netYieldVal, Color.Lime));

            // Execution Buttons Section
            var buttonPanel = new StackPanel
            {
                Orientation = Orientation.Horizontal,
                HorizontalAlignment = HorizontalAlignment.Center,
                Margin = new Thickness(0, 10, 0, 15)
            };

            _sellButton = new Button
            {
                Text = "SELL",
                Width = 90,
                Height = 35,
                Margin = new Thickness(0, 0, 10, 0),
                ForegroundColor = Color.White,
                FontWeight = FontWeight.Bold
            };
            _sellButton.Click += args => OnExecuteClick(TradeType.Sell);

            _buyButton = new Button
            {
                Text = "BUY",
                Width = 90,
                Height = 35,
                ForegroundColor = Color.White,
                FontWeight = FontWeight.Bold
            };
            _buyButton.Click += args => OnExecuteClick(TradeType.Buy);

            buttonPanel.AddChild(_sellButton);
            buttonPanel.AddChild(_buyButton);
            mainContainer.AddChild(buttonPanel);

            // Anchor to Chart
            Chart.AddControl(new Border
            {
                Child = mainContainer,
                BorderColor = Color.FromArgb(255, 60, 60, 60),
                BorderThickness = new Thickness(1),
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                Margin = new Thickness(0, 0, 15, 25)
            });
        }

        private StackPanel CreateRow(string labelText, out TextBlock valueTextBlock, Color defaultColor)
        {
            var row = new StackPanel
            {
                Orientation = Orientation.Horizontal,
                Margin = new Thickness(15, 0, 15, 8)
            };

            row.AddChild(new TextBlock
            {
                Text = $"{labelText}:",
                ForegroundColor = Color.LightGray,
                FontFamily = "Arial",
                FontSize = 11,
                Width = 130
            });

            valueTextBlock = new TextBlock
            {
                Text = "0.00",
                ForegroundColor = defaultColor,
                FontFamily = "Consolas",
                FontSize = 12,
                FontWeight = FontWeight.Bold,
                Width = 70,
                TextAlignment = TextAlignment.Right
            };

            row.AddChild(valueTextBlock);
            return row;
        }
        #endregion
    }
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: How many pips a day is realistic for scalping

Post by FTtrader »

Architectural Safeguards Included:

The Double-Check Lock: Notice the OnExecuteClick() method. Even if the UI thread lags by a few milliseconds and the button appears clickable during a spread spike, the execution logic evaluates _currentNetExpectancy synchronously one last time before wrapping the ExecuteMarketOrderAsync request.

Rejection Logging: When a manual click is rejected by the logic layer, it pushes a [GUARD REJECTION] print to your cTrader Automate Journal. This logs the exact mathematical reason (and the specific spread spike) that caused the rejection, preserving the data for post-session analysis.

WPF State Feedback: The buttons don't just stop working; they physically dim to gray when the environment is statistically toxic, giving you immediate peripheral awareness of market liquidity without needing to read the numbers.
LondonScalper
Posts: 770
Joined: Sat Sep 05, 2026 7:54 am

Re: How many pips a day is realistic for scalping

Post by LondonScalper »

FTtrader wrote:A 1.5-pip round-turn cost can surrender 30–50% of a 3–5 pip gross target. I agree on A+ windows and liquidity-based execution — my average holds are minutes after an M15 sweep.
That’s the arithmetic that kills quota scalping. Once 1.5 RT eats a third to half of a 3–5 pip aim, “more tickets” just compounds friction faster.

I check all-in cost versus planned target on the pairs I actually trade in the London open and early overlap before I care about any dashboard. A+ windows only; no forced daily pip count. Minute-scale holds after an M15 sweep fit my desk far better than second-scalps or multi-hour babysitting.

On the Pine follow-ups — fine for logging, but I wouldn’t chase live bid/ask perfection in the script. Fixed conservative friction for expectancy review; spot-check a sample of real fills weekly. If live feed and fixed inputs disagree, I trust the fill log.

Rule: net expectancy after costs first; code second. Do you require the M15 sweep to clear a pre-marked level before the A+ ticket is allowed?
Post Reply