Frequently Asked Questions (FAQ)
What do you get when you buy a Dojo license?
Dojo is primarily a backtesting library. It is also made up of several core components which both make it more comprehensive and more accurate than traditional backtesting software.
Dojo offers:
- Historical marketdata: This is how our backtesting engine replays historical events exactly as they happened.
- Order level accuracy: This means that your code submits orders exactly as it would in real life (and we are working on a feature to enable execution within Dojo using the same code!)
- Exchange Emulation: Our simulations run the actual code powering the decentralised exchanges. It's as though in traditional finance you could run the FIX protocol and the NYSE's entire codebase, with 100% accurate simulation of transaction costs, corner cases, and even bugs in the exchange!
- Complete data privacy and control: Dojo is self-hosted so we never see your data or strategies.
My simulation runs slowly. How can I speed it up?
Simulating at a per-block level can take time. There are several recommendations we can make for speeding up your simulation.
Try to start by considering the following:
- Does your policy need to run every block? If not, try using
dojo.actions.SleepAction
. - Are you running a forked backend? If so, is your configured archive node rate limited? This can signficantly slow down simulations so you might want to increase the rate limit or look into deploying your own archive node.
- Which chain are you using? Arbitrum has 48x the number of blocks as Ethereum so Ethereum simulations will be faster.
- If you switch your log level to INFO, does it look like the simulation sometimes pauses at the end of the block? If so, it might be that the instance of Anvil that backs your Dojo simulation is spending a lot of time mining blocks. You can try installing our patched version Anvil that skips some unnecessary part of mining blocks in simulations: CompassLabs/foundry-test at do-not-hash
I ran my simulation. How do I see my results?
Here are the main ways to see the results of your simulation:
- While the simulation is running, attach the Dojo Dashboard:
- Note down the port that your simulation will serve data. This can be defined when calling
backtest_run
and will be printed to the console. - Consider setting
auto_close=False
inbacktest_run
to make sure Dojo is still serving data to the dashboard until you press ENTER. - Install the Dojo Dashboard.
- Run your simulation as usual (from your IDE or terminal using Python.)
- Enter the port that you noted down in step (1) in the dashboard.
- (Optional) Export the data as JSON from the dashboard.
- Note down the port that your simulation will serve data. This can be defined when calling
- After the simulation has run, load the db file into the Dojo Dashboard:
- Run your simulation as usual (from your IDE or terminal using Python.)
- Find the local db file. It should be in the same directory as you ran your simulation in, potentially your project directory, ending in '.db'
- Install the Dojo Dashboard.
- Load the db file into the dashboard.
- After the simulation has run, convert the db file into a JSON file (useful if you are running in a Jupyter notebook!)
- Run your simulation as usual (from your IDE or terminal using Python.)
- Find the local db file. It should be in the same directory as you ran your simulation in, potentially your project directory, ending in '.db'
- Using Python, use the
dojo.external_data_providers.exports.json
module to convert your db file into a JSON file. - Load the JSON file using a file editor or a JSON library in your programming language.
At the end of my simulation I see that some actions have failed. Is this a problem?
The two main ways of using Dojo are:
- Start with an entirely empty environment and have all actions generated by your own agents.
- Replay historical events exactly as they occurred and insert actions from your own agents.
The second way is what Dojo is most used for. This also allows you to observe real market impact and transaction costs.
For example, if your agent makes a trade in a Uniswap pool, it might move the currently trading tick and then the price of the pool will be different than it was historically.
For another example let's consider GMX. Assume that there was some historical limit order placed by agent A1 at block B. Historically this limit order was filled by agent A2 at block B+5 ; so A1 and A2 traded with each other. In your simulation, however, your agent runs at block B+1, observes the historical limit order, and decides that it would like to fill the order. So now your agent trades with A1!
This means that at block B+5 the order no longer exists as it was completed at block B+1. Dojo then attempts to replay the historical action of A2 filling the trade. This historical actions fails!
This failure is not inherently a problem. The actions that succeed are still accurately simulated.
If there are a large number of market action failures this indicates that the state of your simulation has diverged significantly from what happened historically. To avoid this you might consider breaking up your backtest into multiple parts; for example, instead of simulating an entire month, you can simulate each day individually. This would limit the amount your simulations could diverge from history.
The Compass Labs team is interested in building more tooling to help people understand and configure their backtests for market impact and historical divergence. If you have any strategies for this you would like to see Dojo implement, please let us know!
Uniswap: How can I tell which token in a pool is token0?
Uniswap pools are named after both the tokens in the pool and the fee of the pool. For example, "USDC/WETH-0.05" would be a pool in which you could trade USDC and WETH with a 0.05% fee on each trade.
These names are not part of the UniswapV3 protocol! The solidity code that powers the protocol stores the token0, token1, and fee, but you cannot directly query the name of the pool. On the Uniswap website pools are given names which follow a standard pattern. Each pool is named "{token0}/{token1}-{fee}".
So if you know the name of the pool you know which tokens are token0 and token1. But how are these token ordered initially? That is, what makes USDC token0 instead of token1?
Uniswap (mostly*) decides this by sorting the two tokens' addresses, and selecting the token with the first address to be token0. This is why on Ethereum the pools with USDC and WETH will list USDC first; the ERC20 contract that controls USDC has an address which is alphabetically before the ERC20 contract that controls WETH. You can check this on Etherscan!
This also explains why two identical pools on different chains may have different names. For example, on Ethereum and Arbitrum, USDC's contract is deployed at different addresses. The result is that on Arbitrum the alphabetical ordering of the token addresses is different, WETH is chosen as token0, and the pool is given the name "WETH/USDC-0.05" (Recall that on Ethereum the pool is named "USDC/WETH-0.05".)
*Warning: Uniswap's UI sometimes, but rarely, lists token1 before token0! This seems to be a mistake. In Dojo we try to consistently put token0 before token1 in pool names, which does sometimes introduce inconsistency between Uniswap and Dojo's naming patterns. Your simulations will still be accurate even when names are different.