Impermanent Loss
Background
Impermanent loss is a concept in decentralized finance (DeFi) that occurs when providing liquidity to automated market makers (AMMs) such as Uniswap. It represents the difference between holding tokens in a liquidity pool and simply holding them in a wallet. It is important for liquidity providers to understand and monitor impermanent loss as they could incur losses while trying to earn passive income.
When users provide liquidity to a pool, they deposit pairs of assets (like USDC and WETH) in equal value amounts. As trades occur in the pool, the ratio of these assets changes, causing the liquidity provider's share of each asset to adjust. If the price of one asset increases or decreases relative to the other, the provider's share of assets will diverge from the initial deposit amounts.
Impermanent loss happens when this divergence leads to a scenario where the total value of the assets, when withdrawn from the pool, is less than what it would have been if the provider had simply held the assets without providing liquidity. This loss is "impermanent" because it only becomes permanent if the liquidity provider withdraws their assets when the price ratio is different from the initial one. If the price ratio returns to its original value, the impermanent loss can disappear.
Tracking impermanent loss is crucial for liquidity providers because it can offset the gains made from trading fees earned in the pool. Understanding and monitoring impermanent loss helps liquidity providers make informed decisions about whether the potential returns justify the risk, and when it might be optimal to exit a liquidity pool to minimize losses.
Tracking Impermanent Loss
Our simulation uses the ImpermanentLossPolicy
with an ImpermanentLossAgent
. The ImpermanentLossPolicy
provides us with many signals for research, and the agent's reward shows impermanent loss as the reward
for the agent.
This is the formula we used to calculate impermanent loss in units of token0 and token1 but note that over time the term impermanent loss has been used interchangeably for different PnL measures. You can read more about this here: A Guide through Impermanent Loss on Uniswap.
The first action that the agent performs is providing liquidity using all the tokens they have.
Afterwards, the agent doesn't perform any trade because we're observing the impermanent loss that occurs.
We track the number of tokens we would have if we held onto the tokens, the current number of tokens in the adjusted LP position, the current number of tokens in the adjsted LP position including the fees, and the impermanent loss in the units of token0 or token1.
Plotting the "Hodl Value in Token0" with "Current Token0 Value with Fees" shows us the difference between holding the tokens in a wallet and holding the tokens in a liquidity pool. This difference value is shown on the "Impermanent Loss" signal on the dashboard.
This is the formula we used to calculate the reward of the agent which is the impermanent loss rate, but note that over time the term impermanent loss has been used interchangeably for different PnL measures. You can read more about this here: A Guide through Impermanent Loss on Uniswap.
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.
Running
Download the dashboard to view the simulation results.
To view example simulation data, download impermanent_loss_tracking.db
file from here and click 'Add A Simulation' on the dashboard.
To run the strategy, use the following command.
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 ImpermanentLossPolicy
which inherits from the BasePolicy
class and initializes the self.has_executed_lp_action
variable which makes sure that we only provide liquidity once.
Providing Liquidity
We provide liquidity to the pool at the start. Therefore, we return a UniswapV3Quote
object with the relevant parameters set only the first time we run this simulation.
We set quantities=[portfolio[token0], portfolio[token1]]
which means that we give all of our tokens to the pool. However, the pool will calculate
and only take the max amount of tokens in equal value amounts. For example, if the agent has 10,000 USDC and 1 WETH (worth 2100 USDC), the pool will take
2100 USDC and 1 WETH (not exact amounts).
Signal Calculation
Signals allow us to easily view data on our Dojo dashboard. In this example, we are adding 8 signals (essentially 4 signals but we display each signal in units of token0 or token1):
- Hodl Value - number of total tokens we would have if we held onto the tokens
- Current Token Value - the number of tokens the agent would get back if it withdrew liquidity.
- Current Token Value with Fees - the number of tokens the agent would get back if it withdrew liquidity, including the accrued LP fees.
- Impermanent Loss - if above 0, the agent made profit. If below 0, the agent made a loss as it could have simply held onto the tokens.
We can then add bookmarks on the dashboard to examine when impermanent loss happened, at which block, the number of tokens at that exact time etc.
The get_liquidity_ownership_tokens()
function returns the ids of tokens that the agent provided liquidity for.
The lp_portfolio(token_ids)
function returns the total number of tokens the agent will get back upon withdrawing liquidity.
This includes the quantities of each token and their uncollected fees.
The lp_quantities(token_ids)
function returns the number of tokens the agent will get back upon withdrawing liquidity. It doesn't include the uncollected LP fees.
The current_portfolio
dictionary may be empty if the agent hasn't provided liquidity yet. So we calculate an estimate for the initial signal and populate the dictionary accordingly.
If the current_portfolio
is not empty but the self.has_provided_lp
is still set to False, we set initial_lp_positions
to the quantities we have at that time.
This will be used to calculate how much money we would have had if we simply held onto those tokens, instead of providing liquidity to the pool.
The obs.price(token, unit, pool)
function returns the price of a token in the units of another token in a specific pool at the current block.
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.