Тестирование торговых стратегий с помощью библиотеки tradingWithPython

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

sudo pip install xlrd
sudo pip install tradingWithPython

Для Windows слово sudo в обеих строках опускаем.

Можно также выполнить установку прямо из IPython Notebook:

!pip install tradingWithPython

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

Сначала сделаем всё по шагам в IPython Notebook, чтобы видеть результат выполнения каждой строки программы.

Импортируем нужные модули:

from datetime import datetime, date               # Для работы с датами.
import tradingWithPython.lib.yahooFinance as yf   # Для скачивания котировок с Yahoo Finance.
from tradingWithPython import sharpe              # Для вычисления коэффициента Шарпа.
import pandas as pd                               # Для работы с наборами даных.

Получим цену открытия, максимум, минимум каждого дня и цену закрытия с Yahoo Finance (объёмы игнорируем):

ohlc = yf.getHistoricData(symbol='XLP', sDate=(2009,1,1), eDate=(2012, 12, 31))[['open','high','low','close']]
Got 1006 days of data

Здесь параметр symbol задаёт тикер нужного актива. Второй параметр sDate – это начальная дата. По умолчанию, если параметр eDate (конечная дата) не задан, то будут загружены доступные данные вплоть до последнего (вчерашнего) дня, т.к. сегодняшний день ещё не закончился.

ohlc.tail(3)   # Выводим котировки за последние 3 дня.
Котировки за последние 3 дня
date open high low close
2012-12-27 34.76 34.90 34.56 34.82
2012-12-28 34.63 34.81 34.45 34.45
2012-12-31 34.38 34.90 34.33 34.90

Выведем график, сначала как 4 независимые линии, а потом в виде баров:

ohlc.plot()
../_images/TWP-Example-01_8_1.png
from tradingWithPython import candlestick
candlestick(ohlc)
../_images/TWP-Example-01_9_0.png

Правила стратегии, описанные в блоге Quantified Strategies: открываем длинную позицию, если за вчерашний день цена упала на 0.25% или более, а геп вниз на сегодняшнем открытии составил, по крайней мере, 0.1%; закрываем позицию в конце этого же дня.

Создадим массив данных, строки этого массива будут соответствовать датам, за которые были получены котировки с Yahoo Finance:

data = pd.DataFrame(index=ohlc.index)

Первый столбец массива data будет содержать изменение (в процентах) цены закрытия сегодняшнего дня относительно цены закрытия вчерашнего дня:

data['cc'] = 100*ohlc['close'].pct_change()  # Изменение сегодняшней цены закрытия по отношению
                                             # к цене закрытия предыдущего дня (в процентах).

Для контроля выведем цены закрытия последних 4-х дней и проверим значения в 3-х последних строках массива data:

print ohlc['close'].tail(4)
print '--------------------'
print data['cc'].tail(3)
2012-12-26    34.76
2012-12-27    34.82
2012-12-28    34.45
2012-12-31    34.90
Name: close, dtype: float64
--------------------
2012-12-27    0.172612
2012-12-28   -1.062608
2012-12-31    1.306241
Name: cc, dtype: float64

Действительно, значения в массиве data удовлетворяют формуле:

\[\text{CC}_i = \frac{\text{close}_i - \text{close}_{i-1}}{\text{close}_{i-1}} \cdot 100\%.\]

Теперь добавим к массиву data второй столбец, в нём для каждой даты будет храниться изменение сегодняшней цены открытия (в процентах) относительно вчерашней цены закрытия (так называемые, гепы):

\[\text{CO}_i = \frac{\text{open}_i - \text{close}_{i-1}}{\text{close}_{i-1}} \cdot 100\%.\]
data['co'] = 100*(ohlc['open']/ohlc['close'].shift(1)-1)  # Изменение сегодняшней цены открытия (в процентах)
                                                          # относительно вчерашней цены закрытия.

В третий столбец запишем изменение цены закрытия текущего дня относительно цены его открытия (в процентах):

\[\text{OC}_i = \frac{\text{close}_i - \text{open}_i}{\text{open}_i} \cdot 100\%.\]
data['oc'] = 100*(ohlc['close']/ohlc['open']-1)  # Изменение цены (в процентах) за текущий день.

Проверим корректность значений, выведем первые три записи:

data.head(3)
Первые строки массива
date cc co oc
2009-01-02 NaN NaN 1.127820
2009-01-05 0.413052 -0.413052 0.829531
2009-01-06 -1.480872 0.370218 -1.844262

и последние три записи:

data.tail(3)
Последние строки массива
date cc co oc
2012-12-27 0.172612 0.000000 0.172612
2012-12-28 -1.062608 -0.545663 -0.519781
2012-12-31 1.306241 -0.203193 1.512507

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

Заметим также, что первая строка содержит неверные параметры (NaN - “не число”), поскольку для самой первой цены закрытия нет предыдущего значения. Запомним этот факт, он понадобится нам чуть позже.

Построим график изменения всех трёх параметров:

data.plot()
../_images/TWP-Example-01_25_1.png

Рассчитаем коэффициент Шарпа для стратегии “купи и держи” за тот же период времени:

print 'Sharpe buy-and-hold:', sharpe(data['cc']) # Как обычно, сравниваем со стратегией "купи и держи".
Sharpe buy-and-hold: 0.731243764581

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

data.cumsum().plot(grid=True)  # Накопительная сумма ценовых изменений.
../_images/TWP-Example-01_29_1.png

На этом графике видно, какой вклад в ежедневные изменения цены (кривая cc) вносят ночные гепы (кривая co) и изменения цены в течение дня (кривая oc).

Теперь пора реализовать правила рассматриваемой стратегии:

idx = (data['cc']<-0.25).shift(1) & (data['co'] < -0.1)  # Оба правила стратегии.
idx[0] = False  # В первой строке нет данных (т.к. для соответствующего дня нет предыдущего).
data['long'] = idx  # Добавили новый столбец к массиву data (True, если оба правила стратегии выполнены).
data['pnl'] = 0.0 # Добавили новый столбец для хранения прибыли или убытка, инициализировали нулём.
data['pnl'][idx] = data['oc'][idx] # Для тех дней, которые удовлетворяют правилам стратегии, нашли прибыль или убыток (в процентах).
data.tail(5)  # Вывели последние 5 записей.
Протокол работы алгоритма
date cc co oc long pnl
2012-12-24 -0.312767 -0.341200 0.028531 True 0.028531
2012-12-26 -0.855676 -0.028523 -0.827389 False 0.000000
2012-12-27 0.172612 0.000000 0.172612 False 0.000000
2012-12-28 -1.062608 -0.545663 -0.519781 False 0.000000
2012-12-31 1.306241 -0.203193 1.512507 True 1.512507

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

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

data2 = data[(data['long']==True)] # Только те дни, когда открывались позиции.
data2.tail(5)                      # Вывели результаты последних 5 сделок.
Отчёт по сделкам
date cc co oc long pnl
2012-11-09 0.000000 -0.289017 0.289855 True 0.289855
2012-11-15 -0.029240 -0.263158 0.234535 True 0.234535
2012-12-04 -0.194986 -0.167131 -0.027902 True -0.027902
2012-12-24 -0.312767 -0.341200 0.028531 True 0.028531
2012-12-31 1.306241 -0.203193 1.512507 True 1.512507

Найдём коэффициент Шарпа для нашей стратегии и построим график накопленной прибыли/убытка:

print 'Sharpe:' , sharpe(data['pnl'])
data['pnl'].cumsum().plot()
Sharpe: 0.791394238187
../_images/TWP-Example-01_35_2.png

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

def backtest(ohlc, ccThresh=-0.25, coThresh=-0.1):
    data = pd.DataFrame(index=ohlc.index)
    data['cc'] = 100*ohlc['close'].pct_change()
    data['co'] = 100*(ohlc['open']/ohlc['close'].shift(1)-1)
    data['oc'] = 100*(ohlc['close']/ohlc['open']-1)
    idx = (data['cc']<ccThresh).shift(1) & (data['co'] < coThresh)
    idx[0] = False
    data['goLong'] = idx
    data['pnl'] = 0.0
    data['pnl'][idx] = data['oc'][idx]
    return data['pnl']

Проверим работу этой функции на тех же данных и получим уже известный результат:

pnl = backtest(ohlc)
pnl.cumsum().plot()
print 'Sharpe:', sharpe(pnl)
Sharpe: 0.791394238187
../_images/TWP-Example-01_39_1.png

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

ccThresh = np.linspace(-1, 1, 25)     # Массив из 25 значений от -1 до +1
coThresh = np.linspace(-1, 1, 25)     # Массив из 25 значений от -1 до +1

Sh = np.zeros((len(ccThresh),len(coThresh)))

for i, cc in enumerate(ccThresh):
    for j, co in enumerate(coThresh):
        pnl = backtest(ohlc, ccThresh=cc, coThresh=co)
        Sh[i,j] = sharpe(pnl)

pcolor(coThresh,ccThresh, Sh)
xlabel('opening gap [%]')
ylabel('previous day change [%]');
colorbar();
../_images/TWP-Example-01_41_0.png

Оптимальную комбинацию параметров можно оценить визуально (наибольшим значениям коэффициента Шарпа соответствуют красные области на графике), но лучше доверить это программе:

i,j = np.unravel_index(Sh.argmax(), Sh.shape)
Sh[i,j]
print 'Optimum CC %.2f' % ccThresh[i]
print 'Optimum CO %.2f' % coThresh[j]
Optimum CC 0.67
Optimum CO 0.08

Теперь запустим тест нашей стратегии с найденной комбинацией параметров, вычислим коэффициент Шарпа и построим график накопленной прибыли/убытка:

pnl = backtest(ohlc, ccThresh[i], coThresh[j])
print 'Sharpe:', sharpe(pnl)
pnl.cumsum().plot()
Sharpe: 1.63583062086
../_images/TWP-Example-01_45_2.png

Ссылки


Теги: Python, Торговые системы




Комментарии

Комментариев пока нет.

* Обязательные поля
(Не публикуется)
 
Жирный Курсив Подчеркнутый Перечеркнутый Степень Индекс Код PHP Код Кавычки Вставить линию Вставить маркированный список Вставить нумерованный список Вставить ссылку Вставить e-mail Вставить изображение Вставить видео
 
Улыбка Печаль Удивление Смех Злость Язык Возмущение Ухмылка Подмигнуть Испуг Круто Скука Смущение Несерьёзно Шокирован
 
1000
Captcha
Refresh
 
Введите код:
 
Запомнить информацию введенную в поля формы.