Advanced Operations With cBots
Introduction
This guide is primarily intended as a complement to our extensive list of cBot code samples.
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 over 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. 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);
}
}
}
|
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);
}
}
}
|
Getting Historical Orders Data
cTrader provides the HistoricalOrder
collection to enable users to retrieve information on orders, including completed market orders and pending orders that were executed or cancelled. The retrievable details include the order ID, its type, label, target price, expiration, etc.
The cBot code below shows you how to get the details for historical orders:
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 | using cAlgo.API;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(AccessRights = AccessRights.None)]
public class HistoricalOrdersExample : Robot
{
protected override void OnStart()
{
var historicalOrders = HistoricalOrders
foreach (var order in historicalOrders)
{
Print($"Order ID: {order.Id}");
Print($"Type: {order.OrderType}");
Print($"Volume: {order.VolumeInUnits}");
Print($"Price: {order.TargetPrice}");
Print($"Expiration: {order.ExpirationTime}");
Print($"Comment: {order.Comment}");
Print($"Label: {order.Label}");
Print($"Stop Loss: {order.StopLoss}");
Print($"Take Profit: {order.TakeProfit}");
Print($"TradeType: {order.TradeType}");
Print($"Quantity: {order.Quantity}");
Print($"Label: {order.Label}");
}
}
}
}
|
Using Historical Trades Data
When coding cBots, 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());
}
}
}
|
In cTrader, deals execute orders and cause positions to be opened or closed. Depending on the market liquidity, an order may be filled entirely by a single deal or in parts by several deals. See Positions and Deals to learn more.
The cBot code below shows you how to retrieve information about multiple deals that exist in a single position:
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 | using System;
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(AccessRights = AccessRights.None)]
public class DealsExample : Robot
{
protected override void OnStart()
{
var position = ExecuteMarketOrder(TradeType.Buy, "EURUSD", 10000);
var deals = position.Position.Deals;
foreach (var deal in deals)
{
Print(deal.ExecutionTime + " " + deal.Status + " " + deal.Id);
}
ClosePosition(position.Position, 3000);
ClosePosition(position.Position, 7000);
var closing_deals = History.FindByPositionId(position.Position.Id);
foreach (var closing_deal in closing_deals)
{
Print(closing_deal.ClosingDeal.ExecutionTime + " " + closing_deal.ClosingDeal.VolumeInUnits);
}
}
protected override void OnStop()
{
}
}
}
|
Converting Lots to Units
By default, cTrader algos calculate volume in units instead of lots. However, in some cases you may find that working with lots is more convenient and familiar.
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.
Working With Colors
cBots can use custom colors when performing various operations (e.g., when displaying text on trading charts). To add colour as a customisable parameter, add the following code to your parameter declarations.
| [Parameter("Color", DefaultValue = "#f54242")]
public Color DrawingColor { get; set; }
|
Note that you can use both hexadecimal colour codes and colour names (such as "red"
) when defining the DefaultValue
of such a parameter.
To illustrate how colour parameters work, we will create a simple cBot that, upon being started, will write text on the trading chart on which it is operating. The colour of this text will be customisable via the TextColor
parameter.
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 | using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(AccessRights = AccessRights.None)]
public class SampleColorcBot : Robot
{
[Parameter("Text Color", DefaultValue = "red")]
public Color TextColor { get; set; }
protected override void OnStart()
{
var staticText = Chart.DrawStaticText("static", "Sample text to demonstrate how color works", VerticalAlignment.Center, HorizontalAlignment.Center, TextColor);
}
}
}
|