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 }