Потому что это важнее, чем живая торговля
Это вторая история из серии Создание торгового бота. Вам нужно знать Backtrader, чтобы понять эту историю. Если вы не знаете, что такое Backtrader, или хотите найти другие истории из этой серии, вам следует проверить следующую историю:
Для этой серии я сделал репозиторий на GitHub. Если вы хотите использовать его, чтобы следовать коду, вы можете найти его здесь: Серия торговых ботов.
Что мы получим
В конце этой истории у нас будет гибкий торговый бот с функцией тестирования на истории.
Тестирование на исторических данных означает тестирование стратегии на прошлых данных, чтобы увидеть, является ли она прибыльной. Во время тестирования на истории вы также можете оптимизировать свою стратегию, чтобы найти лучшие параметры.
Вот что вы получите в конце этой истории:
Вход:
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).
Источники данных
В настоящее время мы не можем запустить нашего бота, потому что у нас нет источников данных. Чтобы сделать бота гибким, сделаем интерфейс и создадим подклассы этого интерфейса.
В интерфейсе у нас будет один публичный метод: get_data
. Этот метод должен возвращать кадр данных OHLCV.
Мы будем использовать этот метод, чтобы обернуть другой метод: _get_data
. Этот метод является закрытым и абстрактным. Нам нужно переопределить его, чтобы определить поведение конкретного источника данных.
Кроме того, мы будем использовать другие методы для проверки соответствия параметров конкретному источнику данных. Например, если наш источник данных может работать только с датой и временем, и вы даете ему строку для даты, он не будет работать. Таким образом, наш метод _get_start_date
преобразует строку в дату и время, прежде чем передать ее в _get_data
.
from abc import ABC, abstractmethod class 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 в get_data
). Для себя я буду реализовывать Yfinance API в качестве источника данных:
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
Итак, функция тестирования на истории работает!
Очевидно, вы можете изменить параметры, которые я использовал для запуска кода, и он (надеюсь) по-прежнему будет работать.
Что дальше?
В следующей истории мы попытаемся создать сложные стратегии, а позже мы увидим, как реализовать функцию реальной торговли, чтобы наш бот работал автоматически и следовал правилам, определенным в наших стратегиях.
Чтобы найти другие истории из этой серии и больше о совмещении трейдинга и Python, прочтите эту статью: Улучшите свою торговлю с помощью Python
Чтобы узнать больше о моих рассказах о Python, нажмите здесь!
Если вам понравилась история, не забудьте похлопать и, возможно, подпишитесь на меня, если хотите узнать больше о моем содержании :)
Вы также можете подписаться на меня по электронной почте, чтобы получать уведомления каждый раз, когда я публикую новую историю, просто нажмите здесь!
Если вы еще не подписаны на Medium и хотите поддержать меня или получить доступ ко всем моим историям, вы можете использовать мою ссылку:
Сообщение от InsiderFinance
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в InsiderFinance Wire
- 📚 Пройдите наш БЕСПЛАТНЫЙ мастер-класс
- 📈 Откройте для себя Мощные торговые инструменты