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
We provide 2 examples to track impermanent loss. The first example uses the ImpermanentLossPolicy
with a normal agent and the second example uses the
PassiveConcentratedLP
policy with an ImpermanentLossAgent
. They accomplish the same task and have the same result. However, the ImpermanentLossPolicy
provides us with more signals for research, whereas the second example shows impermanent loss as the reward
for the agent.
Example Using The ImpermanentLossPolicy
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.
Example Using The ImpermanentLossAgent
This is the formula we used to calculate impermanent loss 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.
This agent implements the PassiveConcentratedLP
policy which makes the agent provide liquidity passively to the pool.
Its first action is a swap of tokens which is necessary to have equal value amounts of each token. Then the agent will provide liquidity using all
its tokens.
Afterwards it performs no other action. At each block, the agent will update its reward on the dashboard to be the percentage of impermanent loss.
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 results.db
and click 'Add A Simulation' on the dashboard.
To run the strategy which uses the policy, use the following command.
To run the strategy which uses the agent, 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_total_potential_tokens_on_withdrawal(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.
ImpermanentLossAgent Explanation
The _pool_wealth
function keeps track of the value of the given portfolio. This could be the hold_portfolio
or the current portfolio.
The output of the reward
function will be displayed on our dashboard. Therefore, we return the percentage of impermanent loss in this function by calculating
the difference in value if we held onto the tokens and if we provided liquidity with them.
In the run.py
file, we create a Uniswap environment and an agent that implements the ImpermanentLossPolicy.
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.