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

Пакет bt (backtesting) предназначен для тестирования торговых стратегий, написанных с использованием языка программирования Python.

Установка пакета bt

Для установки пакета bt в системе Linux/Ubuntu достаточно выполнить в командной строке:

sudo pip install bt

Для системы Windows sudo опускаем.

Если лень открывать терминал с командной строкой, то можно выполнить установку прямо на странице IPython Notebook:

!pip install bt

Вместе с пакетом bt будет установлен также ffn.

Загрузка котировок

В пакете bt имеется собственная функция для загрузки котировок с сайта Yahoo Finance. Для примера скачаем цены закрытия (adjusted close) двух биржевых фондов (ETF): GDX и SPY:

import bt
data = bt.get('GDX, SPY', start='2012-01-01')
print data.head(3) # Вывели первые 3 записи для проверки.
                  gdx         spy
Date
2012-01-03  51.964246  117.497250
2012-01-04  51.906295  117.681556
2012-01-05  52.060836  117.994879

Класс Strategy

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

s1 = bt.Strategy('s1', [bt.algos.RunMonthly(),  # Выполняем ежемесячно;
                       bt.algos.SelectAll(),    # выбираем все бумаги;
                       bt.algos.WeighEqually(), # в равной пропорции;
                       bt.algos.Rebalance()])   # выполняем ребалансировку согласно выбранной пропорции.

Здесь мы указали, что будем проводить ежемесячную (RunMonthly) ребалансировку (Rebalance) портфеля, распределяя все имеющиеся средства поровну (WeighEqually) между всеми выбранными бумагами (SelectAll).

Усложним стратегию. Будем проводить еженедельную ребалансировку (RunWeekly), а весовые коэффициенты для каждой бумаги рассчитывать обратно пропорционально волатильности (WeighInvVol):

s2 = bt.Strategy('s2', [bt.algos.RunWeekly(),   # Еженедельно;
                        bt.algos.SelectAll(),   # выбираем все бумаги;
                        bt.algos.WeighInvVol(), # обратно пропорционально волатильности;
                        bt.algos.Rebalance()])  # выполняем ребалансировку согласно весовым коэффициентам.

Теперь проверим классическую стратегию торговли по скользящей средней: будем покупать только те бумаги, которые выше своей 250-дневной скользящей средней. Для выбора бумаг по заданному условию используем встроенный алгоритм SelectWhere:

import pandas as pd
sma1 = pd.rolling_mean(data, 250) # Простая скользящая средняя.
s3 = bt.Strategy('SMA1', [
         bt.algos.SelectWhere(data > sma1), # Выбираем только те бумаги, которые выше скользящей средней;
         bt.algos.WeighEqually(),           # делим средства в равной попорции.
         bt.algos.Rebalance()] )

Результаты торговли по любой системе обычно сравнивают с каким-нибудь бенчмарком. Часто в качестве бенчмарка выбирают стратегию “купи и держи” (buy & hold):

s0 = bt.Strategy('bh', [bt.algos.RunOnce(),       # Только один раз;
                        bt.algos.SelectAll(),     # выбираем все бумаги;
                        bt.algos.WeighEqually(),  # в равной пропорции.
                        bt.algos.Rebalance()])

Итак, мы определили четыре торговые стратегии. Пора их протестировать.

Тестирование торговых стратегий

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

test1 = bt.Backtest(s1, data)
res1 = bt.run(test1)

Результат можно отобразить в виде графика эквити:

res1.plot()
../_images/btIntro_01.png

Таблица результатов:

res1.display()
Stat                 s1
-------------------  ----------
Start                2012-01-03
End                  2015-12-24
Risk-free rate       0.00%

Total Return         -28.53%
Daily Sharpe         -0.27
CAGR                 -8.11%
Max Drawdown         -37.61%

MTD                  1.81%
3m                   4.26%
6m                   -10.86%
YTD                  -8.78%
1Y                   -7.82%
3Y (ann.)            -8.98%
5Y (ann.)            -8.11%
10Y (ann.)           -8.11%
Since Incep. (ann.)  -8.11%

Daily Sharpe         -0.27
Daily Mean (ann.)    -6.01%
Daily Vol (ann.)     22.09%
Daily Skew           -0.06
Daily Kurt           1.34
Best Day             4.80%
Worst Day            -6.40%

Monthly Sharpe       -0.37
Monthly Mean (ann.)  -6.86%
Monthly Vol (ann.)   18.49%
Monthly Skew         0.11
Monthly Kurt         -0.57
Best Month           9.87%
Worst Month          -10.79%

Yearly Sharpe        -0.85
Yearly Mean          -9.06%
Yearly Vol           10.69%
Yearly Skew          -0.12
Yearly Kurt          -
Best Year            1.48%
Worst Year           -19.90%

Avg. Drawdown        -8.51%
Avg. Drawdown Days   201.29
Avg. Up Month        4.41%
Avg. Down Month      -4.26%
Win Year %           33.33%
Win 12m %            24.32%

Гистограмма распределения прибылей/убытков (return):

res1.plot_histogram()
../_images/btIntro_02.png

Весовые коэффициенты для каждой бумаги:

res1.plot_security_weights()
../_images/btIntro_03.png

Можно запустить тест сразу для нескольких стратегий:

test0 = bt.Backtest(s0, data)
test1 = bt.Backtest(s1, data)
test2 = bt.Backtest(s2, data)
test3 = bt.Backtest(s3, data)
res = bt.run(test1, test2, test3, test0) # Запустили четыре теста для последующего сравнения.
res.plot()
../_images/btIntro_04.png

Сравним результаты тестирования всех четырёх стратегий:

res.display()
Stat                 s1          s2          SMA1        bh
-------------------  ----------  ----------  ----------  ----------
Start                2012-01-03  2012-01-03  2012-01-03  2012-01-03
End                  2015-12-24  2015-12-24  2015-12-24  2015-12-24
Risk-free rate       0.00%       0.00%       0.00%       0.00%

Total Return         -28.53%     14.46%      26.69%      1.25%
Daily Sharpe         -0.27       0.30        0.63        0.10
CAGR                 -8.11%      3.46%       6.14%       0.31%
Max Drawdown         -37.61%     -18.92%     -12.25%     -21.40%

MTD                  1.81%       0.45%       -5.24%      -0.15%
3m                   4.26%       5.41%       -5.11%      6.40%
6m                   -10.86%     -7.65%      -10.83%     -4.57%
YTD                  -8.78%      -5.22%      -7.82%      -1.91%
1Y                   -7.82%      -5.43%      -8.81%      -2.24%
3Y (ann.)            -8.98%      2.20%       8.21%       0.69%
5Y (ann.)            -8.11%      3.46%       6.14%       0.31%
10Y (ann.)           -8.11%      3.46%       6.14%       0.31%
Since Incep. (ann.)  -8.11%      3.46%       6.14%       0.31%

Daily Sharpe         -0.27       0.30        0.63        0.10
Daily Mean (ann.)    -6.01%      4.52%       6.49%       1.53%
Daily Vol (ann.)     22.09%      14.97%      10.36%      15.62%
Daily Skew           -0.06       -0.22       -0.35       -0.23
Daily Kurt           1.34        1.83        2.54        1.76
Best Day             4.80%       3.26%       2.74%       3.61%
Worst Day            -6.40%      -5.06%      -3.02%      -4.81%

Monthly Sharpe       -0.37       0.27        0.65        0.01
Monthly Mean (ann.)  -6.86%      3.35%       6.55%       0.11%
Monthly Vol (ann.)   18.49%      12.48%      10.05%      12.69%
Monthly Skew         0.11        0.25        -0.46       0.37
Monthly Kurt         -0.57       -0.59       0.34        -0.40
Best Month           9.87%       8.71%       5.62%       8.56%
Worst Month          -10.79%     -5.99%      -7.16%      -5.97%

Yearly Sharpe        -0.85       0.31        0.46        0.05
Yearly Mean          -9.06%      1.98%       9.46%       0.35%
Yearly Vol           10.69%      6.28%       20.63%      6.84%
Yearly Skew          -0.12       -1.60       1.13        1.33
Yearly Kurt          -           -           -           -
Best Year            1.48%       6.39%       32.30%      8.03%
Worst Year           -19.90%     -5.22%      -7.82%      -5.06%

Avg. Drawdown        -8.51%      -2.32%      -1.71%      -6.13%
Avg. Drawdown Days   201.29      47.31       19.60       143.10
Avg. Up Month        4.41%       2.96%       2.91%       3.42%
Avg. Down Month      -4.26%      -2.77%      -1.54%      -2.52%
Win Year %           33.33%      66.67%      66.67%      33.33%
Win 12m %            24.32%      70.27%      83.78%      48.65%

Построение графиков

Отобразим на графике скользящую среднюю:

plot = bt.merge(data, sma1).plot(figsize=(17, 6)).legend(['GDX','SPY','GDX SMA', 'SPY SMA'], loc='upper left')
../_images/btIntro_05.png

При построении графика можно указать имя какой-то одной стратегии:

res.plot_security_weights('SMA1')
../_images/btIntro_06.png

Оптимизация

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

def above_sma(tickers, period=250, start='2010-01-01', name='above_sma'):
    """
    Покупаем бумагу, если она выше скользящей средней.
    """
    data = bt.get(tickers, start=start)
    sma1 = pd.rolling_mean(data, period)
    s = bt.Strategy(name, [bt.algos.SelectWhere(data > sma1),
                           bt.algos.WeighEqually(),
                           bt.algos.Rebalance()])
    return bt.Backtest(s, data)

Также определим отдельную функцию для бенчмарка – стратегии “купи и держи” (buy & hold), с которой будем сравнивать результаты всех остальных стратегий:

def bh(tickers, start='2010-01-01', name='buy&hold'):
    s = bt.Strategy(name, [bt.algos.RunOnce(),       # Только один раз;
                           bt.algos.SelectAll(),     # выбираем все бумаги;
                           bt.algos.WeighEqually(),  # в равной пропорции.
                           bt.algos.Rebalance()])
    data = bt.get(tickers, start=start)  # Получаем котировки.
    return bt.Backtest(s, data)          # Запускаем тестирование и возвращаем результат.

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

# Бумаги для торговли:
tickers = 'aapl,msft,c,gs,ge'

# Тесты с разными значениями периода скользящей средней:
sma1 = above_sma(tickers, period=10, name='sma10')
sma2 = above_sma(tickers, period=20, name='sma20')
sma3 = above_sma(tickers, period=50, name='sma50')
sma4 = above_sma(tickers, period=100, name='sma100')
sma5 = above_sma(tickers, period=150, name='sma150')
sma6 = above_sma(tickers, period=200, name='sma200')

# Бенчмарк:
benchmark = bh('spy', name='bh_spy')

# Выполняем все тесты:
res = bt.run(sma1, sma2, sma3, sma4, sma5, sma6, benchmark)
res.plot()
../_images/btIntro_07.png
res.display()
Stat                 sma10       sma20       sma50       sma100      sma150      sma200      bh_spy
-------------------  ----------  ----------  ----------  ----------  ----------  ----------  ----------
Start                2010-01-04  2010-01-04  2010-01-04  2010-01-04  2010-01-04  2010-01-04  2010-01-04
End                  2015-12-24  2015-12-24  2015-12-24  2015-12-24  2015-12-24  2015-12-24  2015-12-24
Risk-free rate       0.00%       0.00%       0.00%       0.00%       0.00%       0.00%       0.00%

Total Return         12.69%      39.90%      37.43%      149.84%     171.23%     158.56%     105.02%
Daily Sharpe         0.20        0.40        0.38        0.93        1.06        1.04        0.84
CAGR                 2.02%       5.79%       5.47%       16.58%      18.20%      17.25%      12.78%
Max Drawdown         -25.04%     -28.65%     -33.79%     -17.87%     -19.15%     -19.90%     -18.61%

MTD                  -7.24%      -3.45%      -0.31%      -0.15%      1.85%       1.83%       -0.86%
3m                   3.14%       8.28%       15.01%      12.06%      17.76%      20.10%      7.26%
6m                   -7.14%      1.49%       5.32%       -0.99%      4.81%       5.61%       -1.20%
YTD                  -11.45%     2.16%       13.94%      11.71%      17.04%      16.80%      2.13%
1Y                   -13.09%     -0.06%      11.78%      9.69%       14.89%      14.65%      1.04%
3Y (ann.)            2.28%       8.14%       12.30%      20.43%      25.59%      26.18%      15.34%
5Y (ann.)            0.98%       4.49%       7.87%       19.18%      19.01%      18.96%      12.67%
10Y (ann.)           2.02%       5.79%       5.47%       16.58%      18.20%      17.25%      12.78%
Since Incep. (ann.)  2.02%       5.79%       5.47%       16.58%      18.20%      17.25%      12.78%

Daily Sharpe         0.20        0.40        0.38        0.93        1.06        1.04        0.84
Daily Mean (ann.)    3.80%       7.31%       7.06%       17.01%      18.19%      17.30%      13.27%
Daily Vol (ann.)     18.94%      18.35%      18.60%      18.27%      17.21%      16.67%      15.80%
Daily Skew           -0.41       -0.51       -0.61       -0.18       -0.09       -0.10       -0.37
Daily Kurt           6.60        4.24        4.34        3.95        3.59        3.17        3.99
Best Day             9.54%       5.78%       5.78%       5.86%       5.89%       5.89%       4.65%
Worst Day            -8.01%      -8.01%      -8.01%      -6.39%      -6.39%      -5.46%      -6.51%

Monthly Sharpe       0.26        0.38        0.36        1.00        1.10        1.09        1.07
Monthly Mean (ann.)  5.49%       7.75%       7.76%       16.96%      18.32%      17.39%      13.95%
Monthly Vol (ann.)   21.19%      20.51%      21.72%      16.90%      16.58%      15.96%      13.02%
Monthly Skew         -0.22       0.02        -0.55       0.11        0.08        0.29        -0.14
Monthly Kurt         0.97        -0.06       0.48        -0.26       0.14        0.54        0.23
Best Month           18.23%      14.28%      13.54%      11.60%      14.94%      15.85%      10.91%
Worst Month          -17.40%     -14.76%     -16.33%     -9.44%      -8.78%      -8.40%      -7.95%

Yearly Sharpe        0.13        0.27        0.47        1.03        1.04        1.01        1.05
Yearly Mean          2.57%       6.94%       9.54%       20.58%      20.44%      20.48%      13.16%
Yearly Vol           19.77%      25.74%      20.48%      19.92%      19.65%      20.21%      12.48%
Yearly Skew          0.62        0.79        -0.22       0.15        -0.48       -0.67       0.92
Yearly Kurt          -1.97       -0.50       -2.09       -2.07       0.54        0.78        0.52
Best Year            29.21%      44.86%      33.27%      43.80%      44.27%      43.95%      32.31%
Worst Year           -16.93%     -17.10%     -15.70%     -3.65%      -8.26%      -9.84%      1.90%

Avg. Drawdown        -6.51%      -5.11%      -3.89%      -2.67%      -2.44%      -2.31%      -1.82%
Avg. Drawdown Days   110.26      64.31       72.46       22.76       18.57       19.15       17.77
Avg. Up Month        4.41%       4.83%       4.90%       4.44%       4.42%       4.40%       3.26%
Avg. Down Month      -4.94%      -4.46%      -4.84%      -3.23%      -2.92%      -2.58%      -2.94%
Win Year %           40.00%      60.00%      60.00%      80.00%      80.00%      80.00%      100.00%
Win 12m %            63.93%      57.38%      65.57%      93.44%      93.44%      90.16%      95.08%

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

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

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

data = bt.get('spy', start='2010-01-01')
sma1 = pd.rolling_mean(data, 50)   # Быстрая скользящая средняя.
sma2 = pd.rolling_mean(data, 150)  # Медленная скользящая средняя.
tw = sma2.copy()             # Создали новую колонку копированием.
tw[sma1 > sma2] = 1.0        # Весовой коэффициент для покупки.
tw[sma1 <= sma2] = -1.0      # Весовой коэффициент для короткой продажи.
tw[sma2.isnull()] = 0.0  # Первые значения, когда скользящая средняя не определена.
tmp = bt.merge(tw, data, sma1, sma2)                # Соединили колонки.
tmp.columns = ['tw', 'price', 'sma1', 'sma2']       # Имена колонок.
ax = tmp.plot(figsize=(15,5), secondary_y=['tw'])   # График.
../_images/btIntro_08.png

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

ma_cross = bt.Strategy('ma_cross', [bt.algos.WeighTarget(tw),
                                    bt.algos.Rebalance()])
t = bt.Backtest(ma_cross, data)
res = bt.run(t)
res.plot()
../_images/btIntro_09.png
res.display()
Stat                 ma_cross
-------------------  ----------
Start                2010-01-04
End                  2015-12-24
Risk-free rate       0.00%

Total Return         46.32%
Daily Sharpe         0.51
CAGR                 6.59%
Max Drawdown         -15.33%

MTD                  0.72%
3m                   -7.25%
6m                   -10.05%
YTD                  -7.02%
1Y                   -8.02%
3Y (ann.)            11.78%
5Y (ann.)            7.70%
10Y (ann.)           6.59%
Since Incep. (ann.)  6.59%

Daily Sharpe         0.51
Daily Mean (ann.)    7.42%
Daily Vol (ann.)     14.49%
Daily Skew           0.03
Daily Kurt           5.10
Best Day             6.51%
Worst Day            -4.65%

Monthly Sharpe       0.61
Monthly Mean (ann.)  7.15%
Monthly Vol (ann.)   11.81%
Monthly Skew         -0.76
Monthly Kurt         1.26
Best Month           6.71%
Worst Month          -10.49%

Yearly Sharpe        0.55
Yearly Mean          8.52%
Yearly Vol           15.58%
Yearly Skew          0.90
Yearly Kurt          0.44
Best Year            32.31%
Worst Year           -7.02%

Avg. Drawdown        -2.07%
Avg. Drawdown Days   26.21
Avg. Up Month        3.01%
Avg. Down Month      -2.19%
Win Year %           60.00%
Win 12m %            83.61%

Тетрадка IPython Notebook для этого примера (проверялось на Python 2.7).


Ссылки


Теги: Python




Комментарии

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

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