Overview

Environments in Dojo are representations of different DeFi protocols agents can interact with. We currently support UniswapV3, AAVE and GMX.


Purpose

Environments generally provide the following functionality:

  1. Move the simulation forward in time to the next block at every simulation step
  2. Emit observations to provide the agent with the necessary information about the environment's current state (based on the agent's actions)
  3. Emit rewards generated by the agent to evaluate the agent's actions
  4. Accept a list of actions and execute the action in the environment. Each environment module contains information on the observations, actions and environment object for that environment. For example, to get this information for UniswapV3:
environments.py
from decimal import Decimal
 
from dojo.agents import BaseAgent
from dojo.environments.uniswapV3 import UniswapV3Env, UniswapV3Observation
 
# SNIPPET 2 END
 
 
class ETHAgent(BaseAgent):
  def __init__(
      self,
      initial_portfolio: dict[str, Decimal] = {
          "ETH": Decimal(10),
          "USDC": Decimal(10_000),
      },
  ) -> None:
      super().__init__(initial_portfolio=initial_portfolio)
 
  def reward(self, obs: UniswapV3Observation) -> float:  # type: ignore
      return float(self.quantity("ETH"))
 

Local vs. Forked Backend

One flag you'll have to specify when creating an environment is the backend_type. Understanding the difference is critical to get fast simulations.

backend_type = "forked"

  • Quick to start-up
  • Slow to run

This uses an RPC provider to create a forked chain using anvil. It happens almost instantenously, but at one big disadvantage: For every single transaction that is occuring, dojo needs to talk to the RPC provider.
So unless you have a full archive-node on the same machine as dojo is running on, communication over the internet is happening.
If you think about the millions of transcations occuring in a typical backtest, this will slow down the simulation massively.

backend_type = "local"

  • Slow to start up (could take a few mins.)
  • Runs much faster

In the local backend, we run custom logic to instantiate what is basically a pruned archive node on your local machine.
The main insight here is that on simulation start, you already know what contracts and accounts are relevant. So, we create a local archive node for you, that has just these contracts and accounts, but for the purpose of the simulation is equivalent to a full archive node. No communication to an outside RPC provider is required, thus it runs MUCH faster.

Of course, we do comprehensive CI/CD testing to ensure that the local backend is implemented correctly.

Typically, you'd want to use backend_type = "forked" while you're still developing your strategies (on short simulation periods), and then switch to backend_type = "local" for the actual backtest.