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

Основы Python для алгоритмической торговли

Трейдеры используют Python для создания торговых ботов, технических индикаторов, плагинов и других инструментов, помогающих в алгоритмической торговле или ручных операциях, и эта статья объясняет основы программирования на Python.

Python — это популярный высокоуровневый язык программирования общего назначения, используемый для веб-разработки, науки о данных, автоматизации и, совсем недавно, для алгоритмической торговли в cTrader. Независимо от того, создаете ли вы простых торговых ботов на основе сигналов или сложные стратегии с несколькими индикаторами, Python делает процесс быстрее, эффективнее и проще в управлении.

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

  • Пользуйтесь встроенной поддержкой Python и бесшовным доступом к cTrader Algo API, ведь cTrader — единственная крупная платформа, предлагающая нативную интеграцию из коробки. Не требуются плагины, адаптеры или сложные настройки.
  • Сосредоточьтесь больше на том, чего должна достичь ваша стратегия, поскольку логику становится проще собрать. Наслаждайтесь быстрой итерацией, повторно используемыми компонентами и масштабируемой производительностью.
  • Разрабатывайте быстро, отлаживайте умнее и сотрудничайте без барьеров. Ваш код всегда будет адаптируемым, надежным и удобным в обслуживании.
  • Получайте доступ и пользуйтесь тысячами бесплатных библиотек с открытым исходным кодом, подробными руководствами, примерами кода и сильной поддержкой сообщества по всему миру.

Переменные и типы данных

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

Вы создаете переменные, просто присваивая им значение:

1
2
3
4
price = 105.50         # float
enabled = True         # boolean
symbol = "EURUSD"      # string
orders = []            # empty list

Распространенные типы данных

Тип Описание Пример
int Целые числа 5, 100, api.StepPips
float Десятичные числа 1.1234, 100.5, api.Symbol.Ask
bool Логические значения True, False
str Строки "EURUSD", api.Label
list Коллекция элементов [1, 2, 3], [p for p in api.Positions]

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

Вот пример:

1
2
3
4
class GridcBot():
    def on_start(self):
        self.volumeInUnits = api.Symbol.QuantityToVolumeInUnits(api.VolumeInLots)
        self.enoughMoney = True

В этом случае self.volumeInUnits — это float, используемый для размещения ордеров, а self.enoughMoney — это bool, контролирующий, следует ли продолжать открывать позиции в сеточной стратегии.

Классы и объекты

Python — это объектно-ориентированный язык программирования (ООП), предоставляющий инструменты для организации кода через классы и объекты, которые моделируют реальные сущности и поведение. Класс определяет шаблон для создания объектов, которые являются экземплярами этого класса со своими уникальными данными и поведением. Ключевые концепции описаны ниже:

  • Класс определяется с помощью ключевого слова class.
  • Метод __init__ (конструктор) обычно используется для инициализации состояния объекта.
  • Методы экземпляра определяют поведение.
  • Атрибуты хранят данные, специфичные для каждого объекта.

Например, вы можете создать класс Car:

1
2
3
4
5
6
7
class Car:
    def __init__(self, color, speed):
        self.color = color
        self.speed = speed

    def accelerate(self):
        self.speed += 10

Затем вы можете создать объекты класса Car и взаимодействовать с ними:

1
2
3
my_car = Car("red", 50)
my_car.accelerate()
print(my_car.speed)  # This returns 60

В контексте cTrader

В cTrader сиБоты, индикаторы и плагины реализуются как классы, инкапсулирующие всю их логику. Алгоритмы взаимодействуют с платформой cTrader, используя специальный объект api, который предоставляет доступ к инструментам, графикам, ордерам, индикаторам и многому другому.

Вы можете создать класс SimplecBot в cTrader следующим образом:

1
2
3
class SimplecBot():
    def on_start(self):
        api.Print("cBot started")

Класс SimplecBot включает единственный метод (on_start(self)), который выполняется один раз при запуске торгового бота. Функция api.Print записывает сообщение в лог, демонстрируя, как методы объекта инкапсулируют торговое поведение.

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

Рассмотрим этот пример пересечения MACD, который инициализирует индикатор MACD при запуске и размещает ордер на покупку при возникновении сигнала пересечения MACD:

1
2
3
4
5
6
7
class MACDCrossover():
    def on_start(self):
        self.macd = api.Indicators.MacdCrossOver(api.Source, api.LongCycle, api.ShortCycle, api.SignalPeriods)

    def on_bar_closed(self):
        if self.macd.MACD.Last(0) > self.macd.Signal.Last(0):
            api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, volume)

Ключевое слово self

Внутри класса self ссылается на экземпляр класса и предоставляет доступ к его переменным и методам. self используется для:

  • Хранения данных, таких как результаты индикаторов, объемы позиций и значения конфигурации.
  • Отслеживания внутреннего состояния между различными вызовами методов, такими как on_start(), on_tick() или on_bar_closed().

Этот пример демонстрирует, как используется self:

1
2
3
4
class MACDCrossover():
    def on_start(self):
        self.volume = api.Symbol.QuantityToVolumeInUnits(api.VolumeInLots)
        self.macd = api.Indicators.MacdCrossOver(api.Source, api.LongCycle, api.ShortCycle, api.SignalPeriods)

В этом случае self.volume хранит рассчитанный объем для использования в сделках, а self.macd хранит объект индикатора MACD, позволяя получать доступ к его значениям на протяжении всего жизненного цикла сиБота. Без self эти переменные были бы локальными для on_start() и недоступными в on_bar_closed(), где могут приниматься торговые решения.

Методы и функции

Функции — это блоки повторно используемого кода, применяемые для выполнения конкретной задачи. Когда функции определяются внутри класса, они называются методами и работают с внутренним состоянием класса через параметр self. В контексте алгоритмической торговли с cTrader методы определяют, как ваш торговый алгоритм ведет себя при различных условиях, таких как рыночные тики, закрытие баров или инициализация.

Платформа автоматически вызывает специальные методы (известные как обработчики событий) в подходящее время, например:

on_start(self) — вызывается один раз при инициализации бота.

1
2
def on_start(self):
    api.Print("cBot started")

on_tick(self) — вызывается при каждом входящем рыночном тике.

1
2
def on_tick(self):
    api.Print("Tick received")

on_bar_closed(self) — вызывается при закрытии новой свечи/бара (идеально подходит для стратегий, основанных на времени).

1
2
3
4
def on_bar_closed(self):
    if Functions.HasCrossedAbove(...):
        self.close_positions(TradeType.Sell)
        api.ExecuteMarketOrder(...)

Пользовательские или вспомогательные методы

Чтобы избежать переписывания и дублирования кода, вы можете определить свои собственные методы. Такие пользовательские методы должны инкапсулировать общую логику, которую может повторно использовать ваш торговый робот.

Метод ниже возвращает все открытые позиции, связанные с меткой текущего сиБота, что упрощает отслеживание или закрытие позиций последовательно.

1
2
def get_bot_positions(self):
    return api.Positions.FindAll(api.Label)

Рассмотрим другой метод, который вычисляет расстояние в пипах между текущей ценой и ценой входа позиции, что критически важно для принятия решений в стратегиях на основе сетки.

1
2
def get_distance_in_pips(self, position):
    return (position.EntryPrice - api.Symbol.Ask if position.TradeType == TradeType.Buy else api.Symbol.Bid - position.EntryPrice) / api.Symbol.PipSize

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

Например, после определения get_bot_positions() вы можете вызвать его внутри другого метода для закрытия всех позиций:

1
2
3
4
def close_positions(self, tradeType):
    for position in self.get_bot_positions():   # Helper method call
        if position.TradeType == tradeType:
            api.ClosePosition(position)

Здесь self.get_bot_positions() вызывает вспомогательный метод, возвращая только позиции, связанные с меткой текущего бота.

Аналогично, метод get_distance_in_pips() можно использовать повторно для принятия торговых решений. Например, в стратегии сетки вы можете открыть новый ордер только в том случае, если рынок достаточно отдалился от последней позиции:

1
2
3
4
5
6
7
def on_tick(self):
    grid_positions = self.get_bot_positions()
    if len(grid_positions) > 0:
        position_with_highest_pips = sorted(grid_positions, key=lambda pos: pos.Pips, reverse=True)[0]
        distance = self.get_distance_in_pips(position_with_highest_pips)  # Helper method call
        if distance >= api.StepPips:
            self.open_position()

Другие концепции

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

Пространства имен в Python помогают организовать код и избежать конфликтов имен, группируя связанные идентификаторы (переменные, функции, классы). Импорт позволяет подключать внешние модули, библиотеки или пакеты для расширения функциональности за пределами стандартных возможностей Python.

В Python вы можете импортировать стандартные модули или пользовательские пакеты:

1
2
import math
print(math.sqrt(16))  # 4.0

Вы также можете импортировать конкретные функции или классы:

1
2
from math import sqrt
print(sqrt(25))  # 5.0

При написании алгоритмов для cTrader на Python импорт работает несколько иначе, поскольку cTrader интегрируется с cTrader Algo API на основе .NET. Эта настройка включает мост, который позволяет коду Python взаимодействовать со сборками .NET, и это объясняет, почему типичный сиБот начинается с:

1
2
3
4
5
6
7
8
9
import clr

clr.AddReference("cAlgo.API")

# Import cAlgo API types
from cAlgo.API import *

# Import trading wrapper functions
from robot_wrapper import *

Где:

  • import clr загружает Common Language Runtime (CLR), позволяя Python взаимодействовать с библиотеками .NET.
  • clr.AddReference("cAlgo.API") ссылается на сборку cAlgo API, которая содержит все связанные с торговлей классы, методы и объекты.
  • from cAlgo.API import * импортирует все типы из API (например, TradeType, Color, Position).
  • from robot_wrapper import * загружает вспомогательные функции и обертки, которые упрощают общие торговые операции.

Система пространств имен гарантирует, что:

  • Встроенные имена Python (такие как print) не конфликтуют с именами cTrader (такими как api.Print).
  • Все объекты, связанные с торговлей (api.Symbol, api.Indicators, api.ExecuteMarketOrder), поступают из пространства имен cTrader Algo API, сохраняя их организованными и отдельными.
  • Вы можете расширить функциональность, импортируя собственные пользовательские пакеты для работы вместе со встроенными типами cTrader.

Циклы

Циклы позволяют повторять операции над последовательностью значений или до выполнения условия. В алгоритмической торговле циклы часто используются для обработки позиций (например, закрытия, фильтрации или обновления), проверки условий по нескольким активам или индикаторам, реализации стратегий сетки или мартингейла, где требуется повторное размещение ордеров, и многого другого.

В Python итерация проста и поддерживает как циклы for (для итерации по коллекциям), так и циклы while (для повторения до изменения условия).

Вы можете написать цикл для закрытия позиций:

1
2
3
4
def close_positions(self, tradeType):
    for position in self.get_bot_positions():
        if position.TradeType == tradeType:
            api.ClosePosition(position)

В этом случае:

  • self.get_bot_positions() возвращает все позиции, принадлежащие текущему сиБоту.
  • Цикл for проходит по каждой позиции.
  • Совпадающие позиции закрываются с помощью api.ClosePosition.

Рассмотрим другой цикл, который перебирает все открытые позиции сетки, закрывает их одну за другой и рекурсивно проверяет, остались ли какие-либо позиции (для безопасности в случае частичного закрытия).

1
2
3
4
5
6
def close_grid_positions(self):
    for position in self.get_grid_positions():
        position.Close()

    if len(self.get_grid_positions()) > 0:
        self.close_grid_positions()

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

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

Python поддерживает три основные условные конструкции:

  • if – выполняет код, когда условие истинно.
  • elif (else if) – проверяет другое условие, если первое было ложным.
  • else – предоставляет резервный вариант, когда ни одно условие не выполнено.

Рассмотрим условия пересечения MACD, выраженные в этом коде:

1
2
3
4
if self.macd.MACD.Last(0) > self.macd.Signal.Last(0):
    api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, self.volumeInUnits, api.Label, api.StopLossInPips, api.TakeProfitInPips)
elif self.macd.MACD.Last(0) < self.macd.Signal.Last(0):
    api.ExecuteMarketOrder(TradeType.Sell, api.SymbolName, self.volumeInUnits, api.Label, api.StopLossInPips, api.TakeProfitInPips)

Эта структура:

  • Размещает ордер на покупку, когда линия MACD находится выше сигнальной линии.
  • Размещает ордер на продажу, когда линия MACD находится ниже сигнальной линии.
  • Пропускает торговлю, если нет четкого сигнала.

Списки

Python предоставляет мощные и лаконичные инструменты для работы с коллекциями данных, такими как сделки, цены или индикаторы. Два наиболее распространенных — это списковые включения и встроенные функции агрегации, такие как sum().

Например, вы можете написать код, который перебирает numbers и сохраняет только четные числа таким образом:

1
2
numbers = [1, 2, 3, 4, 5]
squares = [n**2 for n in numbers if n % 2 == 0]

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

1
positions = [p for p in api.Positions if p.SymbolName == api.SymbolName]

Агрегирование прибыли или объема с помощью sum() — стандартный подход в стратегиях сеточной торговли для определения момента, когда общая прибыль достигает целевого значения.

1
net_profit_sum = sum([p.NetProfit for p in grid_positions])

Вы можете комбинировать фильтрацию и агрегацию таким образом:

1
2
buy_positions = [p for p in api.Positions if p.TradeType == TradeType.Buy]
avg_buy_price = sum([p.EntryPrice * p.VolumeInUnits for p in buy_positions]) / sum([p.VolumeInUnits for p in buy_positions])

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

Python по умолчанию следует модели синхронного выполнения. Методы жизненного цикла вашего алгоритма, такие как on_start, on_tick и on_bar_closed, запускаются циклом событий платформы по одному за раз.

Вот базовое представление синхронного торгового потока:

graph TD
    A(Получение сигнала) ==> B(Исполнение ордера)
    B ==> C(Позиция<br>достигает TP/SL)
    C ==> D(Закрытие позиции)
    D ==> A

В синхронных потоках любая длительная операция может заблокировать поток, потенциально задерживая следующее событие. Однако в реальных стратегиях алгоритмы обычно инициируют неблокирующие действия, такие как отправка ордера или установка стоп-лосса и тейк-профита, а затем ожидают будущих событий (таких как новый тик или бар) для обработки результатов. Эта настройка приводит к асинхронной оркестровке, даже если конструкции async/await Python не используются.

Примечание

Для большинства торговых алгоритмов явное асинхронное программирование не требуется.

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

В приведенном ниже примере стратегия независимо оценивает сигналы на покупку и продажу и соответствующим образом реагирует, без какого-либо перекрытия в контексте выполнения.

graph TD
    A([Получение сигнала]) ==> B([Открытие<br>ордера на покупку]) & C([Открытие ордера<br>на продажу<br>для хеджирования]) ==> D([Ордер достигает<br>тейк-профита<br>или стоп-лосса])
    D ==> E([Закрытие позиции])
    E ==> A

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

Image title