Relative Strength Index (RSI) is a commonly used momentum oscillator that measures the speed and change of price movements. It is typically used to identify overbought or oversold conditions in a tradeable asset.

Here are the common strategies applied in RSI:
- Overbought and Oversold Levels: RSI values of 70 or above are generally considered to be overbought, indicating that it may be a good time to sell. Conversely, RSI values of 30 or below are considered to be oversold, which indicates it may be a good time to buy.
- RSI Divergences: Sometimes the price of an asset can make a new high or low, but the RSI doesn’t mirror this. This is called a divergence and can potentially be a sign of future price reversal.
- RSI Swing Rejections: Some traders take a more nuanced approach with RSI—they look for a movement, or a swing, above or below certain levels. For example, some trades are entered when the RSI drops below 30 and then comes back above it, as this can signal the asset was oversold and is now potentially undervalued.
in this experiment, we try overbought and oversold levels at 70 and 30:
class Portfolio:
def __init__(self, price_data, currency, window=14):
self.price_data = price_data
self.currency = currency
self.window = window
self.rsi_data = self._calculate_rsi()
def _calculate_rsi(self):
self.price_data.index = pd.to_datetime(self.price_data['date'])
self.price_data = self.price_data.sort_index()
rsi_data = self.price_data.drop('date', axis=1)[[self.currency]]
rsi_data = rsi_data.apply(self._calculate_single_rsi, axis=0, args=(self.window,))
return rsi_data
def _calculate_single_rsi(self, series, window):
delta = series.diff().dropna()
up, down = delta.copy(), delta.copy()
up[up < 0] = 0
down[down > 0] = 0
roll_up = up.rolling(window).mean()
roll_down = down.rolling(window).mean().abs()
rs = roll_up / roll_down
rsi = 100.0 - (100.0 / (1.0 + rs))
return rsi
def apply_rsi_strategy(self, overbought=70, oversold=30):
self.rsi_data['signal'] = self.rsi_data.apply(self._generate_signal, args=(overbought, oversold), axis=1)
return self.rsi_data
def _generate_signal(self, row, overbought, oversold):
if row[self.currency] < oversold:
return 1 # Buy signal
elif row[self.currency] > overbought:
return -1 # Sell signal
else:
return 0 # Hold signal
BUY_FRACTION = 0.5 # the fraction of available capital to be used per buy signal
def apply_rsi_trading_strategy(rsi_data, initial_capital=1_000_000):
rsi_data = rsi_data.copy().reset_index(drop=True)
rsi_data['portfolio_value'] = initial_capital
rsi_data['holdings'] = 0.0
for i in range(len(rsi_data)):
row = rsi_data.iloc[i]
signal = row['signal']
price = row['price']
if signal == 1: # Buy signal
# We only use a fraction of the current portfolio value to buy
buy_value = rsi_data.at[i, 'portfolio_value'] * BUY_FRACTION
rsi_data.at[i, 'holdings'] += buy_value / price
rsi_data.at[i, 'portfolio_value'] -= buy_value
elif signal == -1 and rsi_data.at[i, 'holdings'] > 0: # Sell signal
rsi_data.at[i, 'portfolio_value'] += rsi_data.at[i, 'holdings'] * price
rsi_data.at[i, 'holdings'] = 0
# Update portfolio value for the next row
if i < len(rsi_data) - 1:
next_price = rsi_data.iloc[i + 1]['price']
rsi_data.at[i + 1, 'portfolio_value'] = rsi_data.at[i, 'portfolio_value'] + rsi_data.at[i, 'holdings'] * next_price
rsi_data['portfolio_return'] = rsi_data['portfolio_value'].pct_change()
rsi_data['cumulative_portfolio_return'] = (1 + rsi_data['portfolio_return']).cumprod() - 1
return rsi_data
traded_data = apply_rsi_trading_strategy(rsi_data_btc,initial_capital=1_000_000)
traded_data.iloc[150:200, ]
traded_data.tail()
# Set figure size and create subplot
fig, ax = plt.subplots(figsize=(12, 6))
# Plotting the data
ax.plot(rsi_data_btc.index, traded_data['cumulative_portfolio_return'], color='dodgerblue', linewidth=2)
# Improve the x-axis date labels
ax.xaxis.set_major_locator(mdates.YearLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
# Grid, spines, and legend
ax.grid(True)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Labeling
ax.set_title('Cumulative Portfolio Return over Time', fontsize=15)
ax.set_xlabel('Date', fontsize=12)
ax.set_ylabel('Cumulative Return', fontsize=12)
fig.autofmt_xdate() # improves date labels appearance
plt.show()
