Page 2 of 2
Re: Recognizing wide-to-tight spread transition on EURUSD M1
Posted: Wed Sep 23, 2026 6:48 pm
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.
Re: Recognizing wide-to-tight spread transition on EURUSD M1
Posted: Wed Sep 23, 2026 6:49 pm
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];
}
}
}
}
}
Re: Recognizing wide-to-tight spread transition on EURUSD M1
Posted: Wed Sep 23, 2026 6:49 pm
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.
Re: Recognizing wide-to-tight spread transition on EURUSD M1
Posted: Wed Sep 23, 2026 6:49 pm
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.
Re: Recognizing wide-to-tight spread transition on EURUSD M1
Posted: Wed Sep 23, 2026 6:49 pm
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);
}
}
}
Re: Recognizing wide-to-tight spread transition on EURUSD M1
Posted: Wed Sep 23, 2026 6:50 pm
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.