Skip to content

Instantly share code, notes, and snippets.

@SeanDaws
Last active December 6, 2021 17:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SeanDaws/05c66c1119515e8f168c9f11c2920486 to your computer and use it in GitHub Desktop.
Save SeanDaws/05c66c1119515e8f168c9f11c2920486 to your computer and use it in GitHub Desktop.
Impermanent Loss Report Mathematica Code
(*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.*)
(*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}
(*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 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