Перейти к содержанию

C# для алгоритмической торговли

Что такое C#

C# — это объектно-ориентированный язык программирования, используемый для создания широкого спектра сервисов и приложений. Его четкий, структурированный синтаксис делает его легким для изучения и чтения даже для новичков, обеспечивая при этом структурированный подход к написанию компактного и многократно используемого кода.

Алгоритмы на C# за одну минуту!

  • Даже если вы никогда раньше не работали с C#, создание и развертывание вашего первого бота или индикатора займет всего пару минут.
  • Поскольку C# прост в использовании, вы можете быстро переписать любые фрагменты кода из этой документации под свои нужды.
  • При использовании C# вы можете получить доступ к большому количеству библиотек, содержащих предопределенные классы и методы. Эти методы могут эффективно справляться с обычными задачами, оставляя вам время для решения сложных торговых задач.
  • На C# вы можете писать код, который не блокирует серверные потоки при выполнении. Другими словами, вы можете запускать определенные задачи одновременно с другими задачами.

Что такое .NET?

Чтобы программы на C# могли работать, их исходный код должен быть скомпилирован в промежуточный язык (IL). Этот IL-код соответствует спецификации Common Language Infrastructure (CLI) и позже компилируется в нативные машинные инструкции во время выполнения. Фреймворк .NET предоставляет виртуальную среду выполнения, построенную на обширных библиотеках классов и Common Language Runtime (CLR) — реализации CLI от Microsoft.

Не вдаваясь в технические тонкости, .NET выполняет следующие функции:

  • Упрощение разработки и выполнения приложений: .NET SDK уже содержит несколько встроенных компиляторов и движков сборки, устраняя необходимость создания каких-либо пользовательских решений.
  • Предоставление библиотек времени выполнения: при добавлении новых типов данных или коллекций в ваш код вы часто обнаружите, что .NET уже предлагает подходящие классы и методы для этой задачи.

Основы C#

Типы данных и объявление переменных

Типы данных — это способ категоризации данных, чтобы C# точно знал, как обрабатывать переменные и свойства. В объявлениях переменных/свойств типы данных всегда предшествуют имени переменной/свойства.

1
string developer = "I am developing cBots, plugins, and indicators!";

В качестве альтернативы вы можете использовать ключевое слово var, чтобы избежать указания типа данных.

1
var developer = "I am developing cBots, plugins, and indicators!";

Объекты и классы

Думайте об объектах как об абстракциях материальных или нематериальных сущностей. Эти сущности могут иметь определенные характеристики (свойства) и могут выполнять различные операции (методы). В свою очередь, классы служат шаблонами для создания объектов.

Поскольку C# является объектно-ориентированным языком, классы также могут наследовать свойства и методы от других классов. Рассмотрим следующий пример, в котором мы объявляем новый класс NewBot, который наследуется от класса Robot. Мы также определяем некоторые новые свойства класса.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* We use the colon sign to designate inheritance.
Additionally, we used expressions in square brackets to
specify the parameters that apply to the entire class */
[Robot(AccessRights = AccessRights.None)]
public class NewBot : Robot
{
    /* We declare a custom class property and make it
    read-only. */
    public string CustomProperty { get; }

    /* In this declaration, we define the default value of a
    custom parameter. */
    [Parameter("BotName", DefaultValue = "Traders First!")]

    /* We declare the BotName property which is changeable via
    the "BotName" parameter. */
    public string BotName { get; }

    /* We also declare the BotComment parameter.
    It can be both read and set. */
    [Parameter("BotComment", DefaultValue = "Our super-duper bot!")]
    public string BotComment { get; set; }
}

Типы данных

Поскольку C# является строго типизированным языком, необходимо указывать типы данных при объявлении переменных и свойств классов. Вкратце, типы данных представляют собой уникальные классы с различными наборами поведений. Рассмотрим следующие примеры, в которых различные типы данных указаны для разных свойств классов и связанных параметров.

1
2
[Parameter("Price")]
public DataSeries Source { get; }

В приведенном выше коде тип данных DataSeries представляет собой список значений только для чтения и поэтому обычно используется для представления рыночных цен.

Когда необходимо создать параметры с несколькими вариантами, вы можете использовать встроенный тип данных enum, как описано ниже. Вы можете думать о enum как о специальных классах, содержащих различные константы.

1
2
3
4
5
6
7
8
9
public enum Option
{
    First,
    Second,
    Third
}

[Parameter("Option", DefaultValue = Option.Third)]
public Option SelectedOption { get; set; }

Для доступа к константе, содержащейся в enum, используйте следующий синтаксис.

1
var newOption = Option.First;

Если параметр или свойство должны быть числом, чаще всего вы будете использовать типы данных int (целое число) или double (десятичное число). Хотя тип double имеет меньшую точность, чем тип decimal, он также менее ресурсоемкий.

1
2
[Parameter("MA Periods", DefaultValue = 14, MinValue = 1, MaxValue = 20)]
public int Periods { get; set; }

Наконец, тип bool обычно представляет собой ввод «да» или «нет». Значения «да» и «нет» соответствуют true и false соответственно.

1
2
[Parameter("Message", DefaultValue = true)]
public bool DisplayChartMessage { get; set; }

Пространства имен

Пространства имен выступают в качестве специальных коллекций классов. После того как вы назначите класс пространству имен, к нему можно будет получить доступ, используя нотацию Namespace.ClassName. Мы назначим наш NewBot пространству имен CoolTradingBots.

1
2
3
4
5
6
7
8
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ...
    }
}

Методы класса

Методы класса определяются после объявления класса. Все объекты нашего класса NewBot смогут вызывать метод CustomTradeOperation(). Он принимает два аргумента, а именно объект Symbol и объект double. Наш метод должен принимать символ и выполнять какое-либо торговое действие для указанного объема

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ... Defining the class parameters.

        // We declare the CustomTradeOperation method. 
        protected override void CustomTradeOperation(string symbolName, double volume)

        {
            // This space is for declaring the method logic. 
        }
    }
}

Библиотеки классов

К библиотекам классов (и, следовательно, к методам классов) можно получить доступ по их пространству имен, как показано в примере ниже.

1
CoolTradingBots.NewBot.CustomTradeOperation(symbolName, 10000)

В качестве альтернативы вы можете ввести ключевое слово using в начале кода, чтобы указать определенные пространства имен и избежать избыточности. Рассмотрим следующий фрагмент кода.

1
2
3
using CoolTradingBots;

NewBot.CustomTradeOperation(symbolName, 10000)

Условные операторы

Для использования условных операторов используйте ключевое слово, за которым следует выражение в круглых скобках. В приведенном ниже примере используется ключевое слово if для завершения нашего метода CustomTradingOperation(). Для этого мы используем метод EvaluateMarket(), который определен в пространстве имен Analytics.Actions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace CoolTradingBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewBot : Robot
    {
        // ... Defining the class parameters.

        protected override void CustomTradeOperation(string symbolName, double volume)
        {
            // We declare and initialize the 'result' variable.
            var result = Analytics.Action.EvaluateMarket();

            // We use a conditional statement based on the IsSuccessful property.
            if (result.IsSuccessful)
            {
                Print("Operation successful!")
            }
        }
    }
}

Коллекции

Коллекции определяются как контейнеры, которые могут хранить один или несколько объектов определенного класса.

Коллекции полностью индексируются, что означает, что к их элементам можно получить доступ, передав определенное значение integer в квадратных скобках. Рассмотрим следующий пример, в котором метод Calculate() выводит свойства бара, доступ к которому осуществляется через его индекс.

1
2
3
4
5
public override void Calculate(int index)
{
       var bar = Bars[index];
       Print($"{bar.Open} | {bar.High} | {bar.Low} | {bar.Close}");
}

Создание расширений cTrader

В приведенном ниже фрагменте мы создаем базового торгового робота, используя только знания, рассмотренные в разделах выше.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System; 

namespace Spotware.cBots
{
    [Robot(AccessRights = AccessRights.None)]
    public class SuperAwesomeBot : Robot
    {

        /* In the below method, we define the operation(s) that our bot should
        perform when it is launched. */
        protected override void OnStart()
        {
            var result = ExecuteMarketOrder(TradeType.Buy, symbolName, 10000);

            /* We use a conditional statement using the IsSuccessful property of the
            TradeResult object. */
            if (result.IsSuccessful)
            {
                var position = result.Position;
                Print ("Position entry price is {0}", position.EntryPrice);
            } 
        }
    }
}

Синхронные и асинхронные операции

Как было сказано ранее, C# полностью поддерживает асинхронные операции. Диаграмма ниже определяет базовый пример того, как торговые действия выполняются при синхронном выполнении.

graph TD
    A(Получение<br>технического<br>сигнала) ==> B(Исполнение<br>рыночного ордера)
    B ==> C(Достижение<br>Take Profit/<br>Stop Loss)
    C ==> D(Закрытие позиции)
    D ==> A

Синхронное выполнение имеет один важный недостаток. В приведенном выше примере действие по исполнению рыночного ордера полностью занимает все потоки сервера, что означает, что ваш торговый робот не может делать ничего другого до завершения этой операции.

Это далеко от идеала, когда вы хотите быстро реагировать на события рынка и волатильность. В идеале ваш бот должен иметь возможность сохранять различные действия или приостанавливать определенные задачи, чтобы заняться другими, более неотложными делами. Мы расширим наш пример, чтобы лучше отразить, как выполняются асинхронные операции.

graph TD
    A([Получение<br>технического<br>сигнала]) ==> B([Размещение<br>ордера 'Buy']) & C([Размещение ордера<br>'Sell' для<br>хеджирования]) ==> D([Ордер достигает<br>Take Profit/<br>Stop Loss])
    D ==> E([Закрытие позиции])
    E ==> A

В приведенном выше примере наш торговый робот одновременно разместил ордера в обоих направлениях для хеджирования своих позиций. В отличие от синхронных операций, нет необходимости ждать завершения одной операции перед переходом к другой. Это значительно расширяет возможности разработчиков для создания эффективных и надежных торговых роботов.

Обратите внимание, что асинхронное выполнение отличается от многопоточности:

  • При асинхронном выполнении все задачи запускаются в одном потоке. Когда они сохраняются или приостанавливаются, они освобождают этот поток, и когда их выполнение продолжается позже, оно происходит в другом потоке, выбранном из пула потоков.
  • При многопоточности все задачи запускаются в разных потоках и продолжают свое выполнение в своих исходных потоках. Перетасовки потоков не происходит.

cTrader никогда не вызывает ваши методы параллельно, поэтому вам не нужно беспокоиться о проблемах многопоточности.

Image title