Протестируйте свои торговые стратегии с помощью векторизации на Python

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

Математика (что такое векторизация и как ее использовать)

Векторизация - это то, что позволяет нам выполнять операции (сложение, умножение и т. Д.) Над скалярами (отдельные числа, такие как 2 и 4,7) и применять обобщение этой операции к большим группам чисел, будь то одномерный массив (вектор) или двумерный массив (матрица). В нашем случае мы будем использовать одномерные массивы: один массив будет доходностью покупки и удержания актива, на который мы смотрим, другой массив будет нашей стратегией и доходностью, которую мы получаем от указанной стратегии.

Это возможно в Python благодаря библиотеке науки о данных «numpy», что позволит нам выполнять векторизованные операции со списками Python.

Стратегия

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

Код

Полный исходный код см. В репозитории Github ниже.



Данные, с которыми мы будем работать, - это цена закрытия биткойнов, взятая из Tiingo api с использованием скрипта «extract_data.py» (см. Ниже). Этот скрипт будет получать данные о ценах на биткойны на конец дня с начала 2021 года до текущего дня. Этот сценарий будет запускаться только тогда, когда пользователь

from secret import Secret
from tiingo import TiingoClient
import pandas as pd 
from pprint import pprint
client = TiingoClient(Secret.config)
from datetime import datetime

start = '2021-01-01'
end = datetime.now().strftime("%Y-%m-%d")
print(end)
crypto_symbols = ''
crypto_prices = client.get_crypto_price_history(
                                            tickers=['BTCUSD'],
                                            resampleFreq='1Day',
                                            startDate=start,
                                            endDate=end)
# pprint(crypto_prices)
# print(crypto_prices[0]['priceData'])
# print(dir(crypto_prices))

crypto_sheet = pd.DataFrame.from_dict(crypto_prices[0]['priceData'])
crypto_sheet['date'] = crypto_sheet['date'].str.rstrip('T00:00:00+00:00')
# print(crypto_sheet)
crypto_sheet.to_csv('data/BTCUSD.csv')

# Historical Prices
# https://api.tiingo.com/tiingo/crypto/prices?tickers=btcusd,fldcbtc&startDate=2019-01-02&resampleFreq=5min

Чтобы запустить тест на истории, мы сначала импортируем необходимые библиотеки и данные.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.style as style
import matplotlib.dates as mdates
style.use('seaborn')

df = pd.read_csv('data/BTCUSD.csv', index_col=[0])

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

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

# log returns of buy and hold (ln base e)
df['returns'] = np.log(df['close'] / df['close'].shift(1))

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

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

# moving average of log returns converted to positive or negative 1 day = 2
df['position'] = np.sign(df['returns'].rolling(day).mean())
df['strategy'] = df['position'].shift(1) * df['returns']

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

После отбрасывания значений nan и построения кумулятивной суммы журнала возвращается

df = df.dropna()

# plots the cumulative sum of log returns of buy and holding and the strategy 
# applied to an exponential
plt.figure(figsize=(10,6))
plt.title('BTCUSD')
plt.plot(df['date'], df[['returns', 'strategy']].cumsum().apply(np.exp))
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=30))
plt.legend(['returns', 'strategy'])
plt.savefig('graphs/BTC_{}.png'.format(day))
plt.show()

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

for i in [1, 3, 5, 7, 9]:
    df['position_%d' % i] = np.sign(df['returns'].rolling(i).mean())
    df['strategy_%d' % i] = (df['position_%d' % i].shift(1) * df['returns']) 
    to_plot.append('strategy_%d' % i)

Глядя на график выше, кажется, что 7-дневная импульсная стратегия в настоящее время работает лучше всего.