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

RSI Simulation Rewards

How To Run

Installation

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

Then clone the dojo_examples repository and go into the relevant directory.

Terminal
git clone https://github.com/CompassLabs/dojo_examples.git
cd dojo_examples/examples/rsi

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

Running

Download the dashboard to view the simulation results. To view example simulation data, download results.db and click 'Add A Simulation' on the dashboard.

To run the simulation yourself, use the following command.

Terminal
python run.py

This command will setup your local blockchain, contracts, accounts and agents. You can then access your results on your Dojo dashboard by connecting to a running simulation.


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 UniswapV3Env 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.

RSI Indicator Signals
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 UniswapV3Trade 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 [
      UniswapV3Trade(
          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 [
      UniswapV3Trade(
          self.agent, pool, (Decimal(0), Decimal(self.agent.quantity(token1)))
      )
  ]
return []

In the USDC/WETH pool, returning a UniswapV3Trade 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.

Results

You can download the results to this example below.

We offer a dashboard desktop application for visualizing your simulation results. This downloaded file can be added to the dashboard application.