Page 3 of 3

Re: Using MAE to Test Whether Your Stop Is the Right Size

Posted: Tue Oct 06, 2026 11:24 am
by PTScalper
Ctrader version 1.0

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 LiquiditySweep : Indicator
    {
        [Parameter("Pivot Left Bars", Group = "Structure", DefaultValue = 5, MinValue = 1)]
        public int LeftBars { get; set; }

        [Parameter("Pivot Right Bars", Group = "Structure", DefaultValue = 5, MinValue = 1)]
        public int RightBars { get; set; }

        // State variables to track the most recent structural liquidity pools
        private double _lastHigh = double.NaN;
        private double _lastLow = double.NaN;
        private int _lastHighIndex = -1;
        private int _lastLowIndex = -1;

        protected override void Initialize()
        {
            // Initialization logic (chart is clean by default)
        }

        public override void Calculate(int index)
        {
            // Ensure enough data is present to calculate lookbacks
            if (index < LeftBars + RightBars)
                return;

            // 1. Identify structural pivots historically at the (index - RightBars) shift
            int shiftIndex = index - RightBars;
            EvaluatePivot(shiftIndex);

            // 2. Sweep Logic evaluating on the current forming candle
            double currentHigh = Bars.High[index];
            double currentLow = Bars.Low[index];
            double currentClose = Bars.Close[index];
            
            // Calculate a dynamic gap to keep arrows from overlapping the candlestick wick
            double atrOffset = (currentHigh - currentLow) * 0.2; 

            // Bearish Sweep: Grab liquidity above the structural high, close below
            string bearSweepName = "BearSweep_" + index;
            if (!double.IsNaN(_lastHigh) && currentHigh > _lastHigh && currentClose < _lastHigh)
            {
                Chart.DrawIcon(bearSweepName, ChartIconType.DownArrow, index, currentHigh + atrOffset, Color.Red);
            }
            else
            {
                // If conditions invalidate mid-tick (e.g., candle closes above the level), erase the arrow
                Chart.RemoveObject(bearSweepName);
            }

            // Bullish Sweep: Grab liquidity below the structural low, close above
            string bullSweepName = "BullSweep_" + index;
            if (!double.IsNaN(_lastLow) && currentLow < _lastLow && currentClose > _lastLow)
            {
                Chart.DrawIcon(bullSweepName, ChartIconType.UpArrow, index, currentLow - atrOffset, Color.LimeGreen);
            }
            else
            {
                // Erase mid-tick if invalidated
                Chart.RemoveObject(bullSweepName);
            }

            // 3. Extend projection dashed lines dynamically to the current bar
            if (!double.IsNaN(_lastHigh))
            {
                Chart.DrawTrendLine("LiqHighLine", _lastHighIndex, _lastHigh, index, _lastHigh, Color.Red, 1, LineStyle.Lines);
            }

            if (!double.IsNaN(_lastLow))
            {
                Chart.DrawTrendLine("LiqLowLine", _lastLowIndex, _lastLow, index, _lastLow, Color.LimeGreen, 1, LineStyle.Lines);
            }
        }

        private void EvaluatePivot(int shiftIndex)
        {
            bool isHigh = true;
            bool isLow = true;

            double shiftHigh = Bars.High[shiftIndex];
            double shiftLow = Bars.Low[shiftIndex];

            // Check Left Side
            for (int i = 1; i <= LeftBars; i++)
            {
                if (Bars.High[shiftIndex - i] > shiftHigh) isHigh = false;
                if (Bars.Low[shiftIndex - i] < shiftLow) isLow = false;
            }

            // Check Right Side
            for (int i = 1; i <= RightBars; i++)
            {
                if (Bars.High[shiftIndex + i] >= shiftHigh) isHigh = false;
                if (Bars.Low[shiftIndex + i] <= shiftLow) isLow = false;
            }

            // Register Swing High
            if (isHigh)
            {
                _lastHigh = shiftHigh;
                _lastHighIndex = shiftIndex;
                Chart.DrawIcon("PivotHigh_" + shiftIndex, ChartIconType.DownTriangle, shiftIndex, shiftHigh, Color.Gray);
            }

            // Register Swing Low
            if (isLow)
            {
                _lastLow = shiftLow;
                _lastLowIndex = shiftIndex;
                Chart.DrawIcon("PivotLow_" + shiftIndex, ChartIconType.UpTriangle, shiftIndex, shiftLow, Color.Gray);
            }
        }
    }
}

Re: Using MAE to Test Whether Your Stop Is the Right Size

Posted: Tue Oct 06, 2026 11:25 am
by PTScalper
Key cTrader/C# Translation Differences:

Chart.RemoveObject() over Buffer Resets: Because Calculate(int index) executes on every incoming tick for the current live forming bar, we simply assign a unique ID (BearSweep_ + index) to the arrow. If the candle wicks past the high but later breaks structure and pushes its Close above the level, the else block fires and removes the false-trigger arrow instantly.

Modern Bars API: It uses Bars.High[index] instead of the older MarketSeries syntax, adhering to current cTrader standards.

LineStyle.Lines: This renders the projected liquidity pool lines as a clean dashed line matching the style of the MT4/MT5 versions. As the index increments forward, the DrawTrendLine command simply overwrites the line ending, stretching it cleanly across the chart without duplicating objects.