Last active
December 6, 2021 17:19
-
-
Save SeanDaws/05c66c1119515e8f168c9f11c2920486 to your computer and use it in GitHub Desktop.
Impermanent Loss Report Mathematica Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(*This is code that generates Example 1 of the report. Change these \ | |
parameters to see how they affect IL*) | |
Rval = 1;(*This is the skew ratio*) | |
\[Sigma]0val = | |
1; (*This is the baseline volatility for the given expiry*) | |
\ | |
\[Alpha]val = | |
0.0125; (*This is the skew ratio update parameter*) | |
\[Beta]val = | |
0.01; (*This is the baseline update parameter*) | |
ssexample = | |
20; (*number of contracts in one standard size*) | |
(*\[Alpha]val,\ | |
\[Beta]val and ssexample are the current values on testnet*) | |
rep = {R -> | |
Rval, \[Sigma]0 -> \[Sigma]0val, \[Alpha] -> \[Alpha]val, \[Beta] \ | |
-> \[Beta]val, ss -> ssexample}; | |
\[Sigma]update = (R + \[Alpha]*n/ss)*(\[Sigma]0 + \[Beta]*n/ss) /. | |
rep;(*Update volatility given some number of trades*) | |
(*some functions need to use Black Scholes*) | |
Nn[x_] := CDF[NormalDistribution[0, 1], x]; | |
Nnprime[x_] := 1/Sqrt[2 \[Pi]]*Exp[-x^2/2]; | |
\[Sigma][n_, Rb_, \[Sigma]0_, \[Alpha]_, \[Beta]_, | |
ss_] := (Rb + \[Alpha]*n/ss)*(\[Sigma]0 + \[Beta]*n/ss); | |
d1[\[Sigma]_, KK_, S_, r_, | |
T_] := (Return[ | |
1/(\[Sigma]*Sqrt[T])*(Log[S/KK] + (r + \[Sigma]^2/2) (T))]); | |
d2[\[Sigma]_, KK_, S_, r_, T_] := | |
Return[d1[\[Sigma], KK, S, r, T] - \[Sigma]*Sqrt[T]]; | |
NumericCost[\[Sigma]_, KK_, S_, r_, T_] := | |
Return[Nn[d1[\[Sigma], KK, S, r, T]]*S - | |
Nn[d2[\[Sigma], KK, S, r, T]]*KK* | |
Exp[-r*(T)]];(*BS cost for a call*) | |
\[Sigma]0TrueMarket = 3;(*This is the true market volatility.*) | |
SpotExample = 2000; (*Spot price of ETH*) | |
StrikeExample = 2100; (*The strike we are considering*) | |
interestsrate = 0; (*We consider 0 interest rate for simplicity*) | |
ExpiryExample = 28/365; (*Expiry in years. I.e. 28 days*) | |
contractsrequired = | |
n /. Solve[\[Sigma]update == \[Sigma]0TrueMarket && n > 0, n][[ | |
1]](*Compute how many contracts (N) are needed to update the AMM \ | |
trading vol to the true trading vol. This is also known as the make \ | |
up \[Eta]*) | |
discountedcost = | |
NIntegrate[ | |
NumericCost[\[Sigma][n, Rval, \[Sigma]0val, \[Alpha]val, \[Beta]val, | |
ssexample], StrikeExample, SpotExample, interestsrate, | |
ExpiryExample], {n, 0, | |
contractsrequired}](*Compute the most efficient way to extract \ | |
impermanent loss by buying one contract at a time up to \[Eta].*) | |
\ | |
truecost = | |
contractsrequired* | |
NumericCost[\[Sigma][contractsrequired, | |
Rval, \[Sigma]0val, \[Alpha]val, \[Beta]val, ssexample], | |
StrikeExample, SpotExample, interestsrate, | |
ExpiryExample](*This is the true cost of the (N) options at the \ | |
fair market value*) | |
ImpermanentLossExample = | |
truecost - | |
discountedcost (*This is the impermanent loss Lyra will suffer.*) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(*We now generalise the above example.*) | |
(*The impermanent loss function computes the IL the AMM suffers for a \ | |
set of given parameters. MarketIV is the new market trading vol, \ | |
STRIKEINDEX picks which strike we're tradng, SPOT is the spot price, \ | |
Expiry is the time to expiry in years, SKEWS is a vector of skew \ | |
ratios corresponding to each strike and BASELINE is the baseline vol.*) | |
Clear[\[Sigma]]; | |
\[Sigma][n_, Rb_, \[Sigma]0_, \[Alpha]_, \[Beta]_, | |
ss_] := (Rb + \[Alpha]*n/ss)*(\[Sigma]0 + \[Beta]*n/ss); | |
ImpermanentLoss[MarketIV_, STRIKEINDEX_, SPOT_, Expiry_, BASELINE_, | |
ALPHA_, BETA_, | |
SS_] := | |
(STRIKES = {1800, 2000, 2100, 2300, | |
2500};(*Strikes on ETH currerntly on testnet*) | |
SKEWS = {1, 1, 1, 1, 1};(*Skews for each strike. | |
We assume all are 1 for simplicity*) | |
contractsneeded = | |
n /. Solve[\[Sigma][n, SKEWS[[STRIKEINDEX]], BASELINE, ALPHA, | |
BETA, SS] == MarketIV && n > 0, n][[ | |
1]];(*Find the require contracts to get trading vol to the \ | |
market vol*) | |
CheapestCost = | |
NIntegrate[ | |
NumericCost[\[Sigma][n, SKEWS[[STRIKEINDEX]], BASELINE, ALPHA, | |
BETA, SS], STRIKES[[STRIKEINDEX]], SPOT, 0, Expiry], {n, 0, | |
contractsneeded}];(*This is the minimum cost (i.e. the exploit*) | |
TrueMarketCost = | |
contractsneeded* | |
NumericCost[\[Sigma][contractsneeded, SKEWS[[STRIKEINDEX]], | |
BASELINE, ALPHA, BETA, SS], STRIKES[[STRIKEINDEX]], SPOT, 0, | |
Expiry];(*This is the true market value*) | |
newskew = (SKEWS[[STRIKEINDEX]] + ALPHA*n/SS) /. {n -> | |
contractsneeded};(*This is the new skew after the attack*) | |
newbaseline = (BASELINE + BETA*n/SS) /. {n -> | |
contractsneeded};(*This is the new baseline vol after the \ | |
attack*) | |
Return[{TrueMarketCost - CheapestCost, newbaseline, newskew, | |
contractsneeded}]; | |
(*Return: [Imperrmanent Loss, newbaseline vol, new skew vol, | |
how many contracts the exploit took]*) | |
); | |
(*Figure 1: Changing expiry and spot*) | |
expFig1 = {7/365, 14/365, 21/365, | |
28/365};(*what expiries are we considering*) | |
SpotFig1 = {1000, 3000, 2000};(*what spots?*) | |
StrikeIndexFig1 = | |
3;(*Which strike? 3 \[Rule] the 3rd strike in STRIKES, i.e. K=2100*) | |
MAXVOL = 5;(*What is the maximum external market vol*) | |
changingexpiry = | |
Plot[{ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[3]], | |
expFig1[[1]], 1, .0125, .01, 20][[1]], | |
ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[3]], expFig1[[2]], | |
1, .0125, .01, 20][[1]], | |
ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[3]], expFig1[[3]], | |
1, .0125, .01, 20][[1]], | |
ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[3]], expFig1[[4]], | |
1, .0125, .01, 20][[1]]}, {x, 1.7, MAXVOL}, | |
PlotStyle -> {Green, Red, Magenta, Blue}, | |
AxesLabel -> {"Market IV (g)", "Impermanent Loss ($)"}, | |
ImageSize -> 300];(*effect of changing expiry*) | |
changingspot = | |
Plot[{ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[1]], | |
expFig1[[4]], 1, .0125, .01, 20][[1]], | |
ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[2]], expFig1[[4]], | |
1, .0125, .01, 20][[1]], | |
ImpermanentLoss[x, StrikeIndexFig1, SpotFig1[[3]], expFig1[[4]], | |
1, .0125, .01, 20][[1]]}, {x, 1.7, MAXVOL}, | |
PlotStyle -> {Red, Magenta, Blue}, | |
AxesLabel -> {"Market IV (g)", "Impermanent Loss ($)"}, | |
ImageSize -> | |
300];(*effect ofo changing spot*) | |
Fig1Pics = {changingexpiry, | |
changingspot} | |
(*Figure 2:Plot the IL for strike 2100 with spot 2000 and expiry 28 \ | |
days. Baseline vol is 1. Now we change alpha,beta and ss*) | |
SpotFig2 = 2000; | |
ExpFig2 = 28/365; | |
baseFig2 = 1; | |
StrikeIndexFig2 = 3; (*let's trade the middle strike K=2100*) | |
alphas = {0.0075, 0.0125, 0.0175}; | |
betas = {0.005, 0.01, | |
0.015};(*these are the \[Alpha],\[Beta],S vals we're iterating \ | |
over*) | |
Svals = {30, 20, 10}; | |
MAXVOLFig2 = 5; | |
changingalpha = | |
Plot[{ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, | |
baseFig2, alphas[[1]], betas[[2]], Svals[[2]]][[1]], | |
ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, baseFig2, | |
alphas[[2]], betas[[2]], Svals[[2]]][[1]], | |
ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, baseFig2, | |
alphas[[3]], betas[[2]], Svals[[2]]][[1]]}, {x, 1.7, | |
MAXVOLFig2}, PlotStyle -> {Red, Blue, Magenta}, | |
AxesLabel -> {"Market IV (g)", "Impermanent Loss ($)"}, | |
ImageSize -> 300]; | |
(*Plot IL against market vol but changing the alpha value*) | |
changingbeta = | |
Plot[{ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, | |
baseFig2, alphas[[2]], betas[[1]], Svals[[2]]][[1]], | |
ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, baseFig2, | |
alphas[[2]], betas[[2]], Svals[[2]]][[1]], | |
ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, baseFig2, | |
alphas[[2]], betas[[3]], Svals[[2]]][[1]]}, {x, 1.7, | |
MAXVOLFig2}, PlotStyle -> {Red, Blue, Magenta}, | |
AxesLabel -> {"Market IV (g)", "Impermanent Loss ($)"}, | |
ImageSize -> 300]; | |
(*changing beta*) | |
changingSS = | |
Plot[{ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, | |
baseFig2, alphas[[2]], betas[[2]], Svals[[1]]][[1]], | |
ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, baseFig2, | |
alphas[[2]], betas[[2]], Svals[[2]]][[1]], | |
ImpermanentLoss[x, StrikeIndexFig2, SpotFig2, ExpFig2, baseFig2, | |
alphas[[2]], betas[[2]], Svals[[3]]][[1]]}, {x, 1.7, | |
MAXVOLFig2}, PlotStyle -> {Red, Blue, Magenta}, | |
AxesLabel -> {"Market IV (g)", "Impermanent Loss ($)"}, | |
ImageSize -> 300]; | |
(*Changing number of contracts in one standard size*) | |
Fig2Pics = \ | |
{changingalpha, changingbeta, changingSS} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(*Figure 4 images take a LONG time to run, especially when we have \ | |
max vol =5. I can probably make this more efficient, but it'll do.*) | |
EXP = 28/365; | |
SPOT = 2000; | |
RATE = 0;(*Similar paameters as before*) | |
STRIKES = {1800, 2000, 2100, 2300, 2500}; | |
(*Picker scans over all strikes offered in the expiry, picks the \ | |
strike that suffers the greatest IL and returns it*) | |
Picker[MarketVol_, baseline_, SKEWS_, ALPHA_, BETA_, SS_] := | |
( | |
vols = baseline*SKEWS;(*These are the trading vosl the AMM uses*) | |
TRUECOST = | |
Table[NumericCost[MarketVol, STRIKES[[i]], SPOT, RATE, EXP], {i, | |
1, 5}] // N; | |
AMMCOST = | |
Table[NumericCost[vols[[i]], STRIKES[[i]], SPOT, RATE, EXP], {i, | |
1, 5}] // N ; | |
(*TRUECOST computes the cost of one contract over all strikes \ | |
using the market vol. AMMCOST does the same with the AMM vols*) | |
diffs = TRUECOST - AMMCOST; | |
worstindex = | |
If[Max[diffs] > 0, | |
Return[Flatten[Position[diffs, Max[diffs]]][[1]]], | |
Return["BAD"]];(*Pick the strike with the largest IL and trade \ | |
this*) | |
Return[worstindex]; | |
); | |
nmax = 100000;(*Should be using a while loop but I got lazy, so run \ | |
for some huge number of trades*) | |
(*Optimal takes in the true market \ | |
vol and uses Picker to find the strike with the worst IL, then trades \ | |
dn contracts of it. It updates the baseline vol and skew ratios \ | |
appropiately, then repeats until all AMM vols are equal to MarketVol \ | |
(no more IL). *) | |
dn = 1;(*Below should be done using an integral, but it's too \ | |
annoying to do this with multiple strikes (and quite \ | |
numerically expensive. Here we trade one contract at a time (dn=1). \ | |
Interested readers can change dn to smaller values, but this \ | |
dramatically increases the runtime of the following figures. The \ | |
difference between dn=1 and dn=0.1 for our canonical example (g=3, \ | |
and test net params) gives a difference of ~0.3%, so this difference \ | |
is minimal.*) | |
Optimal[MarketVol_, baseline_, SKEWS_, ALPHA_, BETA_, SS_] := | |
( | |
losses = {}; | |
indexloss = {}; | |
BASEUSE = baseline;(*initialise BASEUSE to the original base vol*) | |
SKEWUSE = SKEWS; (*same for skews*) | |
For[i = 1, i <= nmax, i++, | |
badindex = | |
Picker[MarketVol, BASEUSE, SKEWUSE, ALPHA, BETA, | |
SS];(*pick the strike with the worst IL*) | |
If[badindex == "BAD", | |
Break[]];(*If there are no exploitable strikes, break. | |
The exploit is over*) | |
SKEWUSE[[badindex]] = SKEWUSE[[badindex]] + dn*ALPHA/SS; | |
BASEUSE = BASEUSE + dn*BETA/SS;(*update skew and baseline*) | |
ILadd = dn* | |
NumericCost[MarketVol, STRIKES[[badindex]], SPOT, RATE, EXP] - | |
dn*NumericCost[BASEUSE*SKEWUSE[[badindex]], STRIKES[[badindex]], | |
SPOT, RATE, EXP];(*compute the IL from the trade*) | |
AppendTo[losses, ILadd]; | |
AppendTo[ | |
indexloss, {i, badindex, | |
ILadd}];(*Append to losses (loss from each trade) and indexloss \ | |
which stores info on the indexes (for me)*) | |
]; | |
Return[{losses, indexloss}]; | |
); | |
loss0075 = Optimal[3, 1, {1, 1, 1, 1, 1}, .0075, .01, 20]; | |
loss0175 = Optimal[3, 1, {1, 1, 1, 1, 1}, .0175, .01, 20]; | |
loss0125 = | |
Optimal[3, 1, {1, 1, 1, 1, 1}, .0125, .01, | |
20];(*Fig3a: Lets see how the IL per trade changes as we vary \ | |
alpha*) | |
loss0075plot = | |
ListPlot[loss0075[[1]], PlotStyle -> Red, | |
AxesLabel -> {"Trade Number", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300]; | |
loss0125plot = | |
ListPlot[loss0125[[1]], PlotStyle -> Blue, | |
AxesLabel -> {"Trade Number", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300]; | |
loss0175plot = | |
ListPlot[loss0175[[1]], PlotStyle -> Magenta, | |
AxesLabel -> {"Trade Number", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300]; | |
Show[loss0075plot, loss0125plot, loss0175plot] | |
loss10 = Optimal[3, 1, {1, 1, 1, 1, 1}, .0125, .01, 10]; | |
loss20 = Optimal[3, 1, {1, 1, 1, 1, 1}, .0125, .01, 20]; | |
loss30 = Optimal[3, 1, {1, 1, 1, 1, 1}, .0125, .01, 30]; | |
(*Fig 3b: Changing SS*) | |
loss30plot = | |
ListPlot[loss30[[1]], PlotStyle -> Red, | |
AxesLabel -> {"Trade Number", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300]; | |
loss20plot = | |
ListPlot[loss20[[1]], PlotStyle -> Blue, | |
AxesLabel -> {"Trade Number", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300]; | |
loss10plot = | |
ListPlot[loss10[[1]], PlotStyle -> Magenta, | |
AxesLabel -> {"Trade Number", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300]; | |
Show[loss30plot, loss20plot, loss10plot, PlotRange -> All] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(*THIS TAKE A WHILE (~5-10 MIN) TO RUN, LONGER WITH MORE MARKETVOLS*) | |
\ | |
(*Fig4a: Total IL over an expiry varying alpha*) | |
marketvols = | |
Subdivide[1.5, 3, | |
15];(*These are a sample of market vols we're testing*) | |
TotalLoss0075 = | |
Table[{marketvols[[i]], | |
Total[Optimal[marketvols[[i]], 1, {1, 1, 1, 1, 1}, .0075, .01, | |
20][[1]]]}, {i, 1, Length[marketvols]}]; | |
TotalLoss0125 = | |
Table[{marketvols[[i]], | |
Total[Optimal[marketvols[[i]], 1, {1, 1, 1, 1, 1}, .0125, .01, | |
20][[1]]]}, {i, 1, Length[marketvols]}]; | |
TotalLoss0175 = | |
Table[{marketvols[[i]], | |
Total[Optimal[marketvols[[i]], 1, {1, 1, 1, 1, 1}, .0175, .01, | |
20][[1]]]}, {i, 1, Length[marketvols]}]; | |
TotLossChangeAlpha = | |
ListPlot[{TotalLoss0075, TotalLoss0125, TotalLoss0175}, | |
PlotStyle -> {Red, Blue, Magenta}, | |
AxesLabel -> {"Market Volatility (g)", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300] | |
TotalLoss10 = | |
Table[{marketvols[[i]], | |
Total[Optimal[marketvols[[i]], 1, {1, 1, 1, 1, 1}, .0125, .01, | |
10][[1]]]}, {i, 1, Length[marketvols]}]; | |
TotalLoss20 = | |
Table[{marketvols[[i]], | |
Total[Optimal[marketvols[[i]], 1, {1, 1, 1, 1, 1}, .0125, .01, | |
20][[1]]]}, {i, 1, Length[marketvols]}]; | |
TotalLoss30 = | |
Table[{marketvols[[i]], | |
Total[Optimal[marketvols[[i]], 1, {1, 1, 1, 1, 1}, .0125, .01, | |
30][[1]]]}, {i, 1, Length[marketvols]}]; | |
TotLossChangeS = | |
ListPlot[{TotalLoss30, TotalLoss20, TotalLoss10}, | |
PlotStyle -> {Red, Blue, Magenta}, | |
AxesLabel -> {"Market Volatility (g)", "Impermanent Loss ($)"}, | |
Joined -> True, ImageSize -> 300] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment