top of page
Nikhil Adithyan

Implementing the Relative Vigor Index and Backtesting a Trading Strategy with Python

Learn to implement a less-known indicator with great potential and make your trades look better



People who are in the field of stock trading will most certainly know about the Stochastic Oscillator given its popularity, and today, we are going to explore an indicator that is not only similar but also performs like the Stochastic Oscillator too. It’s none other than the Relative Vigor Index, shortly known as RVI. In this article, we will first build some basic intuitions about the indicator and the mathematics behind it. Then, we will proceed to the programming where we will use Python to build the indicator from scratch, backtest a trading strategy based on it, compare the strategy results with those of SPY ETF (an ETF specifically designed to track the movement of the S&P 500 market index). Without further ado, let’s dive into the article.


Relative Vigor Index (RVI)


The Relative Vigor Index is a momentum indicator that acts as an instrument to determine the current market momentum, either upward or downward. Unlike the Stochastic Oscillator, the Relative Vigor Index is an unbounded oscillator and does not fluctuate between certain thresholds rather it oscillates across a center-line (zero in most cases). The Relative Vigor Index is composed of two components namely the RVI line, and the Signal line. Now, let’s see how each of the components is being calculated.


RVI line calculation


To calculate the readings of the RVI line, we have to first determine the numerator and the denominator.


The numerator can be calculated in five steps:


  • Step one is to find the difference between the current closing price and the current opening price of a stock and let’s consider the difference as ‘a’.

  • The second step is to find the difference between the closing price two periods ago and the opening price two periods ago and this difference is multiplied by 2. This product can be considered as ‘b’.

  • Now, the third step is to determine the difference between the closing price three periods ago and the opening price three periods, then multiplied by two, and let’s consider this as ‘c’.

  • Step four is to multiply the difference that we get by subtracting the opening price four periods ago from the closing price four periods ago by two, and this product can be taken as ‘d’.

  • The final step is to add the determined a, b, c, and d variables to each other and a rolling sum for a specified number of periods is calculated (typical setting is 4). The numerator calculation can be represented as follows:



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 ]

The procedure to calculate the denominator is almost similar to that of the numerator but we just have to replace the closing price and the opening price data with high and low price data respectively.


The denominator can be calculated in five steps:


  • Step one is to find the difference between the current high price and the current low price of a stock and let’s consider the difference as ‘e’.

  • The second step is to find the difference between the high price two periods ago and the low price two periods ago and this difference is multiplied by 2. This product can be considered as ‘f’.

  • Now, the third step is to determine the difference between the high price three periods ago and the low price three periods, then multiplied by two, and let’s consider this as ‘g’.

  • Step four is to multiply the difference that we get by subtracting the low price four periods ago from the high price four periods ago by two, and this product can be taken as ‘h’.

  • The final step is to add the determined e, f, g, and h variables to each other and a rolling sum with 4 as the lookback period is calculated. The denominator calculation can be represented as follows:



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 ]

After getting the numerator and the denominator, we can move on to calculating the readings of the RVI line and it’s really simple. We just have to divide the numerator by the denominator and the result is smoothed by a Simple Moving Average for a specified number of lookback periods (typically 10 as the number of periods). The calculation can be mathematically represented as follows:



RVI LINE = SMA 10 [ NUMERATOR / DENOMINATOR ]

Signal line calculation


Usually, the indicators having the signal line as a component are calculated just by taking either Simple Moving Average or Exponential Moving Average of the main component (RVI line in our case) for a specified number of periods. But, it’s different with the Relative Vigor Index.


There are four main steps involved in the calculation of the Signal line: The first step is to multiply the RVI line readings one period with two and let’s consider this as ‘rvi1’. The next step is to multiply the readings of the RVI line two periods ago with two and this can be taken as ‘rvi2’. The third step is to determine the RVI line readings three periods ago and these values can be considered as ‘rvi3’. The final step is to add the determined ‘rvi1’, ‘rvi2’, ‘rvi3’ along with the readings of the RVI line to each other, and the total sum or the result is divided by 6. The calculation can be represented as follows:



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

That’s the whole process of calculating the components of the Relative Vigor Index. Now, let’s analyze a chart where Apple’s closing price data is plotted along with its Relative Vigor Index calculated with 10 as the lookback period to build a solid understanding of the indicator and how it’s being used.



The above chart is separated into two panels: The upper panel with the plot of Apple’s closing price data, and the lower panel with the components of the Relative Vigor Index calculated with 10 as the lookback period. The main characteristic feature of the indicator is to help traders with identifying the current market momentum and this can be seen clearly in the chart where the components of the RVI indicator rises positively when the market shows a sturdy upward momentum, and decreases when the market is in the state of strong downward momentum. As I said before, the Relative Vigor Index is an unbounded oscillator and the above chart proves this statement where it can be seen that the components of the RVI indicator do not fluctuate between certain upper and lower thresholds like other momentum oscillators but instead, across the zero-line or the center-line.


Speaking about trading strategies, to my knowledge, it is possible to apply three types of trading strategies based on the Relative Vigor Index. The first is the divergence trading strategy, the second one is the classic crossover strategy, and the third one is the overbought and oversold strategy. In this article, we are going to implement the second type of trading strategy which is the crossover strategy. This strategy reveals a buy signal whenever the RVI line crosses from below to above the Signal line, and similarly, reveals a sell signal whenever the RVI line goes from above to below the Signal line. The RVI crossover trading strategy can be represented as follows:



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

In most cases, traders use the Relative Vigor Index accompanied by another technical indicator to build a trading strategy but that is not the scope of this article (recommended to try doing). That’s it! This concludes our theory part on the Relative Vigor Index and let’s move on to the programming part where we will use Python to first build the indicator from scratch, construct the crossover trading strategy, backtest the strategy on Apple stock data, and finally compare the results with that of SPY ETF. Let’s do some coding! Before moving on, a note on disclaimer: This article’s sole purpose is to educate people and must be considered as an information piece but not as investment advice or so.


Implementation in Python


The coding part is classified into various steps as follows:



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

We will be following the order mentioned in the above list and buckle up your seat belts to follow every upcoming coding part.


Step-1: Importing Packages


Importing the required packages into the python environment is a non-skippable step. The primary packages are going to be Pandas to work with data, NumPy to work with arrays and for complex functions, Matplotlib for plotting purposes, and Requests to make API calls. The secondary packages are going to be Math for mathematical functions and Termcolor for font customization (optional).


Python Implementation:



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

Now that we have imported all the required packages into our python. Let’s pull the historical data of Apple with Twelve Data’s API endpoint.


Step-2: Extracting data from Twelve Data


In this step, we are going to pull the historical stock data of Apple using an API endpoint provided by twelvedata.com. Before that, a note on twelvedata.com: Twelve Data is one of the leading market data providers having an enormous amount of API endpoints for all types of market data. It is very easy to interact with the APIs provided by Twelve Data and has one of the best documentation ever. Also, ensure that you have an account on twelvedata.com, only then, you will be able to access your API key (vital element to extract data with an API).


Python Implementation:



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

Output:



Code Explanation: The first thing we did is to define a function named ‘get_historical_data’ that takes the stock’s symbol (‘symbol’) and the starting date of the historical data (‘start_date’) as parameters. Inside the function, we are defining the API key and the URL and stored them into their respective variable. Next, we are extracting the historical data in JSON format using the ‘get’ function and stored it into the ‘raw_df’ variable. After doing some processes to clean and format the raw JSON data, we are returning it in the form of a clean Pandas dataframe. Finally, we are calling the created function to pull the historic data of Apple from the starting of 2019 and stored it into the ‘aapl’ variable.


Step-3: Relative Vigor Index Calculation


In this step, we are going to calculate the components of the Relative Vigor Index by following the methods and formula we discussed before.


Python Implementation:



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

Output:



Code Explanation: We are first defining a function named ‘get_rvi’ that takes a stock’s opening (‘open’), high (‘high’), low (‘low’), closing (‘close’) price data, and the lookback period (‘lookback’) as parameters.


Inside the function, we are first calculating the variables involved in the calculation of the numerator which is a, b, c, and d. These are variables are calculated by following the formulas we discussed before and added to each other to determine the numerator. Next comes the calculation of the denominator that is almost similar to that of the numerator calculation but we are just replacing certain values. Before calculating the readings of the RVI line, we are determining the rolling sum of both the numerator and denominator with 4 as the lookback period and the results are then substituted into the formula of the RVI line to get the readings.


After that, we are calculating the three prerequisite variables which are rvi1, rvi2, and rvi3 we discussed before, and substituting into the formula of the Signal line along with the previously calculated RVI line values to get the readings. Finally, we are returning and calling the function the store Apple’s Relative Vigor Index readings with 10 as the lookback period.


Step-4: Creating the trading strategy


In this step, we are going to implement the discussed Relative Vigor Index crossover trading strategy in python.


Python Implementation:



# 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'])

Code Explanation: First, we are defining a function named ‘implement_rvi_strategy’ which takes the stock prices (‘prices’), and the components of the Relative Vigor Index (‘rvi’, ‘signal_line’) as parameters.

Inside the function, we are creating three empty lists (buy_price, sell_price, and rvi_signal) in which the values will be appended while creating the trading strategy.


After that, we are implementing the trading strategy through a for-loop. Inside the for-loop, we are passing certain conditions, and if the conditions are satisfied, the respective values will be appended to the empty lists. If the condition to buy the stock gets satisfied, the buying price will be appended to the ‘buy_price’ list, and the signal value will be appended as 1 representing to buy the stock. Similarly, if the condition to sell the stock gets satisfied, the selling price will be appended to the ‘sell_price’ list, and the signal value will be appended as -1 representing to sell the stock.


Finally, we are returning the lists appended with values. Then, we are calling the created function and stored the values into their respective variables. The list doesn’t make any sense unless we plot the values. So, let’s plot the values of the created trading lists.


Step-5: Plotting the trading signals


In this step, we are going to plot the created trading lists to make sense out of them.


Python Implementation:



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

Output:



Code Explanation: We are plotting the components of the Relative Vigor Index along with the buy and sell signals generated by the crossover trading strategy. We can observe that whenever the previous reading of the RVI line is below the previous reading of the Signal Line and the current reading of the RVI line is above the current reading of the Signal line, a green-colored buy signal is plotted in the chart. Similarly, whenever the previous reading of the RVI line is above the previous reading of the Signal Line and the current reading of the RVI line is below the current reading of the Signal line, a red-colored sell signal is plotted in the chart.


Step-6: Creating our Position


In this step, we are going to create a list that indicates 1 if we hold the stock or 0 if we don’t own or hold the stock.


Python Implementation:



# 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

Output:



Code Explanation: First, we are creating an empty list named ‘position’. We are passing two for-loops, one is to generate values for the ‘position’ list to just match the length of the ‘signal’ list. The other for-loop is the one we are using to generate actual position values. Inside the second for-loop, we are iterating over the values of the ‘signal’ list, and the values of the ‘position’ list get appended concerning which condition gets satisfied. The value of the position remains 1 if we hold the stock or remains 0 if we sold or don’t own the stock. Finally, we are doing some data manipulations to combine all the created lists into one dataframe.


From the output being shown, we can see that in the first row our position in the stock has remained 1 (since there isn’t any change in the Relative Vigor Index signal) but our position suddenly turned to -1 as we sold the stock when the Relative Vigor Index trading signal represents a sell signal (-1). Our position will remain 0 until some changes in the trading signal occur. Now it’s time to do implement some backtesting process!


Step-7: Backtesting


Before moving on, it is essential to know what backtesting is. Backtesting is the process of seeing how well our trading strategy has performed on the given stock data. In our case, we are going to implement a backtesting process for our Relative Vigor Index trading strategy over the Apple stock data.


Python Implementation:



# 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']))

Output:



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

Code Explanation: First, we are calculating the returns of the Apple stock using the ‘diff’ function provided by the NumPy package and we have stored it as a dataframe into the ‘aapl_ret’ variable. Next, we are passing a for-loop to iterate over the values of the ‘aapl_ret’ variable to calculate the returns we gained from our RVI trading strategy, and these returns values are appended to the ‘rvi_strategy_ret’ list. Next, we are converting the ‘rvi_strategy_ret’ list into a dataframe and stored it into the ‘rvi_strategy_ret_df’ variable.


Next comes the backtesting process. We are going to backtest our strategy by investing a hundred thousand USD into our trading strategy. So first, we are storing the amount of investment into the ‘investment_value’ variable. After that, we are calculating the number of Apple stocks we can buy using the investment amount. You can notice that I’ve used the ‘floor’ function provided by the Math package because, while dividing the investment amount by the closing price of Apple stock, it spits out an output with decimal numbers. The number of stocks should be an integer but not a decimal number. Using the ‘floor’ function, we can cut out the decimals. Remember that the ‘floor’ function is way more complex than the ‘round’ function. Then, we are passing a for-loop to find the investment returns followed by some data manipulation tasks.


Finally, we are printing the total return we got by investing a hundred thousand into our trading strategy and it is revealed that we have made an approximate profit of seventy-one thousand USD in one year. That’s not bad! Now, let’s compare our returns with SPY ETF (an ETF designed to track the S&P 500 stock market index) returns.


Step-8: SPY ETF Comparison


This step is optional but it is highly recommended as we can get an idea of how well our trading strategy performs against a benchmark (SPY ETF). In this step, we will extract the SPY ETF data using the ‘get_historical_data’ function we created and compare the returns we get from the SPY ETF with our RVI crossover trading strategy returns on Apple.


You might have observed that in all of my algorithmic trading articles, I’ve compared the strategy results not with the S&P 500 market index itself but with the SPY ETF and this is because most of the stock data providers (like Twelve Data) don’t provide the S&P 500 index data. So, I have no other choice than to go with the SPY ETF. If you’re fortunate to get the S&P 500 market index data, it is recommended to use it for comparison rather than any ETF.


Python Implementation:



# 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']))

Output:



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

Code Explanation: The code used in this step is almost similar to the one used in the previous backtesting step but, instead of investing in Apple, we are investing in SPY ETF by not implementing any trading strategies. From the output, we can see that our Relative Vigor Index crossover trading strategy has outperformed the SPY ETF by 43%. That’s great!


Final Thoughts!


After an exhaustive process of crushing both theory and coding parts, we have successfully learned what the Relative Vigor Index is all about, the mathematics behind the indicator, and how a simple trading strategy based on it can be implemented in Python.


Even though we managed to surpass the results of the SPY ETF, we are still lagging from those of the actual Apple returns. This may be because of the reason that the Relative Vigor Index is prone to revealing a lot of false signals when the market is ranging and this can also be observed in the chart represented while discussing the buy and sell signals generated by our crossover trading strategy.


The only way to tackle this problem is to accompany the Relative Vigor Index with another technical indicator that acts as a filter whose only task is to classify the false signals from the authentic ones. Since the Relative Vigor Index is a directional indicator (the indicator’s movement is directly proportional to that of the actual market), choosing a non-directional indicator and especially a volatility type would act as a great filter which ultimately leads us in achieving the desired results from the real-world market.


With that being said, you’ve reached the end of the article. If you forgot to follow any of the coding parts, don’t worry. I’ve provided the full source code at the end of the article. Hope you learned something new and useful from this article.


Full code:



# 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']))

 

3 comments

Related Posts

See All

3 Comments


M.Paramasivan Sivan
M.Paramasivan Sivan
Jun 22, 2021

The advantage of using The Relative Vigour Index , its limitations , to use filters to mitigate the limitations, your back testing with stock data, are exhaustive, but quite interesting.

Good work

Like

209847 VEERABADRAN V
209847 VEERABADRAN V
Jun 21, 2021

Good Article Nikhil

Like

saravanakumaar.a
saravanakumaar.a
Jun 21, 2021

Orderly presented. Good Nikhil

Like
bottom of page