'Calculate best compounding period (cryptocurrency)

I have an interesting problem, I am not sure if the best place to put this in here or in stack mathematics but since I tried to solve it programmatically (in R, but any programming language would work) maybe StackOverflow is the best place. The problem involves compounding cryptocurrencies. The problem is the following: You are staking a "K" amount of token in a liquidity pool to get interests. The liquidity pool gives an "Annual Percentage Rate" (APR) to your token, i.e. an annual interest, the APR is not compounded. You can compound your interests any time you want but, every time you do it you have to pay a small fee.

I initially tried to solve it with some for loops, estimating the final return if a hypothetical user was compounding every "D" days. The current example uses CAKE and BNB tokens. A few assumptions to simplify the solution. Imagine that the APR, the bnb_value, and the cake_value are FIXED values (they are not in reality).

APR=1.349  ## the interest value showed in the pool, divided by 100
APR_day=APR/365.0 ## the daily APR
bnb_value=210 ## current value of the BNB token, in euro or dollar or any FIAT
fee_bnb=0.002*bnb_value ## current fee in euro or dollar or any FIAT
initial_cakes=10
cake_value=10

adf=data.frame(col1=NULL, col2=NULL, col3=NULL, col4=NULL)
## we generate a sequence containing fractional days up to the third day
## and then full days starting from the fourth day
comp_intervals=c(seq(0.1, 3, 0.1), 4:30)
for(j in comp_intervals){
  acquired_int=0  ## acquired interest in "j" days
  current_val=initial_cakes  ## current value of the capital
  all_fees=0  ## fees paid for the transactions
  aseq=seq(j, 365, j)  ## list of the compounding events in days
  
  # apr_seq is APR for the "j" period. If "j" is 1 it's the daily APR,
  # but for longer compounding intervals the APR for the "j" period
  # become larger
  apr_seq=APR_day*j
  for(i in aseq){
    acquired_int=current_val*apr_seq
    current_val=current_val+acquired_int
    all_fees=all_fees+fee_bnb
    acquired_int=0
  }
  
  ## we add the interest for the remaining days of the year, if any, to current_val
  acquired_int=(365-max(aseq))*APR_day*current_val
  current_val=current_val+acquired_int
  
  final_gain=round(current_val*cake_value - all_fees, 2)
  # msg=paste0("Final gain in Euro minus fees: ", final_gain)
  # print(msg)
  apy_i_day= round(final_gain/(initial_cakes*cake_value), 5)
  # msg=paste0("apy compounding every ", j, " days is: ", apy_i_day)
  # print(msg)
  # cat("\n")
  adf=rbind(adf, data.frame(fiat_value=final_gain,
      APY_val=100*apy_i_day, compounding_days=j, cakes=current_val))
  
}
# finally we show, who, among the various compounding, had the hiest yield
adf[adf$APY_val==max(adf$APY_val), ]

The problem is that the code you just saw doesn't really tell you what is the best period to compound your interests. It tells more what are the yields if a user compound every "D" days. It's close to the real solution, but it's not it! You can intuitively understand that it's wrong extending the time. You start with a small capital, so to get a good return you compound "rarely" because of the fees, but more time passes and more your capital grows. The more your capital grows more often you should compound.

I tried a different approach then.

The general formula that given the "Annual percentage yield" is the following:

APY = (1 + APR/N)^N -1

if you consider also the fees and the initial capital you have:

Final_capital=Initial_capital*APY - single_fee*N

Where APR is the Percentage rate, N is the number of compounding events (in this formula they are temporally equally distributed).

By differentiating Final_capital by "dN" and finding the zeros of the equation you get the best number of compounding events. If you divide 365 by the best number of compounding events you should get after how many days you should compound your tokens. The results I obtain from the differential formula are different from the first solution, I am not sure why. I also think, but I am not sure, that this latter solution has the same limitation of the previous one.

library(utils)
### interest APY formula minus fees
final_v=function(x, APR, fee_bnb, initial_value){
  return(  initial_value*( (1+APR/x)^x - 1 ) -fee_bnb*x   ) 
}

## differential respect to X of the interest APY formula minus the fees
a_diff_comp=function(x, APR, fee_bnb, initial_value){
  return( (initial_value*( (APR/x + 1)^x )*(  log(APR/x + 1) - APR/(  ((1/x)+1)*x  ) ) ) - fee_bnb )
}

x=3:40
y=sapply(x, a_diff_comp, APR=APR, fee_bnb=fee_bnb, initial_value=(initial_cakes*cake_value))
plot(x,y)

y2=sapply(x, final_v, APR=APR, fee_bnb=fee_bnb, initial_value=(initial_cakes*cake_value))
plot(x,y2)

xmin <- uniroot(a_diff_comp, c(1, 100), tol = 0.000001, APR=APR, fee_bnb=fee_bnb, initial_value=(initial_cakes*cake_value))
xmin$root

So, how to calculate properly the best compounding interval?



Solution 1:[1]

I put a comment on the GitHub page https://github.com/amendez/cakecalc/issues/2#issuecomment-806364370.

But I think I got your point now. I'm agree with you, even my personal experiences prove your point. The way cakeCalc gives you best compounding ratio has some problems. When calculating the final result, it considers your balance is fixed after each time compounding, which seems rather problematic. As a result, every time when you compound your cakes, cakeCalc gives you a different result even if you exactly follow the instruction suggested by cakeCalc, since your balance is now updated

I never tried to write down the equation to derive the best compounding interval, but one thing that I'm sure is that the best compounding interval would not be fixed. For example, you might expect to find the best result something like the following answer:

Compound at day 20, compound at day 38, compound at day 53, ...

So finding the number N as the compounding number wouldn't gives you the best answer. One way to solve the problem could be: Consider you are finding the best compounding rates during one year period. And assume you want to compound "m" times during this period. So now, you have "m" variables and an expression you wish to maximize. I'm not sure right now, but maybe you can solve it using a simple convex optimization approach (Only you need to check the convexity of the equation before, since the constraints between variables are all affine). Then try the same for different number of "m" and find the maximum number among them. This solution is practical, since the number "m" is bounded (and obviously it is an integer!), and you may expect to find "m" so close to the findings of cakeCalc approach. The answers wouldn't be that much different I guess. So you can change "m" for example less than 10 times and find the best solution.

Solution 2:[2]

Let's take seconds as our smallest unit of time.

Suppose APR is our APR and our initial investment is I $, and our compound interval is s seconds.

If we stake our I and harvest after s seconds, then our original investment plus profit minus fee F is

I_2 = I + I * APR / 3155695200 * s - F

Reinvesting,

I_3 = I_2 + I_2 * APR / 3155695200 * s - F

and so on...

You can easily write a program to run this for various s values to determine what the optimal compound interval is, e.g. using a divide and conquer approach.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 RadNi
Solution 2