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  }