owned this note
owned this note
Published
Linked with GitHub
# Effects of static and dynamic fees in the HydraDX omnipool
###### tags: `Hydra` `Impermanent Loss` `fees` `TE Academy`
> Note to team:
> (1) Where there is a pin( :pushpin: ) add tables from Jupyter Notebook.
> (2) Add Oracle for HDX, fee revenue basket for HDX in Code.
> (3) Add AW plot in plot_utils.py
## Introduction
### Impermanent Loss in Liquidity Pools
Internal invariances set in the **Automated Market Maker (AMM)** of a liquidity pool work towards maintaining a stable pool by, for example, incentivising trade such that assets may not be undersupplied or drained from the pool.
However, due to price volatility of the risk assets in external markets there can be an arbitrage incentive whenever the local exchange price in the pool deviates from the external market. This arbitrage effect can produce **impermanent loss (IL)** and drain real value from the liquidity pool.
A **trading fee** can work to mitigate the risk of IL. It compensates liquidity providers for the expected impermanent loss (i.e. re-distribute external arbitrage profit from arbitrageurs to liquidity providers) thereby increasing return on investment (ROI) and discouraging arbitrage related trades.
### Research Question
How can a dynamic fee mechanism mitigate value drainage caused by impermanent loss in liquidity pools without deterring business-as-usual-traders?
### Outline
In this document we will...
1. define the **metrics** Impermanent Loss (IL) and, alternatively, the Arbitrage Wedge (AW),
2. define a **baseline** condition in which IL and AW are calculated in the cadCAD Model based on different price oracles without trading fee,
3. test different **static fee schedules** against the baseline,
4. test a **dynamic fee schedule** against the baseline.
## Defining Impermanent Loss (IL) and, alternatively, the Arbitrage Wedge (AW)
To compare the effects that different static (dynamic) fee mechanisms have on liquidity pool value we choose two metrics: **Impermanent Loss (IL)** and, alternatively, an **Arbitrage Wedge (AW)**.
We assume that external market prices of pool assets change from t= 0 to t= 1 and arbitrage behaviour acts as to balance the pool prices.
### Impermanent Loss (IL)
Impermanent Loss (IL) can be defined as the difference of the alternatives in terms of the holding value, i.e. the "loss in value of an asset portfolio held in a liquidity pool compared to the alternative of holding those assets in a private account (i.e. outside the liquidity pool)" (cf. https://hackmd.io/hzvgC7-TQaqtxDY-HudABA?view#A-derivation-of-Impermanent-Loss-IL).
$$ IL = \frac{V_1-V_H}{V_H}
$$
where V1 is the value of the asset portfolio inside the pool at t= 1 and VHis the value of the asset portfolio outside the pool at t= 1.
### Arbitrage Wedge (AW)
More generally, we can quantify the impacts arbitrage can have on the total pool value by calculating an arbitrage wedge (AW). The arbitrage wedge compares the total value of shares in the pool at any time step t had they been held outside the pool ($Total VHt$) and the total value of the liquidity pool $Total V1t$, i.e. the real value of all shares inside the pool at time t). It is defined as the loss in value of **all** asset portfolios held in a liquidity pool compared to the alternative of holding **all** assets outside the liquidity pool).That is, $IL =AW$ if there is only one liquidity provider.
$$
AWt = \frac{Total V1t -Total VHt}{Total VHt}
$$
$$ Total V1t = \sum\nolimits_{i=1}^N Quantity Asset_it * External Market Price_it $$
$$ Total VHt = \sum\nolimits_{j=1}^t\sum\nolimits_{i=1}^N Quantity AssetAdded_i * External Market Price_it $$
## Baseline
This section defines a **baseline** condition in which IL and AW are calculated in the cadCAD Model based on different price oracles without trading fee.
### Price Oracles
We define oracles for the external market price of each risk assets and HDX based on a constant function, a Dirac delta function, a random stochastic function and a random Gaussian function.
```python
def constant_function(x, constant=0):
"""
Returns a constant value `a` such that f(x) = a.
Parameters
============
x: the input value.
constant: the constant value.
"""
return constant
```
```python
def dirac_delta_function(x, steps=[(1, 1)]):
"""
Returns a Dirac delta function such that
f(x) = y_0 if x = x_0,
y_1 if x = x_1,
...
else 0
Parameters
============
x: the input value.
steps: a list of deltas.
"""
for x_n, y_n in steps:
if x == x_n:
return y_n
else:
return 0
```
```python
def random_stochastic_function(x, delta):
"""
Creates a random stochastic function that adds a value between
[-delta, delta]
Parameters
============
x: the input value.
delta: defines the range
"""
return (np.random.random_sample() * 2 * delta) - delta
```
```python
def random_gaussian_function(x, sigma):
"""
Samples from a Gaussian distribution.
Parameters
============
x: the input value.
delta: defines the variance of the Gaussian curve.
"""
return np.random.normal(0, sigma)
```
### IL and AW plots for Oracle sweeps
We sweep over all oracles (oracles = [constant function, Dirac delta function, random stochastic function and random Gaussian function]) in the cadCAD model and output IL and AW.
:pushpin: TBD
##### No Fee with Constant Oracles $i < j$
![](https://i.imgur.com/Z4Zq5cy.png)
![](https://i.imgur.com/b4be0As.png)
##### No Fee with $i$ as Increasing Step Function
![](https://i.imgur.com/4pygMJ0.png)
![](https://i.imgur.com/AbvjIuD.png)
The price of $i$ increases by $0.3$ at timestep $100$.
##### No Fee with $\Delta i$ from a Random Uniform Distribution
![](https://i.imgur.com/sMGtcM0.png)
![](https://i.imgur.com/WTAyjhs.png)
The maximum change in price of $i$ is $\pm0.3$.
##### No Fee with $\Delta i$ from a Random Gaussian Distribution
![](https://i.imgur.com/ndtCi6f.png)
![](https://i.imgur.com/jn4QVWA.png)
## Static Fees
This section tests different **static fee schedules** against the baseline.
A transaction fee can be implemented by subtracting a percentage of the to-be-traded asset ribefore the swap with another asset and adding to a Fee Revenue Basket in the pool.
In the cadCAD model a static fee g(a) with a = [.01, .02, .05] is implemented by swapping $rNew_i = (1-g(a))* r_i$ and adding $FeeRev_i= g(a)* r_i$.
The revenue from a fee system can act to balance the potential loss in total pool value due to arbitrage. Total Fee Revenue can be defined as the sum of the quantity of all assets that have been collected as trading fee until time t ($FeeRev_i$) at market prices:
$$ Total Fee Revenue_t = \sum\nolimits_{j=0}^t\sum\nolimits_{i=1}^N FeeRev_{ij}* OraclePrice_{it} $$
where N: number of assets in pool.
### IL and AW plots for static fee sweeps
We sweep over all static fee schedules (a = [.01, .02, .05] ) in the cadCAD model and output $Total Fee Revenue_t$, as well as plots for IL and AW.
:pushpin: TBD
##### Static Fee $a = 1\%$
![](https://i.imgur.com/O0mdqTy.png)
![](https://i.imgur.com/RbXV8IP.png)
##### Static Fee $a = 2\%$
![](https://i.imgur.com/xjvBUgq.png)
![](https://i.imgur.com/R19hGTA.png)
##### Static Fee $a = 5\%$
![](https://i.imgur.com/A3iDxoX.png)
![](https://i.imgur.com/NT17FrM.png)
## Dynamic Fees
This section tests a **dynamic fee schedule** against the baseline.
Let there be a **dynamic fee based on discrepancy of external market asset price** (i.e. $OraclePrice_{it}$) and liquidity pool asset price ($p_{it}$) at time t:
$$g(a)=(1+\mid p_{it}-OraclePrice_{it}\mid)*a$$
**Example**: If $a = .01$, $p_{it} = 2$, $OraclePrice_{it} = 2.5$, then $g(.01)=.015$.
Alternatively, we can define a dynamic fee that extracts the arbitrage profit of each trade.
$$g(a)=(1+Arbitrage Wedge_{t+1})*a$$
### IL and AW plots for dynamic fee sweeps
We sweep over the dynamic fee schedules (a = [.01, .02, .05] ) in the cadCAD model and output $Total Fee Revenue_t$, as well as plots for IL and AW.
:pushpin: TBD
##### Dynamic Fee $a = 1\%$
![](https://i.imgur.com/Wu0Cu8j.png)
![](https://i.imgur.com/wLjFoX0.png)
##### Dynamic Fee $a = 2\%$
![](https://i.imgur.com/QtzWmKu.png)
![](https://i.imgur.com/6COaAYe.png)
##### Dynamic Fee $a = 5\%$
![](https://i.imgur.com/i677xG1.png)
![](https://i.imgur.com/Pbf3uJh.png)
## Analysis and Conclusions
:pushpin: TBD
## Appendix
### AW Plot
AW Plot in: plot_utils.py
```python
def AW_plot(experiments,test_title,T):
"""
This function plots the Arbitrage Wedge (AW) a metric that defines
overall potential IL in the pool due to external market prices.
"""
AW = []
#Define timesteps as seen in plot_utils.py
df = experiments
df = df[df['substep'] == df.substep.max()]
df.fillna(0,inplace=True)
df.reset_index()
#Iterate through timesteps and calculate AW for each time step.
for i in range(df.substep.max(),T, df.substep.max()):
#Get Quantities for all assets in the pool and in the fee revenue basket.
Ri = df.pool[i].pool['i']['R'] #+ df['fee_revenue'][i]['i']
Rj = df.pool[i].pool['j']['R'] #+ df['fee_revenue'][i]['j']
Q = rdf['Q'][i] #+ df['fee_revenue'][i]['Q']
#Get Quantities for all shares.
Si = df.pool[i].pool['i']['S']
Sj = df.pool[i].pool['j']['S']
Sq = rdf['Sq'][i]
#Get external market prices.
#oracles = rdf[['oracle_price_i', 'oracle_price_j', 'oracle_price_Q']]
MPi = 1 #oracles['oracle_price_i']
MPj = 2 #oracles['oracle_price_j']
MPq = 1 #oracles['oracle_price_Q']
#Calculate Arbitrage Wedge.
Total_V1t = Ri*MPi + Rj*MPj + Q*MPq
Total_VHt = Si*MPi + Sj*MPj + Sq*MPq
AWt = (Total_V1t -Total_VHt) / Total_VHt
AW.append(AWt)
mathAW = np.array(AW)
#Plot Arbitrage Wedge across timesteps.
#plt.figure(figsize=(20,6))
#plt.subplot(141)
plt.plot(df.timestep, mathAW)
plt.xlabel('Timestep')
plt.ylabel('Arbitrage Wedge')
plt.legend()
```
### IL Plot
IL Plot in: plot_utils.py
``` python
def impermanent_loss_plot(rdf):
pool = rdf.pool
oracles = rdf[['oracle_price_i', 'oracle_price_j']]
def get_price(pool, key):
return pool.get_price(key)
price_i = pool.apply(get_price, key='i')
price_j = pool.apply(get_price, key='j')
impermanent_loss_i = (price_i - oracles['oracle_price_i']) / oracles['oracle_price_i']
impermanent_loss_j = (price_j - oracles['oracle_price_j']) / oracles['oracle_price_j']
plt.figure(figsize=(20, 6))
plt.subplot(121)
plt.plot(impermanent_loss_i)
plt.xlabel("Timestep")
plt.ylabel("Impermanent Loss")
plt.subplot(122)
plt.plot(impermanent_loss_j)
plt.xlabel("Timestep")
plt.ylabel("Impermanent Loss")
```