Developer Resources

Build Your Own
cTrader Bot

Everything you need to get started writing automated trading algorithms in C# for the cTrader platform - from first principles to working code.

C# / .NET cTrader API Strategy Examples No experience required
Getting Started

What you need

Building a cTrader bot is more accessible than it sounds. You need basic C# knowledge and a few free tools - that's it.

1

cTrader Account

Open a free demo account at any cTrader-compatible broker. Pepperstone, IC Markets, and Skilling all support cTrader. No deposit required for development.

2

cTrader Desktop

Download the cTrader desktop app. The built-in cTrader Automate IDE lets you write, compile, and backtest bots without any other tools.

3

Basic C# Knowledge

You don't need to be a senior developer. Understanding classes, methods, variables, and conditionals is enough to get started. The cTrader API handles the hard parts.

4

A Trading Idea

The best bots come from clear, testable rules: "buy when X, sell when Y, stop loss at Z." The clearer your rules, the easier the implementation.

Core Concepts

How a cBot works

A cBot is a C# class that inherits from Robot. cTrader calls your methods automatically as the market moves. Here are the key building blocks.

📊

Bars & Ticks

A Bar is a completed candle (OHLCV). A Tick is every price update. Use OnBar() for candle-based logic, OnTick() for price-based logic.

Bars.ClosePrices.Last(1)
📈

Indicator Series

Indicators return DataSeries - arrays of values, one per bar. Access them with .Last(n) where 1 is the last completed bar.

Indicators.MovingAverage(...)
💼

Positions

Open trades are Positions. You can filter by label, symbol, and direction. Use ExecuteMarketOrder() to open, ClosePosition() to close.

Positions.FindAll("label", ...)
📋

Pending Orders

Limit and stop orders that haven't triggered yet. Place them with PlaceLimitOrder() or PlaceStopOrder(). They become positions when filled.

PlaceLimitOrder(TradeType.Buy, ...)
⚙️

Parameters

Decorate properties with [Parameter] to expose them in the cTrader UI. Users can configure values without touching the code. Essential for backtesting.

[Parameter("Period", DefaultValue = 14)]
🔒

Symbol Info

Symbol.Bid and Symbol.Ask are the current prices. Symbol.PipSize converts pips to price units. Symbol.QuantityToVolumeInUnits() converts lots.

Symbol.QuantityToVolumeInUnits(0.01)
// ── Minimal cBot skeleton ──────────────────────────────────────────────

using cAlgo.API;
using cAlgo.API.Indicators;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class MyBot : Robot
{
    [Parameter("Volume (lots)", Group = "Settings", DefaultValue = 0.01)]
    public double Volume { get; set; }

    // Called once when the bot starts
    protected override void OnStart()
    {
        // Initialize indicators and state here
        Print("Bot started");
    }

    // Called on every completed bar (candle)
    protected override void OnBar()
    {
        // Your strategy logic goes here
    }

    // Called on every price tick (optional)
    protected override void OnTick()
    {
        // Use for tick-level management, trailing stops, etc.
    }

    // Called when the bot stops
    protected override void OnStop()
    {
        Print("Bot stopped");
    }
}
Strategy Examples

5 strategies, ready to run

Each example is complete, compilable C# code. Paste it into cTrader Automate, hit compile, and run it on a demo account.

📉

Moving Average Crossover

One of the most classic trend-following strategies. Buys when the fast EMA crosses above the slow EMA, sells when it crosses below. Simple, transparent, and a great starting point.

Trend following OnBar Beginner

The bot uses two exponential moving averages: a fast one (default 10) and a slow one (default 50). A crossover is detected by comparing the relationship between the two MAs on the last two completed bars. Only one position in each direction is open at any time.

MACrossoverBot.cs C#
using cAlgo.API;
using cAlgo.API.Indicators;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class MACrossoverBot : Robot
{
    [Parameter("Fast MA Period", Group = "Settings", DefaultValue = 10)]
    public int FastPeriod { get; set; }

    [Parameter("Slow MA Period", Group = "Settings", DefaultValue = 50)]
    public int SlowPeriod { get; set; }

    [Parameter("Volume (lots)", Group = "Settings", DefaultValue = 0.01)]
    public double Volume { get; set; }

    [Parameter("Stop Loss (pips)", Group = "Risk", DefaultValue = 30)]
    public double StopLossPips { get; set; }

    [Parameter("Take Profit (pips)", Group = "Risk", DefaultValue = 60)]
    public double TakeProfitPips { get; set; }

    private MovingAverage _fastMa;
    private MovingAverage _slowMa;

    protected override void OnStart()
    {
        _fastMa = Indicators.MovingAverage(Bars.ClosePrices, FastPeriod, MovingAverageType.Exponential);
        _slowMa = Indicators.MovingAverage(Bars.ClosePrices, SlowPeriod, MovingAverageType.Exponential);
    }

    protected override void OnBar()
    {
        var fast = _fastMa.Result;
        var slow = _slowMa.Result;

        bool crossedAbove = fast.Last(1) > slow.Last(1) && fast.Last(2) <= slow.Last(2);
        bool crossedBelow = fast.Last(1) < slow.Last(1) && fast.Last(2) >= slow.Last(2);

        if (crossedAbove)
        {
            CloseAll(TradeType.Sell);
            if (Positions.FindAll("MA", SymbolName, TradeType.Buy).Length == 0)
                ExecuteMarketOrder(TradeType.Buy, SymbolName,
                    Symbol.QuantityToVolumeInUnits(Volume), "MA", StopLossPips, TakeProfitPips);
        }

        if (crossedBelow)
        {
            CloseAll(TradeType.Buy);
            if (Positions.FindAll("MA", SymbolName, TradeType.Sell).Length == 0)
                ExecuteMarketOrder(TradeType.Sell, SymbolName,
                    Symbol.QuantityToVolumeInUnits(Volume), "MA", StopLossPips, TakeProfitPips);
        }
    }

    private void CloseAll(TradeType type)
    {
        foreach (var p in Positions.FindAll("MA", SymbolName, type))
            ClosePosition(p);
    }
}
🔄

RSI Mean Reversion

Fades extreme RSI readings, betting that price will snap back toward the mean. Buys oversold conditions, sells overbought. Works best in ranging, low-volatility markets.

Mean reversion OnBar Beginner

The RSI (Relative Strength Index) oscillates between 0 and 100. Below 30 is traditionally considered oversold, above 70 overbought. This bot enters when either extreme is touched and exits at the take profit level. Adjust levels based on the instrument's volatility.

RSIMeanReversionBot.cs C#
using cAlgo.API;
using cAlgo.API.Indicators;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class RSIMeanReversionBot : Robot
{
    [Parameter("RSI Period", Group = "Settings", DefaultValue = 14)]
    public int RsiPeriod { get; set; }

    [Parameter("Oversold Level", Group = "Settings", DefaultValue = 30)]
    public double OversoldLevel { get; set; }

    [Parameter("Overbought Level", Group = "Settings", DefaultValue = 70)]
    public double OverboughtLevel { get; set; }

    [Parameter("Volume (lots)", Group = "Settings", DefaultValue = 0.01)]
    public double Volume { get; set; }

    [Parameter("Stop Loss (pips)", Group = "Risk", DefaultValue = 40)]
    public double StopLossPips { get; set; }

    [Parameter("Take Profit (pips)", Group = "Risk", DefaultValue = 80)]
    public double TakeProfitPips { get; set; }

    private RelativeStrengthIndex _rsi;

    protected override void OnStart()
    {
        _rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, RsiPeriod);
    }

    protected override void OnBar()
    {
        double rsi = _rsi.Result.LastValue;

        if (rsi < OversoldLevel && Positions.FindAll("RSI", SymbolName, TradeType.Buy).Length == 0)
        {
            ExecuteMarketOrder(TradeType.Buy, SymbolName,
                Symbol.QuantityToVolumeInUnits(Volume), "RSI", StopLossPips, TakeProfitPips);
        }

        if (rsi > OverboughtLevel && Positions.FindAll("RSI", SymbolName, TradeType.Sell).Length == 0)
        {
            ExecuteMarketOrder(TradeType.Sell, SymbolName,
                Symbol.QuantityToVolumeInUnits(Volume), "RSI", StopLossPips, TakeProfitPips);
        }
    }
}
📐

Bollinger Bands Reversal

Enters when price touches the upper or lower Bollinger Band and bets on a return to the moving average. Combines volatility awareness with mean-reversion timing.

Mean reversion Volatility-aware OnBar Intermediate

Bollinger Bands place an upper and lower channel around a moving average based on standard deviation. When price touches the lower band, it's statistically extended to the downside; the bot buys expecting a snap-back. The bands automatically widen during high-volatility periods, making them more adaptive than fixed levels.

BollingerBandsBot.cs C#
using cAlgo.API;
using cAlgo.API.Indicators;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class BollingerBandsBot : Robot
{
    [Parameter("Periods", Group = "Settings", DefaultValue = 20)]
    public int Periods { get; set; }

    [Parameter("Standard Deviations", Group = "Settings", DefaultValue = 2.0)]
    public double StdDev { get; set; }

    [Parameter("Volume (lots)", Group = "Settings", DefaultValue = 0.01)]
    public double Volume { get; set; }

    [Parameter("Stop Loss (pips)", Group = "Risk", DefaultValue = 50)]
    public double StopLossPips { get; set; }

    [Parameter("Take Profit (pips)", Group = "Risk", DefaultValue = 50)]
    public double TakeProfitPips { get; set; }

    private BollingerBands _bb;

    protected override void OnStart()
    {
        _bb = Indicators.BollingerBands(Bars.ClosePrices, Periods, StdDev, MovingAverageType.Simple);
    }

    protected override void OnBar()
    {
        double close = Bars.ClosePrices.Last(1);
        double upper = _bb.Top.Last(1);
        double lower = _bb.Bottom.Last(1);

        // Buy when price touches or breaks below the lower band
        if (close <= lower && Positions.FindAll("BB", SymbolName, TradeType.Buy).Length == 0)
        {
            ExecuteMarketOrder(TradeType.Buy, SymbolName,
                Symbol.QuantityToVolumeInUnits(Volume), "BB", StopLossPips, TakeProfitPips);
        }

        // Sell when price touches or breaks above the upper band
        if (close >= upper && Positions.FindAll("BB", SymbolName, TradeType.Sell).Length == 0)
        {
            ExecuteMarketOrder(TradeType.Sell, SymbolName,
                Symbol.QuantityToVolumeInUnits(Volume), "BB", StopLossPips, TakeProfitPips);
        }
    }
}
💥

Donchian Channel Breakout

Trades breakouts above the N-bar high or below the N-bar low - the same principle behind classic trend-following systems like the Turtle Trading strategy.

Trend following Breakout OnBar Intermediate

The bot tracks the highest high and lowest low over the last N bars (the Donchian Channel). When price closes above the upper channel, it buys the breakout. When price closes below the lower channel, it sells. The key insight is that new highs and lows indicate potential momentum continuation.

BreakoutBot.cs C#
using cAlgo.API;
using System;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class BreakoutBot : Robot
{
    [Parameter("Lookback Bars", Group = "Settings", DefaultValue = 20)]
    public int Lookback { get; set; }

    [Parameter("Volume (lots)", Group = "Settings", DefaultValue = 0.01)]
    public double Volume { get; set; }

    [Parameter("Stop Loss (pips)", Group = "Risk", DefaultValue = 30)]
    public double StopLossPips { get; set; }

    [Parameter("Take Profit (pips)", Group = "Risk", DefaultValue = 90)]
    public double TakeProfitPips { get; set; }

    protected override void OnBar()
    {
        double channelHigh = double.MinValue;
        double channelLow  = double.MaxValue;

        // Start from bar[2] - bar[1] is the bar that just closed,
        // we want the channel formed BEFORE it to detect a breakout
        for (int i = 2; i <= Lookback + 1; i++)
        {
            channelHigh = Math.Max(channelHigh, Bars.HighPrices.Last(i));
            channelLow  = Math.Min(channelLow,  Bars.LowPrices.Last(i));
        }

        double close = Bars.ClosePrices.Last(1);

        if (close > channelHigh && Positions.FindAll("BO", SymbolName, TradeType.Buy).Length == 0)
        {
            CloseAll(TradeType.Sell);
            ExecuteMarketOrder(TradeType.Buy, SymbolName,
                Symbol.QuantityToVolumeInUnits(Volume), "BO", StopLossPips, TakeProfitPips);
        }

        if (close < channelLow && Positions.FindAll("BO", SymbolName, TradeType.Sell).Length == 0)
        {
            CloseAll(TradeType.Buy);
            ExecuteMarketOrder(TradeType.Sell, SymbolName,
                Symbol.QuantityToVolumeInUnits(Volume), "BO", StopLossPips, TakeProfitPips);
        }
    }

    private void CloseAll(TradeType type)
    {
        foreach (var p in Positions.FindAll("BO", SymbolName, type))
            ClosePosition(p);
    }
}
🔲

Simple Grid Bot

Places a symmetric grid of limit orders above and below the current price. Profits from oscillating price action without predicting direction. Foundation for more complex grid strategies.

Grid OnTick Advanced

The bot anchors to the current price on start and places N buy limit orders below (each step apart) and N sell limit orders above. When filled, each order has its own take profit. When all orders on one side are consumed, the bot resets the grid from the new price. <strong>Note:</strong> grid strategies carry significant risk in strongly trending markets - always run a stop loss at the account level.

SimpleGridBot.cs C#
using cAlgo.API;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class SimpleGridBot : Robot
{
    [Parameter("Grid Step (pips)", Group = "Grid", DefaultValue = 10)]
    public double GridStepPips { get; set; }

    [Parameter("Max Levels", Group = "Grid", DefaultValue = 5)]
    public int MaxLevels { get; set; }

    [Parameter("Volume per Level (lots)", Group = "Grid", DefaultValue = 0.01)]
    public double VolumePerLevel { get; set; }

    [Parameter("Take Profit (pips)", Group = "Grid", DefaultValue = 10)]
    public double TakeProfitPips { get; set; }

    private double _anchor;
    private double _step;
    private long   _vol;

    protected override void OnStart()
    {
        _anchor = Symbol.Bid;
        _step   = GridStepPips * Symbol.PipSize;
        _vol    = Symbol.QuantityToVolumeInUnits(VolumePerLevel);
        PlaceGrid();
    }

    private void PlaceGrid()
    {
        for (int i = 1; i <= MaxLevels; i++)
        {
            double buyPrice  = _anchor - i * _step;
            double sellPrice = _anchor + i * _step;

            PlaceLimitOrder(TradeType.Buy,  SymbolName, _vol, buyPrice,  $"Grid_B{i}", null, TakeProfitPips);
            PlaceLimitOrder(TradeType.Sell, SymbolName, _vol, sellPrice, $"Grid_S{i}", null, TakeProfitPips);
        }
    }

    protected override void OnTick()
    {
        int buysRemaining  = PendingOrders.FindAll("Grid_B", SymbolName).Length;
        int sellsRemaining = PendingOrders.FindAll("Grid_S", SymbolName).Length;

        // Reset grid if one side is fully consumed
        if (buysRemaining == 0 || sellsRemaining == 0)
        {
            foreach (var order in PendingOrders)
                CancelPendingOrder(order);

            _anchor = Symbol.Bid;
            PlaceGrid();
        }
    }
}
📥

Scaling Reversion

Scales into the same direction each time the signal repeats. A small take profit captures quick reversals; a large stop loss absorbs continued adverse movement.

Mean reversion OnBar Advanced

The bot adds a new position in the same direction each time RSI confirms an extreme - with a minimum price step between entries to avoid stacking too tightly. The combination of a small TP (take profit quickly on reversion) and a large SL (give room for continued movement) is the core logic. MaxPositions sets a hard cap on total exposure.

ScalingReversionBot.cs C#
using cAlgo.API;
using cAlgo.API.Indicators;

[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ScalingReversionBot : Robot
{
    [Parameter("RSI Period", Group = "Signal", DefaultValue = 14)]
    public int RsiPeriod { get; set; }

    [Parameter("Oversold Level", Group = "Signal", DefaultValue = 35)]
    public double OversoldLevel { get; set; }

    [Parameter("Overbought Level", Group = "Signal", DefaultValue = 65)]
    public double OverboughtLevel { get; set; }

    [Parameter("Min Step Between Entries (pips)", Group = "Signal", DefaultValue = 15)]
    public double MinStepPips { get; set; }

    [Parameter("Max Positions", Group = "Risk", DefaultValue = 5)]
    public int MaxPositions { get; set; }

    [Parameter("Volume (lots)", Group = "Risk", DefaultValue = 0.01)]
    public double Volume { get; set; }

    [Parameter("Take Profit (pips)", Group = "Risk", DefaultValue = 10)]
    public double TakeProfitPips { get; set; }

    [Parameter("Stop Loss (pips)", Group = "Risk", DefaultValue = 150)]
    public double StopLossPips { get; set; }

    private RelativeStrengthIndex _rsi;

    protected override void OnStart()
    {
        _rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, RsiPeriod);
    }

    protected override void OnBar()
    {
        double rsi = _rsi.Result.LastValue;

        TryScale(TradeType.Buy,  rsi < OversoldLevel,  Symbol.Ask);
        TryScale(TradeType.Sell, rsi > OverboughtLevel, Symbol.Bid);
    }

    private void TryScale(TradeType direction, bool signal, double currentPrice)
    {
        if (!signal) return;

        var positions = Positions.FindAll("SR", SymbolName, direction);
        if (positions.Length >= MaxPositions) return;

        // Require minimum price distance from every existing entry
        double minStep = MinStepPips * Symbol.PipSize;
        foreach (var p in positions)
        {
            if (Math.Abs(currentPrice - p.EntryPrice) < minStep)
                return;
        }

        ExecuteMarketOrder(direction, SymbolName,
            Symbol.QuantityToVolumeInUnits(Volume), "SR",
            StopLossPips, TakeProfitPips);
    }
}

Ready to take your bot to the next level?

Build freely - connect when you're ready. The Novalgo platform gives you two things: sharper signals for your own trading, and the full infrastructure to sell your bot to others.

Step 1 - Improve your trading
AI Signals & News Filter

Connect your bot to Novalgo and get access to directional signals and economic calendar events in real time - without changing your core logic.

  • 🤖
    AI Directional Bias Long, Short, or Neutral - delivered via WebSocket. Use as a top-down filter to only take trades in the bias direction.
  • 📰
    News Filter Automatically pause new entries before high-risk events. The platform subscribes to an economic calendar feed and pushes block signals in real time.
  • Simple Integration One WebSocket connection and an API key. Signals are optional - the bot works without them, but performs better with them.

Requires an active Novalgo license.

Ready to connect? Get in touch and we'll walk you through the integration and what a license costs.