RSI Strategy in Bitcoin Investment

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:

  1. 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.
  2. 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.
  3. 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()

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.