top of page
Nikhil Adithyan

Coding the True Strength Index and Backtesting a Trading Strategy in Python

Updated: Jun 20, 2021

A complete process in implementing a powerful indicator and a trading strategy in python to make better trades in the market



There is a realm where exists exotic technical indicators like the Relative Strength Index, Stochastic Oscillator, MACD, etc., and the indicator we are going to discuss today certainly adds to this list given its performance that exceeds those of the above-mentioned ones. It’s none other than the True Strength Index, shortly known as TSI.


In this article, we will first build some basic intuitions about the indicator and how it is calculated or the mathematics behind it. Then, we will move on to the programming part where we will use Python to build the indicator from scratch, construct a trading strategy, backtest the strategy and compare the results with those of the SPY ETF (an ETF particularly designed to track the movement of the S&P 500 market index). Without further ado, let’s jump into the article!


True Strength Index (TSI)


The True Strength Index (TSI) is a momentum oscillator that is primarily used by traders to determine whether a market is an upward or a downward momentum and trade along with it. It is also used to identify the current state of the market, overbought or oversold but, this is not the indicator’s main forte. The True Strength Index is composed of two components:


  • TSI Line: The first component is the TSI line itself which is calculated by first determining the actual price change (current closing price minus the previous closing price) and the absolute price change (absolute values of the actual price change). Then an EMA with 25 as the number of periods (long) is taken for both the actual price change and the absolute price change. These two EMAs are then again smoothed by a 13-day period (short) Exponential Moving Average. This process of smoothing a data series with two EMAs is known as double smoothing and the goal of doing this is to eliminate noise from the data. Now, the double-smoothed actual price change is divided by the double-smoothed absolute price change and then multiplied by 100 to obtain the readings of the TSI line. Note that the parameters (25, 13) we took into consideration are the typical setting but can be tuned accordingly. The calculation might be fuzzy but can be easily understood if we interpret it in the form of a formula or representation:



TSI LINE = [ DS. ACTUAL PC / DS. ABSOLUTE PC ] * 100

where,
DS. ACTUAL PC = Double smoothed actual price change with the length of 25 and 13
DS. ABSOLUTE PC = Double smoothed absolute price change with the length of 25 and 13

  • Signal line: The next component is the Signal line component which is nothing but an Exponential Moving Average of the TSI for a specified number of periods (falls within 7 to 12 periods). Most traders prefer periods near to 7 for day trading purposes and close to 12 for long-term investing. In this article, we are going with 12 as the number of periods since we will be dealing with a daily timeframe stock data rather than a minute timeframe. The calculation can be represented as follows:



SIGNAL LINE = EXP.MA 13 [ TSI LINE ]

That’s what the indicator is all about and the mathematics behind it. Now, let’s analyze a chart where the readings of the True Strength Index are plotted along with the closing price data of Apple to build more understanding about the indicator and its workings.



The above chart is divided into two panels: the upper panel with the plot of the closing price data of Apple and the lower panel with the readings of TSI’s components. As I said before, the TSI is primarily used to spot the momentum of the market and this can be seen clearly in the chart where the readings of the TSI above the positive territory (greater than zero) directly reveals that the market is in upward momentum and below the negative territory reveals a downward momentum in the market.


Now, let’s see how TSI can be used to determine whether the market is in the state of overbought or oversold. Usually, indicators like RSI have a standard threshold of overbought and oversold levels which is 70 and 30 respectively, and these thresholds are applicable to any tradable asset. Whereas, the levels of overbought and oversold vary from one asset to another while using the True Strength Index, and in our case, we could consider -10 as the oversold level and 10 as the overbought level. But still, it won’t be as effective as the other popular momentum oscillators.


Speaking about trading strategies, to my knowledge, we could apply three types of strategies based on the True Strength Index. The first one is the overbought and oversold levels. This one is the most common strategy among the momentum oscillators which reveals a buy signal whenever the components of the TSI go below the oversold level and a sell signal is generated whenever the readings of both the components rise above the overbought level. I personally don’t use this strategy since I believe the True Strength Index doesn't have a good history with the concept of overbought and oversold levels.


The second trading strategy is the zero-line crossover which reveals a buy signal whenever the TSI’s components cross from below to above zero, likewise, a sell signal is revealed whenever the components of the TSI go from above the below zero. This strategy is effective but not as much as the next trading strategy we are going to discuss.


The final trading strategy and the one we are going to implement in this article is the signal line crossover which reveals a buy signal whenever the TSI line crosses from below to above the Signal line, and similarly, a sell signal is revealed whenever the TSI line goes from above to below the Signal line. This strategy is one of the most-used trading strategies among the others while using the True Strength Index due to its efficiency in the market. This trading strategy can be represented as follows:



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

That’s it! This concludes our theory part on the True Strength Index. Let’s now move on to the programming where we will use Python to first build the indicator from scratch, construct the signal line crossover trading strategy, backtest the strategy on Apple stock data and compare the results with those 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. True Strength Index Calculation
4. Creating the Signal line 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 requests
import matplotlib.pyplot as plt
import numpy as np
from math import floor
from termcolor import colored as cl

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: True Strength Index Calculation


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


Python Implementation:



# TRUE STRENGTH INDEX CALCULATION 

def get_tsi(close, long, short, signal):
    diff = close - close.shift(1)
    abs_diff = abs(diff)
    
    diff_smoothed = diff.ewm(span = long, adjust = False).mean()
    diff_double_smoothed = diff_smoothed.ewm(span = short, adjust = False).mean()
    abs_diff_smoothed = abs_diff.ewm(span = long, adjust = False).mean()
    abs_diff_double_smoothed = abs_diff_smoothed.ewm(span = short, adjust = False).mean()
    
    tsi = (diff_double_smoothed / abs_diff_double_smoothed) * 100
    signal = tsi.ewm(span = signal, adjust = False).mean()
    tsi = tsi[tsi.index >= '2020-01-01'].dropna()
    signal = signal[signal.index >= '2020-01-01'].dropna()
    
    return tsi, signal

aapl['tsi'], aapl['signal_line'] = get_tsi(aapl['close'], 25, 13, 12)
aapl = aapl[aapl.index >= '2020-01-01']
aapl.tail()

Output:



Code Explanation: Firstly, we are defining a function named ‘get_tsi’ that takes a stock’s closing price data (‘close’), the lookback period for the long EMA (‘long’), the lookback period for the short EMA (‘short’), and the lookback period for the signal line (‘signal’) as parameters. Inside the function, we are first calculating and storing the actual price change (‘diff’) and the absolute price change (‘abs_diff’) into their respective variable.


Then, using the ‘ewm’ function provided by the Pandas package for determining the Exponential Moving Average, we are double smoothing the previously calculated price changes to obtain the double smoothed actual price change (‘diff_double_smoothed’) and the double smoothed absolute price change (‘abs_diff_double_smoothed’).


We are then substituting the double smoothed values into the formula of the TSI line to determine its readings. To calculate the signal line’s values, we are taking an EMA of the determined TSI line’s readings for a specified number of periods. Finally, we are returning the calculated components and calling the created function to store Apple’s TSI components’ values.


Step-4: Creating the trading strategy


In this step, we are going to implement the discussed True Strength Index signal line crossover trading strategy in python.


Python Implementation:



# TRUE STRENGTH INDEX STRATEGY

def implement_tsi_strategy(prices, tsi, signal_line):
    buy_price = []
    sell_price = []
    tsi_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if tsi[i-1] < signal_line[i-1] and tsi[i] > signal_line[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        elif tsi[i-1] > signal_line[i-1] and tsi[i] < signal_line[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            tsi_signal.append(0)
            
    return buy_price, sell_price, tsi_signal

buy_price, sell_price, tsi_signal = implement_tsi_strategy(aapl['close'], aapl['tsi'], aapl['signal_line'])

Code Explanation: First, we are defining a function named ‘implement_tsi_strategy’ which takes the stock prices (‘prices’), and the components of the True Strength Index (‘tsi’, ‘signal_line’) as parameters.

Inside the function, we are creating three empty lists (buy_price, sell_price, and tsi_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:



# TRUE STRENGTH 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 TSI TRADING SIGNALS')
ax2.plot(aapl['tsi'], linewidth = 2, color = 'orange', label = 'TSI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#FF006E', label = 'SIGNAL LINE')
ax2.set_title('AAPL TSI 25,13,12')
ax2.legend()
plt.show()

Output:



Code Explanation: We are plotting the components of the True Strength Index along with the buy and sell signals generated by the signal line crossover trading strategy. We can observe that whenever the previous reading of the TSI line is below the previous reading of the Signal Line and the current reading of the TSI 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 TSI line is above the previous reading of the Signal Line and the current reading of the TSI 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(tsi_signal)):
    if tsi_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if tsi_signal[i] == 1:
        position[i] = 1
    elif tsi_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
tsi = aapl['tsi']
signal_line = aapl['signal_line']
tsi_signal = pd.DataFrame(tsi_signal).rename(columns = {0:'tsi_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'tsi_position'}).set_index(aapl.index)

frames = [close_price, tsi, signal_line, tsi_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 two rows our position in the stock has remained 1 (since there isn’t any change in the True Strength Index signal) but our position suddenly turned to -1 as we sold the stock when the True Strength 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 True Strength Index trading strategy over the Apple stock data.


Python Implementation:



# BACKTESTING

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['tsi_position'][i]
    tsi_strategy_ret.append(returns)
    
tsi_strategy_ret_df = pd.DataFrame(tsi_strategy_ret).rename(columns = {0:'tsi_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
tsi_investment_ret = []

for i in range(len(tsi_strategy_ret_df['tsi_returns'])):
    returns = number_of_stocks*tsi_strategy_ret_df['tsi_returns'][i]
    tsi_investment_ret.append(returns)

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

Output:



Profit gained from the tsi strategy by investing $100k in AAPL : 71095.33
Profit percentage of the tsi 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 TSI trading strategy, and these returns values are appended to the ‘tsi_strategy_ret’ list. Next, we are converting the ‘tsi_strategy_ret’ list into a dataframe and stored it into the ‘tsi_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 TSI signal line 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[-1])
    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('TSI Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Output:



Benchmark profit by investing $100k : 23370.75
Benchmark Profit percentage : 23%
TSI Strategy profit is 48% 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 True Strength Index signal line crossover trading strategy has outperformed the SPY ETF by 48%. That’s great!


Final Thoughts!


After an overwhelming process of crushing both theory and programming parts, we have successfully learned what the True Strength Index is all about, the mathematics behind the indicator, and the implementation of a profitable trading strategy based on it. Even though we managed to surpass the results of the SPY ETF, we are still lagging on one specific space.


In this article, our algorithm buys and sells the same amount of stocks for all trades but that would not be an optimal decision to make while trading in the real world since it’s arbitrary, and here is where the concept of position sizing comes into play. For those who don’t have an idea about this concept, position sizing is the process of allocating a share of our portfolio to a specific stock we would like to trade. What we could do is, we can deploy a position sizing system that assesses the risk involved in each trade and allocate the number of shares accordingly. This will not only boost the performance substantially but also prevents us from facing unexpected losses. So, I highly recommend you follow up with this concept and try implementing it into a trading strategy.


With that being said, you have 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. Hope you learned something new and useful from this article.


Full code:



# IMPORTING PACKAGES

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

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

# TRUE STRENGTH INDEX CALCULATION 

def get_tsi(close, long, short, signal):
    diff = close - close.shift(1)
    abs_diff = abs(diff)
    
    diff_smoothed = diff.ewm(span = long, adjust = False).mean()
    diff_double_smoothed = diff_smoothed.ewm(span = short, adjust = False).mean()
    abs_diff_smoothed = abs_diff.ewm(span = long, adjust = False).mean()
    abs_diff_double_smoothed = abs_diff_smoothed.ewm(span = short, adjust = False).mean()
    
    tsi = (diff_double_smoothed / abs_diff_double_smoothed) * 100
    signal = tsi.ewm(span = signal, adjust = False).mean()
    tsi = tsi[tsi.index >= '2020-01-01'].dropna()
    signal = signal[signal.index >= '2020-01-01'].dropna()
    
    return tsi, signal

aapl['tsi'], aapl['signal_line'] = get_tsi(aapl['close'], 25, 13, 12)
aapl = aapl[aapl.index >= '2020-01-01']
aapl.tail()

# TRUE STRENGTH 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 PRICE')
ax2.plot(aapl['tsi'], linewidth = 2, color = 'orange', label = 'TSI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#FF006E', label = 'SIGNAL LINE')
ax2.set_title('AAPL TSI 25,13,12')
ax2.legend()
plt.show()

# TRUE STRENGTH INDEX STRATEGY

def implement_tsi_strategy(prices, tsi, signal_line):
    buy_price = []
    sell_price = []
    tsi_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if tsi[i-1] < signal_line[i-1] and tsi[i] > signal_line[i]:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        elif tsi[i-1] > signal_line[i-1] and tsi[i] < signal_line[i]:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                tsi_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                tsi_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            tsi_signal.append(0)
            
    return buy_price, sell_price, tsi_signal

buy_price, sell_price, tsi_signal = implement_tsi_strategy(aapl['close'], aapl['tsi'], aapl['signal_line'])

# TRUE STRENGTH 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 TSI TRADING SIGNALS')
ax2.plot(aapl['tsi'], linewidth = 2, color = 'orange', label = 'TSI LINE')
ax2.plot(aapl['signal_line'], linewidth = 2, color = '#FF006E', label = 'SIGNAL LINE')
ax2.set_title('AAPL TSI 25,13,12')
ax2.legend()
plt.show()

# STOCK POSITION

position = []
for i in range(len(tsi_signal)):
    if tsi_signal[i] > 1:
        position.append(0)
    else:
        position.append(1)
        
for i in range(len(aapl['close'])):
    if tsi_signal[i] == 1:
        position[i] = 1
    elif tsi_signal[i] == -1:
        position[i] = 0
    else:
        position[i] = position[i-1]
        
close_price = aapl['close']
tsi = aapl['tsi']
signal_line = aapl['signal_line']
tsi_signal = pd.DataFrame(tsi_signal).rename(columns = {0:'tsi_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'tsi_position'}).set_index(aapl.index)

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

strategy
strategy[12:17]

# BACKTESTING

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

for i in range(len(aapl_ret)):
    returns = aapl_ret['returns'][i]*strategy['tsi_position'][i]
    tsi_strategy_ret.append(returns)
    
tsi_strategy_ret_df = pd.DataFrame(tsi_strategy_ret).rename(columns = {0:'tsi_returns'})
investment_value = 100000
number_of_stocks = floor(investment_value/aapl['close'][0])
tsi_investment_ret = []

for i in range(len(tsi_strategy_ret_df['tsi_returns'])):
    returns = number_of_stocks*tsi_strategy_ret_df['tsi_returns'][i]
    tsi_investment_ret.append(returns)

tsi_investment_ret_df = pd.DataFrame(tsi_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(tsi_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the tsi strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the tsi 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[-1])
    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('TSI Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

 

3 comments

3 Comments


M.Paramasivan Sivan
M.Paramasivan Sivan
Jun 18, 2021

An advanced approach to share trading is well dealt. Though more technical inputs are explained, Nikil has the knack of making it interesting. Good effort.

Paramasivan.

Like

209847 VEERABADRAN V
209847 VEERABADRAN V
Jun 18, 2021

Interesting Article Nikhil

Like

saravanakumaar.a
saravanakumaar.a
Jun 15, 2021

Good one Nikhil.

Like
bottom of page