Most complex forex strategy
Re: Most complex forex strategy
Below are two complete, non-repainting implementations: a cTrader (cAlgo C# / .NET 6) indicator that also inspects intrabar M1 volume concentration inside the M15 wick, followed by a fast MetaTrader 5 (MQL5) version with iCustom-ready signal buffers.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
1. cTrader Implementation (cAlgo C# / .NET 6)This indicator tracks active swing pools, validates ATR-bounded stop raids, calculates the M15 Tick-Volume $Z$-Score, and queries the underlying M1 bars inside the M15 candle to verify that heavy volume actually traded inside the swept wick zone.
Code: Select all
using System;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Indicators
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class LiquiditySweepAbsorption : Indicator
{
#region Parameters
[Parameter("Swing Pivot Strength (Bars)", DefaultValue = 5, MinValue = 2, MaxValue = 20, Group = "1. Liquidity Pool Geometry")]
public int SwingStrength { get; set; }
[Parameter("Max Pool Age (M15 Bars)", DefaultValue = 120, MinValue = 20, MaxValue = 500, Group = "1. Liquidity Pool Geometry")]
public int MaxPoolAgeBars { get; set; }
[Parameter("Min Sweep Depth (ATR Mult)", DefaultValue = 0.10, MinValue = 0.01, MaxValue = 1.0, Step = 0.05, Group = "1. Liquidity Pool Geometry")]
public double MinSweepAtr { get; set; }
[Parameter("Max Sweep Depth (ATR Mult)", DefaultValue = 1.25, MinValue = 0.30, MaxValue = 3.0, Step = 0.05, Group = "1. Liquidity Pool Geometry")]
public double MaxSweepAtr { get; set; }
[Parameter("Volume Lookback Period", DefaultValue = 20, MinValue = 5, MaxValue = 100, Group = "2. Tick Absorption Filter")]
public int VolumePeriod { get; set; }
[Parameter("Min Tick Volume Z-Score", DefaultValue = 1.25, MinValue = 0.0, MaxValue = 4.0, Step = 0.1, Group = "2. Tick Absorption Filter")]
public double MinVolumeZScore { get; set; }
[Parameter("Min Rejection Wick Ratio (0-1)", DefaultValue = 0.42, MinValue = 0.20, MaxValue = 0.85, Step = 0.02, Group = "2. Tick Absorption Filter")]
public double MinWickRatio { get; set; }
[Parameter("Verify M1 Intrabar Wick Volume", DefaultValue = true, Group = "2. Tick Absorption Filter")]
public bool UseM1IntrabarCheck { get; set; }
[Parameter("Min M1 Wick Volume Share (0-1)", DefaultValue = 0.35, MinValue = 0.10, MaxValue = 0.80, Step = 0.05, Group = "2. Tick Absorption Filter")]
public double MinM1WickVolumeShare { get; set; }
[Parameter("Enable Sound Alert on Close", DefaultValue = true, Group = "3. Alerts & Visuals")]
public bool EnableSoundAlert { get; set; }
[Parameter("Draw Swept Liquidity Rays", DefaultValue = true, Group = "3. Alerts & Visuals")]
public bool DrawLiquidityRays { get; set; }
[Output("Bullish Sweep Signal", LineColor = "Lime", PlotType = PlotType.Points, Thickness = 5)]
public IndicatorDataSeries BullishSignal { get; set; }
[Output("Bearish Sweep Signal", LineColor = "Red", PlotType = PlotType.Points, Thickness = 5)]
public IndicatorDataSeries BearishSignal { get; set; }
#endregion
#region Internal State
private sealed class LiquidityPool
{
public int BarIndex { get; init; }
public double Price { get; init; }
public bool IsSwingHigh { get; init; }
public bool IsMitigated { get; set; }
}
private readonly List<LiquidityPool> _activePools = new();
private AverageTrueRange _atr;
private Bars _m1Bars;
private int _lastProcessedBar = -1;
#endregion
protected override void Initialize()
{
_atr = Indicators.AverageTrueRange(14, MovingAverageType.Exponential);
if (UseM1IntrabarCheck && Bars.TimeFrame > TimeFrame.Minute)
{
_m1Bars = MarketData.GetBars(TimeFrame.Minute);
}
}
public override void Calculate(int index)
{
BullishSignal[index] = double.NaN;
BearishSignal[index] = double.NaN;
// Process only once per closed bar (index - 1) to guarantee zero repainting
int closedIndex = index - 1;
if (closedIndex <= SwingStrength * 2 + VolumePeriod || closedIndex <= _lastProcessedBar)
return;
_lastProcessedBar = closedIndex;
// 1. Register newly confirmed Swing High / Swing Low at (closedIndex - SwingStrength)
int pivotIndex = closedIndex - SwingStrength;
RegisterConfirmedPools(pivotIndex);
// 2. Prune expired pools and evaluate sweeps on the newly closed bar
EvaluatePoolSweepsAndMitigation(closedIndex);
}
private void RegisterConfirmedPools(int pivotIndex)
{
double candidateHigh = Bars.HighPrices[pivotIndex];
double candidateLow = Bars.LowPrices[pivotIndex];
bool isSwingHigh = true;
bool isSwingLow = true;
for (int offset = 1; offset <= SwingStrength; offset++)
{
if (Bars.HighPrices[pivotIndex - offset] >= candidateHigh ||
Bars.HighPrices[pivotIndex + offset] > candidateHigh)
{
isSwingHigh = false;
}
if (Bars.LowPrices[pivotIndex - offset] <= candidateLow ||
Bars.LowPrices[pivotIndex + offset] < candidateLow)
{
isSwingLow = false;
}
}
if (isSwingHigh)
{
_activePools.Add(new LiquidityPool
{
BarIndex = pivotIndex,
Price = candidateHigh,
IsSwingHigh = true,
IsMitigated = false
});
}
if (isSwingLow)
{
_activePools.Add(new LiquidityPool
{
BarIndex = pivotIndex,
Price = candidateLow,
IsSwingHigh = false,
IsMitigated = false
});
}
}
private void EvaluatePoolSweepsAndMitigation(int barIndex)
{
double open = Bars.OpenPrices[barIndex];
double high = Bars.HighPrices[barIndex];
double low = Bars.LowPrices[barIndex];
double close = Bars.ClosePrices[barIndex];
double range = high - low;
double atr = _atr.Result[barIndex];
if (range <= Symbol.TickSize || atr <= 0)
return;
double volZScore = CalculateTickVolumeZScore(barIndex);
LiquidityPool bestBullishSweep = null;
LiquidityPool bestBearishSweep = null;
for (int i = _activePools.Count - 1; i >= 0; i--)
{
LiquidityPool pool = _activePools[i];
// Expire old liquidity pools
if (barIndex - pool.BarIndex > MaxPoolAgeBars || pool.IsMitigated)
{
_activePools.RemoveAt(i);
continue;
}
if (pool.IsSwingHigh)
{
// Full-body breakout invalidates the pool without triggering a reversal sweep
if (close > pool.Price)
{
pool.IsMitigated = true;
_activePools.RemoveAt(i);
continue;
}
// Check Buy-Side Liquidity Raid (High pierces above pool, Close snaps back below)
double penetration = high - pool.Price;
if (penetration >= MinSweepAtr * atr && penetration <= MaxSweepAtr * atr && close < pool.Price)
{
pool.IsMitigated = true;
bestBearishSweep = pool;
_activePools.RemoveAt(i);
}
}
else
{
// Full-body breakdown invalidates the pool
if (close < pool.Price)
{
pool.IsMitigated = true;
_activePools.RemoveAt(i);
continue;
}
// Check Sell-Side Liquidity Raid (Low pierces below pool, Close snaps back above)
double penetration = pool.Price - low;
if (penetration >= MinSweepAtr * atr && penetration <= MaxSweepAtr * atr && close > pool.Price)
{
pool.IsMitigated = true;
bestBullishSweep = pool;
_activePools.RemoveAt(i);
}
}
}
// 3. Apply Layer 5 Tick-Volume Absorption Verification
if (bestBullishSweep != null)
{
double lowerWick = Math.Min(open, close) - low;
double wickRatio = lowerWick / range;
bool m1Confirmed = VerifyM1WickAbsorption(barIndex, bestBullishSweep.Price, isBullish: true);
if (volZScore >= MinVolumeZScore && wickRatio >= MinWickRatio && m1Confirmed)
{
double absorptionScore = Math.Round((volZScore + 1.0) * (wickRatio * 100.0), 1);
BullishSignal[barIndex] = low - (atr * 0.25);
RenderSweepVisuals(barIndex, bestBullishSweep, isBullish: true, absorptionScore, atr);
}
}
if (bestBearishSweep != null)
{
double upperWick = high - Math.Max(open, close);
double wickRatio = upperWick / range;
bool m1Confirmed = VerifyM1WickAbsorption(barIndex, bestBearishSweep.Price, isBullish: false);
if (volZScore >= MinVolumeZScore && wickRatio >= MinWickRatio && m1Confirmed)
{
double absorptionScore = Math.Round((volZScore + 1.0) * (wickRatio * 100.0), 1);
BearishSignal[barIndex] = high + (atr * 0.25);
RenderSweepVisuals(barIndex, bestBearishSweep, isBullish: false, absorptionScore, atr);
}
}
}
private double CalculateTickVolumeZScore(int barIndex)
{
double sum = 0.0;
for (int i = 0; i < VolumePeriod; i++)
sum += Bars.TickVolumes[barIndex - i];
double mean = sum / VolumePeriod;
double varianceSum = 0.0;
for (int i = 0; i < VolumePeriod; i++)
{
double diff = Bars.TickVolumes[barIndex - i] - mean;
varianceSum += diff * diff;
}
double stdDev = Math.Sqrt(varianceSum / VolumePeriod);
if (stdDev <= 1e-5)
return 0.0;
return (Bars.TickVolumes[barIndex] - mean) / stdDev;
}
private bool VerifyM1WickAbsorption(int m15Index, double sweptPoolPrice, bool isBullish)
{
if (!UseM1IntrabarCheck || _m1Bars == null || _m1Bars.Count == 0)
return true;
DateTime barStart = Bars.OpenTimes[m15Index];
DateTime barEnd = m15Index + 1 < Bars.Count
? Bars.OpenTimes[m15Index + 1]
: barStart.AddMinutes(15);
int startM1Idx = _m1Bars.OpenTimes.GetIndexByTime(barStart);
if (startM1Idx < 0 || _m1Bars.OpenTimes[startM1Idx] < barStart.AddMinutes(-15))
return true; // Fallback gracefully if M1 history does not reach back far enough
double totalM1Volume = 0;
double wickZoneVolume = 0;
for (int m1 = startM1Idx; m1 < _m1Bars.Count; m1++)
{
DateTime t = _m1Bars.OpenTimes[m1];
if (t < barStart) continue;
if (t >= barEnd) break;
double v = _m1Bars.TickVolumes[m1];
totalM1Volume += v;
// Measure volume traded specifically during M1 bars that interacted with the liquidity sweep zone
if (isBullish && _m1Bars.LowPrices[m1] <= sweptPoolPrice)
wickZoneVolume += v;
else if (!isBullish && _m1Bars.HighPrices[m1] >= sweptPoolPrice)
wickZoneVolume += v;
}
if (totalM1Volume <= 0)
return true;
double wickVolumeShare = wickZoneVolume / totalM1Volume;
return wickVolumeShare >= MinM1WickVolumeShare;
}
private void RenderSweepVisuals(int barIndex, LiquidityPool pool, bool isBullish, double score, double atr)
{
string idPrefix = $"LSA_{barIndex}_{(isBullish ? "B" : "S")}";
Color color = isBullish ? Color.Lime : Color.Crimson;
if (DrawLiquidityRays)
{
var line = Chart.DrawTrendLine(
$"{idPrefix}_Ray",
Bars.OpenTimes[pool.BarIndex],
pool.Price,
Bars.OpenTimes[barIndex],
pool.Price,
color,
1,
LineStyle.DotsRare);
line.IsInteractive = false;
}
double labelY = isBullish
? Bars.LowPrices[barIndex] - (atr * 0.45)
: Bars.HighPrices[barIndex] + (atr * 0.45);
var text = Chart.DrawText(
$"{idPrefix}_Txt",
$"ABS [{score}]",
Bars.OpenTimes[barIndex],
labelY,
color);
text.HorizontalAlignment = HorizontalAlignment.Center;
text.FontSize = 8;
if (EnableSoundAlert && IsLastBar)
{
Notifications.PlaySound(SoundType.Confirmation);
Print("{0} Liquidity Sweep + Absorption on {1} ({2}) | Pool: {3:F5} | Score: {4}",
isBullish ? "BULLISH" : "BEARISH",
Symbol.Name,
TimeFrame,
pool.Price,
score);
}
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
2. MetaTrader 5 Implementation (MQL5)For MetaTrader 5, this script uses a fixed-size ring buffer of active swing structures inside OnCalculate(), evaluates tick_volume $Z$-Scores and ATR penetration bounds on bar close, and exposes two indicator buffers (BullishSweepBuffer and BearishSweepBuffer) so an Expert Advisor can read signals via iCustom().
Code: Select all
//+------------------------------------------------------------------+
//| LiquiditySweepAbsorption.mq5 |
//| M15 Institutional Liquidity Sweep & Tick Absorption |
//+------------------------------------------------------------------+
#property copyright "Quant Architecture"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2
//--- Plot 1: Bullish Sweep + Absorption
#property indicator_label1 "Bullish Absorption Sweep"
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrLime
#property indicator_width1 2
//--- Plot 2: Bearish Sweep + Absorption
#property indicator_label2 "Bearish Absorption Sweep"
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrCrimson
#property indicator_width2 2
//--- Input Parameters
input int InpSwingStrength = 5; // Swing Pivot Strength (Left/Right Bars)
input int InpMaxPoolAge = 120; // Max Liquidity Pool Age (Bars)
input double InpMinSweepAtr = 0.10; // Min Sweep Penetration (ATR Mult)
input double InpMaxSweepAtr = 1.25; // Max Sweep Penetration (ATR Mult)
input int InpVolPeriod = 20; // Tick Volume Z-Score Lookback
input double InpMinVolZScore = 1.25; // Min Tick Volume Z-Score
input double InpMinWickRatio = 0.42; // Min Rejection Wick Ratio (0.0 - 1.0)
input bool InpAlertOnClose = true; // Trigger Alert / Push Notification
//--- Indicator Buffers
double BullishSweepBuffer[];
double BearishSweepBuffer[];
//--- Internal Liquidity Pool Struct
struct LiquidityPool
{
int barIndex;
datetime barTime;
double price;
bool isSwingHigh;
bool active;
};
#define MAX_POOLS 256
LiquidityPool g_pools[MAX_POOLS];
int g_atrHandle = INVALID_HANDLE;
datetime g_lastAlertTime = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
SetIndexBuffer(0, BullishSweepBuffer, INDICATOR_DATA);
SetIndexBuffer(1, BearishSweepBuffer, INDICATOR_DATA);
PlotIndexSetInteger(0, PLOT_ARROW, 233); // Up Arrow
PlotIndexSetInteger(1, PLOT_ARROW, 234); // Down Arrow
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
g_atrHandle = iATR(_Symbol, _Period, 14);
if(g_atrHandle == INVALID_HANDLE)
return(INIT_FAILED);
ResetPools();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Helper to clear pool memory |
//+------------------------------------------------------------------+
void ResetPools()
{
for(int i = 0; i < MAX_POOLS; i++)
g_pools[i].active = false;
}
//+------------------------------------------------------------------+
//| Insert new confirmed swing high/low into the pool array |
//+------------------------------------------------------------------+
void AddPool(int barIndex, datetime barTime, double price, bool isSwingHigh)
{
int oldestSlot = 0;
int minBarIdx = INT_MAX;
for(int i = 0; i < MAX_POOLS; i++)
{
if(!g_pools[i].active)
{
oldestSlot = i;
break;
}
if(g_pools[i].barIndex < minBarIdx)
{
minBarIdx = g_pools[i].barIndex;
oldestSlot = i;
}
}
g_pools[oldestSlot].barIndex = barIndex;
g_pools[oldestSlot].barTime = barTime;
g_pools[oldestSlot].price = price;
g_pools[oldestSlot].isSwingHigh = isSwingHigh;
g_pools[oldestSlot].active = true;
}
//+------------------------------------------------------------------+
//| Compute Tick Volume Z-Score at index |
//+------------------------------------------------------------------+
double GetVolumeZScore(int index, const long &tick_volume[])
{
double sum = 0.0;
for(int i = 0; i < InpVolPeriod; i++)
sum += (double)tick_volume[index - i];
double mean = sum / InpVolPeriod;
double varSum = 0.0;
for(int i = 0; i < InpVolPeriod; i++)
{
double diff = (double)tick_volume[index - i] - mean;
varSum += diff * diff;
}
double stdDev = MathSqrt(varSum / InpVolPeriod);
if(stdDev <= 1e-5)
return 0.0;
return ((double)tick_volume[index] - mean) / stdDev;
}
//+------------------------------------------------------------------+
//| 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[])
{
int minBars = InpSwingStrength * 2 + InpVolPeriod + 20;
if(rates_total <= minBars)
return(0);
double atr[];
if(CopyBuffer(g_atrHandle, 0, 0, rates_total, atr) <= 0)
return(0);
int startBar;
if(prev_calculated == 0)
{
ResetPools();
ArrayInitialize(BullishSweepBuffer, EMPTY_VALUE);
ArrayInitialize(BearishSweepBuffer, EMPTY_VALUE);
startBar = minBars;
}
else
{
startBar = prev_calculated - 1;
}
// Always clear current forming bar
BullishSweepBuffer[rates_total - 1] = EMPTY_VALUE;
BearishSweepBuffer[rates_total - 1] = EMPTY_VALUE;
// Process strictly on closed bars up to (rates_total - 2)
for(int bar = startBar; bar < rates_total - 1; bar++)
{
BullishSweepBuffer[bar] = EMPTY_VALUE;
BearishSweepBuffer[bar] = EMPTY_VALUE;
// 1. Confirm Swing Pivot at (bar - InpSwingStrength)
int p = bar - InpSwingStrength;
bool isHigh = true;
bool isLow = true;
for(int k = 1; k <= InpSwingStrength; k++)
{
if(high[p - k] >= high[p] || high[p + k] > high[p]) isHigh = false;
if(low[p - k] <= low[p] || low[p + k] < low[p]) isLow = false;
}
if(isHigh) AddPool(p, time[p], high[p], true);
if(isLow) AddPool(p, time[p], low[p], false);
// 2. Check active pools against bar
double range = high[bar] - low[bar];
double curAtr = atr[bar];
if(range <= _Point || curAtr <= 0.0)
continue;
bool sweptBearish = false;
bool sweptBullish = false;
for(int i = 0; i < MAX_POOLS; i++)
{
if(!g_pools[i].active)
continue;
if(bar - g_pools[i].barIndex > InpMaxPoolAge || g_pools[i].barIndex >= bar)
{
g_pools[i].active = false;
continue;
}
double poolPrice = g_pools[i].price;
if(g_pools[i].isSwingHigh)
{
if(close[bar] > poolPrice)
{
g_pools[i].active = false; // Invalidated by full-body close above
continue;
}
double pen = high[bar] - poolPrice;
if(pen >= InpMinSweepAtr * curAtr && pen <= InpMaxSweepAtr * curAtr && close[bar] < poolPrice)
{
g_pools[i].active = false;
sweptBearish = true;
}
}
else
{
if(close[bar] < poolPrice)
{
g_pools[i].active = false; // Invalidated by full-body close below
continue;
}
double pen = poolPrice - low[bar];
if(pen >= InpMinSweepAtr * curAtr && pen <= InpMaxSweepAtr * curAtr && close[bar] > poolPrice)
{
g_pools[i].active = false;
sweptBullish = true;
}
}
}
// 3. Evaluate Tick-Volume Absorption Filter
if(sweptBullish || sweptBearish)
{
double volZ = GetVolumeZScore(bar, tick_volume);
if(volZ >= InpMinVolZScore)
{
if(sweptBullish)
{
double lowerWick = MathMin(open[bar], close[bar]) - low[bar];
if((lowerWick / range) >= InpMinWickRatio)
{
BullishSweepBuffer[bar] = low[bar] - (curAtr * 0.25);
if(InpAlertOnClose && bar == rates_total - 2 && time[bar] != g_lastAlertTime)
{
g_lastAlertTime = time[bar];
Alert(StringFormat("BULLISH Sweep + Absorption on %s (Z=%.2f)", _Symbol, volZ));
SendNotification(StringFormat("BULLISH Sweep + Absorption on %s (Z=%.2f)", _Symbol, volZ));
}
}
}
if(sweptBearish)
{
double upperWick = high[bar] - MathMax(open[bar], close[bar]);
if((upperWick / range) >= InpMinWickRatio)
{
BearishSweepBuffer[bar] = high[bar] + (curAtr * 0.25);
if(InpAlertOnClose && bar == rates_total - 2 && time[bar] != g_lastAlertTime)
{
g_lastAlertTime = time[bar];
Alert(StringFormat("BEARISH Sweep + Absorption on %s (Z=%.2f)", _Symbol, volZ));
SendNotification(StringFormat("BEARISH Sweep + Absorption on %s (Z=%.2f)", _Symbol, volZ));
}
}
}
}
}
}
return(rates_total);
}
//+------------------------------------------------------------------+Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
Key Engineering Details to Note
Zero Repainting by Design: Both indicators evaluate strictly on index - 1 (rates_total - 2 in MQL5), meaning signals only lock once the M15 candle closes back inside the range and confirms the rejection wick.
Single-Raid Mitigation: Once a swing pool is swept—or invalidated by a full candle body closing beyond the level—it is immediately purged from _activePools. This prevents stale support/resistance lines from firing false signals hours later during chop.
Zero Repainting by Design: Both indicators evaluate strictly on index - 1 (rates_total - 2 in MQL5), meaning signals only lock once the M15 candle closes back inside the range and confirms the rejection wick.
Single-Raid Mitigation: Once a swing pool is swept—or invalidated by a full candle body closing beyond the level—it is immediately purged from _activePools. This prevents stale support/resistance lines from firing false signals hours later during chop.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
Below is the complete, self-contained cTrader cBot (ChronosSweepPyramidBot.cs) for .NET 6. It embeds the M15 Liquidity Sweep + Tick-Volume Absorption + M1 Intrabar Wick detector directly inside the execution engine and pairs it with a mathematically isolated House-Money Pyramiding State Machine.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
How the 4-Phase "House-Money" State Machine Works
Rather than risking new core equity on add-on trades, the cBot treats your initial entry as a low-risk "probe" and forces every subsequent pyramid leg to be collateralized strictly by guaranteed profit already locked in by the trailing stop-loss.
Rather than risking new core equity on add-on trades, the cBot treats your initial entry as a low-risk "probe" and forces every subsequent pyramid leg to be collateralized strictly by guaranteed profit already locked in by the trailing stop-loss.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
Complete cTrader C# cBot Code (ChronosSweepPyramidBot.cs)
To deploy in cTrader:
Open the Automate tab, click New cBot, and name it ChronosSweepPyramidBot.
Replace the entire template with the code below and click Build.
Attach to an M15 chart (e.g., XAUUSD, EURUSD, or GBPUSD).
To deploy in cTrader:
Open the Automate tab, click New cBot, and name it ChronosSweepPyramidBot.
Replace the entire template with the code below and click Build.
Attach to an M15 chart (e.g., XAUUSD, EURUSD, or GBPUSD).
Code: Select all
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ChronosSweepPyramidBot : Robot
{
#region 1. Liquidity Pool & Sweep Parameters
[Parameter("Bot Instance Label", DefaultValue = "Chronos_HMP", Group = "0. Engine Identity")]
public string BotLabel { get; set; }
[Parameter("Swing Pivot Strength (Bars)", DefaultValue = 5, MinValue = 2, MaxValue = 20, Group = "1. Liquidity Pool Geometry")]
public int SwingStrength { get; set; }
[Parameter("Max Pool Age (Bars)", DefaultValue = 120, MinValue = 20, MaxValue = 500, Group = "1. Liquidity Pool Geometry")]
public int MaxPoolAgeBars { get; set; }
[Parameter("Min Sweep Depth (ATR Mult)", DefaultValue = 0.10, MinValue = 0.01, MaxValue = 1.0, Step = 0.05, Group = "1. Liquidity Pool Geometry")]
public double MinSweepAtr { get; set; }
[Parameter("Max Sweep Depth (ATR Mult)", DefaultValue = 1.30, MinValue = 0.30, MaxValue = 3.0, Step = 0.05, Group = "1. Liquidity Pool Geometry")]
public double MaxSweepAtr { get; set; }
#endregion
#region 2. Tick-Volume Absorption Parameters
[Parameter("Volume Z-Score Lookback", DefaultValue = 20, MinValue = 5, MaxValue = 100, Group = "2. Tick Absorption Filter")]
public int VolumePeriod { get; set; }
[Parameter("Min Tick Volume Z-Score", DefaultValue = 1.25, MinValue = 0.0, MaxValue = 4.0, Step = 0.1, Group = "2. Tick Absorption Filter")]
public double MinVolumeZScore { get; set; }
[Parameter("Min Rejection Wick Ratio (0-1)", DefaultValue = 0.42, MinValue = 0.20, MaxValue = 0.85, Step = 0.02, Group = "2. Tick Absorption Filter")]
public double MinWickRatio { get; set; }
[Parameter("Verify M1 Intrabar Wick Volume", DefaultValue = true, Group = "2. Tick Absorption Filter")]
public bool UseM1IntrabarCheck { get; set; }
[Parameter("Min M1 Wick Volume Share (0-1)", DefaultValue = 0.35, MinValue = 0.10, MaxValue = 0.80, Step = 0.05, Group = "2. Tick Absorption Filter")]
public double MinM1WickVolumeShare { get; set; }
#endregion
#region 3. Seed Risk & House-Money Pyramiding Parameters
[Parameter("Seed Risk (% of Balance)", DefaultValue = 0.25, MinValue = 0.05, MaxValue = 2.0, Step = 0.05, Group = "3. House-Money Risk Engine")]
public double SeedRiskPercent { get; set; }
[Parameter("Stop Buffer Beyond Sweep (ATR)", DefaultValue = 0.20, MinValue = 0.05, MaxValue = 1.0, Step = 0.05, Group = "3. House-Money Risk Engine")]
public double StopBufferAtr { get; set; }
[Parameter("Breakeven Trigger (R-Multiple)", DefaultValue = 1.0, MinValue = 0.5, MaxValue = 3.0, Step = 0.1, Group = "3. House-Money Risk Engine")]
public double BreakevenTriggerR { get; set; }
[Parameter("Breakeven Cushion (Pips)", DefaultValue = 1.5, MinValue = 0.2, MaxValue = 10.0, Step = 0.1, Group = "3. House-Money Risk Engine")]
public double BreakevenCushionPips { get; set; }
[Parameter("Max Total Positions (Seed + Pyramids)", DefaultValue = 4, MinValue = 1, MaxValue = 10, Group = "3. House-Money Risk Engine")]
public int MaxBasketPositions { get; set; }
[Parameter("Pyramid Step Distance (R-Multiple)", DefaultValue = 1.6, MinValue = 1.0, MaxValue = 4.0, Step = 0.1, Group = "3. House-Money Risk Engine")]
public double PyramidStepR { get; set; }
[Parameter("House-Money Risk Share (0.1 - 0.8)", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 0.85, Step = 0.05, Group = "3. House-Money Risk Engine")]
public double HouseMoneyRiskFraction { get; set; }
[Parameter("Hard Take-Profit on Seed (R-Multiple)", DefaultValue = 6.0, MinValue = 2.0, MaxValue = 20.0, Step = 0.5, Group = "3. House-Money Risk Engine")]
public double TargetTakeProfitR { get; set; }
#endregion
#region 4. Layer 7 ECN Execution Guards
[Parameter("Max Spread Multiplier (vs 60m Avg)", DefaultValue = 1.8, MinValue = 1.1, MaxValue = 4.0, Step = 0.1, Group = "4. ECN Kill Switches")]
public double MaxSpreadMultiplier { get; set; }
[Parameter("Max Market Range Slippage (Pips)", DefaultValue = 1.5, MinValue = 0.2, MaxValue = 10.0, Step = 0.1, Group = "4. ECN Kill Switches")]
public double MaxSlippagePips { get; set; }
#endregion
#region Internal State Structures
private sealed class LiquidityPool
{
public int BarIndex { get; init; }
public double Price { get; init; }
public bool IsSwingHigh { get; init; }
public bool IsMitigated { get; set; }
}
private readonly List<LiquidityPool> _activePools = new();
private readonly Queue<double> _rollingSpreads = new();
private AverageTrueRange _atr;
private Bars _m1Bars;
// Basket tracking state
private double _seedEntryPrice;
private double _seedInitialRiskPips;
private bool _isSeedBreakevenLocked;
#endregion
protected override void OnStart()
{
_atr = Indicators.AverageTrueRange(14, MovingAverageType.Exponential);
if (UseM1IntrabarCheck && Bars.TimeFrame > TimeFrame.Minute)
{
_m1Bars = MarketData.GetBars(TimeFrame.Minute);
}
// Warm up historical liquidity pools over the last 300 bars
int startWarmup = Math.Max(SwingStrength * 2 + VolumePeriod, Bars.Count - 300);
for (int i = startWarmup; i < Bars.Count - 1; i++)
{
RegisterConfirmedPools(i - SwingStrength);
PruneAndInvalidatePools(i);
}
Print("Chronos Sweep & House-Money Pyramid Engine started on {0} ({1}). Warmed up {2} active pools.",
SymbolName, TimeFrame, _activePools.Count);
}
protected override void OnTick()
{
RecordSpreadSample();
var basket = GetActiveBasket();
if (basket.Count == 0)
{
_isSeedBreakevenLocked = false;
return;
}
Position seedPos = basket[0];
if (_seedInitialRiskPips <= 0)
return;
// Phase 2: Check 1R Fortress Breakeven Lock on the Seed Position
if (!_isSeedBreakevenLocked)
{
double favorablePips = seedPos.TradeType == TradeType.Buy
? (Symbol.Bid - seedPos.EntryPrice) / Symbol.PipSize
: (seedPos.EntryPrice - Symbol.Ask) / Symbol.PipSize;
if (favorablePips >= _seedInitialRiskPips * BreakevenTriggerR)
{
LockSeedAtBreakeven(seedPos);
}
}
// Phase 4: Check Hard Basket Target R-Multiple
double totalRunPips = seedPos.TradeType == TradeType.Buy
? (Symbol.Bid - _seedEntryPrice) / Symbol.PipSize
: (_seedEntryPrice - Symbol.Ask) / Symbol.PipSize;
if (totalRunPips >= _seedInitialRiskPips * TargetTakeProfitR)
{
FlattenBasket($"Target {TargetTakeProfitR:F1}R Reached");
}
}
protected override void OnBar()
{
int closedIndex = Bars.Count - 2;
if (closedIndex <= SwingStrength * 2 + VolumePeriod)
return;
// 1. Register newly confirmed Swing High / Swing Low pivot
RegisterConfirmedPools(closedIndex - SwingStrength);
// 2. Evaluate closed bar for Liquidity Sweep + Tick Absorption
EvaluateClosedBar(closedIndex);
}
#region Core Strategy Logic (Sweep Detection & Pyramiding)
private void EvaluateClosedBar(int barIndex)
{
double open = Bars.OpenPrices[barIndex];
double high = Bars.HighPrices[barIndex];
double low = Bars.LowPrices[barIndex];
double close = Bars.ClosePrices[barIndex];
double range = high - low;
double atr = _atr.Result[barIndex];
if (range <= Symbol.TickSize || atr <= 0)
return;
LiquidityPool bullishSweepPool = null;
LiquidityPool bearishSweepPool = null;
for (int i = _activePools.Count - 1; i >= 0; i--)
{
LiquidityPool pool = _activePools[i];
if (barIndex - pool.BarIndex > MaxPoolAgeBars || pool.IsMitigated)
{
_activePools.RemoveAt(i);
continue;
}
if (pool.IsSwingHigh)
{
if (close > pool.Price)
{
pool.IsMitigated = true;
_activePools.RemoveAt(i);
continue;
}
double pen = high - pool.Price;
if (pen >= MinSweepAtr * atr && pen <= MaxSweepAtr * atr && close < pool.Price)
{
pool.IsMitigated = true;
bearishSweepPool = pool;
_activePools.RemoveAt(i);
}
}
else
{
if (close < pool.Price)
{
pool.IsMitigated = true;
_activePools.RemoveAt(i);
continue;
}
double pen = pool.Price - low;
if (pen >= MinSweepAtr * atr && pen <= MaxSweepAtr * atr && close > pool.Price)
{
pool.IsMitigated = true;
bullishSweepPool = pool;
_activePools.RemoveAt(i);
}
}
}
double volZScore = CalculateTickVolumeZScore(barIndex);
var basket = GetActiveBasket();
// Check Bullish Sweep Signal
bool validBullishSweep = false;
if (bullishSweepPool != null)
{
double lowerWickRatio = (Math.Min(open, close) - low) / range;
if (volZScore >= MinVolumeZScore &&
lowerWickRatio >= MinWickRatio &&
VerifyM1WickAbsorption(barIndex, bullishSweepPool.Price, isBullish: true))
{
validBullishSweep = true;
}
}
// Check Bearish Sweep Signal
bool validBearishSweep = false;
if (bearishSweepPool != null)
{
double upperWickRatio = (high - Math.Max(open, close)) / range;
if (volZScore >= MinVolumeZScore &&
upperWickRatio >= MinWickRatio &&
VerifyM1WickAbsorption(barIndex, bearishSweepPool.Price, isBullish: false))
{
validBearishSweep = true;
}
}
// If flat, execute Phase 1 Seed Probe
if (basket.Count == 0)
{
if (validBullishSweep && IsEcnSpreadHealthy())
{
double stopPrice = low - (atr * StopBufferAtr);
ExecuteSeedPosition(TradeType.Buy, stopPrice, bullishSweepPool);
}
else if (validBearishSweep && IsEcnSpreadHealthy())
{
double stopPrice = high + (atr * StopBufferAtr);
ExecuteSeedPosition(TradeType.Sell, stopPrice, bearishSweepPool);
}
return;
}
// If a basket is active, check for opposing sweep reversal or Phase 3 House-Money Pyramiding
TradeType basketDirection = basket[0].TradeType;
if ((basketDirection == TradeType.Buy && validBearishSweep) ||
(basketDirection == TradeType.Sell && validBullishSweep))
{
FlattenBasket("Opposing Institutional Liquidity Sweep Detected");
return;
}
if (_isSeedBreakevenLocked && basket.Count < MaxBasketPositions && IsEcnSpreadHealthy())
{
TryExecuteHouseMoneyPyramid(barIndex, basket, atr);
}
}
private void ExecuteSeedPosition(TradeType tradeType, double stopPrice, LiquidityPool pool)
{
double entryEstimate = tradeType == TradeType.Buy ? Symbol.Ask : Symbol.Bid;
double stopLossPips = Math.Abs(entryEstimate - stopPrice) / Symbol.PipSize;
if (stopLossPips < 2.0)
return;
double seedRiskCurrency = Account.Balance * (SeedRiskPercent / 100.0);
double rawUnits = seedRiskCurrency / (stopLossPips * Symbol.PipValue);
double volumeInUnits = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
if (volumeInUnits < Symbol.VolumeInUnitsMin)
return;
double takeProfitPips = Math.Round(stopLossPips * TargetTakeProfitR, 1);
TradeResult result = ExecuteMarketRangeOrder(
tradeType,
SymbolName,
volumeInUnits,
MaxSlippagePips,
entryEstimate,
BotLabel,
Math.Round(stopLossPips, 1),
takeProfitPips);
if (result.IsSuccessful && result.Position != null)
{
_seedEntryPrice = result.Position.EntryPrice;
_seedInitialRiskPips = stopLossPips;
_isSeedBreakevenLocked = false;
Chart.DrawTrendLine(
$"Sweep_{Bars.Count}",
Bars.OpenTimes[pool.BarIndex],
pool.Price,
Bars.OpenTimes[Bars.Count - 2],
pool.Price,
tradeType == TradeType.Buy ? Color.Lime : Color.Crimson,
2,
LineStyle.DotsRare);
Print("SEED PROBE OPENED | {0} {1} units @ {2:F5} | SL: {3:F1} pips | Risk: {4:C2} ({5:F2}%)",
tradeType, volumeInUnits, _seedEntryPrice, stopLossPips, seedRiskCurrency, SeedRiskPercent);
}
}
private void LockSeedAtBreakeven(Position seedPos)
{
double newStop = seedPos.TradeType == TradeType.Buy
? Symbol.NormalizePrice(seedPos.EntryPrice + (BreakevenCushionPips * Symbol.PipSize))
: Symbol.NormalizePrice(seedPos.EntryPrice - (BreakevenCushionPips * Symbol.PipSize));
bool isValidImprovement = seedPos.TradeType == TradeType.Buy
? (seedPos.StopLoss == null || newStop > seedPos.StopLoss.Value)
: (seedPos.StopLoss == null || newStop < seedPos.StopLoss.Value);
if (isValidImprovement)
{
var mod = ModifyPosition(seedPos, newStop, seedPos.TakeProfit);
if (mod.IsSuccessful)
{
_isSeedBreakevenLocked = true;
Print("1R FORTRESS LOCKED | Seed Position #{0} SL moved to {1:F5} (+{2:F1} pips cushion). Core equity risk = $0.00.",
seedPos.Id, newStop, BreakevenCushionPips);
}
}
}
private void TryExecuteHouseMoneyPyramid(int barIndex, List<Position> basket, double atr)
{
TradeType direction = basket[0].TradeType;
double currentPrice = direction == TradeType.Buy ? Symbol.Ask : Symbol.Bid;
// 1. Verify price has advanced to the next R-step threshold for this pyramid layer
double requiredAdvancePips = _seedInitialRiskPips * (BreakevenTriggerR + (basket.Count * (PyramidStepR - 0.8)));
double currentAdvancePips = direction == TradeType.Buy
? (currentPrice - _seedEntryPrice) / Symbol.PipSize
: (_seedEntryPrice - currentPrice) / Symbol.PipSize;
if (currentAdvancePips < requiredAdvancePips)
return;
// 2. Confirm a continuation bar in the direction of the trend with rejection structure
bool isBullishContinuation = direction == TradeType.Buy &&
Bars.ClosePrices[barIndex] > Bars.OpenPrices[barIndex] &&
Bars.ClosePrices[barIndex] > Bars.HighPrices[barIndex - 1];
bool isBearishContinuation = direction == TradeType.Sell &&
Bars.ClosePrices[barIndex] < Bars.OpenPrices[barIndex] &&
Bars.ClosePrices[barIndex] < Bars.LowPrices[barIndex - 1];
if (!isBullishContinuation && !isBearishContinuation)
return;
// 3. Calculate the proposed new Unified Structural Stop Loss behind the continuation pullback
double proposedStop = direction == TradeType.Buy
? Symbol.NormalizePrice(Math.Min(Bars.LowPrices[barIndex], Bars.LowPrices[barIndex - 1]) - (atr * StopBufferAtr))
: Symbol.NormalizePrice(Math.Max(Bars.HighPrices[barIndex], Bars.HighPrices[barIndex - 1]) + (atr * StopBufferAtr));
// Ensure the proposed stop strictly ratchets forward for every open leg in the basket
foreach (Position pos in basket)
{
if (pos.StopLoss == null) return;
if (direction == TradeType.Buy && proposedStop <= pos.StopLoss.Value) return;
if (direction == TradeType.Sell && proposedStop >= pos.StopLoss.Value) return;
}
// 4. Calculate Total Guaranteed Locked Profit (in Account Currency) if all existing legs are trailed to proposedStop
double totalLockedProfitCurrency = 0.0;
foreach (Position pos in basket)
{
double lockedPips = direction == TradeType.Buy
? (proposedStop - pos.EntryPrice) / Symbol.PipSize
: (pos.EntryPrice - proposedStop) / Symbol.PipSize;
double legLockedGross = lockedPips * Symbol.PipValue * pos.VolumeInUnits;
double legNetLocked = legLockedGross + (pos.Commissions * 2.0) + pos.Swap;
totalLockedProfitCurrency += legNetLocked;
}
if (totalLockedProfitCurrency <= 0)
return;
// 5. Allocate only HouseMoneyRiskFraction (e.g., 50%) of the guaranteed locked profit to the new leg
double houseMoneyRiskBudget = totalLockedProfitCurrency * HouseMoneyRiskFraction;
double newLegStopPips = Math.Abs(currentPrice - proposedStop) / Symbol.PipSize;
if (newLegStopPips < 2.0)
return;
double rawPyramidUnits = houseMoneyRiskBudget / (newLegStopPips * Symbol.PipValue);
double pyramidVolumeUnits = Symbol.NormalizeVolumeInUnits(rawPyramidUnits, RoundingMode.Down);
if (pyramidVolumeUnits < Symbol.VolumeInUnitsMin)
return;
// 6. First ratchet all existing positions to the new Unified Stop Loss
foreach (Position pos in basket)
{
var mod = ModifyPosition(pos, proposedStop, pos.TakeProfit);
if (!mod.IsSuccessful)
return; // Abort pyramid entry if any existing leg fails to lock its stop
}
// 7. Fire the House-Money Pyramid Order with the exact same unified stop
TradeResult pyrResult = ExecuteMarketRangeOrder(
direction,
SymbolName,
pyramidVolumeUnits,
MaxSlippagePips,
currentPrice,
BotLabel,
Math.Round(newLegStopPips, 1),
null);
if (pyrResult.IsSuccessful && pyrResult.Position != null)
{
// Align exact stop price with the rest of the basket
ModifyPosition(pyrResult.Position, proposedStop, basket[0].TakeProfit);
Print("HOUSE-MONEY PYRAMID LEG #{0} FIRED | {1} {2} units @ {3:F5} | Unified SL: {4:F5} | Locked Profit Buffer: {5:C2} | Allocated Risk: {6:C2} (Worst-case Net: +{7:C2})",
basket.Count + 1,
direction,
pyramidVolumeUnits,
pyrResult.Position.EntryPrice,
proposedStop,
totalLockedProfitCurrency,
houseMoneyRiskBudget,
totalLockedProfitCurrency - houseMoneyRiskBudget);
}
}
#endregion
#region Helper & Microstructure Verification Methods
private void RegisterConfirmedPools(int pivotIndex)
{
if (pivotIndex < SwingStrength)
return;
double candidateHigh = Bars.HighPrices[pivotIndex];
double candidateLow = Bars.LowPrices[pivotIndex];
bool isSwingHigh = true;
bool isSwingLow = true;
for (int offset = 1; offset <= SwingStrength; offset++)
{
if (Bars.HighPrices[pivotIndex - offset] >= candidateHigh ||
Bars.HighPrices[pivotIndex + offset] > candidateHigh)
isSwingHigh = false;
if (Bars.LowPrices[pivotIndex - offset] <= candidateLow ||
Bars.LowPrices[pivotIndex + offset] < candidateLow)
isSwingLow = false;
}
if (isSwingHigh)
_activePools.Add(new LiquidityPool { BarIndex = pivotIndex, Price = candidateHigh, IsSwingHigh = true });
if (isSwingLow)
_activePools.Add(new LiquidityPool { BarIndex = pivotIndex, Price = candidateLow, IsSwingHigh = false });
}
private void PruneAndInvalidatePools(int barIndex)
{
double close = Bars.ClosePrices[barIndex];
for (int i = _activePools.Count - 1; i >= 0; i--)
{
var pool = _activePools[i];
if (barIndex - pool.BarIndex > MaxPoolAgeBars ||
(pool.IsSwingHigh && close > pool.Price) ||
(!pool.IsSwingHigh && close < pool.Price))
{
_activePools.RemoveAt(i);
}
}
}
private double CalculateTickVolumeZScore(int barIndex)
{
double sum = 0.0;
for (int i = 0; i < VolumePeriod; i++)
sum += Bars.TickVolumes[barIndex - i];
double mean = sum / VolumePeriod;
double varSum = 0.0;
for (int i = 0; i < VolumePeriod; i++)
{
double diff = Bars.TickVolumes[barIndex - i] - mean;
varSum += diff * diff;
}
double stdDev = Math.Sqrt(varSum / VolumePeriod);
return stdDev <= 1e-5 ? 0.0 : (Bars.TickVolumes[barIndex] - mean) / stdDev;
}
private bool VerifyM1WickAbsorption(int m15Index, double sweptPoolPrice, bool isBullish)
{
if (!UseM1IntrabarCheck || _m1Bars == null || _m1Bars.Count == 0)
return true;
DateTime barStart = Bars.OpenTimes[m15Index];
DateTime barEnd = m15Index + 1 < Bars.Count
? Bars.OpenTimes[m15Index + 1]
: barStart.AddMinutes(15);
int startM1Idx = _m1Bars.OpenTimes.GetIndexByTime(barStart);
if (startM1Idx < 0 || _m1Bars.OpenTimes[startM1Idx] < barStart.AddMinutes(-15))
return true;
double totalM1Volume = 0;
double wickZoneVolume = 0;
for (int m1 = startM1Idx; m1 < _m1Bars.Count; m1++)
{
DateTime t = _m1Bars.OpenTimes[m1];
if (t < barStart) continue;
if (t >= barEnd) break;
double v = _m1Bars.TickVolumes[m1];
totalM1Volume += v;
if (isBullish && _m1Bars.LowPrices[m1] <= sweptPoolPrice)
wickZoneVolume += v;
else if (!isBullish && _m1Bars.HighPrices[m1] >= sweptPoolPrice)
wickZoneVolume += v;
}
return totalM1Volume <= 0 || (wickZoneVolume / totalM1Volume) >= MinM1WickVolumeShare;
}
private void RecordSpreadSample()
{
_rollingSpreads.Enqueue(Symbol.Spread);
if (_rollingSpreads.Count > 500)
_rollingSpreads.Dequeue();
}
private bool IsEcnSpreadHealthy()
{
if (_rollingSpreads.Count < 30)
return true;
double avgSpread = _rollingSpreads.Average();
return Symbol.Spread <= avgSpread * MaxSpreadMultiplier;
}
private List<Position> GetActiveBasket()
{
return Positions
.Where(p => p.SymbolName == SymbolName && p.Label == BotLabel)
.OrderBy(p => p.EntryTime)
.ToList();
}
private void FlattenBasket(string reason)
{
var basket = GetActiveBasket();
foreach (Position pos in basket)
ClosePosition(pos);
_isSeedBreakevenLocked = false;
Print("BASKET FLATTENED ({0} positions) | Reason: {1}", basket.Count, reason);
}
#endregion
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Most complex forex strategy
Why This Pyramiding Math Cannot Lose Core Equity After Phase 2
Most retail "scaling-in" systems fail because adding a larger second or third position raises the average entry price so close to current spot that a normal pullback wipes out the entire sequence for a net loss.
Look at how TryExecuteHouseMoneyPyramid() prevents that in Steps 4 through 6:
Ratchet First: Before firing Leg 2, the cBot proposes a new unified structural stop-loss (proposedStop) behind the most recent continuation pullback.
Compute Net Locked Dollars: It calculates the exact dollar profit that Leg 1 is guaranteed to bank at proposedStop, subtracting double ECN commission and swap
Most retail "scaling-in" systems fail because adding a larger second or third position raises the average entry price so close to current spot that a normal pullback wipes out the entire sequence for a net loss.
Look at how TryExecuteHouseMoneyPyramid() prevents that in Steps 4 through 6:
Ratchet First: Before firing Leg 2, the cBot proposes a new unified structural stop-loss (proposedStop) behind the most recent continuation pullback.
Compute Net Locked Dollars: It calculates the exact dollar profit that Leg 1 is guaranteed to bank at proposedStop, subtracting double ECN commission and swap
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.