Re: Stop-Loss Placement Is Not Optional Decoration
Posted: Sat Sep 26, 2026 2:23 pm
2. Pro cTrader cBot (C#): Event-Driven Risk Enforcer
If you are executing manually or running algorithmic strategies directly on your broker infrastructure, scanning a loop on every tick wastes CPU cycles. A professional C# implementation hooks into the Positions.Opened event and processes execution asynchronously.
This cBot runs in the background. If you manually open a trade without a stop loss, it instantly calculates a structural stop using an ATR buffer and applies it asynchronously to avoid locking up the main thread.
If you are executing manually or running algorithmic strategies directly on your broker infrastructure, scanning a loop on every tick wastes CPU cycles. A professional C# implementation hooks into the Positions.Opened event and processes execution asynchronously.
This cBot runs in the background. If you manually open a trade without a stop loss, it instantly calculates a structural stop using an ATR buffer and applies it asynchronously to avoid locking up the main thread.
Code: Select all
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ProRiskEnforcer : Robot
{
[Parameter("ATR Period", DefaultValue = 14, Group = "Risk Structure")]
public int AtrPeriod { get; set; }
[Parameter("ATR Multiplier", DefaultValue = 1.5, Group = "Risk Structure")]
public double AtrMultiplier { get; set; }
private AverageTrueRange _atr;
protected override void OnStart()
{
// Initialize ATR for dynamic volatility buffering
_atr = Indicators.AverageTrueRange(MarketData.GetBars(TimeFrame.Minute15), AtrPeriod, MovingAverageType.Exponential);
// Subscribe only to relevant events (highly efficient)
Positions.Opened += OnPositionOpened;
Print("Pro Risk Enforcer initialized. Monitoring for naked positions.");
}
private void OnPositionOpened(PositionOpenedEventArgs args)
{
var position = args.Position;
// Immediately check if the order lacks a Stop Loss
if (!position.StopLoss.HasValue)
{
Print($"[ALERT] Naked position detected on {position.SymbolName} (Ticket: {position.Id}). Calculating structural SL.");
ApplyStructuralStopLoss(position);
}
}
private void ApplyStructuralStopLoss(Position position)
{
var bars = MarketData.GetBars(TimeFrame.Minute15);
double currentAtr = _atr.Result.Last(1);
double buffer = currentAtr * AtrMultiplier;
double? newStopLoss = null;
if (position.TradeType == TradeType.Buy)
{
// Find recent swing low over the last 15 periods
double swingLow = bars.LowPrices.Minimum(15);
newStopLoss = swingLow - buffer;
}
else if (position.TradeType == TradeType.Sell)
{
// Find recent swing high over the last 15 periods
double swingHigh = bars.HighPrices.Maximum(15);
newStopLoss = swingHigh + buffer;
}
if (newStopLoss.HasValue)
{
// Execute modification asynchronously so the bot's thread isn't blocked
ModifyPositionAsync(position, newStopLoss, position.TakeProfit, hasError =>
{
if (hasError.IsSuccessful)
Print($"[SUCCESS] Structural SL applied to {position.Id} at {Math.Round(newStopLoss.Value, 5)}");
else
Print($"[ERROR] Failed to apply SL to {position.Id}: {hasError.Error}");
});
}
}
}
}