Relative Strength Index (RSI)

Background

The Relative Strength Index (RSI) is a momentum oscillator used in technical analysis to measure the speed and change of price movements of an asset. It ranges from 0 to 100 and is typically used to identify overbought or oversold conditions in a market.

Typically, a value above 70 indicates that an asset is overbought, suggesting the price will fall, while a value below 30 indicates that it is oversold, suggesting the price will rise.

Traders use RSI to identify potential reversal points, overbought and oversold conditions, and to confirm trends.

RSI

Relative Strength Index Strategy

The RSI period for our strategy is 14. This means we calculate the RSI value for every 14 blocks.

When self.rsi is less than 30, we set self.buying to True. Further into the program, we check if self.buying is True, in which case we return a trade order to convert asset x to y. This only happens if we have enough x tokens (more than 0).

Similarly, when self.rsi is greater than 70, we set self.selling to True. Further into the program, we check if self.selling is True, in which case we return a trade order to convert asset y to x. This only happens if we have enough y tokens (more than 0).


How To Run

Installation

Follow our Getting Started guide to install the dojo library and other required tools.

Then clone the dojo_examples repository using the following command on your terminal.

Terminal
git clone https://github.com/CompassLabs/dojo_examples.git

Go into the rsi directory to run our strategy.

Terminal
cd dojo_examples/examples/rsi

To run this trading strategy on the Arbitrum network, change the chain variable in your .env file to arbitrum. Currently, arbitrum can only be used with the forked backend.

Running

Run the dojo_examples/start_dashboard.py script using the following command if you would like to access your dashboard.

Terminal
python start_dashboard.py

On another terminal window, copy the following command in the dojo_examples/examples/rsi folder.

Terminal
python run.py

This command will setup your local blockchain, contracts, accounts and agents. You can then access your Dojo dashboard at http://localhost:8051.


Step-By-Step Explanation

Initialization

We create a class called RSIPolicy which inherits from the BasePolicy class and initializes some variables that will be used later on.

policy.py
# a policy that uses the RSI indicator to make decisions
class RSIPolicy(BasePolicy):
  """RSI trading policy for a UniV3Env with a single pool.
 
  :param agent: The agent which is using this policy.
  """
 
  def __init__(self, agent: BaseAgent):
      self.agent = agent
      self.rsi_period = 14
      self.rsi_values = deque(maxlen=self.rsi_period)
      self.rsi = 0
      self.buying = False
      self.selling = False

Signal Calculation

Signals allow us to easily view data on our Dojo dashboard. In this example, we are adding a signal to monitor the RSI value over time. We can then add bookmarks on the dashboard to view when a trade was made and at what RSI value.

policy.py
pool = obs.pools[0]
token0, token1 = obs.pool_tokens(pool)
 
# calculate RSI
self.rsi_values.append(obs.price(token1, token0, pool))
if len(self.rsi_values) == self.rsi_period:
  delta = np.diff(self.rsi_values)
 
  gains = delta[delta > 0]
  losses = -delta[delta < 0]
  if losses.size == 0:
      self.rsi = 100
  elif gains.size == 0:
      self.rsi = 0
  else:
      gain = Decimal(gains.mean())
      loss = Decimal(losses.mean())
      rs = gain / loss
      self.rsi = 100 - 100 / (1 + rs)
 
obs.add_signal("RSI", self.rsi)

Trade Execution

The RSI value being less than 30 shows us that the token is oversold and undervalued. Therefore, we set the buying variable to True. Then, if the agent doesn't have enough balance to trade, we return an empty list which means do no trades. Otherwise, we return a UniV3Trade object specifying the agent, the pool and the amount of each token to buy/sell.

policy.py
# make decision
if self.rsi < 30:
  self.buying = True
elif self.rsi > 70:
  self.selling = True
 
# execute action
if self.buying:
  self.buying = False
  if self.agent.quantity(token0) == Decimal(0):
      return []
  return [
      UniV3Trade(
          self.agent, pool, (Decimal(self.agent.quantity(token0)), Decimal(0))
      )
  ]
elif self.selling:
  self.selling = False
  if self.agent.quantity(token1) == Decimal(0):
      return []
  return [
      UniV3Trade(
          self.agent, pool, (Decimal(0), Decimal(self.agent.quantity(token1)))
      )
  ]
return []

In the USDC/WETH pool, returning a UniV3Trade object with quantities=(Decimal(self.agent.quantity(token0)), Decimal(0)) means we are swapping all of our USDC tokens for WETH tokens, essentially buying WETH. The converse is true for selling WETH.

In the run.py file, we create a pool, a Uniswap environment and an agent that implements the RSI policy.