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.

Screenshot of https://compasslabs.ai/dashboard?example=dollar_cost_averaging

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/dollar_cost_averaging

Running

Download the dashboard to view the simulation results. To view example simulation data, download results.db from the repository 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 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):  # type: ignore
  """Dollar Cost Averaging policy for a UniswapV3Env 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

Screenshot of https://compasslabs.ai/dashboard?example=dollar_cost_averagins#signals

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.

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)

Trade Execution

policy.py
if (
  portfolio[token0] >= self.buying_amount
  and obs.block - self.last_trade_block >= self.min_dist  # type: ignore
):
  self.last_trade_block = obs.block  # type: ignore
  return [
      UniswapV3Trade(
          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 UniswapV3Trade 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.

Results

You can download the results to this example below.

We offer a dashboard desktop application for visualizing your simulation results. You can download the file for the desktop application here, or just open the results in our hosted dashboard.