cTrader Automate Robots — Coding Basics

One of the main reasons you might want to choose cTrader as your Forex platform is to access its Automate part in order to develop and use custom trading robots. In MetaTrader platform, such robots are called expert advisors. In сTrader, they are called cBots. At present, the possibilities offered by cTrader Automate in terms of trading automation are much less exciting than those provided by MetaTrader/MQL. Nevertheless, they are an important part of the platform, so this guide will show you the basics of robot building in cTrader Automate.

Difference with MetaTrader

The main difference between cTrader and MetaTrader robot coding is the programming language. With C# as its base language, cTrader has a lot stronger toolset for coding, while MetaTrader offers a lot more trading-specific classes, events, and other programming entities.

Unlike MetaTrader, cTrader robots lack many of the chart and object interaction functions. If you plan to code a cBot, it will either work with bare price action or with some indicators.

cTrader trading orders are on par with those in MetaTrader 5. However, you won't be able to use multiple order fill types or expiration types. You may just set the plain expiry date/time for a pending order.

Robot structure

The code of a generic cBot consists of the following sections:

  • Declaration of necessary namespaces
  • Creation of the robot’s class:
    • Definition of input parameters
    • Declaration of supplementary objects and variables
    • Implementation of standard Robot class event handlers:
      • OnStart()
      • OnTick()
      • OnBar()
      • OnStop()
      • OnError()
      • MarketDepth.Updated
      • PendingOrders.Created
      • PendingOrders.Filled
      • PendingOrders.Modified
      • PendingOrders.Cancelled
      • Positions.Closed
      • Positions.Opened
      • Positions.Modified
    • Implementation of custom Robot class methods

Read further for detailed description of each of those sections.

Required namespaces

Same as with custom indicators, you will need to use cAlgo.API namespace if you want to create a working cBot. If you plan your robot to be based on some of the standard indicators, cAlgo.API.Indicators namespace is another one to add to your list.

There is optional cAlgo.API.Requests namespace if you want to be able to create trade requests, but the same functionality is reached with default Trade object that does not require this namespace.

Any standard C# namespace (e.g., System or System.Linq) can also be used in your custom robot if you wish to access their language constructions.

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Requests;
using cAlgo.Indicators;

Robot's class

A [Robot] attribute tells cTrader that the next class declaration is a robot. The class should also belong to cAlgo.Robots namespace. [Robot] attribute can use only one property - a text name for your robot.

Your class declaration could start with something like this:

namespace cAlgo.Robots
{
    [Robot("Sample Robot")]
    public class SampleRobot : Robot
    {

Input parameters

Similarly to the custom indicators coding, your robot class can have some input parameters. Every parameter has to be declared as a class member and feature a [Parameter] attribute. It has the following four properties (all are optional):

  • text name
  • input parameter default value
  • input parameter minimum value
  • input parameter maximum value

{ get; set; } accessors are a must if you want your input parameters to work.

        [Parameter("Stop Loss (pips)", DefaultValue = 50, MinValue = 1)]
        public int StopLoss { get; set; }

        [Parameter("Take Profit (pips)", DefaultValue = 50, MinValue = 1)]
        public int TakeProfit { get; set; }

Supplementary objects

The same as in cTrader indicator coding, you might want to add some auxiliary objects to use them inside main event handlers in your trading robot. For example, you can declare a position object to track the status of positions:

        private Position position;

Or a standard indicator object to use in trading algorithm:

        private ParabolicSAR parabolicSAR;

Or some C# system object:

        private Random random = new Random();

Or a simple variable that will be used in more than one class method:

        private double sl;

Standard event handlers

Similarly to MetaTrader and other algorithmic trading models, cTrader robots are executed based on predefined standard event handlers, which are implemented by coders but are called by the trading environment. Neither of the following standard handlers are obligatory, but a working robot will need at least one of them.

OnStart()

This handler is called upon adding an instance of a robot to a chart. It is an analogue of OnInit() MT5 handler and cTrader indicator's Initialize() handler. You may use it to calculate values dependent on input parameters or to prepare indicators for further use inside your robot:

        protected override void OnStart()
        {
            bollingerBands = Indicators.BollingerBands(Source, Periods, Deviations, MAType);
        }
OnTick()

As its name suggests, OnTick() is called every tick. Its MT5 cousin has the same name. Usually, you would put the cBot's main trading logic either inside OnTick() or a related OnBar() handler.

        protected override void OnTick()
        {
            if (position == null) return;

            double distance = Symbol.Bid - position.EntryPrice;

            if (distance >= Trigger * Symbol.PipSize)
            {
                double newStopLossPrice = Symbol.Bid - TrailingStop * Symbol.PipSize;
                if (position.StopLoss == null || newStopLossPrice > position.StopLoss)
                {
                    Trade.ModifyPosition(position, newStopLossPrice, position.TakeProfit);
                }
            }
        }
OnBar()

This is something both MT4 and MT5 are lacking — a special event handler that is called each time a new bar appears on the chart. If you want your robot to act only when the previous bar is closed, this standard handler is where you put your main trading code:

        protected override void OnBar()
        {
            if (Trade.IsExecuting) return;
            bool isLongPositionOpen = _position != null && _position.TradeType == TradeType.Buy;
            bool isShortPositionOpen = _position != null && _position.TradeType == TradeType.Sell;
            double lastValue = _zigZag.Result.LastValue;
            if (!double.IsNaN(lastValue))
            {
                if (lastValue < _prevValue && !isLongPositionOpen)
                {
                     ClosePosition();
                     Buy();
                }
                else if (lastValue > _prevValue && _prevValue > 0.0 && !isShortPositionOpen)
                {
                    ClosePosition();
                    Sell();
                }
                _prevValue = lastValue;                
            }
        }
PendingOrders.Created

An action that is triggered when a pending order is placed in the market. PendingOrders.Created is somewhat similar to OnTradeTransaction() and OnTrade() in MQL5 but is more specific. It can only be used if you need the robot to perform some actions upon a pending order placement.

                PendingOrders.Created += PendingOrdersOnCreated;
                // ...
        protected void PendingOrdersOnCreated(PendingOrderModifiedEventArgs args)
        {
            Print("Pending order with ID {0} was created.", args.PendingOrder.Id);
        }
PendingOrders.Filled

This action is triggered when position is opened in trader's account. Like with PendingOrders.Created, it is covered by OnTradeTransaction() and OnTrade() in MT5. You are expected to perform operations on your positions in this handler. For example, you could apply a stop-loss:

                PendingOrders.Filled += PendingOrdersOnFilled;
                // ...
        protected void PendingOrdersOnFilled(PendingOrderFilledEventArgs args)
        {
            args.Position.ModifyStopLossPrice(GetAbsoluteStopLoss(args.Position, StopLoss));
        }
Positions.Closed

As you have probably guessed by its name, this action gets triggered immediately upon closing a position. In MetaTrader 5, you would use either OnTradeTransaction() or OnTrade() to get the same functionality. It is a useful action if you want to open a new position when the old one closes, or if you want to stop executing your robot altogether:

                Positions.Closed += PositionsOnClosed;
                // ...
        private void PositionsOnClosed(PositionClosedEventArgs args)
        {
            Stop();
        }
OnStop()

A handler that is called on stopping the cBot. It is virtually the same as OnDeinit() in MetaTrader 5.

        protected override void OnStop()
        {
            Print("cBot deinitialized.");
        }
OnError()

A special Robot class member that handles situations with errors. Errors are processed quite differently in MT5. Generally, you would want to stop executing your robot on some errors:

        protected override void OnError(Error error)
        {
            Print("{0}", error.Code);
            if (error.Code == ErrorCode.BadVolume) Stop();
        }
MarketDepth.Updated

Updated event is not a part of the Robot class but is rather a member of MarketDepth interface. It is used to handle changes in Depth of Market for a given currency pair. You would be using OnBookEvent() in its place in MetaTrader.

_marketDepth = MarketData.GetMarketDepth(Symbol);
_marketDepth.Updated += MarketDepthUpdated;

void MarketDepthUpdated()
{
    foreach (var entry in _marketDepth.AskEntries)
    {
        Print("{0}", entry.Price);
    }
}

Custom Robot class methods

While this stage may be avoided for the majority of simple robots, you might want to add your own functions to make things more organized. For example, this custom method can be used for position closing combined with tracking of a currently open position via a separately declared object:

private void ClosePosition()
{
    if (position != null)
    {
        Trade.Close(position);
        position = null;
    }
}

Sample Robot Code Explained

Taking all what we have learned above together yields us a picture of cBot's code as a whole. Below, is the source code of the Sample Martingale Robot, which is bundled with cAlgo installation and is simple enough to be easily understood by a cTrader newbie, yet uses enough API instances to teach something useful. As its name suggests, this robot implements a Martingale trading system. The first trade direction is random. If it is a winner, a new random trade is opened. If it is a loser, a new trade is opened in the same direction but with doubled trading volume, and so on.

My explanations are given as code comments just above the line they explain:

// System namespace will be used for getting a random number via Random class.
using System;
// Main cAlgo API namespace
using cAlgo.API;
// Used for MarketOrderRequest class to send market execution trading orders.
using cAlgo.API.Requests;
// This namespace is not used as this robot does not use any indicators.
using cAlgo.Indicators;

// Next class declaration is a part of cAlgo.Robots namespace.
namespace cAlgo.Robots
{
    // Robot attribute to tell cTrader that the next declaration is actually a robot (declared with UTC timezone).
    [Robot(TimeZone = TimeZones.UTC)]
    // Robot class declaration.
    public class SampleMartingaleRobot : Robot
    {
        // Input parameter for initial volume. Default = 0.1 standard lot, cannot be less than 0.
        [Parameter("Initial Volume", DefaultValue = 10000, MinValue = 0)]
        // It is of type int and can be viewed and modified by user.
        public int InitialVolume { get; set; }

        // Stop-loss parameter in pips. Default is 40.
        [Parameter("Stop Loss", DefaultValue = 40)]
        // Also an integer number.
        public int StopLoss { get; set; }

        // Take-profit parameter in pips.
        [Parameter("Take Profit", DefaultValue = 40)]
        // It is of the same type int.
        public int TakeProfit { get; set; }

        // A new random number generator is declared.
        private Random random = new Random();
        // Position object to store information about trading positions.
        private Position position;

        // This function will be executed on initialization of the cBot.
        // override keyword is used to 'overwrite' the Robot class OnStart() implementation.
        protected override void OnStart()
        {
            // Custom method is called to send a trading order
            // of initial volume and random direction.
            ExecuteOrder(InitialVolume, GetRandomTradeCommand());
        }

        // Custom trading function declaration. Receives volume and trade direction as arguments.
        private void ExecuteOrder(int volume, TradeType tradeType)
        {
            // New request object is declared, passing the same 
            // parameters to MarketOrderRequest class constructor.
            var request = new MarketOrderRequest(tradeType, volume)
                {
                    // Label for the sent order.
                    Label = "SampleMartingaleRobot",
                    // Stop-loss for the order.
                    StopLossPips = StopLoss,
                    // Take-profit for the order.
                    TakeProfitPips = TakeProfit
                };
            // Using standard Trade object to send the formed request.
            Trade.Send(request);

        }

        // Standard event handler that triggers upon position opening.
        // Receives the position object as an argument.
        protected override void OnPositionOpened(Position openedPosition)
        {
            // Remember the position in the custom object.
            position = openedPosition;
        }

        // Standard event handler that triggers upon position closing.
        // Receives the position object as an argument.
        protected override void OnPositionClosed(Position closedPosition)
        {
            // If closed position was in profit
            if (closedPosition.GrossProfit > 0)
            {
                // Call custom class method to send a market order 
                // with initial volume and random direction.
                ExecuteOrder(InitialVolume, GetRandomTradeCommand());
            }
            // If closed position was in loss or at breakeven
            else
            {
                // Call custom class method to send a market order 
                // with doubled volume and the same direction.
                ExecuteOrder((int) position.Volume * 2, position.TradeType);
            }
        }

        // Standard error-handling method. Receives error object as an argument.
        protected override void OnError(Error error)
        {
            // If error is generated due to badly formed volume
            if (error.Code == ErrorCode.BadVolume)
                // Stop executing the cBot.
                Stop();
        }

        // Custom function to get random trade direction based on random number.
        private TradeType GetRandomTradeCommand()
        {
            // If random integer number between 0 and 1 is 0, then return Buy direction.
            // If random integer number between 0 and 1 is 1, then return Sell direction.
            return random.Next(2) == 0 ? TradeType.Buy : TradeType.Sell;
        }
    }
}

What's next?

Now that you are acquainted with the basics of Forex robot-building in cTrader Automate trading environment, you may start developing your own automated system. It is recommend following these steps when you decide to build a cTrader robot:

  1. Look through sample robots' source code, which is bundled with cTrader installation.
  2. Download some community-contributed robots from cTDN and browse their code.
  3. Use cTrader API Reference Explorer to get more information on something you cannot understand.
  4. Visit cTDN forums when you need help.

If you have some questions about cTrader Automate robots (cBots) or the IDE used to code these robots, please don't hesitate to participate in our forum on trading platforms.


If you want to get news of the most recent updates to our guides or anything else related to Forex trading, you can subscribe to our monthly newsletter.

© 2005–2021

EarnForex.com

Design — Mart Studio

Forex trading bears intrinsic risks of loss. You must understand that Forex trading, while potentially profitable, can make you lose your money. Never trade with the money that you cannot afford to lose! Trading with leverage can wipe your account even faster.

CFDs are leveraged products and as such loses may be more than the initial invested capital. Trading in CFDs carry a high level of risk thus may not be appropriate for all investors.