code.vegaprotocol.io/vega@v0.79.0/core/risk/liquidation_calculation.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 risk 17 18 import ( 19 "fmt" 20 "sort" 21 22 "code.vegaprotocol.io/vega/libs/num" 23 ) 24 25 type OrderInfo struct { 26 TrueRemaining uint64 27 Price num.Decimal 28 IsMarketOrder bool 29 } 30 31 // Clone - Not really necessary, just added to avoid future pointers 32 // copy if some were added. 33 func (o *OrderInfo) Clone() *OrderInfo { 34 return &OrderInfo{ 35 TrueRemaining: o.TrueRemaining, 36 Price: o.Price.Copy(), 37 IsMarketOrder: o.IsMarketOrder, 38 } 39 } 40 41 func CalculateLiquidationPriceWithSlippageFactors(sizePosition int64, buyOrders, sellOrders []*OrderInfo, currentPrice, collateralAvailable num.Decimal, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, fundingPaymentPerUnitPosition num.Decimal, isolatedMarginMode bool, marginFactor num.Decimal) (liquidationPriceForOpenVolume, liquidationPriceWithBuyOrders, liquidationPriceWithSellOrders num.Decimal, err error) { 42 openVolume := num.DecimalFromInt64(sizePosition).Div(positionFactor) 43 44 if sizePosition != 0 { 45 liquidationPriceForOpenVolume, err = calculateLiquidationPrice(openVolume, currentPrice, collateralAvailable, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, fundingPaymentPerUnitPosition) 46 } 47 48 liquidationPriceWithSellOrders, liquidationPriceWithBuyOrders = liquidationPriceForOpenVolume, liquidationPriceForOpenVolume 49 if err != nil || len(buyOrders)+len(sellOrders) == 0 { 50 return 51 } 52 53 // assume market orders will trade immediately 54 for _, o := range buyOrders { 55 if o.IsMarketOrder { 56 o.Price = currentPrice 57 } 58 } 59 60 for _, o := range sellOrders { 61 if o.IsMarketOrder { 62 o.Price = currentPrice 63 } 64 } 65 66 sort.Slice(buyOrders, func(i, j int) bool { 67 return buyOrders[i].Price.GreaterThan(buyOrders[j].Price) 68 }) 69 sort.Slice(sellOrders, func(i, j int) bool { 70 return sellOrders[i].Price.LessThan(sellOrders[j].Price) 71 }) 72 73 liquidationPriceWithBuyOrders, err = calculateLiquidationPriceWithOrders(liquidationPriceForOpenVolume, buyOrders, true, openVolume, currentPrice, collateralAvailable, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, fundingPaymentPerUnitPosition, isolatedMarginMode, marginFactor) 74 if err != nil { 75 liquidationPriceWithBuyOrders = num.DecimalZero() 76 return 77 } 78 liquidationPriceWithSellOrders, err = calculateLiquidationPriceWithOrders(liquidationPriceForOpenVolume, sellOrders, false, openVolume, currentPrice, collateralAvailable, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, fundingPaymentPerUnitPosition, isolatedMarginMode, marginFactor) 79 if err != nil { 80 liquidationPriceWithSellOrders = num.DecimalZero() 81 return 82 } 83 84 return liquidationPriceForOpenVolume, liquidationPriceWithBuyOrders, liquidationPriceWithSellOrders, nil 85 } 86 87 func calculateLiquidationPrice(openVolume num.Decimal, currentPrice, collateralAvailable num.Decimal, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, fundingPaymentPerUnitPosition num.Decimal) (num.Decimal, error) { 88 if openVolume.IsZero() { 89 return num.DecimalZero(), nil 90 } 91 92 rf := riskFactorLong 93 if openVolume.IsNegative() { 94 rf = riskFactorShort 95 } 96 97 denominator := calculateSlippageFactor(openVolume, linearSlippageFactor, quadraticSlippageFactor).Add(openVolume.Abs().Mul(rf)).Sub(openVolume) 98 if denominator.IsZero() { 99 return num.DecimalZero(), fmt.Errorf("liquidation price not defined") 100 } 101 102 numerator := collateralAvailable.Sub(openVolume.Mul(currentPrice)) 103 if !fundingPaymentPerUnitPosition.IsZero() { 104 numerator = numerator.Sub(num.MaxD(num.DecimalZero(), openVolume.Mul(fundingPaymentPerUnitPosition))) 105 } 106 107 ret := numerator.Div(denominator) 108 if ret.IsNegative() { 109 return num.DecimalZero(), nil 110 } 111 return ret, nil 112 } 113 114 func calculateLiquidationPriceWithOrders(liquidationPriceOpenVolumeOnly num.Decimal, orders []*OrderInfo, buySide bool, openVolume num.Decimal, currentPrice, collateralAvailable num.Decimal, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constant num.Decimal, isolatedMarginMode bool, marginFactor num.Decimal) (num.Decimal, error) { 115 var err error 116 liquidationPrice := liquidationPriceOpenVolumeOnly 117 exposureWithOrders := openVolume 118 collateralWithOrders := collateralAvailable 119 zero := num.DecimalZero() 120 for _, o := range orders { 121 if !exposureWithOrders.IsZero() && ((buySide && exposureWithOrders.IsPositive() && o.Price.LessThan(liquidationPrice)) || (!buySide && exposureWithOrders.IsNegative() && o.Price.GreaterThan(liquidationPrice))) { 122 // party gets marked for closeout before this order gets a chance to fill 123 break 124 } 125 mtm := exposureWithOrders.Mul(o.Price.Sub(currentPrice)) 126 currentPrice = o.Price 127 128 collateralWithOrders = collateralWithOrders.Add(mtm) 129 adjTrueRemaining := num.DecimalFromInt64(int64(o.TrueRemaining)).Div(positionFactor) 130 131 // account for amount that would be moved from order margin account to magin account when exposure-increasing limit order fills 132 if isolatedMarginMode && !o.IsMarketOrder { 133 collateralIncreaseVolume := adjTrueRemaining 134 if buySide { 135 if isolatedMarginMode && exposureWithOrders.LessThan(zero) { 136 newExposure := exposureWithOrders.Add(adjTrueRemaining) 137 if newExposure.GreaterThan(zero) { 138 collateralIncreaseVolume = newExposure 139 } else { 140 collateralIncreaseVolume = zero 141 } 142 } 143 } else { 144 if isolatedMarginMode && exposureWithOrders.GreaterThan(zero) { 145 newExposure := exposureWithOrders.Sub(adjTrueRemaining) 146 if newExposure.LessThan(zero) { 147 collateralIncreaseVolume = newExposure 148 } else { 149 collateralIncreaseVolume = zero 150 } 151 } 152 } 153 collateralWithOrders = collateralIncreaseVolume.Mul(o.Price).Mul(marginFactor) 154 } 155 if buySide { 156 exposureWithOrders = exposureWithOrders.Add(adjTrueRemaining) 157 } else { 158 exposureWithOrders = exposureWithOrders.Sub(adjTrueRemaining) 159 } 160 161 liquidationPrice, err = calculateLiquidationPrice(exposureWithOrders, o.Price, collateralWithOrders, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constant) 162 if err != nil { 163 return num.DecimalZero(), err 164 } 165 } 166 return liquidationPrice, nil 167 }