diff --git a/.gitmodules b/.gitmodules index 3b1fdc1..cc7ff72 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/v4-core"] path = lib/v4-core url = https://github.com/Uniswap/v4-core +[submodule "lib/periphery-next"] + path = lib/periphery-next + url = https://github.com/Uniswap/periphery-next diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a7502b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Decipher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/lib/periphery-next b/lib/periphery-next new file mode 160000 index 0000000..63d64fc --- /dev/null +++ b/lib/periphery-next @@ -0,0 +1 @@ +Subproject commit 63d64fcd82bff9ec0bad89730ce28d7ffa8e4225 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..aea4e0d --- /dev/null +++ b/remappings.txt @@ -0,0 +1,4 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@uniswap/v4-core/=lib/v4-core/ +@uniswap/v4-periphery/=lib/periphery-next/ +forge-std/=lib/forge-std/src/ \ No newline at end of file diff --git a/simulations/.env.example b/simulations/.env.example new file mode 100644 index 0000000..1e11fbc --- /dev/null +++ b/simulations/.env.example @@ -0,0 +1 @@ +PATH_TO_PNGS="/YOUR_ABSOLUTE_FILE_PATH_HERE/" \ No newline at end of file diff --git a/simulations/.gitignore b/simulations/.gitignore new file mode 100644 index 0000000..d41988d --- /dev/null +++ b/simulations/.gitignore @@ -0,0 +1,9 @@ +# Byte-compiled files +__pycache__/ + +# Environments +.venv/ +.env + +# Images +*.png \ No newline at end of file diff --git a/simulations/ConversionFreq.py b/simulations/ConversionFreq.py new file mode 100644 index 0000000..5e592ac --- /dev/null +++ b/simulations/ConversionFreq.py @@ -0,0 +1,244 @@ +""" +@author: sm-stack +""" +import pandas as pd +import math +import random +import matplotlib.pyplot as plt +import os +from dotenv import load_dotenv + +load_dotenv() + +def vaultFuturesStrategy(r0,r1,price,netVault0,netVault1,block,kStart,activeFuturePositions,conversionFrequency,alpha): + + r0new=math.sqrt(price*r0*r1) + r1new=r0new/price + + vault0,vault1=0,0 + """(r1-r1new)>0 means the token 1 has increased in value""" + if (r1-r1new)>0: + + vault1=vault1+(r1-r1new)*(alpha) + """repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + else: + vault0=vault0+(r0-r0new)*(alpha) + """repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + """settles (1/conversionFrequency) of the active futures contracts""" + if block % conversionFrequency==0: + for i in range(0,conversionFrequency): + valueToBeAdded=activeFuturePositions[i][0]*(price-activeFuturePositions[i][1]) + r0ValueAdd=(valueToBeAdded/2) + r1ValueAdd=(valueToBeAdded/2)/price + r0=r0+r0ValueAdd + r1=r1+r1ValueAdd + kStart=r0*r1 + if vault00 means the token 1 has increased in value""" + if (r1-r1new)>0: + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + """now r1 is the amount supposed to be in the pool GIVEN the updated r0""" + """r1new is the amount actually there, and >r1""" + """This difference goes into the vault""" + vault1=vault1+(r1new-r1) + + else: + vault0=vault0+(r0-r0new)*(alpha) + + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + + + """performs the conversion every conversionFrequency""" + if True: + if vault00.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + + for i in range(0,3): + r0Futs[i],r1Futs[i],netVault0Futs[i],netVault1Futs[i],kStartFuts[i],activeFuturePositionsFut[i]=vaultFuturesStrategy( + r0Futs[i],r1Futs[i],price,netVault0Futs[i],netVault1Futs[i],block,kStartFuts[i],activeFuturePositionsFut[i],conversionFrequencyFuts[i],alpha + ) + r0Futs[i],r1Futs[i]=addTXFees(r0Futs[i],r1Futs[i],dailyFeesVsK/numBlocksPerDay) + + r0Low,r1Low,vault0Low,vault1Low=vaultLowImpactReAdding(r0Low,r1Low,price,vault0Low,vault1Low,pctToReAdd,alpha) + r0Low,r1Low=addTXFees(r0Low,r1Low,dailyFeesVsK/numBlocksPerDay) + + for i in range(0,3): + vaultStrategyValueFuts[i]=(r1Futs[i]+netVault1Futs[i])*price + r0Futs[i]+netVault0Futs[i] + vaultStrategyValueLow=(r1Low+vault1Low)*price + r0Low+vault0Low + + results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLowImpact":vaultStrategyValueLow, + "vaultFuturesFreq2":vaultStrategyValueFuts[0], + "vaultFuturesFreq5":vaultStrategyValueFuts[1], + "vaultFuturesFreq10":vaultStrategyValueFuts[2], + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + + + +b=results["vaultFuturesFreq2"]/results["AMM"] +c=results["HODL"]/results["AMM"] +# d=results["vaultLowImpact"]/results["AMM"] +e=results["vaultFuturesFreq5"]/results["AMM"] +f=results["vaultFuturesFreq10"]/results["AMM"] + + +plt.figure(5) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=c.loc[firstSet].values.tolist(),c="orange",label="HODL") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=e.loc[firstSet].values.tolist(),c="blue",label="Conversion vs. Futures, 5") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=b.loc[firstSet].values.tolist(),c="magenta",label="Conversion vs. Futures, 2") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=f.loc[firstSet].values.tolist(),c="green",label="Conversion vs. Futures, 10") +plt.legend(loc="upper left") +plt.title("Conversion vs. Futures with different conversion frequencies") +plt.ylabel("Strategy / CFMM") +plt.xlabel("final price") + +path=os.getenv("PATH_TO_PNGS") +plt.savefig(path + 'CONVFREQ.png',dpi=500) + +# print("vaultFutures",b.loc[firstSet].describe()) +# print("HODL",c.loc[firstSet].describe()) +# print("vaultLowImpact",d.loc[firstSet].describe()) \ No newline at end of file diff --git a/simulations/MainProtocolSimulation.py b/simulations/MainProtocolSimulation.py new file mode 100644 index 0000000..12d7cfb --- /dev/null +++ b/simulations/MainProtocolSimulation.py @@ -0,0 +1,528 @@ +""" +@author: u176198 +""" +import pandas as pd +import math +import random +import matplotlib.pyplot as plt +import os +from dotenv import load_dotenv + +load_dotenv() + +def vaultFuturesStrategy(r0,r1,price,netVault0,netVault1,block,kStart,activeFuturePositions,conversionFrequency,alpha): + + r0new=math.sqrt(price*r0*r1) + r1new=r0new/price + + vault0,vault1=0,0 + """"(r1-r1new)>0 means the token 1 has increased in value""" + if (r1-r1new)>0: + + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + else: + vault0=vault0+(r0-r0new)*(alpha) + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + """settles (1/conversionFrequency) of the active futures contracts""" + if block % conversionFrequency==0: + for i in range(0,conversionFrequency): + valueToBeAdded=activeFuturePositions[i][0]*(price-activeFuturePositions[i][1]) + r0ValueAdd=(valueToBeAdded/2) + r1ValueAdd=(valueToBeAdded/2)/price + r0=r0+r0ValueAdd + r1=r1+r1ValueAdd + kStart=r0*r1 + if vault00 means the token 1 has increased in value""" + if (r1-r1new)>0: + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + + else: + vault0=vault0+(r0-r0new)*(alpha) + + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + + + """performs the conversion every conversionFrequency""" + if block % conversionFrequency==0: + if vault00.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + + r0Futs,r1Futs,netVault0Futs,netVault1Futs,kStartFuts,activeFuturePositions=vaultFuturesStrategy(r0Futs,r1Futs,price,netVault0Futs,netVault1Futs,block,kStartFuts,activeFuturePositions,conversionFrequency,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + + vaultStrategyValueFuts=(r1Futs+netVault1Futs)*price + r0Futs+netVault0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + + results= results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLazyConvert":vaultStrategyValueLazy, + "vaultFutures":vaultStrategyValueFuts, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + + +results["vaultLazyConvert"]=results["vaultLazyConvert"]/results["AMM"] + + +plt.figure(0) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=results["vaultLazyConvert"].loc[firstSet].values.tolist(),c="magenta",label="0.5") +plt.scatter(x=results["finalPrice"].loc[secondSet].values.tolist(),y=results["vaultLazyConvert"].loc[secondSet].values.tolist(),c="orange",label=" 0.95") +plt.legend(loc="upper left") +plt.title("alpha") +plt.ylabel("Diamond / CFMM") +plt.xlabel("final price") + +path=os.getenv("PATH_TO_PNGS") +plt.savefig(path + 'LVRAlphaComparison.png',dpi=500) + +results=pd.DataFrame(data={"dailyExpectedVol":[], + "alpha":[], + "vaultLazyConvert":[], + "vaultFutures":[], + "AMM":[], + "HODL":[], + "finalPrice":[]}) + +alpha=0.95 +conversionFrequency=numBlocksPerDay +"""dailyFeesVsK=0.0003 is equiv to 10% TVL trading in a 0.3% fee pool""" + + +for dailyExpectedVol in [1.05]: + for numDaysSimulation in [365,365*3]: + blocksForSim=numBlocksPerDay*numDaysSimulation + for sim in range(0,numberOfSimsPerCombination): + price=r0start/r1start + + """Futures Strategy Variables""" + netVault0Futs=0 + netVault1Futs=0 + r0Futs=r0start + r1Futs=r1start + kStartFuts=r0Futs*r1Futs + + """Lazy Conversion Strategy Variables""" + r0Lazy=r0start + r1Lazy=r1start + vault0Lazy=0 + vault1Lazy=0 + + activeFuturePositions=[[0,0] for i in range(0,conversionFrequency)] + for block in range(0,blocksForSim): + """ **(2*random.random()) introduces a vol of vol""" + perBlockVol=1+((1-dailyExpectedVol)/math.sqrt(numBlocksPerDay)) + if random.random()>0.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + + r0Futs,r1Futs,netVault0Futs,netVault1Futs,kStartFuts,activeFuturePositions=vaultFuturesStrategy(r0Futs,r1Futs,price,netVault0Futs,netVault1Futs,block,kStartFuts,activeFuturePositions,conversionFrequency,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + + vaultStrategyValueFuts=(r1Futs+netVault1Futs)*price + r0Futs+netVault0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + + results= results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLazyConvert":vaultStrategyValueLazy, + "vaultFutures":vaultStrategyValueFuts, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + + +results["vaultLazyConvert"]=results["vaultLazyConvert"]/results["AMM"] +plt.figure(1) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=results["vaultLazyConvert"].loc[firstSet].values.tolist(),c="magenta",label="1 year") +plt.scatter(x=results["finalPrice"].loc[secondSet].values.tolist(),y=results["vaultLazyConvert"].loc[secondSet].values.tolist(),c="orange",label="3 years") +plt.legend(loc="upper left") +plt.title("Lifetime of Pool") +plt.ylabel("Diamond / CFMM") +plt.xlabel("final price") + +plt.savefig('/home/conor/LVR/LVRRuntimeComparison.png',dpi=500) + +results=pd.DataFrame(data={"dailyExpectedVol":[], + "alpha":[], + "vaultLazyConvert":[], + "vaultFutures":[], + "AMM":[], + "HODL":[], + "finalPrice":[]}) + +alpha=0.95 +conversionFrequency=numBlocksPerDay +"""dailyFeesVsK=0.0003 is equiv to 10% TVL trading in a 0.3% fee pool""" + +results + +for dailyExpectedVol in [1.05,1.1,1.15]: + for numDaysSimulation in [365]: + blocksForSim=numBlocksPerDay*numDaysSimulation + for sim in range(0,numberOfSimsPerCombination): + price=r0start/r1start + + """Futures Strategy Variables""" + netVault0Futs=0 + netVault1Futs=0 + r0Futs=r0start + r1Futs=r1start + kStartFuts=r0Futs*r1Futs + + """Lazy Conversion Strategy Variables""" + r0Lazy=r0start + r1Lazy=r1start + vault0Lazy=0 + vault1Lazy=0 + + activeFuturePositions=[[0,0] for i in range(0,conversionFrequency)] + for block in range(0,blocksForSim): + """ **(2*random.random()) introduces a vol of vol""" + perBlockVol=1+((1-dailyExpectedVol)/math.sqrt(numBlocksPerDay)) + if random.random()>0.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + r0Futs,r1Futs,netVault0Futs,netVault1Futs,kStartFuts,activeFuturePositions=vaultFuturesStrategy(r0Futs,r1Futs,price,netVault0Futs,netVault1Futs,block,kStartFuts,activeFuturePositions,conversionFrequency,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + + vaultStrategyValueFuts=(r1Futs+netVault1Futs)*price + r0Futs+netVault0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + + results= results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLazyConvert":vaultStrategyValueLazy, + "vaultFutures":vaultStrategyValueFuts, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + +Q1 = results["finalPrice"].quantile(0.25) +Q3 = results["finalPrice"].quantile(0.75) +IQR = Q3 - Q1 + +# Define lower and upper bounds +lower_bound = Q1 - 2.5 * IQR +upper_bound = Q3 + 2.5 * IQR + +# Filter the results to exclude outliers +filtered_results = results[(results["finalPrice"] >= lower_bound) & (results["finalPrice"] <= upper_bound)] + +vol1 = filtered_results[filtered_results["dailyExpectedVol"] == 1.05] +vol2 = filtered_results[filtered_results["dailyExpectedVol"] == 1.1] + +vol3 = filtered_results[filtered_results["dailyExpectedVol"] == 1.15] + + + +results["vaultLazyConvert"]=results["vaultLazyConvert"]/results["AMM"] +plt.figure(2) +plt.scatter(x=vol1["finalPrice"].values.tolist(),y=vol1["vaultLazyConvert"].values.tolist(),c="magenta",label="5%") +plt.scatter(x=vol2["finalPrice"].values.tolist(),y=vol2["vaultLazyConvert"].values.tolist(),c="orange",label="10%") +plt.scatter(x=vol3["finalPrice"].values.tolist(),y=vol3["vaultLazyConvert"].values.tolist(),c="green",label="15%") +plt.legend(loc="upper left") +plt.title("Expected Daily Price Move") +plt.ylabel("Diamond / CFMM") +plt.xlabel("final price") + + +plt.savefig('/home/conor/LVR/LVRVolComparison.png',dpi=500) + + +results=pd.DataFrame(data={"dailyExpectedVol":[], + "alpha":[], + "vaultLazyConvert":[], + "vaultFutures":[], + "AMM":[], + "HODL":[], + "finalPrice":[]}) + +alpha=0.95 +numDaysSimulation=365 +"""dailyFeesVsK=0.0003 is equiv to 10% TVL trading in a 0.3% fee pool""" +dailyFeesVsK=0.0000 + +for dailyExpectedVol in [1.05]: + for conversionFrequency in [numBlocksPerDay,numBlocksPerDay*7]: + blocksForSim=numBlocksPerDay*numDaysSimulation + for sim in range(0,numberOfSimsPerCombination): + price=r0start/r1start + + """Futures Strategy Variables""" + netVault0Futs=0 + netVault1Futs=0 + r0Futs=r0start + r1Futs=r1start + kStartFuts=r0Futs*r1Futs + + """Lazy Conversion Strategy Variables""" + r0Lazy=r0start + r1Lazy=r1start + vault0Lazy=0 + vault1Lazy=0 + + activeFuturePositions=[[0,0] for i in range(0,conversionFrequency)] + for block in range(0,blocksForSim): + """ **(2*random.random()) introduces a vol of vol""" + perBlockVol=1+((1-dailyExpectedVol)/math.sqrt(numBlocksPerDay)) + if random.random()>0.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + r0Futs,r1Futs,netVault0Futs,netVault1Futs,kStartFuts,activeFuturePositions=vaultFuturesStrategy(r0Futs,r1Futs,price,netVault0Futs,netVault1Futs,block,kStartFuts,activeFuturePositions,conversionFrequency,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + + + vaultStrategyValueFuts=(r1Futs+netVault1Futs)*price + r0Futs+netVault0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + + rresults= results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLazyConvert":vaultStrategyValueLazy, + "vaultFutures":vaultStrategyValueFuts, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + + +results["vaultLazyConvert"]=results["vaultLazyConvert"]/results["AMM"] +plt.figure(3) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=results["vaultLazyConvert"].loc[firstSet].values.tolist(),c="magenta",label="every day") +plt.scatter(x=results["finalPrice"].loc[secondSet].values.tolist(),y=results["vaultLazyConvert"].loc[secondSet].values.tolist(),c="orange",label="every week") +plt.legend(loc="upper left") +plt.title("Conversion Frequency") +plt.ylabel("Diamond / CFMM") +plt.xlabel("final price") + +plt.savefig('/home/conor/LVR/LVRConvFreqComparison.png',dpi=500) + +print("1 day mean",results["vaultLazyConvert"].loc[firstSet].describe()) +print("1 week mean",results["vaultLazyConvert"].loc[secondSet].describe()) + +results=pd.DataFrame(data={"dailyExpectedVol":[], + "alpha":[], + "vaultLazyConvert":[], + "vaultFutures":[], + "AMM":[], + "HODL":[], + "finalPrice":[]}) + +alpha=0.95 +numDaysSimulation=365 +conversionFrequency=numBlocksPerDay +"""dailyFeesVsK=0.0003 is equiv to 10% TVL trading in a 0.3% fee pool""" +conversionFrequency =numBlocksPerDay + +for dailyExpectedVol in [1.05]: + for dailyFeesVsK in [0,0.00003,0.0003]: + blocksForSim=numBlocksPerDay*numDaysSimulation + for sim in range(0,numberOfSimsPerCombination): + price=r0start/r1start + + """Futures Strategy Variables""" + netVault0Futs=0 + netVault1Futs=0 + r0Futs=r0start + r1Futs=r1start + kStartFuts=r0Futs*r1Futs + + """Lazy Conversion Strategy Variables""" + r0Lazy=r0start + r1Lazy=r1start + vault0Lazy=0 + vault1Lazy=0 + + activeFuturePositions=[[0,0] for i in range(0,conversionFrequency)] + for block in range(0,blocksForSim): + """ **(2*random.random()) introduces a vol of vol""" + perBlockVol=1+((1-dailyExpectedVol)/math.sqrt(numBlocksPerDay)) + if random.random()>0.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + r0Futs,r1Futs,netVault0Futs,netVault1Futs,kStartFuts,activeFuturePositions=vaultFuturesStrategy(r0Futs,r1Futs,price,netVault0Futs,netVault1Futs,block,kStartFuts,activeFuturePositions,conversionFrequency,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + + """add TxFees""" + r0Lazy,r1Lazy=addTXFees(r0Lazy,r1Lazy,dailyFeesVsK/numBlocksPerDay) + r0Futs,r1Futs=addTXFees(r0Futs,r1Futs,dailyFeesVsK/numBlocksPerDay) + + vaultStrategyValueFuts=(r1Futs+netVault1Futs)*price + r0Futs+netVault0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + + results= results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLazyConvert":vaultStrategyValueLazy, + "vaultFutures":vaultStrategyValueFuts, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + +a=results["vaultLazyConvert"]/results["AMM"] +b=results["vaultFutures"]/results["AMM"] +c=results["HODL"]/results["AMM"] +plt.figure(4) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=a.loc[firstSet].values.tolist(),c="magenta",label="fee = 0%") +plt.scatter(x=results["finalPrice"].loc[secondSet].values.tolist(),y=a.loc[secondSet].values.tolist(),c="orange",label="fee = 0.03%") +plt.scatter(x=results["finalPrice"].loc[thirdSet].values.tolist(),y=a.loc[thirdSet].values.tolist(),c="green",label="fee = 0.3%") +plt.legend(loc="upper left") +plt.title("Fees (Given 10% of pool TVL trades per day)") +plt.ylabel("Diamond / CFMM") +plt.xlabel("final price") + +plt.savefig('/home/conor/LVR/LVRFeeComparison.png',dpi=500) + + + +plt.figure(5) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=a.loc[firstSet].values.tolist(),c="magenta",label="Periodic Conversion Auction") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=b.loc[firstSet].values.tolist(),c="orange",label="Conversion vs. Futures") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=c.loc[firstSet].values.tolist(),c="green",label="HODL") +plt.legend(loc="upper left") +plt.title("Fees") +plt.ylabel("Strategy / CFMM") +plt.xlabel("final price") + +plt.savefig(path + 'HODL.png',dpi=500) + +print("vaultLazyConvert",a.loc[firstSet].describe()) +print("vaultFutures",b.loc[firstSet].describe()) + + + diff --git a/simulations/README.md b/simulations/README.md new file mode 100644 index 0000000..b790f12 --- /dev/null +++ b/simulations/README.md @@ -0,0 +1,35 @@ +# Simulation Scripts for Diamond Protocol + +Inspired by [LVR simulation scripts](https://github.com/The-CTra1n/LVR) from @The-CTra1n, +this python scripts simulate the behaviours of various method suggested in the [thesis on Diamond protocol](https://eprint.iacr.org/2022/1420.pdf) +and [LVR minimization research on ethresearch](https://ethresear.ch/t/lvr-minimization-in-uniswap-v4/15900), and generate relative outputs as a form +of graph pngs. + +## How to run scripts + +1. Create venv and configure .env files to decide where to save png files +``` +python3 -m venv .venv +cp .env.example .env +vi .env + +// Inside .env, insert an appropriate path for the graphs to be saved. +PATH_FOR_PNGS="/YOUR_PATH/" +``` + +2. Activate venv +``` +source .venv/bin/activate +``` + +3. Install requirements +``` +pip install -r requirements.txt +``` + +4. Run scripts and deactivate +``` +python Slippage.py + +deactviate +``` \ No newline at end of file diff --git a/simulations/ReAdd.py b/simulations/ReAdd.py new file mode 100644 index 0000000..c5ed491 --- /dev/null +++ b/simulations/ReAdd.py @@ -0,0 +1,379 @@ +""" +@author: The-CTra1n +""" +import pandas as pd +import math +import random +import matplotlib.pyplot as plt +import os +from dotenv import load_dotenv + +load_dotenv() + +def vaultFuturesStrategy(r0,r1,price,netVault0,netVault1,block,kStart,activeFuturePositions,conversionFrequency,alpha): + + r0new=math.sqrt(price*r0*r1) + r1new=r0new/price + + vault0,vault1=0,0 + """(r1-r1new)>0 means the token 1 has increased in value""" + if (r1-r1new)>0: + + vault1=vault1+(r1-r1new)*(alpha) + """repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + else: + vault0=vault0+(r0-r0new)*(alpha) + """repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + """settles (1/conversionFrequency) of the active futures contracts""" + if block % conversionFrequency==0: + for i in range(0,conversionFrequency): + valueToBeAdded=activeFuturePositions[i][0]*(price-activeFuturePositions[i][1]) + r0ValueAdd=(valueToBeAdded/2) + r1ValueAdd=(valueToBeAdded/2)/price + r0=r0+r0ValueAdd + r1=r1+r1ValueAdd + kStart=r0*r1 + if vault00 means the token 1 has increased in value""" + if (r1-r1new)>0: + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + + else: + vault0=vault0+(r0-r0new)*(alpha) + + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + + + """performs the conversion every conversionFrequency""" + if block % conversionFrequency==0: + if vault00 means the token 1 has increased in value""" + if (r1-r1new)>0: + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + """now r1 is the amount supposed to be in the pool GIVEN the updated r0""" + """r1new is the amount actually there, and >r1""" + """This difference goes into the vault""" + vault1=vault1+(r1new-r1) + + else: + vault0=vault0+(r0-r0new)*(alpha) + + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + + + """performs the conversion every conversionFrequency""" + if True: + if vault00.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + r0Low,r1Low,vault0Low,vault1Low=vaultLowImpactReAdding(r0Low,r1Low,price,vault0Low,vault1Low,pctToReAdd,alpha) + r0Low,r1Low=addTXFees(r0Low,r1Low,dailyFeesVsK/numBlocksPerDay) + + + vaultStrategyValueLow=(r1Low+vault1Low)*price + r0Low+vault0Low + + results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLowImpact":vaultStrategyValueLow, + "vaultLazyConvert":0, + "vaultFutures":0, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + + + + +results["vaultLowImpact"]=results["vaultLowImpact"]/results["AMM"] +plt.figure(0) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=results["vaultLowImpact"].loc[firstSet].values.tolist(),c="magenta",label="0.01") +plt.scatter(x=results["finalPrice"].loc[secondSet].values.tolist(),y=results["vaultLowImpact"].loc[secondSet].values.tolist(),c="orange",label=" 0.05") +plt.scatter(x=results["finalPrice"].loc[thirdSet].values.tolist(),y=results["vaultLowImpact"].loc[thirdSet].values.tolist(),c="green",label="0.125") +#plt.scatter(x=results["finalPrice"].loc[fourthSet].values.tolist(),y=results["vaultLowImpact"].loc[fourthSet].values.tolist(),c="blue",label="0.5") + + +plt.legend(loc="upper left") +plt.title("% ReAdd from Vault") +plt.ylabel("Low Impact ReAdd / CFMM") +plt.xlabel("final price") + + +path=os.getenv("PATH_TO_PNGS") +plt.savefig(path + 'ReAddPercentages.png',dpi=500) + + + +print("0.01",results["vaultLowImpact"].loc[firstSet].describe()) +print("0.05",results["vaultLowImpact"].loc[secondSet].describe()) +print("0.125",results["vaultLowImpact"].loc[thirdSet].describe()) +#print("0.5",results["vaultLowImpact"].loc[fourthSet].describe()) + +results=pd.DataFrame(data={"dailyExpectedVol":[], + "alpha":[], + "vaultLazyConvert":[], + "vaultFutures":[], + "AMM":[], + "HODL":[], + "finalPrice":[]}) + + +for dailyExpectedVol in [1.05]: + for pctToReAdd in [0.01]: + blocksForSim=numBlocksPerDay*numDaysSimulation + for sim in range(0,numberOfSimsPerCombination): + price=r0start/r1start + + """Futures Strategy Variables""" + netVault0Futs=0 + netVault1Futs=0 + slippageFut=0 + r0Futs=r0start + r1Futs=r1start + kStartFuts=r0Futs*r1Futs + + """Lazy Conversion Strategy Variables""" + r0Lazy=r0start + r1Lazy=r1start + slippageLazy=0 + vault0Lazy=0 + vault1Lazy=0 + + """Low Impact Strategy Variables""" + r0Low=r0start + r1Low=r1start + slippageLow=0 + vault0Low=0 + vault1Low=0 + + activeFuturePositions=[[0,0] for i in range(0,conversionFrequency)] + activeFuturePositionsFut=[[0,0] for i in range(0,conversionFrequencyFut)] + for block in range(0,blocksForSim): + """ **(2*random.random()) introduces a vol of vol""" + perBlockVol=1+((1-dailyExpectedVol)/math.sqrt(numBlocksPerDay)) + if random.random()>0.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + r0Futs,r1Futs,netVault0Futs,netVault1Futs,kStartFuts,activeFuturePositions=vaultFuturesStrategy(r0Futs,r1Futs,price,netVault0Futs,netVault1Futs,block,kStartFuts,activeFuturePositionsFut,conversionFrequencyFut,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + r0Low,r1Low,vault0Low,vault1Low=vaultLowImpactReAdding(r0Low,r1Low,price,vault0Low,vault1Low,pctToReAdd,alpha) + # r0Lazy,r1Lazy=addTXFees(r0Lazy,r1Lazy,dailyFeesVsK/numBlocksPerDay) + r0Futs,r1Futs=addTXFees(r0Futs,r1Futs,dailyFeesVsK/numBlocksPerDay) + r0Low,r1Low=addTXFees(r0Low,r1Low,dailyFeesVsK/numBlocksPerDay) + + """add TxFees""" + # r0Lazy,r1Lazy=addTXFees(r0Lazy,r1Lazy,dailyFeesVsK/numBlocksPerDay) + # r0Low,r1Low=addTXFees(r0Low,r1Low,dailyFeesVsK/numBlocksPerDay) + + vaultStrategyValueFuts=(r1Futs+netVault1Futs)*price + r0Futs+netVault0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + vaultStrategyValueLow=(r1Low+vault1Low)*price + r0Low+vault0Low + + results=pd.concat([results,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "vaultLowImpact":vaultStrategyValueLow, + # "vaultLazyConvert":vaultStrategyValueLazy, + "vaultFutures":vaultStrategyValueFuts, + "AMM":(math.sqrt(r0start*r1start*price)+math.sqrt(r0start*r1start/price)*price)*(1+dailyFeesVsK/numBlocksPerDay)**numberOfSimsPerCombination, + "HODL":r0start+r1start*price},index=[0])],ignore_index=True) + + + +# a=results["vaultLazyConvert"]/results["AMM"] +b=results["vaultFutures"]/results["AMM"] +c=results["HODL"]/results["AMM"] +d=results["vaultLowImpact"]/results["AMM"] + + + +plt.figure(5) +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=c.loc[firstSet].values.tolist(),c="orange",label="HODL") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=d.loc[firstSet].values.tolist(),c="blue",label="Low Impact ReAdd") +# plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=a.loc[firstSet].values.tolist(),c="magenta",label="Periodic Conversion w/ Auction") +plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=b.loc[firstSet].values.tolist(),c="magenta",label="Conversion vs. Futures") +plt.legend(loc="upper left") +plt.title("Theoretical vs. Low Impact Re-Add") +plt.ylabel("Strategy / CFMM") +plt.xlabel("final price") + +plt.savefig(path + 'HODL.png',dpi=500) + +# print("vaultLazyConvert",a.loc[firstSet].describe()) +print("vaultFutures",b.loc[firstSet].describe()) +print("HODL",c.loc[firstSet].describe()) +print("vaultLowImpact",d.loc[firstSet].describe()) \ No newline at end of file diff --git a/simulations/Slippage.py b/simulations/Slippage.py new file mode 100644 index 0000000..9d037bc --- /dev/null +++ b/simulations/Slippage.py @@ -0,0 +1,305 @@ +""" +@author: sm-stack +""" +import pandas as pd +import math +import random +import matplotlib.pyplot as plt +import os +from dotenv import load_dotenv + +load_dotenv() + +def vaultFuturesStrategy(r0,r1,price,block,kStart,activeFuturePositions,conversionFrequency,alpha): + + r0new=math.sqrt(price*r0*r1) + r1new=r0new/price + + vault0,vault1=0,0 + """(r1-r1new)>0 means the token 1 has increased in value""" + if (r1-r1new)>0: + + vault1=vault1+(r1-r1new)*(alpha) + """repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + else: + vault0=vault0+(r0-r0new)*(alpha) + """repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + """settles (1/conversionFrequency) of the active futures contracts""" + if block % conversionFrequency==0: + for i in range(0,conversionFrequency): + valueToBeAdded=activeFuturePositions[i][0]*(price-activeFuturePositions[i][1]) + r0ValueAdd=(valueToBeAdded/2) + r1ValueAdd=(valueToBeAdded/2)/price + r0=r0+r0ValueAdd + r1=r1+r1ValueAdd + kStart=r0*r1 + if vault00 means the token 1 has increased in value""" + if (r1-r1new)>0: + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + vault1=vault1+(r1new-r1) + + else: + vault0=vault0+(r0-r0new)*(alpha) + + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + + + """performs the conversion every conversionFrequency""" + if block % conversionFrequency==0: + if vault00 means the token 1 has increased in value""" + if (r1-r1new)>0: + vault1=vault1+(r1-r1new)*(alpha) + """"repay the arbitrageur""" + r0=r0new+(r0-r0new)*(alpha) + """"rebalance the pool""" + r1=r0/price + """now r1 is the amount supposed to be in the pool GIVEN the updated r0""" + """r1new is the amount actually there, and >r1""" + """This difference goes into the vault""" + vault1=vault1+(r1new-r1) + + else: + vault0=vault0+(r0-r0new)*(alpha) + + """"repay the arbitrageur""" + r1=r1new+(r1-r1new)*(alpha) + r0=r1*price + vault0=vault0+(r0new-r0) + + + """performs the conversion every conversionFrequency""" + if True: + if vault00.5: + price=price*(perBlockVol) + else: + price = price*(1-(perBlockVol-1)) + r0Futs,r1Futs,kStartFuts,activeFuturePositions,slippageFut=vaultFuturesStrategy(r0Futs,r1Futs,price,block,kStartFuts,activeFuturePositionsFut,conversionFrequencyFut,alpha) + r0Lazy,r1Lazy,vault0Lazy,vault1Lazy,slippageLazy=vaultLazyConversionStrategy(r0Lazy,r1Lazy,price,vault0Lazy,vault1Lazy,block,conversionFrequency,alpha) + r0Low,r1Low,vault0Low,vault1Low,slippageLow=vaultLowImpactReAdding(r0Low,r1Low,price,vault0Low,vault1Low,pctToReAdd,alpha) + # r0Lazy,r1Lazy=addTXFees(r0Lazy,r1Lazy,dailyFeesVsK/numBlocksPerDay) + r0Futs,r1Futs=addTXFees(r0Futs,r1Futs,dailyFeesVsK/numBlocksPerDay) + r0Low,r1Low=addTXFees(r0Low,r1Low,dailyFeesVsK/numBlocksPerDay) + + """add TxFees""" + # r0Lazy,r1Lazy=addTXFees(r0Lazy,r1Lazy,dailyFeesVsK/numBlocksPerDay) + # r0Low,r1Low=addTXFees(r0Low,r1Low,dailyFeesVsK/numBlocksPerDay) + + vaultStrategyValueFuts=(r1Futs)*price + r0Futs + vaultStrategyValueLazy=(r1Lazy+vault1Lazy)*price + r0Lazy+vault0Lazy + vaultStrategyValueLow=(r1Low+vault1Low)*price + r0Low+vault0Low + + slippageResults=pd.concat([slippageResults,pd.DataFrame({"dailyExpectedVol":dailyExpectedVol, + "alpha":alpha, + "finalPrice": price, + "slippageLow":slippageLow, + "slippageLazy":slippageLazy, + "slippageFut":slippageFut},index=[0])],ignore_index=True) + + +b_slippage=slippageResults["slippageFut"] +c_slippage=slippageResults["slippageLazy"] +d_slippage=slippageResults["slippageLow"] + + +plt.figure(3) +# plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=c_slippage.loc[firstSet].values.tolist(),c="orange",label="Periodic Conversion w/ Auction") +plt.scatter(x=slippageResults["finalPrice"].loc[firstSet].values.tolist(),y=d_slippage.loc[firstSet].values.tolist(),c="blue",label="Low Impact ReAdd") +# plt.scatter(x=results["finalPrice"].loc[firstSet].values.tolist(),y=a.loc[firstSet].values.tolist(),c="magenta",label="Periodic Conversion w/ Auction") +plt.scatter(x=slippageResults["finalPrice"].loc[firstSet].values.tolist(),y=b_slippage.loc[firstSet].values.tolist(),c="magenta",label="Conversion vs. Futures") +plt.legend(loc="upper left") +plt.title("Slippage") +plt.ylabel("Slippage (%)") +plt.xlabel("final price") + +path = os.getenv("PATH_TO_PNGS") +plt.savefig(path + 'Slippage.png',dpi=500) \ No newline at end of file diff --git a/simulations/requirements.txt b/simulations/requirements.txt new file mode 100644 index 0000000..ac22f51 --- /dev/null +++ b/simulations/requirements.txt @@ -0,0 +1,15 @@ +contourpy==1.2.0 +cycler==0.12.1 +fonttools==4.47.0 +kiwisolver==1.4.5 +matplotlib==3.8.2 +numpy==1.26.3 +packaging==23.2 +pandas==2.1.4 +pillow==10.2.0 +pyparsing==3.1.1 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +pytz==2023.3.post1 +six==1.16.0 +tzdata==2023.4