code.vegaprotocol.io/vega@v0.79.0/core/execution/amm/estimator.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package amm
    17  
    18  import (
    19  	"code.vegaprotocol.io/vega/libs/num"
    20  )
    21  
    22  type EstimatedBounds struct {
    23  	PositionSizeAtLower     num.Decimal
    24  	LossOnCommitmentAtLower num.Decimal
    25  	LiquidationPriceAtLower num.Decimal
    26  	TooWideLower            bool
    27  
    28  	PositionSizeAtUpper     num.Decimal
    29  	LossOnCommitmentAtUpper num.Decimal
    30  	LiquidationPriceAtUpper num.Decimal
    31  	TooWideUpper            bool
    32  }
    33  
    34  func EstimateBounds(
    35  	sqrter *Sqrter,
    36  	lowerPrice, basePrice, upperPrice *num.Uint,
    37  	leverageLower, leverageUpper num.Decimal,
    38  	balance *num.Uint,
    39  	linearSlippageFactor, initialMargin,
    40  	riskFactorShort, riskFactorLong,
    41  	priceFactor, positionFactor num.Decimal,
    42  	allowedMaxEmptyLevels uint64,
    43  ) EstimatedBounds {
    44  	r := EstimatedBounds{}
    45  
    46  	balanceD := balance.ToDecimal()
    47  
    48  	oneTick, _ := num.UintFromDecimal(priceFactor)
    49  	oneTick = num.Max(num.UintOne(), oneTick)
    50  
    51  	if lowerPrice != nil {
    52  		unitLower := LiquidityUnit(sqrter, basePrice, lowerPrice)
    53  
    54  		avgEntryLower := AverageEntryPrice(sqrter, unitLower, basePrice)
    55  		riskFactorLower := RiskFactor(leverageLower, riskFactorLong, linearSlippageFactor, initialMargin)
    56  		lowerPriceD := lowerPrice.ToDecimal()
    57  		boundPosLower := PositionAtLowerBound(riskFactorLower, balanceD, lowerPriceD, avgEntryLower, positionFactor)
    58  
    59  		// if the commitment is *so low* that the position at the bound is 0 then we will panic trying to calculate the rest
    60  		// and the "too wide" check below will flag it up as an invalid AMM defn
    61  		if !boundPosLower.IsZero() {
    62  			lossLower := LossOnCommitment(avgEntryLower, lowerPriceD, boundPosLower)
    63  
    64  			liquidationPriceAtLower := LiquidationPrice(balanceD, lossLower, boundPosLower, lowerPriceD, linearSlippageFactor, riskFactorLong)
    65  
    66  			r.PositionSizeAtLower = boundPosLower.Mul(positionFactor)
    67  			r.LiquidationPriceAtLower = liquidationPriceAtLower
    68  			r.LossOnCommitmentAtLower = lossLower
    69  		}
    70  
    71  		// now lets check that the lower bound is not too wide that the volume is spread too thin
    72  		l := unitLower.Mul(boundPosLower).Abs()
    73  
    74  		cu := &curve{
    75  			l:        l,
    76  			high:     basePrice,
    77  			low:      lowerPrice,
    78  			sqrtHigh: sqrter.sqrt(basePrice),
    79  			isLower:  true,
    80  			pv:       r.PositionSizeAtLower,
    81  		}
    82  
    83  		if err := cu.check(sqrter.sqrt, oneTick, allowedMaxEmptyLevels); err != nil {
    84  			r.TooWideLower = true
    85  		}
    86  	}
    87  
    88  	if upperPrice != nil {
    89  		unitUpper := LiquidityUnit(sqrter, upperPrice, basePrice)
    90  
    91  		avgEntryUpper := AverageEntryPrice(sqrter, unitUpper, upperPrice)
    92  		riskFactorUpper := RiskFactor(leverageUpper, riskFactorShort, linearSlippageFactor, initialMargin)
    93  		upperPriceD := upperPrice.ToDecimal()
    94  
    95  		boundPosUpper := PositionAtUpperBound(riskFactorUpper, balanceD, upperPriceD, avgEntryUpper, positionFactor)
    96  
    97  		// if the commitment is *so low* that the position at the bound is 0 then we will panic trying to calculate the rest
    98  		// and the "too wide" check below will flag it up as an invalid AMM defn
    99  		if !boundPosUpper.IsZero() {
   100  			lossUpper := LossOnCommitment(avgEntryUpper, upperPriceD, boundPosUpper)
   101  
   102  			liquidationPriceAtUpper := LiquidationPrice(balanceD, lossUpper, boundPosUpper, upperPriceD, linearSlippageFactor, riskFactorShort)
   103  			r.PositionSizeAtUpper = boundPosUpper.Mul(positionFactor)
   104  			r.LiquidationPriceAtUpper = liquidationPriceAtUpper
   105  			r.LossOnCommitmentAtUpper = lossUpper
   106  		}
   107  
   108  		// now lets check that the lower bound is not too wide that the volume is spread too thin
   109  		l := unitUpper.Mul(boundPosUpper).Abs()
   110  
   111  		cu := &curve{
   112  			l:        l,
   113  			high:     upperPrice,
   114  			low:      basePrice,
   115  			sqrtHigh: sqrter.sqrt(upperPrice),
   116  			pv:       r.PositionSizeAtUpper.Neg(),
   117  		}
   118  		if err := cu.check(sqrter.sqrt, oneTick, allowedMaxEmptyLevels); err != nil {
   119  			r.TooWideUpper = true
   120  		}
   121  	}
   122  
   123  	return r
   124  }
   125  
   126  // Lu = (sqrt(pu) * sqrt(pl)) / (sqrt(pu) - sqrt(pl)).
   127  func LiquidityUnit(sqrter *Sqrter, pu, pl *num.Uint) num.Decimal {
   128  	sqrtPu := sqrter.sqrt(pu)
   129  	sqrtPl := sqrter.sqrt(pl)
   130  
   131  	return sqrtPu.Mul(sqrtPl).Div(sqrtPu.Sub(sqrtPl))
   132  }
   133  
   134  // Rf = min(Lb, 1 / (Fs + Fl) * Fi).
   135  func RiskFactor(lb, fs, fl, fi num.Decimal) num.Decimal {
   136  	b := num.DecimalOne().Div(fs.Add(fl).Mul(fi))
   137  	return num.MinD(lb, b)
   138  }
   139  
   140  // Pa = Lu * sqrt(pu) * (1 - (Lu / (Lu + sqrt(pu)))).
   141  func AverageEntryPrice(sqrter *Sqrter, lu num.Decimal, pu *num.Uint) num.Decimal {
   142  	sqrtPu := sqrter.sqrt(pu)
   143  	// (1 - Lu / (Lu + sqrt(pu)))
   144  	oneSubLuDivLuWithUpSquared := num.DecimalOne().Sub(lu.Div(lu.Add(sqrtPu)))
   145  	return lu.Mul(sqrtPu).Mul(oneSubLuDivLuWithUpSquared)
   146  }
   147  
   148  // Pvl = rf * b / (pl * (1 - rf) + rf * pa).
   149  func PositionAtLowerBound(rf, b, pl, pa, positionFactor num.Decimal) num.Decimal {
   150  	oneSubRf := num.DecimalOne().Sub(rf)
   151  	rfMulPa := rf.Mul(pa)
   152  
   153  	pv := rf.Mul(b).Div(
   154  		pl.Mul(oneSubRf).Add(rfMulPa),
   155  	)
   156  	return pv
   157  }
   158  
   159  // Pvl = -rf * b / (pl * (1 + rf) - rf * pa).
   160  func PositionAtUpperBound(rf, b, pl, pa, positionFactor num.Decimal) num.Decimal {
   161  	onePlusRf := num.DecimalOne().Add(rf)
   162  	rfMulPa := rf.Mul(pa)
   163  
   164  	pv := rf.Neg().Mul(b).Div(
   165  		pl.Mul(onePlusRf).Sub(rfMulPa),
   166  	)
   167  	return pv
   168  }
   169  
   170  // lc = |(pa - pb) * pB|.
   171  func LossOnCommitment(pa, pb, pB num.Decimal) num.Decimal {
   172  	res := pa.Sub(pb).Mul(pB).Abs()
   173  	return res
   174  }
   175  
   176  // Pliq = (b - lc - Pb * pb) / (|Pb| * (fl + mr) - Pb).
   177  func LiquidationPrice(b, lc, pB, pb, fl, mr num.Decimal) num.Decimal {
   178  	// (b - lc - Pb * pb)
   179  	numer := b.Sub(lc).Sub(pB.Mul(pb))
   180  
   181  	// (|Pb| * (fl + mr) - Pb)
   182  	denom := pB.Abs().Mul(fl.Add(mr)).Sub(pB)
   183  
   184  	return num.MaxD(num.DecimalZero(), numer.Div(denom))
   185  }