Построение торгового бота

  • Часть 1. Знакомство
  • Часть 2. Функция бэктестинга
  • Часть 3. Шаблоны стратегий
  • Часть 4. Живая торговля
  • Как реализовать бэктестер на Python

Часть 1. Знакомство

Отказ

Прежде чем я начну эту историю, я хочу вам кое-что рассказать. Я не финансовый консультант, и торговый бот, который мы собираемся построить, не о зарабатывании денег. Алгоритмическая торговля — это единое целое, и вы не можете зарабатывать деньги, просто прочитав несколько историй на Medium.

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

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

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

Я проведу вас через этот проект, чтобы объяснить вам его шаг за шагом. Во-первых, я стану с введением (эта история). Он будет очень общим, потому что цель состоит только в том, чтобы дать ключевые понятия. Все, кто заинтересован в создании торгового бота, могут прочитать его. Затем я поделюсь конкретной реализацией этого в Python. Если вы не Pythonista, возможно, вам это будет не интересно.

Выберите инструменты

Для создания торгового бота вам понадобятся только два инструмента:

  • Язык программирования
  • Сервер

Вы можете построить торгового бота на многих языках программирования. В следующей из этой серии мы сделаем это с помощью Python. Другой возможностью мог быть JavaScript с использованием среды Node.JS. И еще одной возможностью был бы MQL (если вы знаете Metatrader, вы знаете, о чем я говорю).

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

Затем о сервере. Это просто то, что отправляет запросы в API. Это то, что позволяет нам взаимодействовать с внешним миром, например, с брокером.

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

Для меня я запускаю своего торгового бота на Raspberry Pi. Это веселый и классный вариант. Если вы хотите пойти с ним, просто сделайте это. Наличие Pi не является пустой тратой денег, так как оно может быть полезно для других проектов.

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

Принцип абстракции

Вы должны попытаться абстрагировать своего бота как можно больше. Я имею в виду, что вы не хотите делать торгового бота Binance, например, (или, возможно, вы хотите, я не знаю). Вы хотите сделать торгового бота.

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

Таким образом, ваш бот должен быть максимально абстрактным. Это не должно зависеть от обмена. Это не должно зависеть от рынка. Он не должен зависеть от инфраструктуры, на которой он будет работать. Это не должно зависеть от [заполните этот текст тем, чем вы хотите].

Определите свои потребности

Что должен делать ваш бот? Должен ли он иметь возможность проводить бэктестинг? Должен ли он иметь возможность размещать стоп-ордера? И так далее…

Благодаря принципу абстракции, это не проблема, если ваши потребности развиваются при программировании вашего торгового бота. Но в качестве общего совета в программировании важно определить свои потребности перед началом проекта.

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

Пример спецификаций, которые вы можете сделать:

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

Блок-схема действий бота

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

Например:

  1. Подключитесь к базе данных.
  2. Извлеките данные базы данных.
  3. Есть ли стратегия?
  4. ДА: получение рыночных данных.
  5. Рассчитайте показатели.
  6. Встречается ли условие остановки стратегии?
  7. НЕТ: встречается ли условие открытого ордера?
  8. ДА: получить баланс пользователя, чтобы проверить, можно ли открыть ордер.
  9. И так далее…

Благодаря этой блок-схеме (сделайте это графически, здесь я ее написал, но лучше иметь визуальное представление) мы можем различать некоторые большие блоки:

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

Функции

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

Например, абстракции хранилища данных потребуются некоторые функции для записи данных и чтения данных. Но что, если мы хотим где-то записать данные, но прочитать их в другом месте? Нам нужно сделать Data Writer и Data Reader.

Цикл

Наша блок-схема также может заставить нас задуматься о нашем цикле торгового бота и в более общем плане о том, как он будет работать.

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

Часть 2. Функция бэктестинга

Есть репозиторий GitHub, который я сделал для этой серии. Если вы хотите использовать его для отслеживания кода, вы можете найти его здесь: Trading Bot Series.

Что мы получим

В конце этой истории у нас будет гибкий торговый бот с функцией бэктестинга.

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

Вот что вы получите в конце этой истории:

Ввод:

results = bot.backtest(strategy, some_parameters)
for result in results:
print(f"Net profit: {profit}")

Выпуск:

Net profit: 14075.772535935259
Net profit: 9407.347764489063
Net profit: 27047.968861593035
Net profit: 23669.66175297717
Net profit: 15670.56778873867

Начало работы

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

На данный момент вам понадобятся Backtrader, Pandas и Yfinance.

pip install backtrader2
pip install pandas
pip install yfinance

С чего начать?

Начнем с создания нового класса для нашего бота. Мы также реализуем основной метод, который нам понадобится:

class TradingBot:    def backtest():
pass

Хорошо, теперь, как провести бэктест? Если вы следили за серией Backtrader, которую я сделал, вы знаете, как мы можем это сделать.

Нам нужно сначала объявить Cerebro, затем добавить данные, добавить нашу стратегию, наш сайзер, наши анализаторы и т. Д.

Это дает нам представление о параметрах, которые нам нужно передать:backtest

def backtest(self, strategy, backtest_parameters, data_source, sizer=bt.sizers.FixedSize, strategy_parameters=None,
sizer_parameters=None, analyzers=None):

Некоторые параметры могут сбивать с толку:

  • backtest_parameters: дата начала бэктеста, дата окончания, начальные денежные средства, символ и т.д.
  • data_source: класс, который мы будем использовать для извлечения данных бэктеста (Open High Low Close DataFrame)
  • sizer: объект, используемый для работы с размером нашей позиции.

Теперь у нас есть все необходимое для реализации нашего метода:backtest

def backtest(self, strategy, backtest_parameters, data_source, sizer=bt.sizers.FixedSize, strategy_parameters=None,
sizer_parameters=None, analyzers=None):
cerebro = bt.Cerebro()

data = data_source.get_data(backtest_parameters)
datafeed = bt.feeds.PandasData(dataname=data)
cerebro.adddata(datafeed)

initial_cash = backtest_parameters.get('initial_cash', 10000)
commission = backtest_parameters.get('commission', 0.001)
slippage = backtest_parameters.get('slippage', 0.001)

cerebro.broker.setcash(initial_cash)
cerebro.broker.setcommission(commission=commission)
cerebro.broker.set_slippage_perc(slippage)

cerebro.adddata(datafeed)

if not strategy_parameters:
strategy_parameters = {}
cerebro.optstrategy(strategy, **strategy_parameters)

if not sizer_parameters:
sizer_parameters = {}
cerebro.addsizer(sizer, **sizer_parameters)

if analyzers:
for analyzer in analyzers:
cerebro.addanalyzer(analyzer)

results = cerebro.run(maxcpus=1)
return results

Несколько слов об этой строке:

datafeed = bt.feeds.PandasData(dataname=data)

Здесь мы просто создаем DataFeed с нашими данными (OHLCV DataFrame).

Источники данных

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

В интерфейсе у нас будет один публичный метод: . Этот метод должен возвращать OHLCV DataFrame.get_data

Мы будем использовать этот метод для обертывания другого метода: . Этот метод является частным и абстрактным. Нам нужно переопределить его, чтобы определить поведение конкретного источника данных._get_data

Кроме того, мы будем использовать другие методы, чтобы проверить, соответствуют ли параметры конкретному источнику данных. Например, если наш источник данных может работать только с datetimes, и вы дадите ему строку для даты, он не будет работать. Таким образом, наш метод преобразует строку в datetime перед передачей ее в ._get_start_date_get_data


from abc import ABC, abstractmethodclass DataSource(ABC):

def get_data(self, backtest_parameters):
start_date = backtest_parameters.get('start_date', dt.datetime(2019, 1, 1))
end_date = backtest_parameters.get('end_date', dt.datetime(2020, 1, 1))
timeframe = backtest_parameters.get('timeframe', Timeframes.d1)
symbol = backtest_parameters.get('symbol', 'BTC-USD')

print(f'Getting data for {symbol} from {start_date} to {end_date} with {timeframe.name} timeframe with {self.__class__.__name__} data source')
return self._get_data(self._get_start_date(start_date), self._get_end_date(end_date), self._get_timeframe(timeframe), self._get_symbol(symbol))

@abstractmethod
def _get_data(self, start_date, end_date, timeframe, symbol) -> pd.DataFrame:
pass

def _get_start_date(self, start_date):
return start_date

def _get_end_date(self, end_date):
return end_date

def _get_timeframe(self, timeframe):
return timeframe

def _get_symbol(self, symbol):
return symbol

Теперь мы реализуем конкретный источник данных. Но прежде чем сделать это, мы реализуем Enum для определения таймфреймов.

from enum import Enum
import backtrader as bt


class Timeframes(Enum):
m1 = (bt.TimeFrame.Minutes, 1)
m5 = (bt.TimeFrame.Minutes, 5)
m15 = (bt.TimeFrame.Minutes, 15)
m30 = (bt.TimeFrame.Minutes, 30)
h1 = (bt.TimeFrame.Minutes, 60)
h4 = (bt.TimeFrame.Minutes, 240)
d1 = (bt.TimeFrame.Days, 1)
w1 = (bt.TimeFrame.Weeks, 1)
mo1 = (bt.TimeFrame.Months, 1)

Вы можете реализовать любой таймфрейм, который вы хотите, и реализовать их так, как вы хотите. Я выбрал формат Backtrader, который является .(timeframe, resolution)

Теперь давайте создадим источник данных. Если есть API, который вам нравится, вы можете реализовать свой собственный источник данных и делать то, что вы хотите для этой части. Просто убедитесь, что вы возвращаете OHLCV DataFrame (я должен был добавить тест, чтобы проверить, верен ли DataFrame в ). Для меня я буду реализовывать API Yfinance в качестве источника данных:get_data

import yfinance as yf
class Yfinance(DataSource):

Во-первых, я должен сделать свои таймфреймы совместимыми с Yfinance. Итак, я буду использовать метод:_get_timeframe

def _get_timeframe(self, timeframe):
try:
timeframe = timeframe.name[-1] + timeframe.name[:-1]
if timeframe not in ['1m', '5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo']:
raise ValueError
return timeframe
except ValueError:
raise ValueError(f'Yfinance does not support {timeframe} timeframe')

Затем я должен убедиться, что мой символ поддерживается Yfinance. Например, если я хочу извлечь данные «BTC-USDT» из Yfinance, это не сработает, поэтому я должен поймать ошибку.

def _get_symbol(self, symbol):
try:
ticker = yf.Ticker(symbol)
info = ticker.info
if not info.get('regularMarketPrice', None):
raise ValueError
return symbol
except ValueError as e:
raise ValueError(f'Yfinance does not support {symbol} symbol')

Теперь я могу переопределить метод:_get_data

def _get_data(self, start_date, end_date, timeframe, symbol):
data = yf.download(symbol, start=start_date, end=end_date, interval=timeframe)
return yf.download(symbol, start_date, end_date, interval=timeframe)

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

Бег беги беги!

Итак, теперь у меня есть класс TradingBot, и у меня есть источник данных Yfinance, который я могу использовать для загрузки данных бэктестинга, чего не хватает?

Ничего, я могу просто поместить все в скрипт и запустить его:

import backtrader as bt

from trading_bot import TradingBot
from timeframes import Timeframes
from data_sources.yfinance import Yfinance


bot = TradingBot()
data_source = Yfinance()

backtest_parameters = {
'start_date': '2010-01-01',
'end_date': '2022-01-01',
'timeframe': Timeframes.d1,
'symbol': 'AAPL',
'initial_cash': 10000,
'commission': 0.001,
'slippage': 0.001
}

strategy = bt.strategies.MA_CrossOver
strategy_parameters = {
'fast': range(10, 15),
}

sizer = bt.sizers.PercentSizer
sizer_parameters = {
'percents': 99
}

analyzers = [
bt.analyzers.TradeAnalyzer
]

results = bot.backtest(strategy, backtest_parameters, data_source, strategy_parameters=strategy_parameters, sizer=sizer,
sizer_parameters=sizer_parameters, analyzers=analyzers)
for result in results:
print(f"Net profit: {result[0].analyzers.tradeanalyzer.get_analysis()['pnl']['net']['total']}")

Когда я запускаю этот код, он дает мне следующее:

Net profit: 14075.772535935259
Net profit: 9407.347764489063
Net profit: 27047.968861593035
Net profit: 23669.66175297717
Net profit: 15670.56778873867

Таким образом, функция обратного тестирования работает!

Очевидно, что вы можете изменить параметры, которые я использовал для запуска кода, и он (надеюсь) все еще будет работать.

Часть 3. Шаблоны стратегий

Есть репозиторий GitHub, который я сделал для этой серии. Если вы хотите использовать его для отслеживания кода, вы можете найти его здесь: Trading Bot Series.

Что мы получим

В конце истории у нас будет два шаблона стратегий:

  • Базовый шаблон: используется для разработки стратегий с открытыми и закрытыми условиями.
  • Шаблон скобки: используется для разработки стратегий без близких условий. Вместо этого эти стратегии будут использовать лимитные ордера для стоп-лосс и тейк-профит.

Как мы можем легко разрабатывать стратегии?

Если вы следили за серией Backtrader, вы видели, насколько сложно и утомительно разрабатывать стратегии. Чтобы свести к минимуму необходимую работу, единственное, что мы хотим изменить от одной стратегии к другой, это условия входа / выхода трейдера.

Вот пример стратегии, разработанной за 30 секунд благодаря шаблону:

class MyStrategy(BracketStrategy):

params = (
('ema_period', 20),
)

def _init_indicators(self):
self.ema = EMA(period=self.p.ema_period)

def _open_long_condition(self) -> bool:
if self.ema[0] < self.datas[0].close[0]:
return True

def _open_short_condition(self) -> bool:
if self.ema[0] > self.datas[0].close[0]:
return True

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

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

БазоваяСтратегия

Начнем с первого шаблона: совершенно пустая стратегия.

Сначала мы определим некоторые параметры, которые имеют смысл для каждой стратегии:

  • logging: включить/выключить ведение журнала стратегии.
  • longs_enabled/shorts_enabled: включить/выключить длинные и короткие сделки.
  • стоп-лосс/вознаграждение за риск: 2 параметра для управления рисками.

Объявим их со значениями по умолчанию:

class BaseStrategy(bt.Strategy):
params = (
('logging', False),
('longs_enabled', True),
('shorts_enabled', True),
('stop_loss', 0),
('risk_reward', 0),
)

Теперь, что нам нужно инициализировать в методе? Наш пустой список ордеров, некоторые атрибуты для отслеживания прибыли нашей стратегии и наши показатели:__init__

def __init__(self):
self.orders_ref = list()
self.total_profit = 0
self.initial_cash = self.broker.cash

self._init_indicators()

Метод зависит от стратегии, поэтому мы оставим его пустым:_init_indicators

def _init_indicators(self):
pass

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

from abc import abstractmethod...@abstractmethod
def _open_short_condition(self) -> bool:
pass

@abstractmethod
def _open_long_condition(self) -> bool:
pass

@abstractmethod
def _close_short_condition(self) -> bool:
pass

@abstractmethod
def _close_long_condition(self) -> bool:
pass

Затем мы можем создавать функции для ведения журнала. Нам понадобится одна базовая функция, которую мы будем повторно использовать друг в друге функции ведения журнала:log

def _log(self, txt):
if self.p.logging:
print(f"{self.datas[0].datetime.datetime(0)}: {txt}")

Теперь мы можем сделать одну функцию для каждого действия, связанного со стратегией, например:

  • log_order: что показывать при открытии/обновлении ордера.
  • log_trade: что показывать при закрытии сделки.
  • log_start: что показывать, когда мы начинаем нашу стратегию.
  • log_stop: что показывать, когда мы останавливаем нашу стратегию.
  • log_total_profit: как будет отображаться общая прибыль для каждой итерации нашей стратегии.
  • log_every_iter: что отображать для каждой итерации.
  • log_long_order: что показывать, когда мы идем долго.
  • log_short_order: что показывать, когда мы заходим в шорты.

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

def _log_trade(self, trade):
self._log(colored('OPERATION PROFIT, GROSS %.2f, NET %.2f'
%(trade.pnl, trade.pnlcomm), 'green' if trade.pnl > 0 else 'red'))

def _log_total_profit(self):
self._log(colored('TOTAL PROFIT %.2f' % self.total_profit, 'green' if self.total_profit > 0 else 'red'))

def _log_order(self, order):
self._log('Order ref: {} / Type {} / Status {}'.format(
order.ref,
colored('BUY' if order.isbuy() else 'SELL', 'green' if order.isbuy() else 'red'),
order.getstatusname()
))

def _log_iter(self):
self._log(f"Close : {self.datas[0].close[0]}")

def _log_long_order(self, stop_price, take_profit_price):
self._log(colored(f"LONG ORDER SIGNAL: Stop loss: {stop_price} / Take profit: {take_profit_price}", 'green'))

def _log_short_order(self, stop_price, take_profit_price):
self._log(colored(f"SHORT ORDER SIGNAL: Stop loss: {stop_price} / Take profit: {take_profit_price}", 'red'))

def _log_start(self):
self._log(colored('Starting Portfolio Value: %.2f' % self.broker.getvalue(), 'green'))

def _log_stop(self):
portfolio_value = self.broker.getvalue()
total_return_pct = (portfolio_value - self.initial_cash) / self.initial_cash * 100

portfolio_value_color = 'green' if self.broker.getvalue() > self.initial_cash else 'red'
total_profit_color = 'green' if self.total_profit > 0 else 'red'
total_return_color = 'green' if total_return_pct > 0 else 'red'

self._log(colored('Final Portfolio Value: %.2f' % portfolio_value, portfolio_value_color))
self._log(colored(f"Total Return: {total_return_pct:.2f}%", total_return_color))
self._log_total_profit()

Примечание: поставляется из пакета и используется для отображения цветного текста.coloredtermcolor

Приятно, но когда эти функции будут вызываться?

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

Вот мои реализации:

def notify_data(self, data, status, *args, **kwargs):
status = data._getstatusname(status)
print(status)

def notify_trade(self, trade):
if not trade.isclosed:
return

self._log_trade(trade)

self.total_profit += trade.pnlcomm
self._log_total_profit()

def notify_order(self, order):
self._log_order(order)
self._del_order_if_not_alive(order)

В , мы называем do delete бесполезные ордера, такие как ордер стоп-лосс после того, как был активирован тейк-профит.notify_orderdel_order_if_not_alive

def _del_order_if_not_alive(self, order):
if not order.alive() and order.ref in self.orders_ref:
self.orders_ref.remove(order.ref)

Затем мы должны реализовать наш метод. Это метод, называемый для каждой итерации нашей стратегии:next

def next(self):
self._log_iter()

if not self.position:
self._not_yet_in_market()
else:
self._in_market()

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

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

def _in_market(self):
if self._close_long_condition() and self.position.size > 0:
self.close()
if self._close_short_condition() and self.position.size < 0:
self.close()

Примечание: когда мы идем в длинную позицию, это > 0, а когда мы идем коротко, это < 0. Таким образом, добавление этого условия гарантирует, что мы не закроем длинную боковую сделку, например, с использованием короткого условия.self.position.size

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

def _not_yet_in_market(self):
if self._long_condition():
stop_price = self._get_long_stop_loss_price()
take_profit_price = self._get_long_take_profit_price()

self._go_long(stop_price, take_profit_price)
self._log_long_order(stop_price, take_profit_price)

if self._short_condition():
stop_price = self._get_short_stop_loss_price()
take_profit_price = self._get_short_take_profit_price()

self._go_short(stop_price, take_profit_price)
self._log_short_order(stop_price, take_profit_price)def _long_condition(self):
return self._open_long_condition() and self.params.longs_enabled

def _short_condition(self):
return self._open_short_condition() and self.params.shorts_enabled

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

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

def _get_long_stop_loss_price(self) -> float:
return self.datas[0].close[0]

def _get_long_take_profit_price(self) -> float:
return self.datas[0].close[0]

def _get_short_stop_loss_price(self) -> float:
return self.datas[0].close[0]

def _get_short_take_profit_price(self) -> float:
return self.datas[0].close[0]

Теперь давайте реализуем и . Эти методы будут отправлять ордера брокеру.go_longgo_short

def _go_long(self, stop_price, take_profit_price):
orders = self._get_long_orders_from_stop_and_take_profit(stop_price, take_profit_price)
self.orders_ref = [order.ref for order in orders if order]
self.entry_bar = len(self)

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

Теперь, как мы можем отправить наши заказы? Это просто какой-то код Backtrader:

def _get_long_orders_from_stop_and_take_profit(self, stop_price, take_profit_price):
ACTUAL_PRICE = self.datas[0].close[0]
if stop_price != ACTUAL_PRICE and take_profit_price != ACTUAL_PRICE:
orders = self.buy_bracket(price=ACTUAL_PRICE, stopprice=stop_price, limitprice=take_profit_price)
elif stop_price != ACTUAL_PRICE and take_profit_price == ACTUAL_PRICE:
orders = [self.buy(), self.sell(exectype=bt.Order.Stop, price=stop_price)]
elif stop_price == ACTUAL_PRICE and take_profit_price != ACTUAL_PRICE:
orders = [self.buy(), self.sell(exectype=bt.Order.Limit, price=take_profit_price)]
else:
orders = [self.buy()]
return orders

В зависимости от того, предоставили ли мы функции цены стоп-лосс или/или тейк-профит, она подает брокеру только необходимые ордера.

Например, если в другой стратегии мы не указываем расчет стоп-лосса, а указываем расчет для тейк-профита, эта функция вернет наш рыночный ордер + тейк-профит, но не стоп-лосс.

Эти функции почти одинаковы для короткой стороны:

def _go_short(self, stop_price, take_profit_price):
orders = self._get_short_orders_from_stop_and_take_profit(stop_price, take_profit_price)
self.orders_ref = [order.ref for order in orders if order]
self.entry_bar = len(self)

def _get_short_orders_from_stop_and_take_profit(self, stop_price, take_profit_price):
ACTUAL_PRICE = self.datas[0].close[0]
if stop_price != ACTUAL_PRICE and take_profit_price != ACTUAL_PRICE:
orders = self.sell_bracket(price=ACTUAL_PRICE, stopprice=stop_price, limitprice=take_profit_price)
elif stop_price != ACTUAL_PRICE and take_profit_price == ACTUAL_PRICE:
orders = [self.sell(), self.buy(exectype=bt.Order.Stop, price=stop_price)]
elif stop_price == ACTUAL_PRICE and take_profit_price != ACTUAL_PRICE:
orders = [self.sell(), self.buy(exectype=bt.Order.Limit, price=take_profit_price)]
else:
orders = [self.sell()]
return orders

Последние функции, которые нам нужны, — это просто функции, чтобы сказать, что делать, когда мы начинаем / прекращаем использовать нашу стратегию:

def start(self):
self._log_start()

def stop(self):
self._log_stop()

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

БрекетСтратегия

Брекет-стратегия – это стратегия, которая будет подавать 3 ордера на каждый сигнал:

  • Рыночный ордер
  • Стоп-лосс
  • Тейк-профит

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

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

class BracketStrategy(BaseStrategy):

@abstractmethod
def _open_short_condition(self) -> bool:
pass

@abstractmethod
def _open_long_condition(self) -> bool:
pass

# In a bracket strat, closing is done with stop loss or take profit -> no need to implement close conditions
def _close_short_condition(self) -> bool:
pass

def _close_long_condition(self) -> bool:
pass

Последнее, что остается, это изменить наши расчеты тейк-профита и стоп-лосса, потому что мы хотим использовать эти типы ордеров.

Для этой части это просто математика. Если я подам ордер на покупку по цене 1000€, и я хочу, чтобы мой стоп-лосс был на 1% ниже, моя цена стоп-лосса составит 1000€ — 1% от 1000€ = 990€. Если мой коэффициент вознаграждения за риск = 2, мой тейк-профит составит 1000€ + (1000€ — 990€) * 2 = 1020€. Вот как вы превращаете это в код:

# Default calculation of stop price is using a stop loss in % and the actual market price
def _get_long_stop_loss_price(self):
return self.datas[0].close[0] * (1 - self.params.stop_loss / 100)

def _get_short_stop_loss_price(self):
return self.datas[0].close[0] * (1 + self.params.stop_loss / 100)

# For take profit, we use a risk reward parameter, and it's a trivial calculation
def _get_long_take_profit_price(self):
stop_price = self._get_long_stop_loss_price()
return self.datas[0].close[0] + (self.datas[0].close[0] - stop_price) * self.params.risk_reward

def _get_short_take_profit_price(self):
stop_price = self._get_short_stop_loss_price()
return self.datas[0].close[0] - (stop_price - self.datas[0].close[0]) * self.params.risk_reward

Примечание: поскольку я использовал параметр, вычисление не совпадает с тем, как если бы параметр был . risk_rewardtake_profit

И мы закончили с этим шаблоном!

Пример стратегии

Для этой части я не буду объяснять много вещей, я просто скопирую и вставлю код, который вы можете найти на моем GitHub. Это комментируется, поэтому вы должны легко понять.

from backtrader.indicators import MACD as MACD
from backtrader.indicators import CrossOver as CrossOver
from backtrader.indicators import ExponentialMovingAverage as EMA

from strategies.bracket_strategy import BracketStrategy


# This is a test strategy, to help you understand how to build one
class BracketStrategyExample(BracketStrategy):

# First, you define some parameters
params = (
('period_me1', 12),
('period_me2', 26),
('period_signal', 9),
('trend_ema_period', 100),
('movav', EMA),
)

# Then, you initialize indicators
def _init_indicators(self):
self.macd = MACD(period_me1=self.p.period_me1, period_me2=self.p.period_me2, period_signal=self.p.period_signal,
movav=self.p.movav)
self.ema = EMA(period=self.p.trend_ema_period)
self.cross = CrossOver(self.macd.macd, self.macd.signal)

# Finally, you implement your open conditions
# Closing is done with stop loss or take profit
def _open_long_condition(self) -> bool:
if self.ema[0] < self.datas[0].close[0] and self.cross[0] == 1:
return True
return False

def _open_short_condition(self) -> bool:
if self.ema[0] > self.datas[0].close[0] and self.cross[0] == -1:
return True
return False

Теперь мы можем запустить эту стратегию в нашем боте. Вот параметры, которые я использовал, например:

strategy = BracketStrategyExample
strategy_parameters = {
'period_me1': 12, 'logging': True, 'stop_loss': 1, 'risk_reward': range(1, 5)
}

Примечание: вам нужно предоставить и , иначе сделки не будут закрыты.stop_lossrisk_reward

Он дает такого рода выходы:

Постройте торгового бота — Стратегии

Заключительное примечание

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

Часть 4. Живая торговля

Есть репозиторий GitHub, который я сделал для этой серии. Если вы хотите использовать его для отслеживания кода, вы можете найти его здесь: Trading Bot Series.

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

Можно сделать торгового бота совместимым с любым брокером, которого вы хотите. Тем не менее, наш бот работает с Backtrader, и брокеров, совместимых с Backtrader, не так много. Одним из них является Оанда. Существуют также интерактивные брокеры и визуальные графики. Существует также интеграция Backtrader CCXT, позволяющая нам торговать в реальном времени на большинстве криптовалютных бирж (Binance, Coinbase и т. Д.).

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

Я начну с объяснения различий между реальной торговлей и бэктестингом.

Живая торговля

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

За живой торговлей стоит концепция «Магазина». Магазин является шлюзом между Backtrader и вашим брокером. Он предоставляет данные и брокера Backtrader.

Таким образом, при реальной торговле вам придется настраивать магазин. Вот пример реальной торговли на Binance:

store = CCXTStore(
    exchange='binance',
    currency='USD',
    config={
        'apiKey': binance_api_key,
        'secret': binance_api_secret,
        'nonce': lambda: str(int(time.time() * 1000)),
        'enableRateLimit': True,
    },
    sandbox=sandbox
)

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

Некоторые параметры, используемые в приведенном выше примере:

  • обмен: Binance, Coinbase и т.д.
  • валюта: валюта котировки.
  • nonce: параметр, используемый для шифрования запросов.
  • enableRateLimit: чтобы бот не рассылал спам-запросы.
  • песочница: к бумажной торговле.

Затем вы можете получить канал данных из хранилища с помощью . Вы также можете получить брокера с помощью .store.getdata()store.getbroker()

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

Кроме того, данные, которые вы получаете, иногда являются отложенными данными, потому что это могут быть данные, необходимые Backtrader для выполнения вычислений. Например, если вы используете EMA 200, Backtrader нужны некоторые исторические данные для расчета этой скользящей средней.

При изменении состояния данных Cerebro отправляет уведомление стратегии через . Итак, давайте реализуем это в нашей базовой стратегии:notify_data

class BaseStrategy(bt.Strategy):

    def __init__(self):
    
        ...
        
        self.active = True
        
    def notify_data(self, data, status, *args, **kwargs):
        if status == data.LIVE:
            self._log('Data live notification')
            self.active = True
        if status == data.DELAYED:
            self._log('Data delayed notification')
            self.active = False
    
    def next(self):
        self._log_iter()

        if not self.active:
            return
            
        ...

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

 def live(self, strategy, live_parameters, store, sizer=bt.sizers.FixedSize, strategy_parameters=None,
             sizer_parameters=None, analyzers=None):
        data = store.get_data(dataname=live_parameters.get('dataname'))
        broker = store.getbroker()

        cerebro = bt.Cerebro()
        cerebro.adddata(data)
        cerebro.setbroker(broker)

        self.configure_cerebro(cerebro, strategy, strategy_parameters, sizer, sizer_parameters, analyzers, live=True)

        result = cerebro.run()
        return result

    @staticmethod
    def configure_cerebro(cerebro, strategy, strategy_parameters, sizer, sizer_parameters, analyzers, live=False):
        if not strategy_parameters:
            strategy_parameters = {}
        if live:
            cerebro.addstrategy(strategy, **strategy_parameters)
        else:
            cerebro.optstrategy(strategy, **strategy_parameters)

        if not sizer_parameters:
            sizer_parameters = {}
        cerebro.addsizer(sizer, **sizer_parameters)

        if analyzers:
            for analyzer in analyzers:
                cerebro.addanalyzer(analyzer)

Живой сценарий

Теперь все, что нам нужно сделать, это создать скрипт, чтобы сделать торговлю в реальном времени легкой. Но сначала мы должны установить некоторые зависимости.

Во-первых, это . Если вы попытаетесь установить с помощью только этого, это не сработает, вместо этого сделайте это следующим образом:oandapyoandapypip install oandapy

pip install git+https://github.com/oanda/oandapy.git

Кроме того, есть:ccxt

pip install ccxt

Наконец, есть:ccxt-bt

pip install git+https://github.com/Dave-Vallance/bt-ccxt-store.git

Теперь давайте создадим наш маленький скрипт:

import backtrader as bt
from backtrader.stores.oandastore import OandaStore
from ccxtbt import CCXTStore
import time

from trading_bot import TradingBot
from strategies.bracket_strategy_example import BracketStrategyExample


oanda_token = 'YOUR_OANDA_TOKEN'
oanda_account = 'YOUR_OANDA_ACCOUNT'

binance_api_key = 'YOUR_BINANCE_API_KEY'
binance_api_secret = 'YOUR_BINANCE_API_SECRET'

timezone = 'America/New_York'
sandbox = True

store1 = OandaStore(
    token=oanda_token,
    account=oanda_account,
    practice=sandbox,
    timezone=timezone,
)
store2 = CCXTStore(
    exchange='binance',
    currency='USD',
    config={
        'apiKey': binance_api_key,
        'secret': binance_api_secret,
        'nonce': lambda: str(int(time.time() * 1000)),
        'enableRateLimit': True,
    },
    sandbox=sandbox,
    timezone=timezone,
)

bot = TradingBot()

strategy = BracketStrategyExample
strategy_parameters = {
    'period_me1': 12, 'logging': True, 'stop_loss': 1, 'risk_reward': range(1, 5)
}

sizer = bt.sizers.PercentSizer
sizer_parameters = {
    'percents': 99
}

analyzers = [
    bt.analyzers.TradeAnalyzer
    ]

live_parameters = {
    'dataname': 'EUR_USD',
}
bot.live(strategy, store1, strategy_parameters=strategy_parameters, sizer=sizer,
         sizer_parameters=sizer_parameters, analyzers=analyzers)

Вы должны все понимать. Как видите, магазин Oanda и магазин CCXT не сильно отличаются.

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

Заключительное примечание

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

Как реализовать бэктестер на Python

И какую библиотеку использовать, если вы не хотите писать ее самостоятельно

Знакомство

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

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

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

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

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

В этом руководстве мы углубимся в основы реализации бэктестера — как векторизованным, так и итеративным способом. Итак, без лишних слов, давайте приступим к делу!

Реализация

Ниже мы рассмотрим шаг за шагом, и вы также можете следить за ними в блокноте.

Загрузка данных и расчет доходности

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

https://medium.com/media/0e27eff62426fe6cfc4dfefc5d46d9a6

Первые 5 строк фрейма данных

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

Примечание: Использование логарифмической доходности имеет несколько преимуществ, наиболее заметным из которых является то, что оно позволяет нам взять сумму как длинной, так и короткой доходности, чтобы получить общую доходность стратегии. Более подробно я остановлюсь на этой теме в следующем посте.

https://medium.com/media/7d8a8c6cfbf5037382282fa0c34b6b5c

Первые 5 строк данных с возвращаемыми значениями

Расчет сигналов стратегии

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

https://medium.com/media/3baeb8a01e00a80ef4be4bbe5fdc75e7

Мы рассчитали скользящую среднюю с периодом 20, а затем присвоили 1 для периодов, когда цена close выше скользящей средней, и -1 для периодов, когда она ниже. Затем мы удаляем строки, в которых у нас есть пропущенные значения. Таким образом, мы получаем следующий фрейм данных.

Первые 5 строк данных с сигналом

Векторизованное тестирование на истории

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

Разбивая эту операцию, мы как бы доходим до конца периода (скажем, до конца первого периода выше, 2023-02–09 16:00), вычисляем соответствующий сигнал по цене, по которой этот период закрылся (цена close), затем ждем окончания следующего периода (в данном случае 1 часа, так как мы используем часовые данные), А затем умножьте return этого периода на позицию, которая у нас была в течение этого периода (длинная или короткая). В векторизованном виде это соответствует следующему:

https://medium.com/media/c02a0ffe20b0c2e2d8d2998e30295878

Первые 5 строк данных с возвратом стратегии

Как мы видим, теперь у нас есть столбец strategy_returns, который аналогичен столбцу returns, но имеет противоположный знак, так как сигналом для этих строк всегда является -1.

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

https://medium.com/media/f599fa16ef352d8a8d1aed29e141741f

Последние 5 строк данных с кумулятивными доходами

Наконец, если мы хотим получить отдачу за все тестирование на истории, нам просто нужно взять соответствующее значение из последней строки. Таким образом, наше тестирование на истории дает общую совокупную доходность 1,020306 для нашей стратегии и 0,968810 для стратегии «купи и держи», что означает, что наша стратегия обеспечила бы нам положительную доходность, в то время как простое удержание актива привело бы к отрицательной доходности.

Учет торговых издержек

Для того, чтобы учесть торговые издержки при тестировании на истории, предполагая, что торговые издержки составляют процент от общей стоимости сделки, нам нужно только вычесть торговые издержки из доходности стратегии, так как доходность уже составляет процент. Для этого нам понадобится вспомогательный столбец trades, в котором регистрируются сделки, совершенные по строке. Так, например, если мы переходим из нейтральной позиции в длинную позицию, это будет 1 сделка, тогда как из длинной позиции в короткую эквивалентно 2 сделкам. Общая торговая стоимость равна умножению количества сделок на унитарную торговую стоимость.

https://medium.com/media/e368115dff1778cb0c458e6a00ddd2ac

Вышесказанное приведет к следующим результатам.

Последние 5 строк результатов с торговыми издержками

Видно, что общая доходность при торговых издержках 0,1% приводит нашу итоговую совокупную доходность к 1,010895, что, как и ожидалось, ниже доходности по стратегии без учета торговых издержек.

Итеративное тестирование на истории

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

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

Ниже приведена простая реализация такого тестера на истории, и она разделена на несколько функций для модульности. Основная логика происходит в цикле for, показанном ниже:

https://medium.com/media/cd753f5c1e6fe5ebbdacdbac31acd822

Мы начинаем с нескольких инициализаций, а затем перемещаемся внутри цикла for, где перебираем данные, вычисляем сигнал в соответствии со стратегией (в данном случае стратегия Moving Average жестко запрограммирована), а затем вызываем другую функцию trade, где собственно и выполняем торговую логику.

https://medium.com/media/0aa7a68f8e9254434be88209d8afde58

trade функция имеет несколько условий if else для того, чтобы определить, что произойдет дальше. Таким образом, если, например, наша текущая позиция короткая, и у нас есть длинный сигнал, мы сначала совершим сделку с текущими единицами, чтобы вернуть текущую позицию в нейтральное состояние, а затем еще одну сделку с полным балансом счета, чтобы открыть длинную позицию. Та же логика в обратном порядке следует, если у нас есть сигнал на продажуtrade использует функции buy_asset и sell_asset, показанные ниже.

https://medium.com/media/aed4fe60bc5b15dba8a85f4ef33f7d52

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

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

Начиная с эквити в 1000 USD, если мы выполним тестер на истории по нашей стратегии скользящей средней, мы получим 1019,65 USD.

Учет торговых издержек

Чтобы принять во внимание торговые издержки, мы должны думать так, как будто мы платим премию за актив, то есть, по сути, платим более высокую цену, если покупаем актив, и получаем более низкую цену, если продаем актив. Измененные buy_asset и sell_asset будут следующими:

https://medium.com/media/442cf4b5bd26cbcba74cac5d8c679770

Окончательный баланс составит 1009,59 USD, что, как и ожидалось, меньше, чем когда мы не учитывали торговые издержки.

Заключение

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

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

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

Источник

Источник