<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ctrader</title>
<link>https://coderlegion.com/tag/ctrader</link>
<description>Powered by Question2Answer</description>
<item>
<title>cTrader cBot Risk Engine: High-Precision C# Dynamic Stop-Loss Architecture</title>
<link>https://coderlegion.com/27900/ctrader-cbot-risk-engine-high-precision-c-dynamic-stop-loss-architecture</link>
<description>&lt;p&gt;Static stop-losses break across volatility regimes. A fixed 20-pip stop that is conservative on EUR/USD during Asia session is noise-level on XAU/USD during New York open. In cTrader Automate (C#), the robust pattern is a dedicated risk engine: signal logic decides direction, the engine decides size and stop distance from live volatility.&lt;/p&gt;
&lt;p&gt;This post documents the architecture — educational, no trade signals, no return claims.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-separate-sizing-from-signals&quot;&gt;1. Separate Sizing From Signals&lt;/h3&gt;
&lt;p&gt;The most common cBot flaw is inline risk math inside &lt;code&gt;OnBar()&lt;/code&gt;: volume computed next to entry conditions, stop distance hardcoded. When volatility doubles, the same stop gets hunted and the same volume carries twice the dollar risk.&lt;/p&gt;
&lt;p&gt;Split the cBot into two units:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Signal module&lt;/strong&gt; — produces direction only (e.g., breakout, trend filter).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Risk engine&lt;/strong&gt; — given direction, computes stop distance from ATR and volume from a fixed monetary budget.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// RiskEngine.cs — pure logic, no cTrader dependencies (unit-testable)
public static class RiskEngine
{
    public static double StopDistancePrice(double atr, double multiplier)
    {
        return atr * multiplier;
    }

    public static long VolumeInUnits(double riskMoney, double stopDistancePrice,
                                     double tickValuePerUnit, double pipSize,
                                     long volumeMin, long volumeMax, long volumeStep,
                                     out bool clampedToMin, out double effectiveRisk)
    {
        double raw = riskMoney / (stopDistancePrice / pipSize * tickValuePerUnit);
        long floored = (long)Math.Floor(raw / volumeStep) * volumeStep;
        clampedToMin = false;
        if (floored &amp;lt; volumeMin) { floored = volumeMin; clampedToMin = true; }
        long finalVolume = Math.Min(floored, volumeMax);
        effectiveRisk = finalVolume * (stopDistancePrice / pipSize * tickValuePerUnit);
        return finalVolume;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;clampedToMin&lt;/code&gt; flag: when the budgeted volume falls below the broker minimum, the engine clamps up &lt;strong&gt;and reports the effective risk&lt;/strong&gt;, which now exceeds the budget. Silently reporting the original budget while carrying higher exposure is the exact failure we dissected in &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/27300/the-silent-position-sizer-bug-when-accounts-are-small-your-risk-inadvertently-doubles&quot;&gt;The Silent Position Sizer Bug&lt;/a&gt; — never repeat it.&lt;/p&gt;
&lt;h3 id=&quot;2-atr-based-dynamic-stops&quot;&gt;2. ATR-Based Dynamic Stops&lt;/h3&gt;
&lt;p&gt;Average True Range adapts the stop to current volatility. A 1.5x–2.0x ATR stop keeps the exit outside normal noise on any symbol and any session:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private double GetAtrStopPips(int atrPeriod = 14, double multiplier = 2.0)
{
    var atr = Indicators.AverageTrueRange(atrPeriod, MovingAverageType.Exponential);
    double stopPrice = atr.Result.LastValue * multiplier;
    return stopPrice / Symbol.PipSize;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the stop distance is measured, not assumed, the same cBot behaves sanely on a 0.0001-pip Forex pair and a 2-decimal metal without retuning magic numbers.&lt;/p&gt;
&lt;h3 id=&quot;3-wiring-it-into-the-cbot&quot;&gt;3. Wiring It Into the cBot&lt;/h3&gt;
&lt;p&gt;Volume in cTrader is expressed in units (100,000 units = 1.0 standard lot on FX). Always read the broker limits from the symbol — never hardcode them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;protected override void OnBar()
{
    if (Positions.Count &amp;gt; 0) return; // single-position example
    if (!GetSignal()) return;

    double stopPips = GetAtrStopPips();
    double stopPrice = stopPips * Symbol.PipSize;

    long volume = RiskEngine.VolumeInUnits(
        RiskMoney, stopPrice, Symbol.TickValue / Symbol.TickSize * Symbol.PipSize,
        Symbol.PipSize, Symbol.VolumeInUnitsMin,
        Symbol.VolumeInUnitsMax, Symbol.VolumeInUnitsStep,
        out bool clamped, out double effectiveRisk);

    if (clamped)
        Print(&quot;WARNING: volume clamped to broker minimum. &quot; +
              &quot;Effective risk {0} exceeds budget {1}.&quot;, effectiveRisk, RiskMoney);

    ExecuteMarketOrder(TradeType.Buy, SymbolName, volume,
                       &quot;atr-risk&quot;, stopPips, null);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-trailing-without-chasing&quot;&gt;4. Trailing Without Chasing&lt;/h3&gt;
&lt;p&gt;Move the stop only when price has banked a multiple of the ATR distance — ratcheting on every tick bleeds to spread:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;private void TrailAtr(Position position, double atrDistancePrice)
{
    double newStop = Symbol.Bid - 1.0 * atrDistancePrice; // long example
    if (newStop &amp;gt; position.StopLoss + Symbol.PipSize)
        ModifyPosition(position, newStop, position.TakeProfit);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-validation-checklist-demo-first-&quot;&gt;5. Validation Checklist (Demo First)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Run 60 days on demo across at least two volatility regimes (e.g., Forex + metal).&lt;/li&gt;
&lt;li&gt;Log every clamp event; if clamps exceed ~5% of trades, the risk budget is too small for the account — top up or widen the stop, never silence the warning.&lt;/li&gt;
&lt;li&gt;Backtest with real-tick data; reject any result you cannot reproduce forward on demo.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Educational content under Colombia SFC Decreto 2555 de 2010: this is financial-engineering study material, not investment advice. No signals, no managed accounts.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;By &lt;strong&gt;Mahdi Goodarzi&lt;/strong&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://g.dev/mahdigoodarzi&quot;&gt;g.dev/mahdigoodarzi&lt;/a&gt;), Gueta Quant — interactive risk tools at &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com&quot;&gt;guetaquant.com&lt;/a&gt;. Companion guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://guetaquant.com/ctrader-copy-trading/&quot;&gt;cTrader Copy setup in Colombia&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27900/ctrader-cbot-risk-engine-high-precision-c-dynamic-stop-loss-architecture</guid>
<pubDate>Mon, 21 Sep 2026 08:26:11 +0000</pubDate>
</item>
</channel>
</rss>