Создание комбинированной стратегии Ultimate Oscillator и Stochastic Oscillator в Python.

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

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



Стохастический осциллятор

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

Чтобы иметь возможность манипулировать данными, нам сначала нужно иметь массив OHLC (а не фрейм данных) и определить следующие три небольшие функции манипулирования:

# 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
# 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

Мы можем попробовать кодировать функцию нормализации на питоне. Ниже приводится нормализация данного временного ряда:

def normalizer(Data, lookback, what, where):
        
    for i in range(len(Data)):
        
        try:
            Data[i, where] = (Data[i, what] - min(Data[i - lookback + 1:i + 1, what])) / (max(Data[i - lookback + 1:i + 1, what]) - min(Data[i - lookback + 1:i + 1, what]))
        
        except ValueError:
            pass
    
    Data[:, where] = Data[:, where] * 100    
    Data = jump(Data, lookback)
    return Data

Если мы применим функцию к цене закрытия часового таймфрейма EURUSD с периодом ретроспективного анализа 50 (то есть функция будет смотреть на последние 50 значений и выбирать оттуда минимальное и максимальное значения), мы получим следующий график.

# Using the function
my_data = normalizer(my_data, 50, 3, 4)

Осциллятор стохастик пытается найти зоны перепроданности и перекупленности, объединяя максимумы и минимумы, используя формулу нормализации, как показано ниже:

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

Мы создадим следующую функцию, которая вычисляет стохастик по данным OHLC:

def stochastic(Data, lookback, what, high, low, where):
        
    for i in range(len(Data)):
        
        try:
          Data[i, where] = (Data[i, what] - min(Data[i - lookback + 1:i + 1, low])) / (max(Data[i - lookback + 1:i + 1, high]) - min(Data[i - lookback + 1:i + 1, low]))
        
        except ValueError:
            pass
    
    Data[:, where] = Data[:, where] * 100                     
    return Data
# The Data variable refers to the OHLC array
# The lookback variable refers to the period (5, 14, 21, etc.)
# The what variable refers to the closing price
# The high variable refers to the high price
# The low variable refers to the low price
# The where variable refers to where to put the Oscillator

На приведенном выше графике показаны значения EURUSD с помощью 14-периодного стохастического осциллятора. Обратите внимание, что индикатор всегда будет ограничен между 0 и 100 из-за характера его функции нормализации, которая улавливает значения между минимумом и максимумом.

Окончательный осциллятор

Создатель осциллятора имел цель обнаруживать растянутые движения импульса в разные периоды ретроспективного анализа. Это, в свою очередь, снижает избыточную волатильность, вызванную более короткими периодами ретроспективного анализа. Чтобы построить Ultimate Oscillator, мы выполним следующие шаги, прежде чем представить их более подробно прямо ниже:

  • Выберите периоды ретроспективного анализа трех скользящих средних, которые мы будем использовать. По умолчанию создатель осциллятора устанавливает периоды 7, 14 и 28. Как поклонник последовательности Фибоначчи, я буду использовать 5, 13 и 21.
  • Рассчитайте покупательное давление для каждого периода.
  • Рассчитайте истинный диапазон для каждого периода.
  • Разделите покупательское давление на истинный диапазон для каждого периода.
  • Рассчитайте экспоненциальную скользящую среднюю предыдущего результата трижды, используя три разных периода.
  • Вычислите средневзвешенное значение трех экспоненциальных скользящих средних и умножьте на 100.

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

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

def ma(Data, lookback, what, where):
    
  for i in range(len(Data)):
      try:
        Data[i, where] = (Data[i - lookback + 1:i + 1, what].mean())
        
            except IndexError:
                pass
    return Data
def ema(Data, alpha, lookback, what, where):
    
    alpha = alpha / (lookback + 1.0)
    beta  = 1 - alpha
    
    # First value is a simple SMA
    Data = ma(Data, lookback, what, where)
    
    # Calculating first EMA
    Data[lookback + 1, where] = (Data[lookback + 1, what] * alpha) + (Data[lookback, where] * beta)
    # Calculating the rest of EMA
    for i in range(lookback + 2, len(Data)):
            try:
                Data[i, where] = (Data[i, what] * alpha) + (Data[i - 1, where] * beta)
        
            except IndexError:
                pass
    return Data

Давайте теперь приступим к определению давления покупателей. Это простая формула, показанная ниже:

Что можно записать на языке Python как:

# Buying pressure
for i in range(len(Data)):
        
  Data[i, where] = Data[i, close] - min(Data[i, low], Data[i - 1, close])

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

Что можно записать на языке Python как:

# True range
for i in range(len(Data)):
        
  Data[i, where + 1] = max(Data[i, high], Data[i - 1, close]) - min(Data[i, low], Data[i - 1, close]) 
        if Data[i, where + 1] == 0:
            Data[i, where + 1] = 0.01

Условие в последних строках приведенного выше фрагмента кода заключается в том, что на следующем шаге мы разделим давление покупки на истинный диапазон, и в случае, если истинный диапазон равен нулю, мы получим неопределенное значение и испортим следующие вычисления. Условие - это просто исправление, в котором говорится, что если мы получим нулевое значение, мы будем считать его равным 0,01.

Теперь мы можем продолжить деление, используя следующий код:

# BP / TR
Data[:, where + 2] = Data[:, where] / Data[:, where + 1]

Используемые скользящие средние будут экспоненциального типа. В версии Ultimate Oscillator по умолчанию используются простые скользящие средние, но, поскольку я предпочитаю настройки, я буду использовать экспоненциальные скользящие средние. Вот что мы будем делать:

  • Рассчитайте 5-периодную экспоненциальную скользящую среднюю BP / TR.
  • Рассчитайте 13-периодную экспоненциальную скользящую среднюю BP / TR.
  • Рассчитайте 21-периодную экспоненциальную скользящую среднюю BP / TR.

Что можно записать на языке Python как:

# A5
Data = ema(Data, 2, 5, where + 2, where + 3)
    
# A13
Data = ema(Data, 2, 13, where + 2, where + 4)  
    
# A21
Data = ema(Data, 2, 21, where + 2, where + 5)

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

# Adding a few columns using the adder function
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
# Using the adder function
my_data = adder(my_data, 20)

И, наконец, мы готовы применить приведенную ниже формулу для расчета Ultimate Oscillator:

Даем нам ниже полный код Python для Ultimate Oscillator:

def ultimate_oscillator(Data, high, low, close, where):    
    # Buying pressure
    for i in range(len(Data)):
        
        Data[i, where] = Data[i, close] - min(Data[i, low], Data[i - 1, close])
    
    # True range
    for i in range(len(Data)):
        
        Data[i, where + 1] = max(Data[i, high], Data[i - 1, close]) - min(Data[i, low], Data[i - 1, close]) 
        if Data[i, where + 1] == 0:
            Data[i, where + 1] = 0.01
    
    # BP / TR
    Data[:, where + 2] = Data[:, where] / Data[:, where + 1]
    
    # A5
    Data = ema(Data, 2, 5, where + 2, where + 3)
    
    # A13
    Data = ema(Data, 2, 13, where + 2, where + 4)  
    
    # A21
    Data = ema(Data, 2, 21, where + 2, where + 5)
      
    # Ultimate Oscillator
    for i in range(len(Data)):
        
        Data[i, where + 6] = (Data[i, where + 3] * 4) +  (Data[i, where + 4] * 2) + (Data[i, where + 5])
        Data[i, where + 6] = (Data[i, where + 6] / 7) * 100
        
    Data = deleter(Data, where, 6)
    
    return Data

Создание комбинированной стратегии

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

Условия стратегии таковы:

  • Открывайте длинную позицию (покупайте), когда 14-периодный стохастический осциллятор достигает 25, а конечный осциллятор в то же время ниже 35. Удерживайте позицию до получения нового сигнала или до тех пор, пока система управления рисками не остановит вас.
  • Открывайте короткую позицию (продавайте), когда 14-периодный стохастический осциллятор достигает 75, а конечный осциллятор одновременно выше 65. Удерживайте позицию до получения нового сигнала или до тех пор, пока система управления рисками не остановит вас.

Торговые условия основаны на почасовых данных с января 2010 года с использованием теоретического соотношения риск-вознаграждение 1: 5, определяемого средним истинным диапазоном. Спред составляет 0,2 пункта за раунд сделки.

Слово об управлении рисками

Когда я говорю, что использую систему управления рисками на основе ATR (средний истинный диапазон), это означает, что алгоритм будет выполнять следующие шаги в отношении занимаемой позиции.

Длинная позиция (покупка):

  • Алгоритм инициирует ордер на покупку после того, как был сгенерирован сигнал по определенной стратегии.
  • Затем алгоритм будет отслеживать тики, и всякий раз, когда максимум равен определенной константе, умноженной на значение ATR во время открытия сделки, инициируется ордер выхода (с прибылью). Одновременно, если виден минимум, равный определенной константе, умноженной на значение ATR на момент открытия сделки, инициируется выход (с убытком). Первым встреченный выход является, естественно, принятым событием.

Короткая (продажа) позиция:

  • Алгоритм инициирует короткий ордер на продажу после того, как был сгенерирован сигнал в соответствии с определенной стратегией.
  • Затем алгоритм будет отслеживать тики, и всякий раз, когда минимум равен определенной константе, умноженной на значение ATR во время открытия сделки, инициируется ордер выхода (с прибылью). Одновременно, если виден максимум, равный определенной константе, умноженной на значение ATR на момент открытия сделки, инициируется выход (с убытком). Первым встреченный выход является, естественно, принятым событием.

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



Взгляните на последнее значение ATR. Это около 0,0014 (14 пунктов). Если мы инициируем ордер на покупку, следуя простому соотношению риска и прибыли 2,00 (рискуя половиной того, что мы ожидаем получить), мы можем разместить ордер следующим образом:

  • Купить по текущей рыночной цене.
  • Тейк-профит по текущей рыночной цене + (2 x 14 пунктов).
  • Остановите позицию по текущей рыночной цене - (1 x 14 пунктов).

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

def ema(Data, alpha, lookback, what, where):
    
    # alpha is the smoothing factor
    # window is the lookback period
    # what is the column that needs to have its average calculated
    # where is where to put the exponential moving average
    
    alpha = alpha / (lookback + 1.0)
    beta  = 1 - alpha
    
    # First value is a simple SMA
    Data = ma(Data, lookback, what, where)
    
    # Calculating first EMA
    Data[lookback + 1, where] = (Data[lookback + 1, what] * alpha) + (Data[lookback, where] * beta)                
    # Calculating the rest of EMA
    for i in range(lookback + 2, len(Data)):
            try:
                Data[i, where] = (Data[i, what] * alpha) + (Data[i - 1, where] * beta)
        
            except IndexError:
                pass
    return Datadef atr(Data, lookback, high, low, close, where):
    
    # Adding the required columns
    Data = adder(Data, 2)
    
    # True Range Calculation
    for i in range(len(Data)):
        try:
            
          Data[i, where] = max(Data[i, high] - Data[i, low],
                           abs(Data[i, high] - Data[i - 1, close]),
                           abs(Data[i, low] - Data[i - 1, close]))
            
        except ValueError:
            pass
        
    Data[0, where] = 0   
    
    # Average True Range Calculation
    Data = ema(Data, 2, lookback, where, where + 1)
    
    # Cleaning
    Data = deleter(Data, where, 1)
    Data = jump(Data, lookback)    
    return Data

Заключение и важный отказ от ответственности

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

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

Я должен отметить несколько моментов в моих тестах на истории и статьях:

  • Я использую спред, основанный на институциональных котировках небольшой доли пунктов. Как правило, розничным трейдерам предоставляется колоссальный спред в размере 0,80–3,00 пункта за сделку. Это огромно и несправедливо по отношению к ним. Тестирую на истории спред 0,2–0,5. Однако большинство «прибыльных» стратегий, использующих часовой таймфрейм, по-прежнему работают со спредом в 1 пункт. Для тех, кто использует таймфреймы M15 или M5, они не могут быть прибыльными со спредом в 1 пункт.
  • Я использую расчет периода удержания, близкий к близкому, если нет процесса управления рисками.
  • Некоторые из представленных мною тестов на истории неудачны, и они публикуются либо для того, чтобы развенчать миф о торговле, либо для того, чтобы представить интересные функции, которые читатели могут запрограммировать.
  • Наконец, я твердо убежден, что нельзя кормить с ложечки. Я научился на практике, а не копируя. Вы должны понять идею, функцию, интуицию, условия стратегии, а затем разработать (даже лучше) одну из них самостоятельно, чтобы вы протестировали ее на исторических данных и улучшили, прежде чем принимать решение о том, чтобы реализовать ее или устранить. Так вы сможете поделиться со мной своей лучшей стратегией, и мы вместе разбогатеем.

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