Бэктестинг: алгоритм на основе MACD

Индикатор MACD широко известен среди трейдеров💵. Мне его сигналы помогают находить развороты⤴ и предупреждения о коррекциях⛔. Много написано, как использовать его сигналы для открытия позиций, а мы сегодня рассмотрим прикладное применение в алготрейдинге🤖.

Все будет тестироваться на Quantopian (см. сюда), писать код будем на Python🐍. Рассмотрим следующие стратегии:

  • Что надо знать и как не надо делать.
  • Как есть: гистограмма, линия MACD, сигнальная.
  • Добавим стоп-лосс.
  • Торгуем в двух направлениях.
  • Отфильтруем боковики и волатильность.

Пара слов о MACD

Индикатор основан на экспоненциальных скользящих средних и показывает их пересечения. Когда основная линия MACD над нулем, это означает, что быстрая средняя находится над медленной, а цена растет в краткосрочном периоде. Положение гистограммы над нулем говорит о росте цены. Дополнительно на индикаторе ищут расхождения движения цены и сигнальных линий, что может предупредить о развороте тенденции, но это уже другая история.

Базовые параметры:

  • быстрая скользящая — EMA(12)
  • медленная скользащая — EMA(26)
  • сигнальная линия — EMA(9)

Разница первых двух используется для построения линии MACD. Сигнальная линия сглаживает линию MACD. Разница сигнальной и линии MACD дают гистограмму.

🎓Гипотеза

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

Исходя из этого, мы должны входить в рынок только на росте и будем брать все растущие движения. Звучит отлично!👍

Условия тестирования:

  • SPY
  • c 01/01/2004 до 30/12/2016 (13 лет)
  • торгуем спустя 1 час после открытия рынка

☔Что надо знать и как не надо делать

Начнем с простого теста, попробуем покупать актив по факту пересечения нуля снизу вверх каждой составляющей индикатора MACD: гистограмма, линия, сигнальная линия. Каждый день будем брать 40 дней истории и рассчитывать значения индикатора с помощью библиотеки ta-lib. Внизу графики тестов:

Данные оказались обратными предположению, гистограмма дает наилучший результат, затем идет линия, а хуже всех показатели у сигнальной. Пристальное изучение позволило найти ошибку, все дело в нестабильном периоде экспоненциальной средней (EMA). Ее значения напрямую зависят от длины анализируемой истории. Если история короткая, то библиотека ta-lib рассчитывает ее равной обычной скользящей средней (SMA). А это дает удивительно большую ошибку, так как сам MACD весь состоит из EMA.

Увеличив период истории сразу до 500 дней, исключим любой намек на подобные ошибки и получим результаты, которые и предположили. Далее рассмотрим стратегию «Как есть».

👎Как есть: гистограмма, линия MACD, сигнальная

Простой подход, чтобы посмотреть, как алгоритм работает. Какие результаты будут для каждого сигнала и, может быть, нас это наведет на какие-то интересные мысли. Сводная таблица результатов доступна в конце статьи. Графики результатов (правильных на этот раз) доступны ниже. Гипотеза подтвердилась:

  • Гистограмма дает много сигналов, но среди них много ложных.
  • Линия MACD уже чище и показывает лучшие результаты.
  • Сигнальная линия дает наибольшую прибыль и наименьшую просадку.

Код алгоритма:

import talib


# настраиваем переменные
def initialize(context):
    # тестируем только на SPY
    context.stocks = symbols('SPY')  # 'DIA', 'SPY', 'QQQ', 'IWM'
    context.pct_per_stock = 1.0 / len(context.stocks)
    
    # вызываем раз в день, спустя 1 час после открытия
    schedule_function(only_macd_long, date_rules.every_day(), time_rules.market_open(hours=1))
    
# ежедневный вызов
def only_macd_long(context, data):
    
    # история для рассчета индикаторов (мин. в 2 раза больше нужной EMA)
    prices = data.history(context.stocks, 'price', 500, '1d')
    
    # обрабатываем список акций
    for stock in context.stocks:        
        # получаем индикаторы MACD
        macd_line, signal, hist = talib.MACD(prices[stock], fastperiod=12, 
                                        slowperiod=26, signalperiod=9)
        # используем последнее значение гистограммы MACD
        macd = hist[-1]        
        
        current_position = context.portfolio.positions[stock].amount
        
        # закрываем имеющуюся позицию, если сигнал под нулем
        if macd < 0 and current_position > 0 and data.can_trade(stock):
            order_target(stock, 0)
            
        # открываем позицию, если сигнал над нулем и портфель пуст
        elif macd > 0 and current_position == 0 and data.can_trade(stock):
            order_target_percent(stock, context.pct_per_stock)

👎Добавим стоп-лосс

Так как мы торгуем только в лонг и у нас есть просадка в -20%, попробуем добавить стопы в 3% от цены открытия. До этого мы закрывались при пересечении сигнальной линией MACD и нуля. Теперь будем контролировать просадку и закрываться за час до конца торгов, если цена опустилась ниже 3% порога.

На графике ниже видны не сногсшибательные результаты. Удалось отыграть ~1% просадки и поплатиться частью прибыли. Дополнительно поставлен фильтр, чтобы алгоритм не перезаходил на падающей цене — цена предыдущего дня должна быть ниже текущей.

Код алгоритма ниже:

import talib


# настраиваем переменные
def initialize(context):
    # тестируем только на SPY
    context.stocks = symbols('SPY')  # 'DIA', 'SPY', 'QQQ', 'IWM'
    context.pct_per_stock = 1.0 / len(context.stocks)
    
    # вызываем раз в день, спустя 1 час после открытия
    schedule_function(only_macd_long, date_rules.every_day(), time_rules.market_open(hours=1))
    
    context.stoploss = {s: 0 for s in context.stocks}
    context.sl = 0.03
    # вызываем раз в день, за 1 час до закрытия, чтобы закрыть позицию по стопу
    schedule_function(check_to_stop, date_rules.every_day(), time_rules.market_close(hours=1))
    
    
# ежедневная проверка сигналов
def only_macd_long(context, data):
    
    # история для рассчета индикаторов (мин. в 2 раза больше нужной EMA)
    prices = data.history(context.stocks, 'price', 500, '1d')
    
    # обрабатываем список акций
    for stock in context.stocks:
        # пропускаем, если нельзя торговать
        if not data.can_trade(stock):
            continue
        
        # получаем индикаторы MACD
        macd_line, signal, hist = talib.MACD(prices[stock], fastperiod=12, 
                                        slowperiod=26, signalperiod=9)
        # используем последнее значение гистограммы MACD
        macd = signal[-1]        
        
        current_position = context.portfolio.positions[stock].amount
        
        # закрываем имеющуюся позицию, если сигнал под нулем
        if macd < 0 and current_position > 0:
            order_target(stock, 0)
            
        # открываем позицию, если сигнал над нулем и портфель пуст
        elif macd > 0 and current_position == 0 and prices[stock][-2] < prices[stock][-1]:
            order_target_percent(stock, context.pct_per_stock)
            # ставим уровень стопа
            context.stoploss[stock] = prices[stock][-1] * (1 - context.sl)
            
            
# ежедневная проверка на стопы за 1 час до закрытия
def check_to_stop(context, data):
    
    # получение цен
    prices = data.current(context.stocks, 'price')
    
    # обрабатываем список акций
    for stock in context.stocks:
        # пропускаем, если нельзя торговать
        if not data.can_trade(stock):
            continue
        
        current_position = context.portfolio.positions[stock].amount
        
        # пододвигаем стоп
        sl = prices[stock] * (1 - context.sl)
        if context.stoploss[stock] < sl:
            context.stoploss[stock] = sl
        else:
            sl = context.stoploss[stock]
        
        # ставим стоп-ордер, если есть позиция
        if prices[stock] < sl and current_position > 0:            
            order_target(stock, 0)

👎Торгуем в двух направлениях

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

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

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

👎Отфильтруем волатильность и боковики

Волатильность будем фильтровать повышенным значением ATR относительно среднего за последние 200 дней. Боковики постараемся найти положением средних, когда SMA20 находится в 1,5% от SMA50. В это время позиции не открываем.

Результаты показывают, что купи-держи значительно лучше, а вот просадку удается сократить до 13%. Хоть что-то…

Код алгоритма ниже:

🥇Результаты

Стратегия Время торгов Кол-во сделок PnL Доходность Просадки
Купи-Держи Open 53/0 - 160.1% -54.9%
Как есть: гистограмма Open+1 163/163 13% 30.8% -29.3%
Как есть: линия MACD Open+1 69/68 19% 68% -22.3%
Как есть: сигнальная линия Open+1 40/39 19% 90.2% -21.6%
Стоп-лосс Open+1 92/91 18% 77% -19%
В обе стороны Open+1 40/39 6% 28% -27%
Фильтр Open+1 24/23 19% 49.6% -12.9%
  • Время торгов — время, когда алгоритм начинает торговать: Open — открытие рынка, Open+1 — через 1 час после открытия рынка.
  • Кол-во сделок — количество сделок на покупку и продажу. В поле указаны Покупки/Продажи.
  • PnL — отношение прибыльных дней к убыточным. Если прибыльных больше — значение положительное.

🏁Заключение

Результаты тестов показывают, что в голом виде индикатор MACD не подходит для создания несметного богатства💰, во всяком случае на SPY. Удивительно, но обнаружил относительно хорошее поведение MACD и QQQ (здесь не публикую, попробуйте сами), в сравнении с другими «голыми» индикаторами.

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

💬Напишите в комментариях, как можно улучшить алгоритм с MACD. Где корень неудач? Или предложите, какие стратегии стоит рассмотреть в будущем.

Александр Румянцев aka "i.am.raa"
Автор Quantrum.me
Интересуетесь алготрейдингом на Python? Присоединяйтесь к команде. Пишите в личку или на email.
Готовы начать торговать💰? Тогда открывайте счет поближе к профессионалам👍.