Dollar Cost Averaging

Background

Dollar cost averaging (DCA) is an investment strategy that involves regularly investing a fixed amount of money into a particular asset regardless of the asset's price. This means purchasing more shares when prices are low and fewer shares when prices are high. The main objective is to reduce the impact of volatility on the overall purchase and avoid the pitfalls of trying to time the market.

Dollar Cost Averaging Strategy

Dollar Cost Averaging Strategy

Regardless of the price of token y, we swap self.buying_amount number of token x for token y. We do this trade every self.min_dist block if we have enough token x (more than 0) in the agent's portfolio.


How To Run

Installation

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

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

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

Go into the dollar_cost_averaging directory to run our strategy.

Terminal
cd dojo_examples/examples/dollar_cost_averaging

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/dollar_cost_averaging 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 subclass of BasePolicy called DCAPolicy which takes in 2 parameters: buying_amount and min_dist.

  • buying_amount specifies the amount of tokens we should trade for the other token
  • min_dist specifies the time interval in blocks between each trade.

On the Ethereum blockchain, the block time is approximately 12 seconds, while the block time on Arbitrum is ~0.26 seconds.

policy.py
class DCAPolicy(BasePolicy):
  """Dollar Cost Averaging policy for a UniV3Env with a single pool.
 
  :param agent: The agent which is using this policy.
  :param buying_amount: The number of tokens to swap at each trade.
  :param min_dist: The interval to swap tokens. The agent will only swap tokens if the
      last trade was at least min_dist blocks ago.
  """
 
  def __init__(self, agent: BaseAgent, buying_amount: float, min_dist: int) -> None:
      super().__init__(agent=agent)
      self.buying_amount = buying_amount
      self.min_dist = min_dist
      self.last_trade_block = 0

Signal Calculation

policy.py
pool = obs.pools[0]
token0, token1 = obs.pool_tokens(pool)
portfolio = self.agent.portfolio()
 
# add a signal to obs to monitor the difference in wealth if the agent bought the token all at once vs. dollar cost averaging
token0_balance = portfolio.get(token0, 0)
token1_balance = portfolio.get(token1, 0)
 
# calculate the current value of the portfolio if the agent bought the token all at once
value_if_held = self.agent.initial_portfolio[
  token0
] + self.agent.initial_portfolio[token1] * obs.price(
  token1, unit=token0, pool=pool
)
 
# calculate the current value of the portfolio if the agent used dollar cost averaging
dca_value = token0_balance * Decimal(1.0) + token1_balance * obs.price(
  token1, unit=token0, pool=pool
)
 
wealth_difference = dca_value - value_if_held
obs.add_signal("Wealth Difference", wealth_difference)

Signals allow us to easily view data on our Dojo dashboard. In this example, we are adding a signal to monitor the difference in wealth if the agent bought the token all at once at current price vs. dollar cost averaging.

Trade Execution

policy.py
if (
  portfolio[token0] >= self.buying_amount
  and obs.block - self.last_trade_block >= self.min_dist
):
  self.last_trade_block = obs.block
  return [
      UniV3Trade(
          agent=self.agent,
          pool=pool,
          quantities=(Decimal(self.buying_amount), Decimal(0)),
      )
  ]
return []

Here, we check if we have sufficient funds to purchase tokens and if enough time has passed since our last trade. If both conditions are met, we set the last_trade_block to the current block and return a UniV3Trade object specifying the buying amount. Otherwise, we return an empty list which means do nothing.

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