Page 3 of 4

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:40 pm
by PTScalper
Copy and Check the Data (in OnTick):

Code: Select all

// Copy just the current candle (index 0, count 1)
if(CopyBuffer(dangerZoneHandle, 0, 0, 1, dangerBuffer) > 0) {
    if(dangerBuffer[0] != EMPTY_VALUE) {
        // A danger zone is active. Abort trading logic here.
        return;
    }
}

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:41 pm
by PTScalper
Because cTrader runs natively on the .NET framework, migrating this microstructure logic into C# is significantly cleaner than the MT5 version. You completely bypass the manual array binding, CopyBuffer wrangling, and pointer iterations required in MQL.

In cAlgo, you can pass the output series of one indicator (the ATR) directly into the constructor of another (the SMA) with a single line of code.

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:42 pm
by PTScalper
Here is the native C# translation for cTrader:

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class HolidayDangerZones : Indicator
    {
        // =============================================================================
        // INPUTS & TOGGLES
        // =============================================================================
        [Parameter("Christmas to New Year (Dec 24 - Jan 2)", Group = "Calendar Holidays", DefaultValue = true)]
        public bool ShowXmasNY { get; set; }

        [Parameter("Japanese Golden Week (Apr 29 - May 5)", Group = "Calendar Holidays", DefaultValue = true)]
        public bool ShowGolden { get; set; }

        [Parameter("US Thanksgiving & Friday", Group = "Calendar Holidays", DefaultValue = true)]
        public bool ShowUSThanks { get; set; }

        [Parameter("August Summer Drain", Group = "Calendar Holidays", DefaultValue = false)]
        public bool ShowAugust { get; set; }

        [Parameter("Enable Dynamic Vol Filter", Group = "Dynamic Volatility", DefaultValue = true)]
        public bool UseVolFilter { get; set; }

        [Parameter("ATR Length", Group = "Dynamic Volatility", DefaultValue = 14)]
        public int AtrLen { get; set; }

        [Parameter("Baseline Length (SMA of ATR)", Group = "Dynamic Volatility", DefaultValue = 100)]
        public int BaselineLen { get; set; }

        [Parameter("Vol Drop Threshold (%)", Group = "Dynamic Volatility", DefaultValue = 70.0)]
        public double DropThreshold { get; set; }

        [Parameter("Enable Spread Proxy", Group = "Spread Widening Proxy", DefaultValue = true)]
        public bool UseSpreadFilter { get; set; }

        [Parameter("Max Allowed Gap (% of ATR)", Group = "Spread Widening Proxy", DefaultValue = 15.0)]
        public double GapAtrPercent { get; set; }

        [Parameter("Max Body Size (% of Total)", Group = "Spread Widening Proxy", DefaultValue = 25.0)]
        public double BodyMaxPercent { get; set; }

        [Parameter("Xmas Color", Group = "Colors", DefaultValue = "Red")]
        public string ColorXmas { get; set; }

        [Parameter("Golden Week Color", Group = "Colors", DefaultValue = "Orange")]
        public string ColorGolden { get; set; }

        [Parameter("US Holiday Color", Group = "Colors", DefaultValue = "Blue")]
        public string ColorUS { get; set; }

        [Parameter("August Color", Group = "Colors", DefaultValue = "Purple")]
        public string ColorAug { get; set; }

        [Parameter("Volatility Drop Color", Group = "Colors", DefaultValue = "Gray")]
        public string ColorVol { get; set; }

        [Parameter("Spread Spike Color", Group = "Colors", DefaultValue = "Yellow")]
        public string ColorSpread { get; set; }

        // =============================================================================
        // OUTPUTS & INTERNAL VARIABLES
        // =============================================================================
        [Output("Danger Marker", LineColor = "Red", PlotType = PlotType.Points, Thickness = 4)]
        public IndicatorDataSeries DangerMarker { get; set; }

        private AverageTrueRange _atr;
        private SimpleMovingAverage _baselineAtr;

        protected override void Initialize()
        {
            // C# elegance: Pass the ATR directly into the SMA calculation
            _atr = Indicators.AverageTrueRange(AtrLen, MovingAverageType.Simple);
            _baselineAtr = Indicators.SimpleMovingAverage(_atr.Result, BaselineLen);
        }

        public override void Calculate(int index)
        {
            if (index < BaselineLen) return;

            double currentAtr = _atr.Result[index];
            double baselineAtr = _baselineAtr.Result[index];

            DateTime t = Bars.OpenTimes[index];
            int m = t.Month;
            int d = t.Day;
            DayOfWeek dow = t.DayOfWeek;

            // 1. Calendar Logic
            bool isXmasNY = ShowXmasNY && ((m == 12 && d >= 24) || (m == 1 && d <= 2));
            bool isGoldenWeek = ShowGolden && ((m == 4 && d >= 29) || (m == 5 && d <= 5));
            
            bool isThanksgiving = (m == 11 && dow == DayOfWeek.Thursday && d >= 22 && d <= 28);
            bool isBlackFriday = (m == 11 && dow == DayOfWeek.Friday && d >= 23 && d <= 29);
            bool isUSHoliday = ShowUSThanks && (isThanksgiving || isBlackFriday);
            
            bool isAugust = ShowAugust && (m == 8);

            // 2. Dynamic Volatility Logic
            bool isVolDead = UseVolFilter && (currentAtr < (baselineAtr * (DropThreshold / 100.0)));

            // 3. Spread Widening Proxy Logic
            double prevClose = index > 0 ? Bars.ClosePrices[index - 1] : Bars.OpenPrices[index];
            double gapSize = Math.Abs(Bars.OpenPrices[index] - prevClose);
            bool isGapSpike = UseSpreadFilter && (gapSize > (currentAtr * (GapAtrPercent / 100.0)));

            double candleRange = Bars.HighPrices[index] - Bars.LowPrices[index];
            double candleBody = Math.Abs(Bars.ClosePrices[index] - Bars.OpenPrices[index]);
            bool isWickSpike = UseSpreadFilter && (candleRange > (currentAtr * 0.8)) && (candleBody < (candleRange * (BodyMaxPercent / 100.0)));

            bool isSpreadSpike = isGapSpike || isWickSpike;

            // Master Filter
            bool isDangerZone = isXmasNY || isGoldenWeek || isUSHoliday || isAugust || isVolDead || isSpreadSpike;
            string objName = "DZ_VLine_" + index;

            if (isDangerZone)
            {
                // Plot cross slightly below the candle
                DangerMarker[index] = Bars.LowPrices[index] - (currentAtr * 0.5);

                // Determine hierarchy and apply alpha transparency (40 out of 255)
                Color bgColor = Color.Transparent;
                if (isXmasNY) bgColor = Color.FromArgb(40, Color.FromName(ColorXmas));
                else if (isGoldenWeek) bgColor = Color.FromArgb(40, Color.FromName(ColorGolden));
                else if (isUSHoliday) bgColor = Color.FromArgb(40, Color.FromName(ColorUS));
                else if (isAugust) bgColor = Color.FromArgb(40, Color.FromName(ColorAug));
                else if (isVolDead) bgColor = Color.FromArgb(40, Color.FromName(ColorVol));
                else if (isSpreadSpike) bgColor = Color.FromArgb(40, Color.FromName(ColorSpread));

                // Draw background block
                Chart.DrawVerticalLine(objName, t, bgColor, 5, LineStyle.Solid);
            }
            else
            {
                // Clear state
                DangerMarker[index] = double.NaN;
                Chart.RemoveObject(objName);
            }
        }
    }
}

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:42 pm
by PTScalper
Implementing in a cBot

Integrating this into your automated cAlgo execution scripts is highly intuitive compared to MT5. In your cBot's OnStart() method, you initialize the indicator, and in your OnBar() or OnTick() logic, you simply check if the current marker is NaN.

Code: Select all

private HolidayDangerZones _dangerZoneIndicator;

protected override void OnStart()
{
    // Initialize with default parameters
    _dangerZoneIndicator = Indicators.GetIndicator<HolidayDangerZones>(
        true, true, true, false, true, 14, 100, 70.0, true, 15.0, 25.0, 
        "Red", "Orange", "Blue", "Purple", "Gray", "Yellow"
    );
}

protected override void OnBar()
{
    // If the marker is a valid number, we are in a danger zone. Hard reject.
    if (!double.IsNaN(_dangerZoneIndicator.DangerMarker.Last(0)))
    {
        Print("Liquidity drain detected. Execution paused.");
        return; 
    }
    
    // ... proceed with price action structure checks ...
}

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:43 pm
by PTScalper
For an enterprise-grade cTrader deployment, the previous script works but commits several cardinal sins for high-frequency execution. Calling reflection methods like Color.FromName() and performing floating-point division (/ 100.0) inside the Calculate loop creates unnecessary Garbage Collection (GC) pressure and CPU overhead.

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:43 pm
by PTScalper
This refactored version treats the indicator like a production microservice. It implements:

Pre-computed Constants: Percentage divisions are converted to decimal multipliers during Initialize() to eliminate floating-point division on every tick.

Color Caching: String-to-Color reflection and Alpha channel blending are executed exactly once on startup.

Bitwise State Machine: Instead of a tangled chain of if-else booleans, the script uses a [Flags] enum. This allows the logic to evaluate multiple overlapping states simultaneously and resolve the highest-priority visual hierarchy in O(1) time.

Single Responsibility Principle (SRP): The monolithic loop is broken down into pure, isolated evaluation methods.

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:43 pm
by PTScalper
Ctrader version: 2.00

Code: Select all

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

namespace cAlgo
{
    // 1. BITWISE ENUMERATION FOR STATE HIERARCHY
    [Flags]
    public enum DangerState
    {
        None = 0,
        SpreadSpike = 1 << 0,       // Lowest priority visual
        VolatilityDrop = 1 << 1,
        AugustDrain = 1 << 2,
        USHoliday = 1 << 3,
        GoldenWeek = 1 << 4,
        ChristmasNewYear = 1 << 5   // Highest priority visual
    }

    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class HolidayDangerZonesPro : Indicator
    {
        // =============================================================================
        // INPUTS
        // =============================================================================
        [Parameter("Christmas/NY", Group = "Calendar", DefaultValue = true)]
        public bool ShowXmasNY { get; set; }

        [Parameter("Golden Week", Group = "Calendar", DefaultValue = true)]
        public bool ShowGolden { get; set; }

        [Parameter("Thanksgiving", Group = "Calendar", DefaultValue = true)]
        public bool ShowUSThanks { get; set; }

        [Parameter("August Drain", Group = "Calendar", DefaultValue = false)]
        public bool ShowAugust { get; set; }

        [Parameter("Enable Vol Filter", Group = "Microstructure", DefaultValue = true)]
        public bool UseVolFilter { get; set; }

        [Parameter("ATR Length", Group = "Microstructure", DefaultValue = 14)]
        public int AtrLen { get; set; }

        [Parameter("Baseline SMA Length", Group = "Microstructure", DefaultValue = 100)]
        public int BaselineLen { get; set; }

        [Parameter("Vol Drop Threshold (%)", Group = "Microstructure", DefaultValue = 70.0)]
        public double DropThresholdPct { get; set; }

        [Parameter("Enable Spread Proxy", Group = "Microstructure", DefaultValue = true)]
        public bool UseSpreadFilter { get; set; }

        [Parameter("Max Gap (% of ATR)", Group = "Microstructure", DefaultValue = 15.0)]
        public double GapAtrPct { get; set; }

        [Parameter("Max Body Size (% of Total)", Group = "Microstructure", DefaultValue = 25.0)]
        public double BodyMaxPct { get; set; }

        [Parameter("Alpha Transparency", Group = "UI Optimization", DefaultValue = 40, MinValue = 10, MaxValue = 255)]
        public int UiAlpha { get; set; }

        [Output("Danger Marker", LineColor = "Red", PlotType = PlotType.Points, Thickness = 4)]
        public IndicatorDataSeries DangerMarker { get; set; }

        // =============================================================================
        // CACHED OPTIMIZATIONS
        // =============================================================================
        private AverageTrueRange _atr;
        private SimpleMovingAverage _baselineAtr;

        private double _volDropMultiplier;
        private double _gapMultiplier;
        private double _bodyMultiplier;

        private Color _colXmas, _colGolden, _colUS, _colAug, _colVol, _colSpread;

        protected override void Initialize()
        {
            // Indicator Piping
            _atr = Indicators.AverageTrueRange(AtrLen, MovingAverageType.Simple);
            _baselineAtr = Indicators.SimpleMovingAverage(_atr.Result, BaselineLen);

            // Pre-compute multipliers to avoid division in the tick loop
            _volDropMultiplier = DropThresholdPct * 0.01;
            _gapMultiplier = GapAtrPct * 0.01;
            _bodyMultiplier = BodyMaxPct * 0.01;

            // Cache colors & alpha channels once (eliminates GC allocs from reflection)
            _colXmas = Color.FromArgb(UiAlpha, Color.Red);
            _colGolden = Color.FromArgb(UiAlpha, Color.Orange);
            _colUS = Color.FromArgb(UiAlpha, Color.Blue);
            _colAug = Color.FromArgb(UiAlpha, Color.Purple);
            _colVol = Color.FromArgb(UiAlpha, Color.Gray);
            _colSpread = Color.FromArgb(UiAlpha, Color.Yellow);
        }

        public override void Calculate(int index)
        {
            if (index < BaselineLen) return;

            // 2. STATE RESOLUTION
            DangerState currentState = EvaluateCalendar(index) | EvaluateMicrostructure(index);

            string objName = $"DZ_{index}";

            if (currentState != DangerState.None)
            {
                DangerMarker[index] = Bars.LowPrices[index] - (_atr.Result[index] * 0.5);
                Chart.DrawVerticalLine(objName, Bars.OpenTimes[index], ResolveHighestPriorityColor(currentState), 5, LineStyle.Solid);
            }
            else
            {
                DangerMarker[index] = double.NaN;
                
                // Only attempt removal if we assume an object might exist 
                // (cTrader ignores invalid keys gracefully, but skipping this saves cycles)
                Chart.RemoveObject(objName); 
            }
        }

        // =============================================================================
        // ISOLATED EVALUATION METHODS
        // =============================================================================
        private DangerState EvaluateCalendar(int index)
        {
            DateTime t = Bars.OpenTimes[index];
            DangerState state = DangerState.None;

            if (ShowXmasNY && ((t.Month == 12 && t.Day >= 24) || (t.Month == 1 && t.Day <= 2)))
                state |= DangerState.ChristmasNewYear;

            if (ShowGolden && ((t.Month == 4 && t.Day >= 29) || (t.Month == 5 && t.Day <= 5)))
                state |= DangerState.GoldenWeek;

            if (ShowUSThanks && t.Month == 11)
            {
                bool isThanksgiving = t.DayOfWeek == DayOfWeek.Thursday && t.Day >= 22 && t.Day <= 28;
                bool isBlackFriday = t.DayOfWeek == DayOfWeek.Friday && t.Day >= 23 && t.Day <= 29;
                if (isThanksgiving || isBlackFriday)
                    state |= DangerState.USHoliday;
            }

            if (ShowAugust && t.Month == 8)
                state |= DangerState.AugustDrain;

            return state;
        }

        private DangerState EvaluateMicrostructure(int index)
        {
            DangerState state = DangerState.None;
            double currentAtr = _atr.Result[index];

            if (UseVolFilter && currentAtr < (_baselineAtr.Result[index] * _volDropMultiplier))
                state |= DangerState.VolatilityDrop;

            if (UseSpreadFilter)
            {
                double prevClose = index > 0 ? Bars.ClosePrices[index - 1] : Bars.OpenPrices[index];
                double gapSize = Math.Abs(Bars.OpenPrices[index] - prevClose);
                
                double candleRange = Bars.HighPrices[index] - Bars.LowPrices[index];
                double candleBody = Math.Abs(Bars.ClosePrices[index] - Bars.OpenPrices[index]);

                bool isGap = gapSize > (currentAtr * _gapMultiplier);
                bool isWick = (candleRange > (currentAtr * 0.8)) && (candleBody < (candleRange * _bodyMultiplier));

                if (isGap || isWick)
                    state |= DangerState.SpreadSpike;
            }

            return state;
        }

        // 3. O(1) HIERARCHICAL RESOLUTION
        private Color ResolveHighestPriorityColor(DangerState state)
        {
            // Evaluates from highest flag priority down to lowest
            if (state.HasFlag(DangerState.ChristmasNewYear)) return _colXmas;
            if (state.HasFlag(DangerState.GoldenWeek))       return _colGolden;
            if (state.HasFlag(DangerState.USHoliday))        return _colUS;
            if (state.HasFlag(DangerState.AugustDrain))      return _colAug;
            if (state.HasFlag(DangerState.VolatilityDrop))   return _colVol;
            
            return _colSpread; // SpreadSpike is the only remaining valid state
        }
    }
}

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:44 pm
by PTScalper
Because cTrader's native IndicatorDataSeries can only store double values, you cannot directly bind an enum to a chart output. To expose the exact bitwise state to your cBot, you must cache the evaluated state into a managed C# array inside the indicator, and then expose a public getter method.

Here is how to modify the indicator and build the cBot logic to unpack those bitwise flags.

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:44 pm
by PTScalper
1. Modifying the Indicator

Add a private array to store the states and a public GetState() method. Because cTrader feeds real-time ticks, the total bar count dynamically grows, so we must safely resize the array when a new candle forms.

Add this to HolidayDangerZonesPro.cs:

Code: Select all

// Inside the HolidayDangerZonesPro class:

// 1. Declare a private array to cache the states
private DangerState[] _stateCache;

protected override void Initialize()
{
    // ... (previous initialization code) ...

    // Initialize the array with a buffer to handle current historical bars + future bars
    _stateCache = new DangerState[Bars.Count + 1000];
}

public override void Calculate(int index)
{
    if (index < BaselineLen) return;

    // Safely resize the cache if the live market generates enough new bars to exceed the buffer
    if (index >= _stateCache.Length)
    {
        Array.Resize(ref _stateCache, _stateCache.Length + 1000);
    }

    DangerState currentState = EvaluateCalendar(index) | EvaluateMicrostructure(index);
    
    // Cache the state for the cBot to read
    _stateCache[index] = currentState;

    string objName = $"DZ_{index}";

    if (currentState != DangerState.None)
    {
        DangerMarker[index] = Bars.LowPrices[index] - (_atr.Result[index] * 0.5);
        Chart.DrawVerticalLine(objName, Bars.OpenTimes[index], ResolveHighestPriorityColor(currentState), 5, LineStyle.Solid);
    }
    else
    {
        DangerMarker[index] = double.NaN;
        Chart.RemoveObject(objName); 
    }
}

// 2. Expose the public getter for the cBot
public DangerState GetState(int index)
{
    if (index < 0 || index >= _stateCache.Length) 
        return DangerState.None;
        
    return _stateCache[index];
}

Re: Golden Week and Thanksgiving: Holiday-Specific Liquidity Patterns

Posted: Wed Oct 07, 2026 6:45 pm
by PTScalper
2. Reading the Flags in the cBot

To use the indicator in your cBot, you must first click Manage References in the cBot editor and check the box next to your HolidayDangerZonesPro indicator.

Once referenced, you can use the .HasFlag() method to build highly specific execution logic. For example, you might decide to completely halt trading during Christmas, but only tighten your stop-loss during a generic Volatility Drop.

Inside your cBot:

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Indicators;
// Ensure you have a using statement for the indicator's namespace if it differs

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class LiquidityAwareBot : Robot
    {
        private HolidayDangerZonesPro _dangerZone;

        protected override void OnStart()
        {
            // Initialize the indicator
            _dangerZone = Indicators.GetIndicator<HolidayDangerZonesPro>(
                true, true, true, false, true, 14, 100, 70.0, true, 15.0, 25.0, 40);
        }

        protected override void OnBar()
        {
            int currentIndex = Bars.Count - 1;
            
            // Fetch the exact state from our public method
            DangerState currentState = _dangerZone.GetState(currentIndex);

            // If it's a completely normal market, proceed as usual
            if (currentState == DangerState.None)
            {
                ExecuteStandardStrategy();
                return;
            }

            // --- GRANULAR BITWISE FILTERING ---

            // 1. Hard Rejects (Do not trade under any circumstances)
            if (currentState.HasFlag(DangerState.SpreadSpike))
            {
                Print("Spread Spike / Whipsaw detected. Suspending entries.");
                return; 
            }

            if (currentState.HasFlag(DangerState.ChristmasNewYear) || currentState.HasFlag(DangerState.GoldenWeek))
            {
                Print("Major Bank Holiday detected. Tier-1 Liquidity is offline.");
                CloseAllOpenPositions(); // Defensive action
                return;
            }

            // 2. Soft Rejects / Strategy Adjustments
            if (currentState.HasFlag(DangerState.VolatilityDrop))
            {
                Print("Volatility has dropped below baseline. Switching to range-bound strategy.");
                ExecuteRangeStrategy();
                return;
            }
        }

        private void ExecuteStandardStrategy()
        {
            // Standard breakout/trend logic goes here
        }

        private void ExecuteRangeStrategy()
        {
            // Mean-reversion logic with tighter profit targets goes here
        }
        
        private void CloseAllOpenPositions()
        {
            foreach (var position in Positions)
            {
                ClosePosition(position);
            }
        }
    }
}