Because cTrader is built on modern C#/.NET, migrating this logic into a cBot is significantly cleaner than in MQL. The cAlgo API manages historical data collections natively via the Bars interface, which means you do not have to write manual array-copying functions or manage memory allocation on every tick.
Here is the institutional-grade cBot execution architecture. It utilizes an event-driven cache that completely ignores the executing timeframe's ticks unless the Higher Timeframe (HTF) bar has structurally closed.
The Self-Adjusting Trendline: Kaufman’s Adaptive Moving Average (KAMA) + MT4/MT5 Code
Re: The Self-Adjusting Trendline: Kaufman’s Adaptive Moving Average (KAMA) + MT4/MT5 Code
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The Self-Adjusting Trendline: Kaufman’s Adaptive Moving Average (KAMA) + MT4/MT5 Code
Ctrader version 1.00
Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
public enum RegimeState
{
Chop,
Transition,
BullTrend,
BearTrend,
WarmingUp // Failsafe state during initialization
}
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class InstMTFERRegimeBot : Robot
{
[Parameter("HTF Vector", DefaultValue = "Minute15", Group = "MTF Configuration")]
public TimeFrame Htf { get; set; }
[Parameter("ER Lookback", DefaultValue = 14, MinValue = 1, Group = "MTF Configuration")]
public int Lookback { get; set; }
[Parameter("Trend Floor (Min)", DefaultValue = 0.40, Group = "Regime Thresholds")]
public double TrendThreshold { get; set; }
[Parameter("Chop Ceiling (Max)", DefaultValue = 0.25, Group = "Regime Thresholds")]
public double ChopThreshold { get; set; }
// Core data structures
private Bars _htfBars;
private DateTime _lastHtfOpenTime;
private RegimeState _currentRegime = RegimeState.WarmingUp;
protected override void OnStart()
{
// 1. Subscribe to the HTF data series natively
_htfBars = MarketData.GetBars(Htf);
// 2. Force an initial calculation before the first tick
UpdateRegimeState();
}
protected override void OnTick()
{
// 3. Cache Check: Only spend CPU cycles if the HTF bar has actually rolled over
if (_htfBars.OpenTimes.LastValue != _lastHtfOpenTime)
{
UpdateRegimeState();
}
// 4. Playbook Execution Logic
if (_currentRegime == RegimeState.Chop || _currentRegime == RegimeState.WarmingUp)
{
// Lockdown: Stand aside or execute strict mean-reversion
return;
}
if (_currentRegime == RegimeState.BullTrend)
{
// Run your M1 price action / long pullback entry logic here
// if (IsBullishPullbackOver()) ExecuteMarketOrder(TradeType.Buy...);
}
else if (_currentRegime == RegimeState.BearTrend)
{
// Run your M1 price action / short pullback entry logic here
// if (IsBearishPullbackOver()) ExecuteMarketOrder(TradeType.Sell...);
}
}
private void UpdateRegimeState()
{
// _htfBars.Count - 1 is the currently forming (repainting) bar.
// _htfBars.Count - 2 is the last fully closed, strictly non-repainting bar.
int closedBarIndex = _htfBars.Count - 2;
// Failsafe: Ensure enough history exists on the chart
if (closedBarIndex - Lookback < 0)
{
_currentRegime = RegimeState.WarmingUp;
return;
}
double currentClose = _htfBars.ClosePrices[closedBarIndex];
double oldClose = _htfBars.ClosePrices[closedBarIndex - Lookback];
double netChange = currentClose - oldClose;
double absChange = Math.Abs(netChange);
double volatility = 0.0;
// Accumulate bar-to-bar volatility using the closed bar as the anchor
for (int i = 0; i < Lookback; i++)
{
volatility += Math.Abs(_htfBars.ClosePrices[closedBarIndex - i] - _htfBars.ClosePrices[closedBarIndex - i - 1]);
}
// Guard against division by zero in dead markets
double er = volatility == 0 ? 0 : absChange / volatility;
// State Routing
if (er < ChopThreshold)
{
_currentRegime = RegimeState.Chop;
}
else if (er >= TrendThreshold)
{
_currentRegime = netChange > 0 ? RegimeState.BullTrend : RegimeState.BearTrend;
}
else
{
_currentRegime = RegimeState.Transition;
}
// Update the cache time on successful calculation
_lastHtfOpenTime = _htfBars.OpenTimes.LastValue;
// Optional telemetry for backtesting logs
// Print($"New HTF Bar. ER: {Math.Round(er, 2)} | Regime: {_currentRegime}");
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The Self-Adjusting Trendline: Kaufman’s Adaptive Moving Average (KAMA) + MT4/MT5 Code
Architectural Advantages in cAlgo
Memory Efficiency via Native Collections: In MQL, checking Multi-Timeframe data requires declaring double arrays and running CopyClose on every trigger. cTrader automatically updates the _htfBars collection asynchronously in the background. Your code merely reads the indexed properties (_htfBars.ClosePrices), resulting in near-zero memory allocation during runtime.
Immutable State Locking: By anchoring the calculation strictly to _htfBars.Count - 2, the UpdateRegimeState() method isolates your execution logic from the live tick. No matter how violently the current 15-minute candle whips around, the ER is locked to the structural reality of the previous 15 minutes.
The WarmingUp Failsafe: cTrader environments can sometimes initialize cBots before the full historical timeframe data is populated from the broker's server. The WarmingUp state guarantees the bot will stand aside rather than crashing with an IndexOutOfRangeException if the HTF data is lagging.
Memory Efficiency via Native Collections: In MQL, checking Multi-Timeframe data requires declaring double arrays and running CopyClose on every trigger. cTrader automatically updates the _htfBars collection asynchronously in the background. Your code merely reads the indexed properties (_htfBars.ClosePrices), resulting in near-zero memory allocation during runtime.
Immutable State Locking: By anchoring the calculation strictly to _htfBars.Count - 2, the UpdateRegimeState() method isolates your execution logic from the live tick. No matter how violently the current 15-minute candle whips around, the ER is locked to the structural reality of the previous 15 minutes.
The WarmingUp Failsafe: cTrader environments can sometimes initialize cBots before the full historical timeframe data is populated from the broker's server. The WarmingUp state guarantees the bot will stand aside rather than crashing with an IndexOutOfRangeException if the HTF data is lagging.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The Self-Adjusting Trendline: Kaufman’s Adaptive Moving Average (KAMA) + MT4/MT5 Code
To bring this up to enterprise C# standards for a production cBot, we must eliminate the OnTick evaluation loop entirely, decouple the mathematical engine from the trade execution logic, and implement the EMA smoothing filter from the Pine Script version to prevent micro-flicking.
This architecture leverages Event-Driven Execution (BarOpened events), Dependency Isolation (separating the indicator logic from the Robot class), and includes a WPF-style UI Dashboard using cTrader’s native canvas controls.
This architecture leverages Event-Driven Execution (BarOpened events), Dependency Isolation (separating the indicator logic from the Robot class), and includes a WPF-style UI Dashboard using cTrader’s native canvas controls.
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
#region Enums & Interfaces
public enum RegimeState
{
Chop,
Transition,
BullTrend,
BearTrend,
WarmingUp
}
public interface IRegimeFilter
{
RegimeState CurrentState { get; }
double CurrentEr { get; }
void Update();
}
#endregion
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class EnterpriseMTFERRegimeBot : Robot
{
#region Parameters
[Parameter("HTF Vector", DefaultValue = "Minute15", Group = "MTF Engine")]
public TimeFrame Htf { get; set; }
[Parameter("Lookback", DefaultValue = 14, MinValue = 1, Group = "MTF Engine")]
public int Lookback { get; set; }
[Parameter("Smoothing (EMA)", DefaultValue = 3, MinValue = 1, Group = "MTF Engine")]
public int Smoothing { get; set; }
[Parameter("Trend Floor", DefaultValue = 0.40, Group = "Regime Thresholds")]
public double TrendThreshold { get; set; }
[Parameter("Chop Ceiling", DefaultValue = 0.25, Group = "Regime Thresholds")]
public double ChopThreshold { get; set; }
[Parameter("Show Telemetry HUD", DefaultValue = true, Group = "UI")]
public bool ShowHud { get; set; }
#endregion
private Bars _htfBars;
private ErRegimeEngine _regimeEngine;
private TextBlock _hudRegimeText;
private TextBlock _hudErText;
protected override void OnStart()
{
// 1. Native Data Subscription
_htfBars = MarketData.GetBars(Htf);
// 2. Engine Injection & Initialization
_regimeEngine = new ErRegimeEngine(_htfBars, Lookback, Smoothing, ChopThreshold, TrendThreshold);
_regimeEngine.PrimeEngine(); // Calculates historical EMA states to align with live data
// 3. Event-Driven Wiring (Replaces OnTick time-checking)
_htfBars.BarOpened += OnHtfBarOpened;
if (ShowHud)
InitializeHud();
Print($"[Engine Initialized] MTF ER anchored to {Htf}. Current State: {_regimeEngine.CurrentState}");
}
// Triggered asynchronously ONLY when the HTF candle closes/opens
private void OnHtfBarOpened(BarOpenedEventArgs args)
{
_regimeEngine.Update();
if (ShowHud)
UpdateHud();
}
protected override void OnTick()
{
// Execution logic completely isolated from calculation overhead
if (_regimeEngine.CurrentState == RegimeState.Chop || _regimeEngine.CurrentState == RegimeState.WarmingUp)
return; // Lockdown
if (_regimeEngine.CurrentState == RegimeState.BullTrend)
{
// Execute M1 Bullish Pullback logic
}
else if (_regimeEngine.CurrentState == RegimeState.BearTrend)
{
// Execute M1 Bearish Pullback logic
}
}
#region UI Dashboard
private void InitializeHud()
{
var panel = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
BackgroundColor = Color.FromArgb(220, 20, 20, 20),
Margin = new Thickness(0, 50, 20, 0)
};
var border = new Border
{
BorderColor = Color.FromArgb(100, 128, 128, 128),
BorderThickness = new Thickness(1),
Padding = new Thickness(10),
Child = panel
};
var header = new TextBlock { Text = $"MTF ER REGIME ({Htf})", Foreground = Color.Gray, FontWeight = FontWeight.Bold, Margin = new Thickness(0,0,0,5) };
_hudRegimeText = new TextBlock { Text = "--", FontSize = 14, FontWeight = FontWeight.ExtraBold, Margin = new Thickness(0, 0, 0, 2) };
_hudErText = new TextBlock { Text = "ER: --", Foreground = Color.DarkGray };
panel.AddChild(header);
panel.AddChild(_hudRegimeText);
panel.AddChild(_hudErText);
Chart.AddControl(border);
UpdateHud();
}
private void UpdateHud()
{
_hudErText.Text = $"ER: {Math.Round(_regimeEngine.CurrentEr, 3)}";
switch (_regimeEngine.CurrentState)
{
case RegimeState.BullTrend:
_hudRegimeText.Text = "BULL TREND";
_hudRegimeText.Foreground = Color.LimeGreen;
break;
case RegimeState.BearTrend:
_hudRegimeText.Text = "BEAR TREND";
_hudRegimeText.Foreground = Color.Tomato;
break;
case RegimeState.Chop:
_hudRegimeText.Text = "CHOP (LOCKDOWN)";
_hudRegimeText.Foreground = Color.Gray;
break;
case RegimeState.Transition:
_hudRegimeText.Text = "TRANSITION";
_hudRegimeText.Foreground = Color.Goldenrod;
break;
}
}
#endregion
}
#region Mathematical Engine (Isolated)
public class ErRegimeEngine : IRegimeFilter
{
private readonly Bars _bars;
private readonly int _lookback;
private readonly double _alpha;
private readonly double _chopThresh;
private readonly double _trendThresh;
public RegimeState CurrentState { get; private set; } = RegimeState.WarmingUp;
public double CurrentEr { get; private set; }
public ErRegimeEngine(Bars bars, int lookback, int smoothing, double chopThresh, double trendThresh)
{
_bars = bars;
_lookback = lookback;
_alpha = 2.0 / (smoothing + 1.0);
_chopThresh = chopThresh;
_trendThresh = trendThresh;
}
/// <summary>
/// Calculates historical ER data to prime the EMA smoothing, preventing calculation
/// discrepancies when the bot is first attached to a live chart.
/// </summary>
public void PrimeEngine()
{
int maxHistory = Math.Min(100, _bars.Count - 2);
for (int i = maxHistory; i >= 0; i--)
{
CalculateAt(i);
}
}
public void Update()
{
// Always calculate at index 1 (the last fully closed bar)
CalculateAt(1);
}
private void CalculateAt(int closedBarOffset)
{
int index = _bars.Count - 1 - closedBarOffset;
if (index - _lookback < 0) return;
double currentClose = _bars.ClosePrices[index];
double oldClose = _bars.ClosePrices[index - _lookback];
double netChange = currentClose - oldClose;
double absChange = Math.Abs(netChange);
double volatility = 0.0;
for (int i = 0; i < _lookback; i++)
{
volatility += Math.Abs(_bars.ClosePrices[index - i] - _bars.ClosePrices[index - i - 1]);
}
double rawEr = volatility == 0 ? 0 : absChange / volatility;
// EMA Smoothing
CurrentEr = CurrentEr == 0 ? rawEr : (rawEr - CurrentEr) * _alpha + CurrentEr;
// State Evaluation
if (CurrentEr < _chopThresh)
{
CurrentState = RegimeState.Chop;
}
else if (CurrentEr >= _trendThresh)
{
CurrentState = netChange > 0 ? RegimeState.BullTrend : RegimeState.BearTrend;
}
else
{
CurrentState = RegimeState.Transition;
}
}
}
#endregion
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: The Self-Adjusting Trendline: Kaufman’s Adaptive Moving Average (KAMA) + MT4/MT5 Code
Enterprise Architecture Upgrades
1. Asynchronous Event Model (BarOpened):
Instead of forcing OnTick to perform a DateTime comparison on every single tick, this architecture subscribes directly to the _htfBars.BarOpened event. The engine sits entirely dormant at the CPU level until the exact millisecond the 15-minute bar rolls over.
2. The PrimeEngine() Method (EMA Synchronization):
When applying EMA smoothing, you cannot simply calculate the current bar; the formula requires the previous value. If you only calculate live, your ER will be inaccurate for the first few hours of deployment. The PrimeEngine() loop calculates the last 100 periods instantly upon OnStart(), ensuring the live smoothed value perfectly matches historical backtest data.
3. Dependency Isolation (ErRegimeEngine):
The mathematical logic is decoupled from the cBot into an isolated class implementing IRegimeFilter. This strictly adheres to Single Responsibility Principles (SRP). If you build a library of automated tools, you can extract ErRegimeEngine into an external .dll and inject it across multiple bots or custom indicators without duplicating code.
4. cTrader WPF-Style HUD:
This incorporates cAlgo's native StackPanel and Border classes to render an unobtrusive dashboard in the top-right corner. It provides instant visual verification of the HTF state without requiring you to attach an external indicator to the chart.
1. Asynchronous Event Model (BarOpened):
Instead of forcing OnTick to perform a DateTime comparison on every single tick, this architecture subscribes directly to the _htfBars.BarOpened event. The engine sits entirely dormant at the CPU level until the exact millisecond the 15-minute bar rolls over.
2. The PrimeEngine() Method (EMA Synchronization):
When applying EMA smoothing, you cannot simply calculate the current bar; the formula requires the previous value. If you only calculate live, your ER will be inaccurate for the first few hours of deployment. The PrimeEngine() loop calculates the last 100 periods instantly upon OnStart(), ensuring the live smoothed value perfectly matches historical backtest data.
3. Dependency Isolation (ErRegimeEngine):
The mathematical logic is decoupled from the cBot into an isolated class implementing IRegimeFilter. This strictly adheres to Single Responsibility Principles (SRP). If you build a library of automated tools, you can extract ErRegimeEngine into an external .dll and inject it across multiple bots or custom indicators without duplicating code.
4. cTrader WPF-Style HUD:
This incorporates cAlgo's native StackPanel and Border classes to render an unobtrusive dashboard in the top-right corner. It provides instant visual verification of the HTF state without requiring you to attach an external indicator to the chart.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.