Since cTrader is a true event-driven platform (unlike TradingView, which evaluates historical arrays on every tick), this architecture handles signal generation natively on bar closes (OnBar) while managing the aggressive ATR trailing stop asynchronously tick-by-tick (OnTick).
Pro HTF Scalper with ATR Trail (cTrader C#)
Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
public enum ExitStrategy
{
FixedPips,
ATRTrailingStop
}
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ProHTFScalper : Robot
{
// =====================================================================
// 1. HIGHER TIMEFRAME CONTEXT
// =====================================================================
[Parameter("Primary HTF", Group = "1. Higher Timeframe Context", DefaultValue = "Hour")]
public TimeFrame Htf1TimeFrame { get; set; }
[Parameter("Secondary HTF", Group = "1. Higher Timeframe Context", DefaultValue = "Hour4")]
public TimeFrame Htf2TimeFrame { get; set; }
[Parameter("HTF Trend EMA", Group = "1. Higher Timeframe Context", DefaultValue = 50)]
public int HtfEmaPeriod { get; set; }
// =====================================================================
// 2. SCALP TRIGGERS (LTF)
// =====================================================================
[Parameter("Fast EMA Trigger", Group = "2. Scalp Triggers", DefaultValue = 9)]
public int FastEmaPeriod { get; set; }
[Parameter("Slow EMA Trigger", Group = "2. Scalp Triggers", DefaultValue = 21)]
public int SlowEmaPeriod { get; set; }
// =====================================================================
// 3. RISK MANAGEMENT & EXITS
// =====================================================================
[Parameter("Volume (Lots)", Group = "3. Risk Management", DefaultValue = 0.1)]
public double VolumeInLots { get; set; }
[Parameter("Exit Mode", Group = "3. Risk Management", DefaultValue = ExitStrategy.ATRTrailingStop)]
public ExitStrategy ExitMode { get; set; }
[Parameter("Fixed Stop Loss (Pips)", Group = "3. Risk Management", DefaultValue = 10)]
public double FixedSlPips { get; set; }
[Parameter("Fixed Take Profit (Pips)", Group = "3. Risk Management", DefaultValue = 20)]
public double FixedTpPips { get; set; }
[Parameter("ATR Length", Group = "3. Risk Management", DefaultValue = 14)]
public int AtrLength { get; set; }
[Parameter("ATR Multiplier", Group = "3. Risk Management", DefaultValue = 2.0)]
public double AtrMultiplier { get; set; }
// =====================================================================
// 4. TRADING WINDOW
// =====================================================================
[Parameter("Enable Time Filter", Group = "4. Trading Window", DefaultValue = true)]
public bool UseSession { get; set; }
[Parameter("Start Hour (Server Time)", Group = "4. Trading Window", DefaultValue = 8)]
public int SessionStart { get; set; }
[Parameter("End Hour (Server Time)", Group = "4. Trading Window", DefaultValue = 17)]
public int SessionEnd { get; set; }
// =====================================================================
// 5. DISPLAY
// =====================================================================
[Parameter("Show On-Chart HUD", Group = "5. Display", DefaultValue = true)]
public bool ShowHud { get; set; }
// --- Core Objects ---
private Bars _htf1Bars, _htf2Bars;
private ExponentialMovingAverage _htf1Ema, _htf2Ema;
private ExponentialMovingAverage _fastEma, _slowEma;
private AverageTrueRange _atr;
private const string Label = "ProHTFScalper";
protected override void OnStart()
{
// Initialize Multi-Timeframe Data
_htf1Bars = MarketData.GetBars(Htf1TimeFrame);
_htf2Bars = MarketData.GetBars(Htf2TimeFrame);
_htf1Ema = Indicators.ExponentialMovingAverage(_htf1Bars.ClosePrices, HtfEmaPeriod);
_htf2Ema = Indicators.ExponentialMovingAverage(_htf2Bars.ClosePrices, HtfEmaPeriod);
// Initialize Current Timeframe Data (LTF)
_fastEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastEmaPeriod);
_slowEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowEmaPeriod);
_atr = Indicators.AverageTrueRange(AtrLength, MovingAverageType.Simple);
}
protected override void OnBar()
{
ManageSession();
UpdateHud();
if (UseSession && !InSession()) return;
if (Positions.FindAll(Label, SymbolName).Length > 0) return; // Wait until flat
// 1. Check HTF Structure (Using shift 1 for strictly closed candles)
bool isHtf1Bull = _htf1Bars.ClosePrices.Last(1) > _htf1Ema.Result.Last(1);
bool isHtf2Bull = _htf2Bars.ClosePrices.Last(1) > _htf2Ema.Result.Last(1);
bool isHtf1Bear = _htf1Bars.ClosePrices.Last(1) < _htf1Ema.Result.Last(1);
bool isHtf2Bear = _htf2Bars.ClosePrices.Last(1) < _htf2Ema.Result.Last(1);
bool htfUptrend = isHtf1Bull && isHtf2Bull;
bool htfDowntrend = isHtf1Bear && isHtf2Bear;
// 2. LTF Trigger conditions (Crossover on closed bar)
bool buyTrigger = _fastEma.Result.Last(1) > _slowEma.Result.Last(1) && _fastEma.Result.Last(2) <= _slowEma.Result.Last(2);
bool sellTrigger = _fastEma.Result.Last(1) < _slowEma.Result.Last(1) && _fastEma.Result.Last(2) >= _slowEma.Result.Last(2);
double volume = Symbol.QuantityToVolumeInUnits(VolumeInLots);
double currentAtr = _atr.Result.Last(1);
// 3. Execution
if (htfUptrend && buyTrigger)
{
if (ExitMode == ExitStrategy.FixedPips)
{
ExecuteMarketOrder(TradeType.Buy, SymbolName, volume, Label, FixedSlPips, FixedTpPips);
}
else // ATR Trail starting stop
{
double initialSl = (currentAtr * AtrMultiplier) / Symbol.PipSize;
ExecuteMarketOrder(TradeType.Buy, SymbolName, volume, Label, initialSl, null);
}
}
else if (htfDowntrend && sellTrigger)
{
if (ExitMode == ExitStrategy.FixedPips)
{
ExecuteMarketOrder(TradeType.Sell, SymbolName, volume, Label, FixedSlPips, FixedTpPips);
}
else
{
double initialSl = (currentAtr * AtrMultiplier) / Symbol.PipSize;
ExecuteMarketOrder(TradeType.Sell, SymbolName, volume, Label, initialSl, null);
}
}
}
protected override void OnTick()
{
// Dynamically trail the stop loss with tick precision based on the live ATR
if (ExitMode == ExitStrategy.ATRTrailingStop)
{
var positions = Positions.FindAll(Label, SymbolName);
if (positions.Length == 0) return;
double atrValue = _atr.Result.LastValue;
foreach (var pos in positions)
{
if (pos.TradeType == TradeType.Buy)
{
double newSl = Symbol.Bid - (atrValue * AtrMultiplier);
if (!pos.StopLoss.HasValue || newSl > pos.StopLoss.Value)
{
ModifyPositionAsync(pos, newSl, pos.TakeProfit);
}
}
else if (pos.TradeType == TradeType.Sell)
{
double newSl = Symbol.Ask + (atrValue * AtrMultiplier);
if (!pos.StopLoss.HasValue || newSl < pos.StopLoss.Value)
{
ModifyPositionAsync(pos, newSl, pos.TakeProfit);
}
}
}
}
}
// --- Utility Methods ---
private bool InSession()
{
var hour = Server.Time.Hour;
return hour >= SessionStart && hour < SessionEnd;
}
private void ManageSession()
{
if (UseSession && !InSession())
{
var openPositions = Positions.FindAll(Label, SymbolName);
foreach (var pos in openPositions)
{
ClosePositionAsync(pos);
}
}
}
private void UpdateHud()
{
if (!ShowHud) return;
bool isHtf1Bull = _htf1Bars.ClosePrices.Last(1) > _htf1Ema.Result.Last(1);
bool isHtf2Bull = _htf2Bars.ClosePrices.Last(1) > _htf2Ema.Result.Last(1);
bool isHtf1Bear = _htf1Bars.ClosePrices.Last(1) < _htf1Ema.Result.Last(1);
bool isHtf2Bear = _htf2Bars.ClosePrices.Last(1) < _htf2Ema.Result.Last(1);
string htf1Status = isHtf1Bull ? "BULL" : (isHtf1Bear ? "BEAR" : "CHOP");
string htf2Status = isHtf2Bull ? "BULL" : (isHtf2Bear ? "BEAR" : "CHOP");
bool isUptrend = isHtf1Bull && isHtf2Bull;
bool isDowntrend = isHtf1Bear && isHtf2Bear;
string masterStatus = isUptrend ? "LONG ONLY" : (isDowntrend ? "SHORT ONLY" : "NO TRADE");
string hudText = $"HTF FILTER\n------------------\n" +
$"{Htf1TimeFrame}: {htf1Status}\n" +
$"{Htf2TimeFrame}: {htf2Status}\n\n" +
$"MASTER: {masterStatus}";
Chart.DrawText("HudPanel", hudText, Server.Time, Chart.TopY, Color.LightGray);
}
}
}