Создание технического сканера с множественными временными рамками на Python.

Многократный анализ временных рамок открывает больше возможностей для более высокого качества анализа, и это связано с отслеживанием большего количества сигналов и шаблонов. Технические индикаторы рассчитываются на определенном временном интервале, например, на часовом временном интервале. Но что они говорят, когда мы применяем их к 30-минутным графикам двухчасовых графиков? Подтверждают ли они ту же предвзятость? В этом суть статьи. Создадим сканер технического индикатора на 8 разных таймфреймах.

Я только что опубликовал новую книгу после успеха New Technical Indicators in Python. Он содержит более полное описание и добавление сложных торговых стратегий со страницей Github, посвященной постоянно обновляемому коду. Если вы считаете, что это вас заинтересует, не стесняйтесь перейти по приведенной ниже ссылке или, если вы предпочитаете купить версию в формате PDF, вы можете связаться со мной в Linkedin.



Загрузка исторических данных OHLC

Одна из самых известных торговых платформ в розничном сообществе - это программа 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

Все, что идет после «как», является ярлыком. Ярлык 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 год.

Импорт данных с несколькими временными интервалами

Следующим шагом является выбор временных рамок, которые мы хотим просматривать в нашем сканере. Давайте сохраним его краткосрочным и выберем 1-минутный, 5-минутный, 10-минутный, 15-минутный, 20-минутный, 30-минутный, часовой и 2-часовой периоды времени. Это дает нам 8 графиков, на которых мы увидим значения технического индикатора, которые мы определим позже. Код для импорта необходимых графиков с выбранными таймфреймами может быть следующим.

def mass_import(asset, horizon):
    
    if horizon == 'MN1':
        data = get_quotes(frame_MIN1, 2021, 7, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)    
    
    if horizon == 'M5':
        data = get_quotes(frame_M5, 2021, 6, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)
if horizon == 'M10':
        data = get_quotes(frame_M10, 2021, 1, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)
        
    if horizon == 'M15':
        data = get_quotes(frame_M15, 2021, 1, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)
        
    if horizon == 'M30':
        data = get_quotes(frame_M30, 2016, 8, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)   
        
    if horizon == 'M20':
        data = get_quotes(frame_M20, 2018, 8, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)           
        
    if horizon == 'H1':
        data = get_quotes(frame_H1, 2011, 1, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)        
        
    if horizon == 'H2':
        data = get_quotes(frame_H2, 2010, 1, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)        
return data
pair = 0
# Mass imports 
my_data_MN1 = mass_import(pair, 'MN1')
my_data_M5  = mass_import(pair, 'M5')
my_data_M10 = mass_import(pair, 'M10')
my_data_M15 = mass_import(pair, 'M15')
my_data_M20 = mass_import(pair, 'M20')
my_data_M30 = mass_import(pair, 'M30')
my_data_H1  = mass_import(pair, 'H1')
my_data_H2  = mass_import(pair, 'H2')

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

# The function to add a certain number of columns
def adder(Data, times):
    
    for i in range(1, times + 1):
    
        z = np.zeros((len(Data), 1), dtype = float)
        Data = np.append(Data, z, axis = 1)
    return Data 
my_data_MN1 = adder(my_data_MN1, 10)
my_data_M5  = adder(my_data_M5,  10)
my_data_M10 = adder(my_data_M10, 10)
my_data_M15 = adder(my_data_M15, 10)
my_data_M20 = adder(my_data_M20, 10)
my_data_M30 = adder(my_data_M30, 10)
my_data_H1  = adder(my_data_H1,  10)
my_data_H2  = adder(my_data_H2,  10)

Индекс относительной силы

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

RSI рассчитывается довольно простым способом. Сначала мы берем разницу в ценах за один период. Это означает, что мы должны вычесть каждую цену закрытия из предыдущей. Затем мы вычислим сглаженное среднее положительных разностей и разделим его на сглаженное среднее отрицательных разностей. Последний расчет дает нам относительную силу, которая затем используется в формуле RSI для преобразования в меру от 0 до 100.

Для расчета индекса относительной силы нам понадобится массив OHLC (а не фрейм данных). Это означает, что мы будем смотреть на массив из 4 столбцов. Таким образом, функция индекса относительной силы:

def rsi(Data, lookback, close, where, width = 1, genre = 'Smoothed'):
    
    # Adding a few columns
    Data = adder(Data, 7)
    
    # Calculating Differences
    for i in range(len(Data)):
        
        Data[i, where] = Data[i, close] - Data[i - width, close]
     
    # Calculating the Up and Down absolute values
    for i in range(len(Data)):
        
        if Data[i, where] > 0:
            
            Data[i, where + 1] = Data[i, where]
            
        elif Data[i, where] < 0:
            
            Data[i, where + 2] = abs(Data[i, where])
            
    # Calculating the Smoothed Moving Average on Up and Down
    absolute values    
    if genre == 'Smoothed':                          
      lookback = (lookback * 2) - 1 # From exponential to smoothed
      Data = ema(Data, 2, lookback, where + 1, where + 3)
      Data = ema(Data, 2, lookback, where + 2, where + 4)
    
    if genre == 'Simple':                             
      Data = ma(Data, lookback, where + 1, where + 3)
      Data = ma(Data, lookback, where + 2, where + 4)
    
    # Calculating the Relative Strength
    Data[:, where + 5] = Data[:, where + 3] / Data[:, where + 4]
    
    # Calculate the Relative Strength Index
    Data[:, where + 6] = (100 - (100 / (1 + Data[:, where + 5])))  
    
    # Cleaning
    Data = deleter(Data, where, 6)
    Data = jump(Data, lookback)                        
    return Data

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

# The function to deleter a certain number of columns
def deleter(Data, index, times):
    
    for i in range(1, times + 1):
    
        Data = np.delete(Data, index, axis = 1)               
    
    return Data
# The function to delete a certain number of rows from the beginning
def jump(Data, jump):
    
    Data = Data[jump:, ]
    
    return Data

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



Создание сканера множественных таймфреймов

Следующим шагом является вычисление индекса относительной силы для различных массивов, которые мы импортировали. На данный момент у нас есть 8 массивов, каждый из которых содержит данные OHLC EURUSD, причем каждый имеет разные временные рамки. Нас действительно интересует последнее значение, чтобы увидеть, является ли индекс относительной силы перепроданным или перекупленным.

lookback = 13
my_data_MN1 = rsi(my_data_MN1, lookback, 3, 4)
my_data_M5  = rsi(my_data_M5, lookback,  3, 4)
my_data_M10 = rsi(my_data_M10, lookback, 3, 4)
my_data_M15 = rsi(my_data_M15, lookback, 3, 4)
my_data_M20 = rsi(my_data_M20, lookback, 3, 4)
my_data_M30 = rsi(my_data_M30, lookback, 3, 4)
my_data_H1  = rsi(my_data_H1, lookback,  3, 4)
my_data_H2  = rsi(my_data_H2, lookback,  3, 4)

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

  • Для каждого массива, если 13-периодный RSI ниже 25, появится соответствующее сообщение.
  • Для каждого массива, если 13-периодный RSI больше 75, появится соответствующее сообщение.
  • Для каждого массива, если 13-периодный RSI находится между 25 и 75, появится соответствующее сообщение.
upper_barrier = 75
lower_barrier = 25
if my_data_MN1[-1, 4] < lower_barrier:
    
    print('RSI 1-Minute  Chart Oversold')
elif my_data_MN1[-1, 4] > upper_barrier:
    
    print('RSI 1-Minute  Chart Overbought')   
    
else:
    
    print('RSI 1-Minute  Chart Normal')   
    
    
    
if my_data_M5[-1, 4] < lower_barrier:
    
    print('RSI 5-Minute  Chart Oversold')
elif my_data_M5[-1, 4] > upper_barrier:
    
    print('RSI 5-Minute  Chart Overbought')   
    
else:
    
    print('RSI 5-Minute  Chart Normal')
if my_data_M10[-1, 4] < lower_barrier:
    
    print('RSI 10-Minute Chart Oversold')
elif my_data_M10[-1, 4] > upper_barrier:
    
    print('RSI 10-Minute Chart Overbought')   
    
else:
    
    print('RSI 10-Minute Chart Normal')
if my_data_M15[-1, 4] < lower_barrier:
    
    print('RSI 15-Minute Chart Oversold')
elif my_data_M15[-1, 4] > upper_barrier:
    
    print('RSI 15-Minute Chart Overbought')   
    
else:
    
    print('RSI 15-Minute Chart Normal')
if my_data_M20[-1, 4] < lower_barrier:
    
    print('RSI 20-Minute Chart Oversold')
elif my_data_M20[-1, 4] > upper_barrier:
    
    print('RSI 20-Minute Chart Overbought')   
    
else:
    
    print('RSI 20-Minute Chart Normal')
if my_data_M30[-1, 4] < lower_barrier:
    
    print('RSI 30-Minute Chart Oversold')
elif my_data_M30[-1, 4] > upper_barrier:
    
    print('RSI 30-Minute Chart Overbought')   
    
else:
    
    print('RSI 30-Minute Chart Normal')
if my_data_H1[-1, 4] < lower_barrier:
    
    print('RSI Hourly    Chart Oversold')
elif my_data_H1[-1, 4] > upper_barrier:
    
    print('RSI Hourly    Chart Overbought')   
    
else:
    
    print('RSI Hourly    Chart Normal')     
    
    
    
if my_data_H2[-1, 4] < lower_barrier:
    
    print('RSI 2-Hour    Chart Oversold')
elif my_data_H2[-1, 4] > upper_barrier:
    
    print('RSI 2-Hour    Chart Overbought')   
    
else:
    
    print('RSI 2-Hour    Chart Normal')

# Output RSI 1-Minute  Chart Normal
# Output RSI 5-Minute  Chart Normal
# Output RSI 10-Minute Chart Normal
# Output RSI 15-Minute Chart Normal
# Output RSI 20-Minute Chart Normal
# Output RSI 30-Minute Chart Normal
# Output RSI Hourly    Chart Normal
# Output RSI 2-Hour    Chart Normal

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

fig, ax = plt.subplots(2, 2, figsize = (10, 7))
ax[0, 0].plot(my_data_MN1[-100:, 4]) 
ax[0, 0].axhline(y = upper_barrier, color = 'black')
ax[0, 0].axhline(y = lower_barrier, color = 'black')
ax[0, 0].set_title('1-Minute', loc = 'left')
ax[0, 0].grid()
ax[1, 0].plot(my_data_M5[-100:, 4]) 
ax[1, 0].axhline(y = upper_barrier, color = 'black')
ax[1, 0].axhline(y = lower_barrier, color = 'black')
ax[1, 0].set_title('5-Minute', loc = 'left')
ax[1, 0].grid()
ax[0, 1].plot(my_data_M10[-100:, 4]) 
ax[0, 1].axhline(y = upper_barrier, color = 'black')
ax[0, 1].axhline(y = lower_barrier, color = 'black')
ax[0, 1].set_title('10-Minute', loc = 'right')
ax[0, 1].grid()
ax[1, 1].plot(my_data_M15[-100:, 4]) 
ax[1, 1].axhline(y = upper_barrier, color = 'black')
ax[1, 1].axhline(y = lower_barrier, color = 'black')
ax[1, 1].set_title('15-Minute', loc = 'right')
ax[1, 1].grid()
fig, ax = plt.subplots(2, 2, figsize = (10, 7))
ax[0, 0].plot(my_data_M20[-100:, 4]) 
ax[0, 0].axhline(y = upper_barrier, color = 'black')
ax[0, 0].axhline(y = lower_barrier, color = 'black')
ax[0, 0].set_title('20-Minute', loc = 'left')
ax[0, 0].grid()
ax[1, 0].plot(my_data_M30[-100:, 4]) 
ax[1, 0].axhline(y = upper_barrier, color = 'black')
ax[1, 0].axhline(y = lower_barrier, color = 'black')
ax[1, 0].set_title('30-Minute', loc = 'left')
ax[1, 0].grid()
ax[0, 1].plot(my_data_H1[-100:, 4]) 
ax[0, 1].axhline(y = upper_barrier, color = 'black')
ax[0, 1].axhline(y = lower_barrier, color = 'black')
ax[0, 1].set_title('Hourly', loc = 'right')
ax[0, 1].grid()
ax[1, 1].plot(my_data_H2[-100:, 4]) 
ax[1, 1].axhline(y = upper_barrier, color = 'black')
ax[1, 1].axhline(y = lower_barrier, color = 'black')
ax[1, 1].set_title('2-Hour', loc = 'right')
ax[1, 1].grid()


Заключение

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

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

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