Кодирование и бэк-тестирование стохастического RSI на Python.

Stochastic RSI - еще один известный индикатор, созданный путем объединения уже известных индикаторов RSI и Stochastic. Его полезность неоднозначна, но мы попытаемся пролить свет на него, используя его в рамках систематической торговли. Цель статьи - закодировать индикаторы RSI, Stochastic и StochRSI, а затем протестировать их на исторических данных, чтобы увидеть, полезны они или нет.

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



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

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

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

def rsi(Data, rsi_lookback, what1, what2):
    
    # From exponential to smoothed
    rsi_lookback = (rsi_lookback * 2) - 1  
        
    # Get the difference in price from previous step
    delta = []
   
    for i in range(len(Data)):
        try:
            diff = Data[i, what1] - Data[i - 1, what1] 
            delta = np.append(delta, diff)                  
        except IndexError:
            pass
        
    delta = np.insert(delta, 0, 0, axis = 0)               
    delta = delta[1:] 
    
    # Make the positive gains (up) and negative gains (down) Series
    up, down = delta.copy(), delta.copy()
    up[up < 0] = 0
    down[down > 0] = 0
    
    up = np.array(up)
    down = np.array(down)
    
    roll_up = up
    roll_down = down
    
    roll_up = np.reshape(roll_up, (-1, 1))
    roll_down = np.reshape(roll_down, (-1, 1))
    
    roll_up = adder(roll_up, 3)
    roll_down = adder(roll_down, 3)
    
    roll_up = ema(roll_up, 2, rsi_lookback, what2, 1)
    roll_down = ema(abs(roll_down), 2, rsi_lookback, what2, 1)
    
    roll_up = roll_up[rsi_lookback:, 1:2]
    roll_down = roll_down[rsi_lookback:, 1:2]
    Data = Data[rsi_lookback + 1:,]
    
    # Calculate the RS & RSI
    RS = roll_up / roll_down
    RSI = (100.0 - (100.0 / (1.0 + RS)))
    RSI = np.array(RSI)
    RSI = np.reshape(RSI, (-1, 1))
    RSI = RSI[1:,]
    
    Data = np.concatenate((Data, RSI), axis = 1)    
    return Data
# Using the funtion on OHLC Data
my_data = rsi(my_data, lookback 3, 0)

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

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

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

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

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

Чтобы закодировать индикатор, нам нужно иметь массив 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

Мы создадим следующую функцию, которая вычисляет стохастик по данным 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

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



Стохастический RSI

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

Наша цель здесь - создать первый вариант стохастического RSI, также известный как StochRSI (второй вариант будет обсуждаться в следующей статье). Затем мы проверим его на исторических данных, используя обычные торговые условия.

def stoch_rsi(Data, lookback, where):
    
    # Calculating RSI of the Closing prices
    Data = rsi(Data, lookback, 3, 0)
    
    # Adding two columns
    Data = adder(Data, 2)
    
    for i in range(len(Data)):
        
        try:
            Data[i, where + 1] = (Data[i, where] - min(Data[i - lookback + 1:i + 1, where])) / (max(Data[i - lookback + 1:i + 1, where]) - min(Data[i - lookback + 1:i + 1, where]))
        
        except ValueError:
            pass
    
    Data[:, where + 1] = Data[:, where + 1] * 100 
    
    # Signal Line using a 3-period moving average
    Data = ma(Data, 3, where + 1, where + 2)
    
    Data = deleter(Data, where, 2)
    Data = jump(Data, lookback)    
    
    return Data

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

my_data = stoch_rsi(my_data, 14, 4)

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



Бэк-тестирование стратегии

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

Условия такие, как ожидалось:

  • Открывайте длинную позицию (Покупайте), когда StochRSI достигает 20 при двух предыдущих значениях выше 20. Удерживайте эту позицию до получения нового сигнала или до тех пор, пока система управления рисками не остановит вас.
  • Открывайте короткую позицию (продавайте), когда StochRSI достигает 80 при двух предыдущих значениях ниже 80. Удерживайте эту позицию до получения нового сигнала или до тех пор, пока система управления рисками не остановит вас.
def signal(Data, what, buy, sell):
    
    for i in range(len(Data)):
            
        if Data[i, what] < lower_barrier and Data[i - 1, what] > lower_barrier and Data[i - 2, what] > lower_barrier :
            Data[i, buy] = 1
            
        if Data[i, what] > upper_barrier and Data[i - 1, what] < upper_barrier and Data[i - 2, what] < upper_barrier :
            Data[i, sell] = -1

Примечательно, что сигналов достаточно много и они не кажутся такими хорошими, однако с установленной нами системой управления рисками мы не можем судить, пока не увидим результаты. Система управления рисками основана на субоптимальном соотношении риска и вознаграждения (настоятельно НЕ рекомендуется), которое закрывается на уровне 1x ATR для прибыли и 4x ATR для стопов. Спред составляет 0,5 пункта за раунд сделки, а данные, проверенные на исторических данных, представлены ежечасно с 2010 года.

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

Заключение

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

Прежде всего, я постоянно публикую свои торговые журналы в Твиттере до и после запуска, чтобы показать результаты. Это обеспечивает прозрачность. Я также публикую в Твиттере каждые 1–3 месяца. Тем не менее, я никогда не гарантирую ни возврата ни превосходного мастерства. Что касается индикаторов, которые я разрабатываю, я постоянно использую их в своей личной торговле. Следовательно, у меня нет мотива публиковать предвзятые исследования. Моя цель - поделиться тем, что я узнал от онлайн-сообщества.

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

Рыночную цену невозможно предсказать или ее очень трудно предсказать более чем в 50% случаев. Но реакцию рынка можно предсказать.

Приведенная выше цитата означает, что мы можем сформировать небольшую зону вокруг области и сказать с некоторой степенью уверенности, что рыночная цена покажет реакцию вокруг этой области. Но мы не можем сказать, что он упадет оттуда на 4%, затем снова протестирует его и пробьет с третьей попытки до 103,85 доллара. Параметр ошибки становится экспоненциально выше, потому что мы делаем прогнозы, а не прогнозы.

Пока мы обсуждаем эту тему, я должен отметить несколько моментов в моих тестах на истории и статьях:

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

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