Skip to content

Additional cBot Features

Introduction

This guide is primarily intended as a complement to our extensive list of cBot code samples provided in the core documentation.

Trading Other Symbols

You can develop a cBot that trades multiple symbols in addition to the one specified when creating a new instance.

Making it possible, Symbols is a collection of all symbols available to your trading account. You can iterate over it by using loops; alternatively, you can search it to find specific symbols.

To correctly work with multiple symbols, cBots need to know the following information.

  • The minimum/maximum allowed volume.
  • The volume step.
  • The pip tick size.
  • The pip tick value (the monetary value of one pip/tick in the account deposit currency).

This data can be learned from a Symbol object instance. In the below code, we find a symbol with "GBPUSD" as its name and then create a market order for it. We also use its minimum allowed volume as order volume.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  [Robot()]
  public class Sample_cBot : Robot
  {
      protected override void OnStart()
      {
          var symbol = Symbols.GetSymbol("GBPUSD");

          if (symbol is not null)
          {
              _ = ExecuteMarketOrder(TradeType.Sell, symbol.Name, symbol.VolumeInUnitsMin);
          }
      }
  }

In the example above, the underscore represents a discarded variable. We are not interested in its value, we just want to execute a market order.

Calculating the Risk Percentage for Positions/Orders

When placing orders, most traders use a certain percentage of their account equity as order volume. In turn, it is possible to calculate the risk percentage for a given symbol and, subsequently, place an order using this percentage as its volume.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace Samples
{
    [Robot(AccessRights = AccessRights.None)]
    public class RiskPercentageSample : Robot
    {
        [Parameter("Risk %", DefaultValue = 1, MinValue = 0.1, MaxValue = 100)]
        public double RiskPercentage { get; set; }

        [Parameter("Stop Loss (Pips)", DefaultValue = 100, MinValue = 0)]
        public double StopLossInPips { get; set; }

        protected override void OnStart()
        {
            // You can also use 'Account.Equity' instead of 'Balance'
            var volume = Symbol.GetVolume(RiskPercentage, Account.Balance, StopLossInPips);

            _ = ExecuteMarketOrder(TradeType.Buy, SymbolName, volume, string.Empty, StopLossInPips, default);
        }
    }

    public static class SymbolExtensions
    {
        /// <summary>
        /// Returns the amount of risk percentage based on stop loss amount
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="stopLossInPips">Stop loss amount in Pips</param>
        /// <param name="accountBalance">The account balance</param>
        /// <param name="volume">The volume amount in units (Not lots)</param>
        /// <returns>double</returns>
        public static double GetRiskPercentage(this Symbol symbol, double stopLossInPips, double accountBalance, double volume)
        {
            return Math.Abs(stopLossInPips) * symbol.PipValue / accountBalance * 100.0 * volume;
        }

        /// <summary>
        /// Returns the amount of stop loss in Pips based on risk percentage amount
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="riskPercentage">Risk percentage amount</param>
        /// <param name="accountBalance">The account balance</param>
        /// <param name="volume">The volume amount in units (Not lots)</param>
        /// <returns>double</returns>
        public static double GetStopLoss(this Symbol symbol, double riskPercentage, double accountBalance, double volume)
        {
            return riskPercentage / (symbol.PipValue / accountBalance * 100.0 * volume);
        }

        /// <summary>
        /// Returns the amount of volume based on your provided risk percentage and stop loss
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="riskPercentage">Risk percentage amount</param>
        /// <param name="accountBalance">The account balance</param>
        /// <param name="stopLossInPips">Stop loss amount in Pips</param>
        /// <returns>double</returns>
        public static double GetVolume(this Symbol symbol, double riskPercentage, double accountBalance, double stopLossInPips)
        {
            return symbol.NormalizeVolumeInUnits(riskPercentage / (Math.Abs(stopLossInPips) * symbol.PipValue / accountBalance * 100));
        }
    }
}

Converting Pips To Ticks

When coding cBots, it is easy to encounter bugs arising from the fact that some variable values are calculated in units while other values are calculated in pips.

The below SymbolExtensions class showcases how pips can be converted to ticks. It also demonstrates how pips can be added to an absolute price value.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace Samples
{
    [Robot(AccessRights = AccessRights.None)]
    public class Sample : Robot
    {
        protected override void OnStart()
        {
            var spreadInPips = Symbol.NormalizePips(Symbol.ToPips(Symbol.Spread));

            Print(spreadInPips);

            var spreadInTicks = Math.Round(Symbol.ToTicks(Symbol.Spread));

            Print(spreadInTicks);

            /* Calculating a long position stop loss using the
            absolute price value. */
            var stopLossInPips = 60;

            /* We use 'NormalizePips' to avoid the 'invalid decimal places'
            error in case our stop loss value has too many decimals. */
            var stopLossInPrice = Symbol.Ask - (Symbol.NormalizePips(stopLossInPips) * Symbol.PipSize);

            Print(stopLossInPrice);
        }
    }

    public static class SymbolExtensions
    {
        /// <summary>
        /// Returns a symbol pip value
        /// </summary>
        /// <param name="symbol"></param>
        /// <returns>double</returns>
        public static double GetPip(this Symbol symbol)
        {
            return symbol.TickSize / symbol.PipSize * Math.Pow(10, symbol.Digits);
        }

        /// <summary>
        /// Returns a price value in terms of pips
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="price">The price level</param>
        /// <returns>double</returns>
        public static double ToPips(this Symbol symbol, double price)
        {
            return price * symbol.GetPip();
        }

        /// <summary>
        /// Returns a price value in terms of ticks
        /// </summary>
        /// <param name="symbol"></param>
        /// <param name="price">The price level</param>
        /// <returns>double</returns>
        public static double ToTicks(this Symbol symbol, double price)
        {
            return price * Math.Pow(10, symbol.Digits);
        }

        /// <summary>
        /// Rounds a price level to the number of symbol digits
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="price">The price level</param>
        /// <returns>double</returns>
        public static double Round(this Symbol symbol, double price)
        {
            return Math.Round(price, symbol.Digits);
        }

        /// <summary>
        /// Normalize x Pips amount decimal places to something that can be used as a stop loss or take profit for an order.
        /// For example if symbol is EURUSD and you pass to this method 10.456775 it will return back 10.5
        /// </summary>
        /// <param name="symbol">The symbol</param>
        /// <param name="pips">The amount of Pips</param>
        /// <returns>double</returns>
        public static double NormalizePips(this Symbol symbol, double pips)
        {
            var currentPrice = Convert.ToDecimal(symbol.Bid);

            var pipSize = Convert.ToDecimal(symbol.PipSize);

            var pipsDecimal = Convert.ToDecimal(pips);

            var pipsAddedToCurrentPrice = Math.Round((pipsDecimal * pipSize) + currentPrice, symbol.Digits);

            var tickSize = Convert.ToDecimal(symbol.TickSize);

            var result = (pipsAddedToCurrentPrice - currentPrice) * tickSize / pipSize * Convert.ToDecimal(Math.Pow(10, symbol.Digits));

            return decimal.ToDouble(result);
        }
    }
}

Using Historical Trades Data

When using the cTrader Automate API, you have access to your account history. This means that you can iterate over your past trades and use their data for whatever purposes you have.

The below code snippet creates a CSV file containing all of our past trades and their trade data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using cAlgo.API;
using System.Text;
using System.IO;

namespace Samples
{
    /* We provide the access rights required for
    reading and writing in locally stored files. */
    [Robot(AccessRights = AccessRights.FullAccess)]
    public class Sample : Robot
    {
        protected override void OnStart()
        {
            var stringBuilder = new StringBuilder();

            _ = stringBuilder.AppendLine($"PositionId,TradeType,SymbolName,VolumeInUnits,EntryTime,EntryPrice,ClosingTime,ClosingPrice,NetProfit,Balance");

            // All trades are inside the 'History' collection
            foreach (var trade in History)
            {
                _ = stringBuilder.AppendLine($"{trade.PositionId},{trade.TradeType},{trade.SymbolName},{trade.VolumeInUnits},{trade.EntryTime:o},{trade.EntryPrice},{trade.ClosingTime:o},{trade.ClosingPrice},{trade.NetProfit},{trade.Balance}");
            }

            // We will save the CSV file on our desktop
            var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

            var filePath = Path.Combine(desktopPath, $"{Account.Number}_History.csv");

            File.WriteAllText(filePath, stringBuilder.ToString());
        }
    }
}

Converting Lots to Units

The cTrader Automate API calculates volume in units instead of lots. However, most Forex traders are only familiar with lots as the measure of volume.

You can convert lots to units by using the Symbol.QuantityToVolumeUnits() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
using System;
using cAlgo.API;
using System.Text;
using System.IO;

namespace Samples
{
    [Robot(AccessRights = AccessRights.None)]
    public class Sample : Robot
    {
        protected override void OnStart()
        {
            var lots = 2.5;

            var volumeInUnits = Symbol.QuantityToVolumeInUnits(lots);

            _ = ExecuteMarketOrder(TradeType.Sell, SymbolName, volumeInUnits);
        }
    }
}

Alternatively, you can use the Symbol.VolumeInUnitsToQuantity() method to convert units to lots.


Last update: December 5, 2022

Comments