Индекс относительной энергии и тестирование торговой стратегии с помощью Python

Люди, которые работают в сфере биржевой торговли, наверняка знают о стохастическом осцилляторе, учитывая его популярность, и сегодня мы рассмотрим индикатор, который не только похож, но и работает как стохастический осциллятор. Это не что иное, как индекс относительной энергии, вскоре известный как RVI. В этой статье мы сначала построим некоторые основные интуиции об индикаторе и математике, стоящей за ним. Затем мы перейдем к программированию, где будем использовать Python для построения индикатора с нуля, тестирования торговой стратегии на его основе, сравнения результатов стратегии с результатами SPY ETF (ETF, специально предназначенного для отслеживания движения рыночного индекса S&P 500). Без лишних слов, давайте углубимся в статью.

Прежде чем двигаться дальше, если вы хотите протестировать свои торговые стратегии без какого-либо кодирования, для этого есть решение. Это BacktestZone. Это платформа для тестирования любого количества торговых стратегий на различных типах торгуемых активов бесплатно без кодирования. Вы можете использовать инструмент сразу, используя ссылку здесь: https://www.backtestzone.com/

Индекс относительной энергии (RVI)

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

Расчет линии RVI

Чтобы вычислить показания линии RVI, мы должны сначала определить числитель и знаменатель.

Числитель может быть рассчитан в пять этапов:

  • Первый шаг заключается в том, чтобы найти разницу между текущей ценой закрытия и текущей ценой открытия акции, и давайте рассмотрим разницу как «а».
  • Второй шаг — найти разницу между ценой закрытия два периода назад и ценой открытия два периода назад, и эта разница умножается на 2. Этот продукт можно рассматривать как «b».
  • Теперь третий шаг — определить разницу между ценой закрытия три периода назад и ценой открытия три периода, затем умножить на два, и давайте рассмотрим это как «c».
  • Шаг четвертый состоит в том, чтобы умножить разницу, которую мы получаем, вычитая цену открытия четыре периода назад из цены закрытия четыре периода назад на два, и этот продукт можно принять как «d».
  • Заключительным этапом является сложение определенных переменных a, b, c и d друг к другу и вычисляется скользящая сумма за указанное количество периодов (типичная настройка равна 4). Расчет числителя может быть представлен следующим образом:
a = CURRENT CLOSE - CURRENT OPEN
b = 2 * ( CLOSE 2 PERIODS AGO - OPEN 2 PERIODS AGO )
c = 2 * ( CLOSE 3 PERIODS AGO - OPEN 3 PERIODS AGO )
d = 2 * ( CLOSE 4 PERIODS AGO - OPEN 4 PERIODS AGO )
Numerator = ROLLING SUM 4 [ a + b + c + d ]

Процедура расчета знаменателя почти аналогична процедуре нумератора, но нам просто нужно заменить данные о цене закрытия и цене открытия данными о высокой и низкой цене соответственно.

Знаменатель можно рассчитать в пять этапов:

  • Первый шаг состоит в том, чтобы найти разницу между текущей высокой ценой и текущей низкой ценой акции, и давайте рассмотрим разницу как «e».
  • Второй шаг — найти разницу между высокой ценой два периода назад и низкой ценой два периода назад, и эта разница умножается на 2. Этот продукт можно рассматривать как ‘f’.
  • Теперь третий шаг состоит в том, чтобы определить разницу между высокой ценой три периода назад и низкой ценой три периода, затем умножить на два, и давайте рассмотрим это как «g».
  • Шаг четвертый состоит в том, чтобы умножить разницу, которую мы получаем, вычитая низкую цену четыре периода назад из высокой цены четыре периода назад на два, и этот продукт можно принять как «h».
  • Заключительным шагом является добавление определенных переменных e, f, g и h друг к другу и скользящей суммы с 4 при вычислении периода просмотра. Расчет знаменателя можно представить следующим образом:
e = CURRENT HIGH - CURRENT LOW
f = 2 * ( HIGH 2 PERIODS AGO - LOW 2 PERIODS AGO )
g = 2 * ( HIGH 3 PERIODS AGO - LOW 3 PERIODS AGO )
h = 2 * ( HIGH 4 PERIODS AGO - LOW 4 PERIODS AGO )
Denominator = ROLLING SUM 4 [ e + f + g + h ]

Получив числитель и знаменатель, мы можем перейти к вычислению показаний линии RVI, и это действительно просто. Нам просто нужно разделить числитель на знаменатель, и результат сглаживается простой скользящей средней для заданного количества периодов просмотра (обычно 10 как количество периодов). Расчет может быть математически представлен следующим образом:

RVI LINE = SMA 10 [ NUMERATOR / DENOMINATOR ]

Расчет сигнальной линии

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

Существует четыре основных шага, связанных с расчетом сигнальной линии: Первым шагом является умножение показаний линии RVI на один период на два и давайте рассмотрим это как ‘rvi1’. Следующим шагом является умножение показаний линии RVI два периода назад на два, и это можно принять как «rvi2». Третьим шагом является определение показаний линии RVI три периода назад, и эти значения можно рассматривать как «rvi3». Заключительным шагом является добавление определенных ‘rvi1’, ‘rvi2’, ‘rvi3’ вместе с показаниями линии RVI друг к другу, и общая сумма или результат делится на 6. Расчет можно представить следующим образом:

rvi1 = 2 * RVI LINE 1 PERIOD AGO
rvi2 = 2 * RVI LINE 2 PERIODS AGO
rvi3 = RVI LINE 3 PERIODS AGO
SIGNAL LINE = ( RVI LINE + rvi1 + rvi2 + rvi3 ) / 6

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

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

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

IF PREV.RLINE < PREV.SLINE AND CUR.RLINE > CUR.SLINE ==> BUY SIGNAL
IF PREV.RLINE > PREV.SLINE AND CUR.RLINE < CUR.SLINE ==> SELL SIGNAL

В большинстве случаев трейдеры используют индекс относительной энергии в сопровождении другого технического индикатора для построения торговой стратегии, но это не входит в рамки данной статьи (рекомендуется попробовать). Ну вот! На этом мы завершаем нашу теоретическую часть по индексу относительной энергии, и давайте перейдем к программной части, где мы будем использовать Python, чтобы сначала построить индикатор с нуля, построить торговую стратегию кроссовера, протестировать стратегию на биржевых данных Apple и, наконец, сравнить результаты с результатами SPY ETF. Давайте сделаем кодирование! Прежде чем двигаться дальше, примечание об отказе от ответственности: единственная цель этой статьи — обучить людей и должна рассматриваться как информационная часть, но не как инвестиционный совет или около того.

Реализация на Python

Кодирующая часть классифицируется на различные этапы следующим образом:

1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. Relative Vigor Index Calculation
4. Creating the Crossover Trading Strategy
5. Plotting the Trading Lists
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison

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

Шаг-1: Импорт пакетов

Импорт необходимых пакетов в среду python является шагом, который нельзя пропустить. Основными пакетами будут Pandas для работы с данными, NumPy для работы с массивами и для сложных функций, Matplotlib для целей построения графиков и Requests для выполнения вызовов API. Вторичными пакетами будут Math для математических функций и Termcolor для настройки шрифта (необязательно).

Реализация Python:

# IMPORTING PACKAGES

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import requests
from termcolor import colored as cl
from math import floor

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)

Теперь, когда мы импортировали все необходимые пакеты в наш python. Давайте возьмем исторические данные Apple с конечной точкой API Twelve Data.

Шаг-2: Извлечение данных из двенадцати данных

На этом шаге мы собираемся извлечь исторические данные о запасах Apple, используя конечную точку API, предоставленную twelvedata.com. До этого примечание о twelvedata.com: Twelve Data является одним из ведущих поставщиков рыночных данных, имеющих огромное количество конечных точек API для всех типов рыночных данных. Он очень прост в взаимодействии с API, предоставляемыми Twelve Data, и имеет одну из лучших документов. Кроме того, убедитесь, что у вас есть учетная запись на twelvedata.com, только тогда вы сможете получить доступ к своему ключу API (жизненно важный элемент для извлечения данных с помощью API).

Реализация Python:

# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

aapl = get_historical_data('AAPL', '2019-01-01')
aapl.tail()

Пояснение к коду: Первое, что мы сделали, это определили функцию с именем «get_historical_data», которая принимает символ акции («symbol») и дату начала исторических данных («start_date») в качестве параметров. Внутри функции мы определяем ключ API и URL-адрес и сохраняем их в соответствующей переменной. Далее мы извлекаем исторические данные в формате JSON с помощью функции ‘get’ и сохраняем их в переменной ‘raw_df’. После выполнения некоторых процессов для очистки и форматирования необработанных данных JSON мы возвращаем их в виде чистого фрейма данных Pandas. Наконец, мы называем созданную функцию, чтобы извлечь исторические данные Apple с начала 2019 года и сохранить их в переменной ‘aapl’.

Шаг-3: Расчет индекса относительной энергии

На этом шаге мы рассчитаем компоненты индекса относительной энергии, следуя методам и формулам, которые мы обсуждали ранее.

Реализация Python:

# RELATIVE VIGOR INDEX CALCULATION

def get_rvi(open, high, low, close, lookback):
a = close - open
b = 2 * (close.shift(2) - open.shift(2))
c = 2 * (close.shift(3) - open.shift(3))
d = close.shift(4) - open.shift(4)
numerator = a + b + c + d

e = high - low
f = 2 * (high.shift(2) - low.shift(2))
g = 2 * (high.shift(3) - low.shift(3))
h = high.shift(4) - low.shift(4)
denominator = e + f + g + h

numerator_sum = numerator.rolling(4).sum()
denominator_sum = denominator.rolling(4).sum()
rvi = (numerator_sum / denominator_sum).rolling(lookback).mean()

rvi1 = 2 * rvi.shift(1)
rvi2 = 2 * rvi.shift(2)
rvi3 = rvi.shift(3)
rvi_signal = (rvi + rvi1 + rvi2 + rvi3) / 6

return rvi, rvi_signal

aapl['rvi'], aapl['signal_line'] = get_rvi(aapl['open'], aapl['high'], aapl['low'], aapl['close'], 10)
aapl = aapl.dropna()
aapl = aapl[aapl.index >= '2020-01-01']
aapl.tail()

Пояснение к коду: Сначала мы определяем функцию с именем «get_rvi», которая принимает в качестве параметров данные о цене открытия акции («открытие»), максимум («высокий»), низкий («низкий»), закрытие («закрытие») и период просмотра («обратный взгляд»).

Внутри функции мы сначала вычисляем переменные, участвующие в вычислении числителя, который является a, b, c и d. Эти переменные вычисляются путем следования формулам, которые мы обсуждали ранее, и добавляются друг к другу для определения числителя. Далее следует вычисление знаменателя, который почти аналогичен расчету числителя, но мы просто заменяем определенные значения. Прежде чем вычислять показания линии RVI, мы определяем скользящую сумму как числителя, так и знаменателя с 4 в качестве периода просмотра, и результаты затем подставляются в формулу линии RVI для получения показаний.

После этого мы вычисляем три предварительные переменные, которыми являются rvi1, rvi2 и rvi3, которые мы обсуждали ранее, и подставляем в формулу сигнальной линии вместе с ранее рассчитанными значениями линии RVI для получения показаний. Наконец, мы возвращаемся и называем функцию показаниями индекса относительной активности магазина Apple с 10 в качестве периода обзора.

Шаг-4: Создание торговой стратегии

На этом шаге мы собираемся реализовать обсуждаемую стратегию кроссовера индекса относительной энергии на python.

Реализация Python:

# RELATIVE VIGOR INDEX STRATEGY

def implement_rvi_strategy(prices, rvi, signal_line):
buy_price = []
sell_price = []
rvi_signal = []
signal = 0

for i in range(len(prices)):
if rvi[i-1] < signal_line[i-1] and rvi[i] > signal_line[i]:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
rvi_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
rvi_signal.append(0)
elif rvi[i-1] > signal_line[i-1] and rvi[i] < signal_line[i]:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
rvi_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
rvi_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
rvi_signal.append(0)

return buy_price, sell_price, rvi_signal

buy_price, sell_price, rvi_signal = implement_rvi_strategy(aapl['close'], aapl['rvi'], aapl['signal_line'])

Пояснение к коду: Во-первых, мы определяем функцию с именем «implement_rvi_strategy», которая принимает цены на акции («цены») и компоненты индекса относительной энергии («rvi», «signal_line») в качестве параметров.

Внутри функции мы создаем три пустых списка (buy_price, sell_price и rvi_signal), в которые будут добавлены значения при создании торговой стратегии.

После этого мы реализуем торговую стратегию через for-loop. Внутри цикла for-loop мы проходим определенные условия, и если условия выполнены, соответствующие значения будут добавлены к пустым спискам. Если условие для покупки акции будет выполнено, цена покупки будет добавлена в список «buy_price», а значение сигнала будет добавлено как 1, представляющее покупку акции. Аналогичным образом, если условие о продаже акций будет выполнено, цена продажи будет добавлена к списку «sell_price», а значение сигнала будет добавлено как -1, представляющее продажу акции.

Наконец, мы возвращаем списки, добавленные со значениями. Затем мы вызываем созданную функцию и сохраняем значения в соответствующих переменных. Список не имеет никакого смысла, если мы не построим значения. Итак, давайте построим график значений созданных торговых списков.

Шаг-5: Построение торговых сигналов

На этом шаге мы собираемся построить созданные торговые списки, чтобы извлечь из них смысл.

Реализация Python:

# RELATIVE VIGOR INDEX TRADING SIGNALS PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2)
ax1.plot(aapl.index, buy_price, marker = '^', markersize = 12, color = 'green', linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', markersize = 12, color = 'r', linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL RVI TRADING SIGNALS')
ax2.plot(aapl['rvi'], linewidth = 2, color = 'orange', label = 'RVI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#BA5FE3', label = 'SIGNAL LINE')
ax2.set_title('AAPL RVI 10')
ax2.legend()
plt.show()

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

Шаг-6: Создание нашей позиции

На этом шаге мы создадим список, который указывает 1, если мы держим акцию, или 0, если мы не владеем или не держим акции.

Реализация Python:

# STOCK POSITION

position = []
for i in range(len(rvi_signal)):
if rvi_signal[i] > 1:
position.append(0)
else:
position.append(1)

for i in range(len(aapl['close'])):
if rvi_signal[i] == 1:
position[i] = 1
elif rvi_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]

close_price = aapl['close']
rvi = aapl['rvi']
signal_line = aapl['signal_line']
rvi_signal = pd.DataFrame(rvi_signal).rename(columns = {0:'rvi_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'rvi_position'}).set_index(aapl.index)

frames = [close_price, rvi, signal_line, rvi_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

Пояснение к коду: Во-первых, мы создаем пустой список с именем «позиция». Мы передаем два цикла for-loops, один из которых заключается в генерации значений для списка ‘position’, чтобы они соответствовали длине ‘signal’ списка. Другой цикл for-loop — это тот, который мы используем для генерации фактических значений позиции. Внутри второго цикла for-loop мы перебираем значения списка ‘signal’, и значения списка ‘position’ добавляются относительно того, какое условие выполняется. Значение позиции остается 1, если мы держим акции, или остается 0, если мы продали или не владеем акциями. Наконец, мы делаем некоторые манипуляции с данными, чтобы объединить все созданные списки в один фрейм данных.

Из показанного вывода мы видим, что в первом ряду наша позиция в акции осталась 1 (поскольку в сигнале индекса относительной энергии нет никаких изменений), но наша позиция внезапно превратилась в -1, когда мы продали акции, когда торговый сигнал индекса относительной энергии представляет собой сигнал на продажу (-1). Наша позиция будет оставаться 0 до тех пор, пока не произойдут некоторые изменения в торговом сигнале. Теперь пришло время реализовать некоторый процесс бэктестирования!

Шаг-7: Тестирование

Прежде чем двигаться дальше, важно знать, что такое бэктестинг. Бэктестинг — это процесс наблюдения за тем, насколько хорошо наша торговая стратегия работает на данных по акциям. В нашем случае мы собираемся реализовать процесс бэктестирования для нашей торговой стратегии по индексу относительной энергии на основе данных по акциям Apple.

Реализация Python:

# BACKTESTING

aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
rvi_strategy_ret = []

for i in range(len(aapl_ret)):
returns = aapl_ret['returns'][i]*strategy['rvi_position'][i]
rvi_strategy_ret.append(returns)

rvi_strategy_ret_df = pd.DataFrame(rvi_strategy_ret).rename(columns = {0:'rvi_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
rvi_investment_ret = []

for i in range(len(rvi_strategy_ret_df['rvi_returns'])):
returns = number_of_stocks*rvi_strategy_ret_df['rvi_returns'][i]
rvi_investment_ret.append(returns)

rvi_investment_ret_df = pd.DataFrame(rvi_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(rvi_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the RVI strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the RVI strategy : {}%'.format(profit_percentage), attrs = ['bold']))

Выпуск:

Profit gained from the RVI strategy by investing $100k in AAPL : 71643.4
Profit percentage of the RVI strategy : 71%

Пояснение к коду: Во-первых, мы рассчитываем доходность акций Apple, используя функцию ‘diff’, предоставляемую пакетом NumPy, и мы сохранили ее в виде фрейма данных в переменной ‘aapl_ret’. Затем мы передаем цикл for-loop для итерации значений переменной ‘aapl_ret’, чтобы рассчитать доходность, которую мы получили от нашей торговой стратегии RVI, и эти значения доходности добавляются в список ‘rvi_strategy_ret’. Затем мы преобразуем список ‘rvi_strategy_ret’ в кадр данных и сохраняем его в переменную ‘rvi_strategy_ret_df’.

Далее следует процесс тестирования. Мы собираемся протестировать нашу стратегию, вложив сто тысяч долларов США в нашу торговую стратегию. Итак, во-первых, мы храним сумму инвестиций в переменной «investment_value». После этого мы рассчитываем количество акций Apple, которые мы можем купить, используя сумму инвестиций. Вы можете заметить, что я использовал функцию «floor», предоставляемую пакетом Math, потому что, деля сумму инвестиций на цену закрытия акций Apple, она выдает выход с десятичными числами. Число акций должно быть целым, но не десятичным числом. Используя функцию ‘floor’, мы можем вырезать десятичные дроби. Помните, что функция «этаж» намного сложнее, чем функция «круглый». Затем мы передаем цикл for-loop, чтобы найти доходность инвестиций, за которой следуют некоторые задачи манипулирования данными.

Наконец, мы печатаем общую прибыль, которую мы получили, инвестировав сто тысяч в нашу торговую стратегию, и выясняется, что мы получили приблизительную прибыль в семьдесят одну тысячу долларов США за один год. Это неплохо! Теперь давайте сравним нашу доходность с доходностью SPY ETF (ETF, предназначенный для отслеживания индекса фондового рынка S & P 500).

Шаг-8: Сравнение SPY ETF

Этот шаг является необязательным, но он настоятельно рекомендуется, так как мы можем получить представление о том, насколько хорошо наша торговая стратегия работает против эталона (SPY ETF). На этом шаге мы извлечем данные SPY ETF с помощью созданной нами функции «get_historical_data» и сравним доходность, которую мы получаем от SPY ETF, с нашей перекрестной торговой стратегией RVI на Apple.

Возможно, вы заметили, что во всех моих статьях об алгоритмической торговле я сравнивал результаты стратегии не с самим рыночным индексом S & P 500, а с SPY ETF, и это потому, что большинство поставщиков биржевых данных (таких как Twelve Data) не предоставляют данные индекса S & P 500. Таким образом, у меня нет другого выбора, кроме как пойти с SPY ETF. Если вам повезло получить данные по рыночному индексу S&P 500, рекомендуется использовать его для сравнения, а не любой ETF.

Реализация Python:

# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
spy = get_historical_data('SPY', start_date)['close']
benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})

investment_value = investment_value
number_of_stocks = floor(investment_value/spy[0])
benchmark_investment_ret = []

for i in range(len(benchmark['benchmark_returns'])):
returns = number_of_stocks*benchmark['benchmark_returns'][i]
benchmark_investment_ret.append(returns)

benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
return benchmark_investment_ret_df

benchmark = get_benchmark('2020-01-01', 100000)
investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)
print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('RVI Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Выпуск:

Benchmark profit by investing $100k : 28491.13
Benchmark Profit percentage : 28%
RVI Strategy profit is 43% higher than the Benchmark Profit

Пояснение к коду: Код, используемый на этом шаге, почти аналогичен тому, который использовался на предыдущем этапе тестирования, но вместо того, чтобы инвестировать в Apple, мы инвестируем в SPY ETF, не реализуя никаких торговых стратегий. Из выходных данных мы видим, что наша стратегия кроссоверной торговли индексом относительной энергии превзошла SPY ETF на 43%. Это здорово!

Заключение

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

Несмотря на то, что нам удалось превзойти результаты SPY ETF, мы все еще отстаем от фактических доходов Apple. Это может быть связано с тем, что индекс относительной энергии склонен выявлять множество ложных сигналов, когда рынок находится в диапазоне, и это также можно наблюдать на графике, представленном при обсуждении сигналов на покупку и продажу, генерируемых нашей кроссоверной торговой стратегией.

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

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

Полный код:

# IMPORTING PACKAGES

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import requests
from termcolor import colored as cl
from math import floor

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20,10)

# EXTRACTING STOCK DATA

def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

aapl = get_historical_data('AAPL', '2019-01-01')
aapl.tail()

# RELATIVE VIGOR INDEX CALCULATION

def get_rvi(open, high, low, close, lookback):
a = close - open
b = 2 * (close.shift(2) - open.shift(2))
c = 2 * (close.shift(3) - open.shift(3))
d = close.shift(4) - open.shift(4)
numerator = a + b + c + d

e = high - low
f = 2 * (high.shift(2) - low.shift(2))
g = 2 * (high.shift(3) - low.shift(3))
h = high.shift(4) - low.shift(4)
denominator = e + f + g + h

numerator_sum = numerator.rolling(4).sum()
denominator_sum = denominator.rolling(4).sum()
rvi = (numerator_sum / denominator_sum).rolling(lookback).mean()

rvi1 = 2 * rvi.shift(1)
rvi2 = 2 * rvi.shift(2)
rvi3 = rvi.shift(3)
rvi_signal = (rvi + rvi1 + rvi2 + rvi3) / 6

return rvi, rvi_signal

aapl['rvi'], aapl['signal_line'] = get_rvi(aapl['open'], aapl['high'], aapl['low'], aapl['close'], 10)
aapl = aapl.dropna()
aapl = aapl[aapl.index >= '2020-01-01']
aapl.tail()

# RELATIVE VIGOR INDEX PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2.5)
ax1.set_title('AAPL CLOSING PRICES')
ax2.plot(aapl['rvi'], linewidth = 2, color = 'orange', label = 'RVI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#BA5FE3', label = 'SIGNAL LINE')
ax2.legend()
ax2.set_title('AAPL RVI 10')
plt.show()

# RELATIVE VIGOR INDEX STRATEGY

def implement_rvi_strategy(prices, rvi, signal_line):
buy_price = []
sell_price = []
rvi_signal = []
signal = 0

for i in range(len(prices)):
if rvi[i-1] < signal_line[i-1] and rvi[i] > signal_line[i]:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
rvi_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
rvi_signal.append(0)
elif rvi[i-1] > signal_line[i-1] and rvi[i] < signal_line[i]:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
rvi_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
rvi_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
rvi_signal.append(0)

return buy_price, sell_price, rvi_signal

buy_price, sell_price, rvi_signal = implement_rvi_strategy(aapl['close'], aapl['rvi'], aapl['signal_line'])

# RELATIVE VIGOR INDEX TRADING SIGNALS PLOT

ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2)
ax1.plot(aapl.index, buy_price, marker = '^', markersize = 12, color = 'green', linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', markersize = 12, color = 'r', linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL RVI TRADING SIGNALS')
ax2.plot(aapl['rvi'], linewidth = 2, color = 'orange', label = 'RVI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#BA5FE3', label = 'SIGNAL LINE')
ax2.set_title('AAPL RVI 10')
ax2.legend()
plt.show()

# STOCK POSITION

position = []
for i in range(len(rvi_signal)):
if rvi_signal[i] > 1:
position.append(0)
else:
position.append(1)

for i in range(len(aapl['close'])):
if rvi_signal[i] == 1:
position[i] = 1
elif rvi_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]

close_price = aapl['close']
rvi = aapl['rvi']
signal_line = aapl['signal_line']
rvi_signal = pd.DataFrame(rvi_signal).rename(columns = {0:'rvi_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'rvi_position'}).set_index(aapl.index)

frames = [close_price, rvi, signal_line, rvi_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy
strategy[19:24]

# BACKTESTING

aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
rvi_strategy_ret = []

for i in range(len(aapl_ret)):
returns = aapl_ret['returns'][i]*strategy['rvi_position'][i]
rvi_strategy_ret.append(returns)

rvi_strategy_ret_df = pd.DataFrame(rvi_strategy_ret).rename(columns = {0:'rvi_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
rvi_investment_ret = []

for i in range(len(rvi_strategy_ret_df['rvi_returns'])):
returns = number_of_stocks*rvi_strategy_ret_df['rvi_returns'][i]
rvi_investment_ret.append(returns)

rvi_investment_ret_df = pd.DataFrame(rvi_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(rvi_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)

print(cl('Profit gained from the RVI strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the RVI strategy : {}%'.format(profit_percentage), attrs = ['bold']))

# SPY ETF COMPARISON

def get_benchmark(start_date, investment_value):
spy = get_historical_data('SPY', start_date)['close']
benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'})

investment_value = investment_value
number_of_stocks = floor(investment_value/spy[0])
benchmark_investment_ret = []

for i in range(len(benchmark['benchmark_returns'])):
returns = number_of_stocks*benchmark['benchmark_returns'][i]
benchmark_investment_ret.append(returns)

benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'})
return benchmark_investment_ret_df

benchmark = get_benchmark('2020-01-01', 100000)

investment_value = 100000
total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2)
benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100)

print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold']))
print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold']))
print(cl('RVI Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Источник