Наталья Кайда 21 августа 2024

🐍📈 Бэктест на Python: оцениваем торговую стратегию

Бэктестинг — мощный инструмент, который помогает определить потенциальную доходность, выявить неоправданные риски и внести необходимые коррективы до того, как вы начнете торговать на реальные деньги.
🐍📈 Бэктест на Python: оцениваем торговую стратегию

Что такое бэктест

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

  • Понять, насколько эффективна стратегия в ситуации, приближенной к реальной.
  • Выявить слабые места и внести необходимые коррективы до того, как в инвестиции или трейдинг будут вложены реальные деньги.
🐍 Библиотека питониста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
🐍🎓 Библиотека Python для собеса
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека Python для собеса»
🐍🧩 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Почему бэктест так важен

Бэктестинг дает трейдерам и инвесторам несколько важных преимуществ:

  • Принятие обоснованных решений. Математический анализ исторических данных помогает принимать решения, основанные на фактах, а не на эмоциях или предположениях.
  • Объективная оценка прибыльности стратегии. Использование исторических данных позволяет объективно оценить эффективность и жизнеспособность торговой стратегии — показывает, насколько прибыльной могла бы быть стратегия, какие риски она несет, как соотносятся ее сильные и слабые стороны.
  • Детальная оценка возможных рисков. Бэктестинг помогает оценить реальные риски стратегии, анализируя потенциальные потери, волатильность и просадки на основе исторических данных. Это позволяет установить нужные параметры для поддержания оптимального соотношения риска и прибыли.
  • Повышение шансов на успешную торговлю. Бэктест помогает трейдерам правильно распределять инвестиции между различными активами, секторами и регионами. Кроме того, в процессе исследования и анализа результатов стратегий трейдеры начинают лучше понимать факторы, влияющие на результаты, а это способствует дисциплинированному подходу и развивает навыки оценки долгосрочной перспективы.
  • Углубленное понимание рыночных механизмов и динамики. Бэктестинг позволяет глубже разобраться в инструментах инвестирования, рыночных силах и экономических показателях. Это помогает трейдерам точнее прогнозировать поведение рынка.
  • Укрепление уверенности. Наблюдение за результатами поведения стратегии в различных рыночных условиях и сценариях помогает трейдерам развить уверенность. Это, в свою очередь, укрепляет дисциплину и помогает принимать правильные решения во время реальной торговли.

Компоненты бэктестинга

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

Гипотеза для тестирования

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

  • Сигнал на покупку — когда краткосрочная скользящая средняя (например, 50-дневная) пересекает долгосрочную (например, 200-дневную) снизу вверх.
  • Сигнал на продажу — когда краткосрочная скользящая средняя пересекает долгосрочную сверху вниз. Это может быть сигналом для выхода из длинной позиции или рассмотрения возможности открытия короткой позиции.

Выбор подходящего рынка или типа активов

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

Данные для бэктестинга

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

Зависимость от длины позиции

Для длительных стратегий (когда вы держите позицию больше месяца) нужно тестировать на длинном периоде, около 15 лет. Для коротких стратегий (когда позиция держится менее недели) достаточно тестировать на периоде около 10 лет, а для внутридневных стратегий хватит периода в 3-4 года.

Зависимость от типа стратегии

  • Трендовые стратегии пытаются поймать длительные движения рынка. Для них нужно много исторических данных, минимум 10 лет, это позволяет проверить, как стратегия работает в разных рыночных циклах.
  • Стратегии возврата к среднему основаны на идее, что цена всегда возвращается к некоему среднему уровню. Период тестирования зависит от того, как быстро вы ожидаете этого возврата: для коротких периодов может хватить нескольких лет, для длинных нужно больше данных.
  • Стратегии, основанные на волатильности используют изменчивость цен для принятия решений. Период тестирования зависит от того, как часто меняется характер волатильности на рынке: если колебания происходят часто, нужен длинный период тестирования, чтобы охватить разные ситуации.
***
Вебинар «Как меняется математика в разных индустриях: от мобильных игр к фондовым рынкам»
Вебинар «Как меняется математика в разных индустриях: от мобильных игр к фондовым рынкам»

Приглашаем вас на вебинар «Как меняется математика в разных индустриях: от мобильных игр к фондовым рынкам», который состоится 22 августа в 20:00 по МСК, где вы сможете:

  • Узнать, как математические методы влияют на мобильные игры и фондовые рынки.
  • Понять различия в математических подходах в разных сферах бизнеса.
  • Изучить реальные кейсы применения математики в GameDev и финансах.
  • Оценить, какие математические знания необходимы для успешной карьеры в Data Science.

Выбор языка программирования

Для бэктеста чаще всего используют Python, C++ и R:

  • Python — простой, гибкий, располагает огромным количеством библиотек. Не слишком быстр, поэтому критически важные участки кода часто пишут на C++. Также Питон отлично подходит для прототипирования.
  • C++ отличается высокой скоростью и идеально подходит для высокочастотной торговли, но разработка на нем занимает больше времени — можно упустить выгодный тренд.
  • R очень силен в статистическом анализе и эконометрике, но он сложнее Python и работает медленнее C++.

Некоторые трейдеры обходятся Excel и MATLAB, хотя таблицы не слишком подходят для сложных вычислений, а MATLAB не так-то просто интегрировать с торговой системой. Если уровень математических знаний оставляет желать лучшего, стоит воспользоваться специальными бэктестинговыми библиотеками, которые абстрагируют всю сложную математику. Самые популярные бэктестинговые библиотеки для Python — QuantRocket и Zipline Reloaded.

Бэктест торговой стратегии на Python

Мы напишем бэктест для проверки стратегии по торговле акциями NVIDIA. Данные будем брать из надежного источника — Yahoo! Finance. Чтобы не скачивать выборку в CSV, установим библиотеку yfinance. Вместо специальных бэктестинговых библиотек будем использовать NumPy и pandas.

Торговая стратегия, которую мы будем тестировать, основана на скользящих средних и использует два разных периода для расчета средних — короткий и длинный (периоды в 50 и 200 дней соответственно):

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

Основные шаги, которые нужно реализовать:

  • Получение данных с Yahoo! Finance.
  • Вычисление скользящих средних.
  • Генерация торговых сигналов.
  • Вычисление и визуализация кумулятивной доходности стратегии.

Важно отметить, что:

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

Получение данных

        import yfinance as yf
import matplotlib.pyplot as plt
data = yf.download('NVDA', '2015-01-01', '2024-08-20')
data['Close'].plot(figsize=(10,7))
plt.title('Динамика стоимости акций NVIDIA')
plt.ylabel('Цена')
plt.xlabel('Дата')
plt.xlim(['2015-01-01', '2024-12-31'])
plt.show()
    
Впечатляющий рост стоимости акций NVIDIA с 2015 года
Впечатляющий рост стоимости акций NVIDIA с 2015 года

Вычисление скользящих средних

        import yfinance as yf
import matplotlib.pyplot as plt
data = yf.download('NVDA', '2015-01-01', '2024-08-20')

# Значения для краткосрочного и долгосрочного окон
short_window = 50
long_window = 200

# Вычисление скользящих средних
data['short_mavg'] = data['Close'].rolling(short_window).mean()
data['long_mavg'] = data['Close'].rolling(long_window).mean()

plot_data = data[-500:]
plt.figure(figsize=(10, 5))
plt.title('Скользящие средние - долгосрочная и краткосрочная', fontsize=14)
plt.xlabel('Дата')
plt.ylabel('Цена')
plt.plot(plot_data['Close'], label='Цена закрытия')
plt.plot(plot_data['short_mavg'], label='50-дневная скользящая средняя')
plt.plot(plot_data['long_mavg'], label='200-дневная скользящая средняя')
plt.legend()
plt.show()

    
Скользящие средние — 50-дневная и 200-дневная
Скользящие средние — 50-дневная и 200-дневная

Генерация торговых сигналов

        import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
data = yf.download('NVDA', '2015-01-01', '2024-08-20')

# Значения для краткосрочного и долгосрочного окон
short_window = 50
long_window = 200

# Вычисление скользящих средних
data['short_mavg'] = data['Close'].rolling(short_window).mean()
data['long_mavg'] = data['Close'].rolling(long_window).mean()

# Сигналы для покупки
data['long_positions'] = np.where(data['short_mavg'] > data['long_mavg'], 1, 0)

# Сигналы для продажи
data['short_positions'] = np.where(data['short_mavg'] < data['long_mavg'], -1, 0)
data['positions'] = data['long_positions'] + data['short_positions']

plot_data = data[-3000:]
plt.figure(figsize=(10, 7))
plt.title('Сигналы для покупки и продажи', fontsize=14)
plt.xlabel('Дата')
plt.ylabel('Цена')
plt.plot(plot_data['Close'], label='Цена закрытия')
plt.plot(plot_data['short_mavg'], label='50-дневная скользящая средняя')
plt.plot(plot_data['long_mavg'], label='200-дневная скользящая средняя')
plt.plot(plot_data[(plot_data['long_positions'] == 1) &
(plot_data['long_positions'].shift(1) == 0)]['short_mavg'],
'^', ms=15, label='Покупка', color='green')
plt.plot(plot_data[(plot_data['short_positions'] == -1) &
(plot_data['short_positions'].shift(1) == 0)]['short_mavg'],
'^', ms=15, label='Продажа', color='red')
plt.legend()
plt.show()
    
Сигналы на покупку и на продажу
Сигналы на покупку и на продажу

Вычисление и визуализация кумулятивной доходности стратегии

        import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
data = yf.download('NVDA', '2015-01-01', '2024-08-20')

# Значения для краткосрочного и долгосрочного окон
short_window = 50
long_window = 200

# Вычисление скользящих средних
data['short_mavg'] = data['Close'].rolling(short_window).mean()
data['long_mavg'] = data['Close'].rolling(long_window).mean()

# Сигналы для покупки
data['long_positions'] = np.where(data['short_mavg'] > data['long_mavg'], 1, 0)

# Сигналы для продажи
data['short_positions'] = np.where(data['short_mavg'] < data['long_mavg'], -1, 0)
data['positions'] = data['long_positions'] + data['short_positions']

# Ежедневная прибыль
data['returns'] = data['Close'].pct_change()

# Вычисление прибыльности стратегии
data['strategy_returns'] = data['returns'] * data['positions'].shift(1)

cumulative_returns = (data['strategy_returns'] + 1).cumprod()
cumulative_returns.plot(figsize=(10, 7), color='green')
plt.title('Кумулятивная доходность стратегии')
plt.xlabel('Дата')
plt.ylabel('Доходность стратегии')
plt.xlim(['2015-01-01', '2024-12-31'])
plt.show()
    
Доходность выглядит отлично, но есть нюнсы (о них ниже)
Доходность выглядит отлично, но есть нюнсы (о них ниже)

Основные метрики бэктестинга

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

Кумулятивная доходность

Показывает общую прибыль или убыток от инвестиции за определенный период в процентах:

Кумулятивная доходность=Конечная стоимостьНачальная стоимостьНачальная стоимость×100 

Годовая доходность

Отражает среднюю годовую доходность при условии, что прибыль реинвестируется каждый год:

Годовая доходность=((1+Кумулятивная доходность)365Количество дней1)×100

Годовая волатильность

Показывает риск инвестиции и рассчитывается как стандартное отклонение доходностей:

Годовая волатильность=Дневная волатильность×Количество торговых дней в году

Коэффициент Шарпа

Измеряет дополнительную доходность портфеля на единицу риска (стандартного отклонения):

Коэффициент Шарпа=Доходность портфеляБезрисковая доходностьСтандартное отклонение доходностей портфеля

Коэффициент Сортино

Похож на коэффициент Шарпа, но учитывает только негативные отклонения:

Коэффициент Сортино=Доходность портфеляБезрисковая доходностьСтандартное отклонение отрицательных доходностей

Бета

Измеряет, насколько волатильность портфеля соотносится с волатильностью рынка:

Бета=Ковариация доходностей портфеля и рынкаДисперсия доходностей рынка

Максимальная просадка

Показывает наибольшую потерю от пикового значения до самого низкого в течение определенного времени:

Максимальная просадка=Минимальная стоимостьПиковая стоимостьПиковая стоимость

Получение и интерпретация результатов бэктеста

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

        import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
data = yf.download('NVDA', '2015-01-01', '2024-08-20')

# Значения для краткосрочного и долгосрочного окон
short_window = 50
long_window = 200

# Вычисление скользящих средних
data['short_mavg'] = data['Close'].rolling(short_window).mean()
data['long_mavg'] = data['Close'].rolling(long_window).mean()

# Сигналы для покупки
data['long_positions'] = np.where(data['short_mavg'] > data['long_mavg'], 1, 0)

# Сигналы для продажи
data['short_positions'] = np.where(data['short_mavg'] < data['long_mavg'], -1, 0)
data['positions'] = data['long_positions'] + data['short_positions']

# Ежедневная прибыль
data['returns'] = data['Close'].pct_change()

# Вычисление прибыльности стратегии
data['strategy_returns'] = data['returns'] * data['positions'].shift(1)
cumulative_returns = (data['strategy_returns'] + 1).cumprod()

# Общее число торговых дней
days = len(cumulative_returns)

# Вычисление совокупного среднегодового темпа роста
# из расчета 252 торговых дней в год
annualised_returns = (cumulative_returns.iloc[-1]**(252/days)-1)*100

# Ежегодная волатильность
annualised_volatility = np.std(data.strategy_returns)*(252**0.5)*100

# Предполагаемая среднегодовая безрисковая ставка 1%
risk_free_rate = 0.01/252
sharpe_ratio = np.sqrt(252)*(np.mean(data.strategy_returns) -
(risk_free_rate))/np.std(data.strategy_returns)
downside_returns = np.where(data['strategy_returns'] < 0, data['strategy_returns'], 0)
expected_return = np.mean(data['strategy_returns']) - risk_free_rate
downside_risk = np.std(downside_returns) * np.sqrt(252)
sortino_ratio = expected_return / downside_risk

# Текущий максимум
running_max = np.maximum.accumulate(cumulative_returns.dropna())

# Показатель не должен опускаться ниже 1
running_max[running_max < 1] = 1

# Вычисление максимальной просадки
drawdown = (cumulative_returns)/running_max - 1
max_dd = drawdown.min()*100

# Вычисление беты
sp500 = yf.download('^GSPC', '2015-01-01', '2024-08-20')
sp500_returns = sp500['Close'].pct_change()
portfolio_cov = data['strategy_returns'].cov(sp500_returns)
sp500_var = sp500_returns.var()
beta = portfolio_cov / sp500_var

print(f'Ежегодная доходность стратегии: {annualised_returns:.2f}%\n'
f'Ежегодная волатильность: {annualised_volatility:.2f}%\n' 
f'Безрисковая ставка: {risk_free_rate:.8f}\n'
f'Коэффициент Шарпа: {sharpe_ratio:.2f}\n'
f'Максимальная просадка: {max_dd:.2f}%\n'
f'Коэффициент Сортино: {sortino_ratio:.8f}\n'
f'Бета: {beta:.2f}')

    

Показатели:

        Ежегодная доходность стратегии: 72.42%
Ежегодная волатильность: 47.86%
Безрисковая ставка: 0.00003968
Коэффициент Шарпа: 1.35
Максимальная просадка: -47.64%
Коэффициент Сортино: 0.00979917
Бета: 0.75
    

Доходность стратегии очень высокая — 72,42% в год. Это впечатляющий результат. Однако высокая доходность сопровождается также высокой волатильностью47,86% в год, что указывает на значительные колебания стоимости активов в портфеле. Это может привести к большим временным потерям.

Коэффициент Шарпа равен 1,35, это хороший показатель. Он означает, что стратегия генерирует достаточно высокую доходность с учетом принимаемого риска. Однако коэффициент Сортино равен всего 0,00979917, это означает, что стратегия демонстрирует большие просадки, которые могут быть болезненными для инвестора. Максимальная просадка в -47,64% подтверждает высокий уровень риска стратегии.

Бета равна 0,75, это означает, что доходность стратегии менее волатильна, чем доходность рынка в целом.

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

Типичные ошибки в бэктестинге

Бэктест подвержен нескольким ошибкам/сдвигам, которые могут существенно исказить результаты и привести к неверным выводам. Рассмотрим несколько распространенных ошибок, которых следует избегать:

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

Заключение

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

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ