How many pips a day is realistic for scalping
Re: How many pips a day is realistic for scalping
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.
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.
Re: How many pips a day is realistic for scalping
Here are the professional-grade versions for both platforms.
MQL4: Institutional Net Expectancy Model
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);
}Re: How many pips a day is realistic for scalping
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().
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);
}Re: How many pips a day is realistic for scalping
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.
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;
}
}
}Re: How many pips a day is realistic for scalping
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
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
}
}Re: How many pips a day is realistic for scalping
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.
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.
Re: How many pips a day is realistic for scalping
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.
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.
Re: How many pips a day is realistic for scalping
cTrader: Expectancy Guard & Execution cBot
Save this in the Automate tab as a new cBot (not an Indicator).
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
}
}Re: How many pips a day is realistic for scalping
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.
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
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.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.
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?