Advertisement IC Markets

Recognizing wide-to-tight spread transition on EURUSD M1

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Recognizing wide-to-tight spread transition on EURUSD M1

Post by PTScalper »

To make this indicator "professional grade" for cTrader, we need to graduate from basic points to a robust, optimized architecture.

A pro-level cAlgo indicator does three things differently:

1.) $O(1)$ Optimization: Instead of running a nested loop backward on every single tick (which eats CPU during fast markets), it uses an internal buffer to track the state progressively.

2.) Advanced Visuals: It replaces crude dots with clean chart icons and text overlays that exactly mimic your TradingView setup.

3.) Alerts & Native API: It utilizes cTrader’s native TrueRange engine and adds non-spamming alerts for when you step away from the desk.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Recognizing wide-to-tight spread transition on EURUSD M1

Post by PTScalper »

Here is the professional C# build:

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 ProSpreadTransitionFilter : Indicator
    {
        [Parameter("Normal Band Max (Pips)", DefaultValue = 2.0, Group = "Filter Logic", Step = 0.1)]
        public double NormalBandPips { get; set; }

        [Parameter("Measured Tight Period (Bars)", DefaultValue = 3, MinValue = 1, Group = "Filter Logic")]
        public int SustainedBars { get; set; }

        [Parameter("Enable Audio Alert on Clear", DefaultValue = false, Group = "Alerts")]
        public bool EnableAlerts { get; set; }

        private IndicatorDataSeries _tightCountBuffer;
        private TrueRange _trueRange;
        private DateTime _lastAlertTime;

        protected override void Initialize()
        {
            // Initialize an internal buffer to track state efficiently (O(1) memory)
            _tightCountBuffer = CreateDataSeries();
            
            // Utilize cTrader's highly optimized native engine instead of manual math
            _trueRange = Indicators.TrueRange();
        }

        public override void Calculate(int index)
        {
            if (index < 1)
            {
                _tightCountBuffer[index] = 0;
                return;
            }

            double normalBand = NormalBandPips * Symbol.PipSize;
            double tr = _trueRange.Result[index];

            // Core Logic: O(1) State Tracking
            // If the bar is tight, add 1 to the previous bar's count. If wide, reset to 0.
            if (tr <= normalBand)
            {
                _tightCountBuffer[index] = _tightCountBuffer[index - 1] + 1;
            }
            else
            {
                _tightCountBuffer[index] = 0;
            }

            int currentCount = (int)_tightCountBuffer[index];

            // Render Visuals
            string objFirstArrow = "FirstArr_" + index;
            string objFirstText = "FirstTxt_" + index;
            string objClearArrow = "ClearArr_" + index;
            string objClearText = "ClearTxt_" + index;

            if (currentCount == 1)
            {
                double drawPrice = Bars.HighPrices[index] + (2 * Symbol.PipSize);
                
                // Draw clean downward arrow and label
                Chart.DrawIcon(objFirstArrow, ChartIconType.DownArrow, index, drawPrice, Color.DarkOrange);
                Chart.DrawText(objFirstText, "\n 1/2", index, drawPrice, Color.DarkOrange);
            }
            else if (currentCount == SustainedBars)
            {
                double drawPrice = Bars.LowPrices[index] - (2 * Symbol.PipSize);
                
                // Draw clean upward arrow and label
                Chart.DrawIcon(objClearArrow, ChartIconType.UpArrow, index, drawPrice, Color.LimeGreen);
                Chart.DrawText(objClearText, "Clear \n", index, drawPrice, Color.LimeGreen);

                // Anti-Spam Alert Engine (Fires only once per bar on the live edge)
                if (IsLastBar && EnableAlerts && Bars.OpenTimes[index] != _lastAlertTime)
                {
                    Notifications.PlaySound(SoundType.Ui);
                    Print($"[{Symbol.Name}] Spread Filter CLEAR: Normal costs restored.");
                    _lastAlertTime = Bars.OpenTimes[index];
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Recognizing wide-to-tight spread transition on EURUSD M1

Post by PTScalper »

Pro Upgrades Explained:

IndicatorDataSeries _tightCountBuffer: By caching the count in a hidden buffer, the indicator calculates the current state by just looking at the previous candle. This makes execution instant, even with thousands of bars loaded.

Chart.DrawIcon & Chart.DrawText: You will now get precise arrows on the wicks with exact "1/2" and "Clear" text overlaid, heavily mimicking Pine Script's plotshape(text=...).

Indicators.TrueRange(): Handing the math off to cTrader's core API is more stable during erratic news slippage than calculating Math.Abs(Close - High) manually.

Non-Repainting Alert: If you turn on EnableAlerts, it plays a UI chime and logs to your cTrader journal when you get the "Clear" signal, locked to _lastAlertTime so a flickering live bar doesn't blast you with 50 sound alerts.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Recognizing wide-to-tight spread transition on EURUSD M1

Post by PTScalper »

To take this to an institutional level, we need to introduce State Machine architecture, Dynamic UI rendering, and Bar Coloring.

In professional algorithmic trading environments, you don't want to scan for small arrows—you want the chart to instantly communicate the tape's condition, and you want the code to be strictly categorized by states to prevent logic leaks.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Recognizing wide-to-tight spread transition on EURUSD M1

Post by PTScalper »

Here is the ultimate pro-grade cTrader build:

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 InstitutionalSpreadFilter : Indicator
    {
        [Parameter("Normal Band Max (Pips)", DefaultValue = 2.0, Group = "Volatility Thresholds", Step = 0.1)]
        public double NormalBandPips { get; set; }

        [Parameter("Measured Tight Period (Bars)", DefaultValue = 3, MinValue = 1, Group = "Volatility Thresholds")]
        public int SustainedBars { get; set; }

        [Parameter("Paint Candles", DefaultValue = true, Group = "Visuals")]
        public bool PaintCandles { get; set; }

        [Parameter("Show Live HUD", DefaultValue = true, Group = "Visuals")]
        public bool ShowHud { get; set; }

        [Parameter("Alert on Clear", DefaultValue = false, Group = "Alerts")]
        public bool EnableAlerts { get; set; }

        // Pro Upgrade: Strict State Machine Enums instead of relying on magic numbers
        public enum TapeState
        {
            Untradeable,
            HalfSize,
            Clear
        }

        private IndicatorDataSeries _tightCountBuffer;
        private TrueRange _trueRange;
        private DateTime _lastAlertTime;

        protected override void Initialize()
        {
            _tightCountBuffer = CreateDataSeries();
            _trueRange = Indicators.TrueRange();
        }

        public override void Calculate(int index)
        {
            if (index < 1)
            {
                _tightCountBuffer[index] = 0;
                return;
            }

            double normalBand = NormalBandPips * Symbol.PipSize;
            double tr = _trueRange.Result[index];

            // 1. O(1) Memory Tracking
            _tightCountBuffer[index] = (tr <= normalBand) ? _tightCountBuffer[index - 1] + 1 : 0;
            int currentCount = (int)_tightCountBuffer[index];

            // 2. Evaluate State Machine
            TapeState state = TapeState.Untradeable;
            if (currentCount > 0 && currentCount < SustainedBars)
                state = TapeState.HalfSize;
            else if (currentCount >= SustainedBars)
                state = TapeState.Clear;

            // 3. Candle Painting (Replaces TradingView's bgcolor)
            if (PaintCandles)
            {
                Color barColor = state == TapeState.Untradeable ? Color.FromArgb(180, Color.Crimson) :
                                 state == TapeState.HalfSize ? Color.FromArgb(180, Color.DarkOrange) :
                                 Color.FromArgb(180, Color.SeaGreen);
                
                Chart.SetBarColor(index, barColor);
            }

            // 4. Precision Markers (Only drawn on transition bars)
            string objPrefix = "TapeFilter_";
            if (currentCount == 1)
            {
                double drawPrice = Bars.HighPrices[index] + (2 * Symbol.PipSize);
                Chart.DrawIcon(objPrefix + "Icon_" + index, ChartIconType.DownArrow, index, drawPrice, Color.DarkOrange);
                Chart.DrawText(objPrefix + "Txt_" + index, "\n 1/2", index, drawPrice, Color.DarkOrange);
            }
            else if (currentCount == SustainedBars)
            {
                double drawPrice = Bars.LowPrices[index] - (2 * Symbol.PipSize);
                Chart.DrawIcon(objPrefix + "Icon_" + index, ChartIconType.UpArrow, index, drawPrice, Color.LimeGreen);
                Chart.DrawText(objPrefix + "Txt_" + index, "Clear \n", index, drawPrice, Color.LimeGreen);

                // 5. Anti-Spam Alerts
                if (IsLastBar && EnableAlerts && Bars.OpenTimes[index] != _lastAlertTime)
                {
                    Notifications.PlaySound(SoundType.Ui);
                    Print($"[{Symbol.Name}] Tape Clear: Sustained tight cluster confirmed.");
                    _lastAlertTime = Bars.OpenTimes[index];
                }
            }

            // 6. Live HUD Engine
            if (IsLastBar && ShowHud)
            {
                UpdateHud(state, currentCount);
            }
        }

        private void UpdateHud(TapeState state, int count)
        {
            string hudText = $"TAPE STATE: ";
            Color hudColor;

            switch (state)
            {
                case TapeState.Untradeable:
                    hudText += $"WIDE (Untradeable)\nTight Bars: 0 / {SustainedBars}";
                    hudColor = Color.Crimson;
                    break;
                case TapeState.HalfSize:
                    hudText += $"WAITING (Half-Size)\nTight Bars: {count} / {SustainedBars}";
                    hudColor = Color.DarkOrange;
                    break;
                default:
                    hudText += $"CLEAR (Normal Size)\nTight Bars: {count}+";
                    hudColor = Color.SeaGreen;
                    break;
            }

            // Draws a static, non-flickering text block in the top right of the chart
            Chart.DrawStaticText("TapeHUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, hudColor);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Recognizing wide-to-tight spread transition on EURUSD M1

Post by PTScalper »

What makes this "Pro":

Bar Painting: Instead of just plotting arrows, it dynamically overrides the color of the candlesticks themselves (Crimson, DarkOrange, SeaGreen). This replicates Pine Script's background coloring in a much cleaner way, allowing you to instantly assess the tape state in your peripheral vision.

On-Chart HUD (Heads-Up Display): Adds a Chart.DrawStaticText block to the top right of your chart. It updates live on every tick, telling you exactly what state the tape is in and how many tight bars have printed so far. No guessing required.

State Machine Architecture: Moving from if/else integers to enum TapeState is how institutional quants structure execution logic. If you decide to build an automated cBot later, you simply import this indicator and check Indicator.TapeState == TapeState.Clear before allowing the bot to fire an entry.

Opacity Handling: The colors use Color.FromArgb(180, Color) to apply a slight transparency, ensuring your wicks and overlays remain readable beneath the painted bars.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply