Создание и тестирование торговой стратегии канала Дончиана и скользящей средней на Python.

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

Я только что опубликовал новую книгу после успеха моей предыдущей «Новые технические индикаторы на Python». Он содержит более полное описание и добавление структурированных торговых стратегий со страницей GitHub, посвященной постоянно обновляемому коду. Если вы считаете, что это вас заинтересует, перейдите по ссылке ниже или, если вы предпочитаете купить версию в формате PDF, вы можете связаться со мной через LinkedIn.



Получение исторических данных FX

Одна из самых известных торговых платформ в розничном сообществе - это программа MetaTrader5. Это мощный инструмент со своим собственным языком программирования и огромной поддержкой онлайн-сообщества. Он также предлагает возможность экспортировать исторические краткосрочные и долгосрочные валютные данные.

Первое, что нам нужно сделать, это просто скачать платформу с официального сайта. Затем, после создания демо-счета, мы готовы импортировать библиотеку на Python, которая позволяет импортировать данные OHLC из MetaTrader5.

Библиотека - это группа структурированных функций, которые можно импортировать в наш интерпретатор Python, откуда мы можем вызывать и использовать те, которые нам нужны.

Самый простой способ установить библиотеку MetaTrader5 - это перейти в командную строку Python на нашем компьютере и ввести:

pip install MetaTrader5

Это должно установить библиотеку в нашем локальном Python. Теперь мы хотим импортировать его в интерпретатор Python (например, PyCharm или SPYDER), чтобы мы могли его использовать. Давайте фактически импортируем все библиотеки, которые мы будем использовать для этого:

import datetime                 # Date acquiring
import pytz                     # Time zone management
import pandas            as pd  # Mostly for Data frame manipulation
import MetaTrader5       as mt5 # Importing OHLC data
import matplotlib.pyplot as plt # Plotting charts
import numpy             as np  # Mostly for array manipulation

Все, что идет после «as», является ярлыком. Ярлык plt существует для того, чтобы каждый раз, когда мы хотим вызвать функцию из этой библиотеки, нам не нужно вводить полный matplotlib.pyplot утверждение.

Официальную документацию по библиотеке Metatrader5 можно найти здесь.

Первое, что мы можем сделать, это выбрать временные рамки, которые мы хотим импортировать. Предположим, что есть только два таймфрейма, 30-минутный и часовой бары. Таким образом, мы можем создавать переменные, которые содержат оператор, чтобы сообщить библиотеке MetaTrader5, какой временной интервал нам нужен.

# Choosing the 30-minute time frame
frame_M30  = mt5.TIMEFRAME_M30
# Choosing the hourly time frame
frame_H1   = mt5.TIMEFRAME_H1

Затем, оставаясь в духе импорта переменных, мы можем определить переменную, которая указывает текущую дату. Это помогает алгоритму узнать дату остановки импорта. Мы можем сделать это с помощью простой строки кода ниже.

# Defining the variable now to give out the current date
now = datetime.datetime.now()

Обратите внимание, что эти фрагменты кода лучше использовать в хронологическом порядке, поэтому я рекомендую вам копировать их по порядку, а затем выполнять их один за другим, чтобы вы понимали эволюцию того, что вы делаете. Ниже представлена ​​функция, которая содержит необходимые нам активы. Обычно я использую 10 или больше, но для простоты давайте предположим, что существует только две валютные пары: EURUSD и USDCHF.

def asset_list(asset_set):
    
    if asset_set == 'FX':assets = ['EURUSD', 'USDCHF']
    return assets

Теперь о ключевой функции, которая получает данные OHLC. Ниже устанавливается соединение с MetaTrader5, применяется текущая дата и извлекаются необходимые данные. Обратите внимание на аргументы год, месяц и день. Они будут заполнены нами, чтобы выбрать, когда мы хотим, чтобы данные начинались. Обратите внимание: я ввел Европу / Париж в качестве своего часового пояса, вы должны использовать свой часовой пояс, чтобы получить более точные данные.

def get_quotes(time_frame, year = 2005, month = 1, day = 1, asset = "EURUSD"):
        
    # Establish connection to MetaTrader 5 
    if not mt5.initialize():
        print("initialize() failed, error code =", mt5.last_error())
        quit()
    
    timezone = pytz.timezone("Europe/Paris")
    
    utc_from = datetime.datetime(year, month, day, tzinfo = timezone)
    utc_to = datetime.datetime(now.year, now.month, now.day + 1, tzinfo = timezone)
    
    rates = mt5.copy_rates_range(asset, time_frame, utc_from, utc_to)
    
    rates_frame = pd.DataFrame(rates)    
    return rates_frame

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

def mass_import(asset, horizon):
        
 if horizon == 'M30':
   data = get_quotes(frame_M30, 2019, 1, 1, asset = assets[asset])
   data = data.iloc[:, 1:5].values
   data = data.round(decimals = 5)
 return data

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

# Choosing the horizon
horizon = 'M30'
# Creating an array called EURUSD having M30 data since 2019
EURUSD = mass_import(0, horizon)

И вуаля, теперь у нас есть данные OHLC по EURUSD за 2019 год.

Канал Дончиана

Этот простой и отличный индикатор, созданный Ричардом Дончианом, используется для определения прорывов и разворотов. Наша цель - объективно определить выход из диапазона по преодолению или преодолению любого из барьеров.

Способ его формирования состоит в том, чтобы сначала вычислить максимум из последних n-периодных максимумов и минимум из последних n-периодных минимумов, а затем вычислить среднее из них обоих. Это дает нам три меры:

  • Верхняя полоса Дончиана.
  • Нижняя полоса Дончиана.
  • Средняя полоса как среднее между двумя полосами. Мы не будем включать эту полосу в исследование, так как она мало что дает.

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

def adder(Data, times):
    
    for i in range(1, times + 1):
    
        new = np.zeros((len(Data), 1), dtype = float)
        Data = np.append(Data, new, axis = 1)
    return Data
def deleter(Data, index, times):
    
    for i in range(1, times + 1):
    
        Data = np.delete(Data, index, axis = 1)
    return Data
   
def jump(Data, jump):
    
    Data = Data[jump:, ]
    
    return Data
def donchian(Data, low, high, lookback, where, median = 1):
    
    for i in range(len(Data)):
        try:
            Data[i, where] = max(Data[i - lookback:i + 1, high])
        
        except ValueError:
            pass
        
    for i in range(len(Data)):
        try:
            Data[i, where + 1] = min(Data[i - lookback:i + 1, low]) 
        
        except ValueError:
            pass
        
    if median == 1:
        
        for i in range(len(Data)): 
            try:
                Data[i, where + 2] = (Data[i, where] + Data[i, where + 1]) / 2 
            
            except ValueError:
                pass      
        
    Data = jump(Data, lookback)
lookback_donchian = 20
# Calling the function
my_data = donchian(my_data, 2, 1, lookback_donchian, 4, median = 0)

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

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



Скользящие средние

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

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

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

def ma(Data, lookback, close, where): 
    
    Data = adder(Data, 1)
    
    for i in range(len(Data)):
           
     try:
       Data[i, where] = (Data[i - lookback + 1:i + 1, close].mean())
            
            except IndexError:
                pass
    
    return Data

Ниже указано, что функция скользящего среднего будет вызываться в массиве с именем my_data для периода ретроспективного анализа 200, в столбце с индексом 3 (цены закрытия в массиве OHLC). Затем значения скользящего среднего будут помещены в столбец с индексом 4, который мы добавили с помощью функции сумматора.

my_data = ma(my_data, 200, 3, 4)

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



Создание и оценка стратегии

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

  • Для сигнала покупки (длинной позиции) рыночная цена должна превышать предыдущий максимум Дончиана (следовательно, текущее закрытие выше, чем самый высокий максимум n периодов назад, за исключением текущего максимума), в то время как одновременно краткосрочная (20-периодная) скользящая средняя находится выше долгосрочной (60-периодной) скользящей средней.
  • Для сигнала продажи (короткой позиции) рыночная цена должна пробить предыдущий минимум Дончиана (следовательно, текущее закрытие ниже, чем самый низкий минимум n периодов назад, исключая текущий минимум), и одновременно краткосрочный ( 20-периодная) скользящая средняя ниже долгосрочной (60-периодной) скользящей средней.

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

def signal(Data, close, donchian_upper, donchian_lower, ma_long_column, ma_short_column, buy, sell):
    
    for i in range(len(Data)):
        
        if Data[i, close] > Data[i - 1, donchian_upper] and \
           Data[i, ma_short_column] > Data[i, ma_long_column] and \
           Data[i - 1, buy] == 0:
               
               Data[i, buy] = 1
elif Data[i, close] < Data[i - 1, donchian_lower] and \
           Data[i, ma_short_column] < Data[i, ma_long_column] and \
           Data[i - 1, sell] == 0:
               
               Data[i, sell] = -1   
               
    return Data
# Strategy Code
lookback_donchian = 20
lookback_ma_long  = 60
lookback_ma_short = 20
# Adding a few empty columns to be populated
my_data = adder(my_data, 10)
# Calculating the 20-period Donchian Channel
my_data = donchian(my_data, 2, 1, lookback_donchian, 4, median = 0)
# Calculating the 60-period moving average
my_data = ma(my_data, lookback_ma_long, 3, 6)
# Calculating the 20-period moving average
my_data = ma(my_data, lookback_ma_short, 3, 7)
# Calling the function
my_data = signal(my_data, 3, 4, 5, 6, 7, 8, 9)

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

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

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

# Choosing a Holding Period for a trend-following strategy
my_data = deleter(my_data, 4, 2)
period = 30
def signal_quality(Data, closing, buy, sell, period, where):
    
    Data = adder(Data, 1)
    
    for i in range(len(Data)):
        
        try:   
            
            if Data[i, buy] == 1:
                
                Data[i + period, where] = Data[i + period, closing] - Data[i, closing]
            
            if Data[i, sell] == -1:
                
                Data[i + period, where] = Data[i, closing] - Data[i + period, closing]
                
        except IndexError:
            
             pass
         
    return Data
# Applying the Signal Quality Function
my_data = signal_quality(my_data, 3, 6, 7, period, 8)
positives = my_data[my_data[:, 8] > 0]
negatives = my_data[my_data[:, 8] < 0]
# Calculating Signal Quality
signal_quality = len(positives) / (len(negatives) + len(positives))
print('Signal Quality = ', round(signal_quality * 100, 2), '%')
# Output Signal Quality EURUSD = 49.75%
# Output Signal Quality USFCHF = 50.06%
# Output Signal Quality GBPUSD = 49.42%
# Output Signal Quality AUDUSD = 48.64%
# Output Signal Quality NZDUSD = 48.58%
# Output Signal Quality USDCAD = 47.60%

Качество сигнала 49,75% означает, что на 100 сделок мы склонны видеть прибыльный результат в 49–50 случаях без учета транзакционных издержек.

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

Заключение

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

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

Подводя итог, можно ли сказать, что стратегии, которые я предлагаю, реалистичны? Да, но только путем оптимизации среды (надежный алгоритм, низкие затраты, честный брокер, надлежащее управление рисками и управление заказами). Предусмотрены ли стратегии исключительно для торговли? Нет, это нужно для стимулирования мозгового штурма и получения новых торговых идей, поскольку мы все устали слышать о перепроданности RSI как о причине для открытия короткой позиции или о преодолении сопротивления как о причине идти долго. Я пытаюсь представить новую область под названием «Объективный технический анализ», в которой мы используем достоверные данные для оценки наших методов, а не полагаемся на устаревшие классические методы.