<jupyter_start><jupyter_text>A momentum strategy basically bets on the continuation of an existing market trend. For example, if the market is or starts going up, we will bet that the trend will continue for a while and we will try to make our trading strategy based on this.Momentum indicators can help us locate the entry and exit points to follow momentum strategies. One of this type of indicators is the Relative Strength Index (RSI). This indicator is the one that we will use in order to define our backtesting strategy with Python. Relative Strength Index (RSI)The Relative Strength Index (RSI) is a technical indicator that measures the speed and change of price movements. It was developed by <NAME>.<jupyter_code>!pip install yfinance
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.transforms as transform
import matplotlib.gridspec as gridspec
import pandas as pd
from pandas.tseries.offsets import MonthEnd
pd.options.mode.chained_assignment = None
pd.set_option('use_inf_as_na', True)
from pandas import DataFrame, concat
ticker = "CL=F"
start_date = dt.datetime(2018,1,1)
end_date = dt.datetime(2020,12,31)
class DataBase():
  def __init__(self, ticker, days):
    self.ticker = ticker
    data = yf.download(ticker, start = start_date, end =end_date )
    self.df = DataFrame(data)
    pd.set_option("display.max_columns", None)
    self.df[self.df.index.dayofweek < 4]
    self.df= self.df[-days:]

  def quote(self):
    return self.df

db = DataBase(ticker, 1000)
df = db.quote()
print(df.tail(10))

df['MA_14'] = df['Adj Close'].rolling(window=14).mean()
df.dropna(inplace=True)

# plt.rcParams.update({'font.size': 10})
# fig, ax1 = plt.subplots(figsize = (15,6))
# ax1.set_ylabel("Price in US$")
# ax1.set_xlabel("Timestamp")
# ax1.plot('Adj Close', data=df, label = "Adj Close Price")
# ax1.plot('MA_15', data=df, label = "3 weeks MA"); plt.grid(True)
# ax1.set_title('Crude Oil price & 15 days rolling average')
# plt.show()
df['MA_14'] = df['Adj Close'].rolling(window=14).mean()
df.dropna(inplace=True)

plt.rcParams.update({'font.size': 10})
fig, ax1 = plt.subplots(figsize = (10,5))
ax1.set_ylabel("Price in US$")
ax1.set_xlabel("Timestamp")
ax1.plot('Adj Close', data=df, label = "Adj Close Price")
ax1.plot('MA_14', data=df, label = "14 days MA"); plt.grid(True)
ax1.set_title('Crude Oil price & 14 days rolling average')
plt.show()<jupyter_output><empty_output><jupyter_text>It measures the relative strength of upside and downside moves in security prices. Formula:- RSI = 100 – 100 / ( 1 + RS )- RS = Relative Strength = AvgU / AvgD- AvgU = average of all up moves in the last N price bars- AvgD = average of all down moves in the last N price bars- N = the period of RSI RSI valuesThe Relative Strenght Index can reach values from 0 to 100. - Values close to zero are a sign of declining (bearish) market; - values close to 100 occur when prices are rising and the rises are greater than the declines.High RSI means that the second part of the RSI formula (behind the minus sign) is very small and RS is very large, which means that recent up moves have been much greater than recent down moves. In other words, high RSI values are a sign of bullish market (which may be overbought – depending on your particular view and trading style).Conversely, low RSI means that RS is also low and recent down moves have been much greater than recent up moves – in other words, bearish (and possibly oversold) market.When there are no down moves and AvgD is zero, RS can’t be calculated (you would be dividing by zero). In such case, we can consider RS close to infinite and RSI is 100. RSI overbought and oversold- RSI value over 80 or 90 is considered overbought.- RSI value under 20 or 10 is considered oversold.The tighter condition we choose (higher RSI value for overbought condition, lower RSI value for oversold condition), the fewer times RSI will get to the overbought or oversold territory and the fewer signals or trading situations we get.<jupyter_code>df['price_change'] = df['Adj Close'].pct_change()
df = df.dropna()

# https://www.macroption.com/rsi/ 

df['up_move'] = df['price_change'].apply(lambda x: x if x > 0 else 0)
df['down_move'] = df['price_change'].apply(lambda x: abs(x) if x < 0 else 0)

df['avg_up'] = df['up_move'].ewm(span=15).mean()
df['avg_down'] = df['down_move'].ewm(span=15).mean()
df = df.dropna()

df['RS'] = df['avg_up'] / df['avg_down']
df = df.dropna()
df['RSI'] = df['RS'].apply(lambda x: 100 - (100/(x+1)))

df.loc[(df['Adj Close'] > df['MA_14']) & (df['RSI'] > 30), "BUY"] = 'YES'
df.loc[(df['Adj Close'] < df['MA_14']) | (df['RSI'] < 30), "BUY"] = 'NO'
df
plt.figure(figsize=(15,6))
df['RSI'].plot(legend=True).axhline(y=50, color="black", lw=2)
df['Adj Close'].plot(legend=True)
plt.grid(True)
pd.value_counts(df.BUY)
PnL = []

for i in range(len(df) - 12):
  if "YES" in df['BUY'].iloc[i]:
    for j in range(1,11):
      if df['RSI'].iloc[i+j] > 70:
        PnL.append(df['Open'].iloc[i+j+1] - df['Open'].iloc[i+1])
        break
      if df['RSI'].iloc[i+j] < 30:
        PnL.append(df['Open'].iloc[i+12] - df['Open'].iloc[i+1])
        break

pd.DataFrame(PnL)
len([i for i in PnL if i > 0])
len(PnL)
# winning rate
print(len([i for i in PnL if i > 0]) / len(PnL))
tickers=["CL=F"]
start_date = dt.datetime(2018,1,1)
end_date = dt.datetime(2020,12,31)

def RSI_calculation(symbol):
  data = yf.download(tickers, start = start_date, end=end_date)
  data['MA_14'] = data['Adj Close'].rolling(window=14).mean()
  data['price_change'] = data['Adj Close'].pct_change()
  data['up_move'] = data['price_change'].apply(lambda x: x if x > 0 else 0)
  data['down_move'] = data['price_change'].apply(lambda x: abs(x) if x < 0 else 0)
  data['avg_up'] = data['up_move'].ewm(span=14).mean()
  data['avg_down'] = data['down_move'].ewm(span=14).mean()
  data = data.dropna()
  data['RS'] = data['avg_up'] / data['avg_down']
  data['RSI'] = data['RS'].apply(lambda x: 100 - (100/(x+1)))
  data.loc[(data['Adj Close'] > data['MA_14']) & (data['RSI'] > 30), "BUY"] = 'YES'
  data.loc[(data['Adj Close'] < data['MA_14']) | (data['RSI'] < 30), "BUY"] = 'NO'
  return (data)

def get_signals(data):
  buy_date = []
  sell_date = []

  for i in range(len(data) - 11):
    if "YES" in data['BUY'].iloc[i]:
      buy_date.append(data.iloc[i+1].name)
      for j in range(1,11):
        if data['RSI'].iloc[i+j] > 70:
          sell_date.append(data.iloc[i+j+1].name)
          break
        elif j == 10:
          sell_date.append(data.iloc[i+j+1].name)

  return (buy_date, sell_date)

dataframe = RSI_calculation(tickers[0])
buy, sell = get_signals(dataframe)

plt.figure(figsize = (15,6))
plt.scatter(dataframe.loc[buy].index, 
            dataframe.loc[buy]['Adj Close'], 
            marker = '^', 
            c = 'k')
plt.plot(dataframe['Adj Close'])
plt.grid(True); plt.show()
profits = (dataframe.loc[sell].Open.values - dataframe.loc[buy].Open.values) / dataframe.loc[sell].Open.values
len([i for i in profits if i > 0]) / len(profits)
len(profits)
matrix_signals = []
matrix_profit = []

for i in range(len(tickers)):
  dataframe = RSI_calculation(tickers[i])
  buy, sell = get_signals(dataframe)
  profits = (dataframe.loc[sell].Open.values - dataframe.loc[buy].Open.values) / dataframe.loc[sell].Open.values
  matrix_signals.append(buy)
  matrix_profit.append(profits)
final_profit = []

for i in matrix_profit:
  for e in i:
    final_profit.append(e)

DataFrame(final_profit)
wins = [i for i in final_profit if i > 0]
len(wins) / len(final_profit)
plt.hist(final_profit, bins=100)
plt.show()
for i in matrix_signals:
  for e in i:
    if e.year == 2020:
      print(e)
import numpy as np
import warnings
warnings.simplefilter(action = "ignore", category = RuntimeWarning)

ticker = "CL=F"
start_date = dt.datetime(2018,1,1)
end_date = dt.datetime(2020,12,31)
class DataBase():
  def __init__(self, ticker, days):
    self.ticker = ticker
    data = yf.download(ticker, start = start_date, end =end_date )
    self.df = DataFrame(data)
    pd.set_option("display.max_columns", None)
    self.df[self.df.index.dayofweek < 5]
    self.df= self.df[-days:]

  def quote(self):
    return self.df

db = DataBase(ticker, 1000)
df = db.quote()
# print(df.tail())

# calculate the return of the day and add as new column
df['price_change'] = np.log(df['Adj Close'] / df['Adj Close'].shift(1) )
df.dropna(inplace=True)

# calculate the movement on the price compared to the previous day closing price
df['movement'] = df['Adj Close'] - df['Adj Close'].shift(1)

df['up'] = np.where((df['movement'] > 0), df['movement'],0)

df['down'] = np.where((df['movement'] < 0), df['movement'],0)

window_size = 14
# calculate moving average of the last 14 days  gains
up = df['up'].rolling(window_size).mean()

# calculate moving average of the last 14 days  losses
down = df['down'].abs().rolling(window_size).mean()

RS = up / down
RSI = 100.0 - (100.0 / (1.0 + RS))

RSI = RSI.rename("RSI")
print(RSI)<jupyter_output>[*********************100%***********************]  1 of 1 completed
Date
2018-01-03          NaN
2018-01-04          NaN
2018-01-05          NaN
2018-01-08          NaN
2018-01-09          NaN
                ...    
2020-12-22    61.153869
2020-12-23    64.519922
2020-12-28    58.076022
2020-12-29    63.494000
2020-12-30    66.393473
Name: RSI, Length: 744, dtype: float64<jupyter_text>We need three lines of code to ensure that our strategy is defined appropriately. E.g. 1 when we need to be long:The first line of code on the long column, ensures that we will have a 1, if RSI is below 30.The second line of code, ensures that we will have 0 if the RSI is above 70.Then, finally, the third line of code, will ‘forward fill’ and propagate the last valid observation forward to get rid of the nan.Above three lines will ensure that we only go long when we cross the line from below 30.Finally, we calculate the gain and loss for each of the day and the accumulative return. Below screenshot shows that with this strategy, we would get an accumulative return of 50% if we followed the strategy during the last 5 years.<jupyter_code>new_df = pd.merge(df, RSI, left_index=True, right_index=True)

#If the indicator’s line crosses the level 30 from below, a long position (Buy) is opened.  
new_df['long'] = np.where((new_df['RSI'] < 20),1,np.nan)
new_df['long'] = np.where((new_df['RSI'] > 80),0, new_df['long'])

new_df['long'].ffill(inplace=True)

new_df['gain_loss'] = new_df['long'].shift(1) * new_df['price_change']

new_df['total'] =  new_df['gain_loss'].cumsum()

new_df[['Adj Close', 'price_change',	'movement',	
       'up','down',	'RSI','long','gain_loss', 'total']].tail(500)
#alternative strategy of buying and hold stock:
buy_hold_strat = (new_df['Adj Close'][-1] - new_df['Adj Close'][1]) / new_df['Adj Close'][1]
print(buy_hold_strat)<jupyter_output>-0.21948068318086333
{'fraction_non_alphanumeric': 0.14871559633027523, 'fraction_numerical': 0.037981651376146786, 'mean_word_length': 4.2994652406417115, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 26, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '498084', 'license': 'MIT', 'n_tokens_mistral': 4164, 'n_tokens_neox': 3815, 'n_words': 1178, 'path': 'RSI_Momentum_strategy.ipynb', 'repo_name': 'saritmaitra/Momentum', 'size': '10910'}