code.vegaprotocol.io/vega@v0.79.0/core/liquidity/supplied/engine.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 supplied
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/core/types/statevar"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  	"code.vegaprotocol.io/vega/logging"
    26  )
    27  
    28  // ErrNoValidOrders informs that there weren't any valid orders to cover the liquidity obligation with.
    29  // This could happen when for a given side (buy or sell) limit orders don't supply enough liquidity and there aren't any
    30  // valid pegged orders (all the prives are invalid) to cover it with.
    31  var (
    32  	ErrNoValidOrders = errors.New("no valid orders to cover the liquidity obligation with")
    33  )
    34  
    35  // RiskModel allows calculation of min/max price range and a probability of trading.
    36  //
    37  //go:generate go run github.com/golang/mock/mockgen -destination mocks/risk_model_mock.go -package mocks code.vegaprotocol.io/vega/core/liquidity/supplied RiskModel
    38  type RiskModel interface {
    39  	ProbabilityOfTrading(currentPrice, orderPrice, minPrice, maxPrice num.Decimal, yearFraction num.Decimal, isBid, applyMinMax bool) num.Decimal
    40  	GetProjectionHorizon() num.Decimal
    41  }
    42  
    43  // PriceMonitor provides the range of valid prices, that is prices that wouldn't trade the current trading mode
    44  //
    45  //go:generate go run github.com/golang/mock/mockgen -destination mocks/price_monitor_mock.go -package mocks code.vegaprotocol.io/vega/core/liquidity/supplied PriceMonitor
    46  type PriceMonitor interface {
    47  	GetValidPriceRange() (num.WrappedDecimal, num.WrappedDecimal)
    48  }
    49  
    50  type StateVarEngine interface {
    51  	RegisterStateVariable(asset, market, name string, converter statevar.Converter, startCalculation func(string, statevar.FinaliseCalculation), trigger []statevar.EventType, result func(context.Context, statevar.StateVariableResult) error) error
    52  }
    53  
    54  // Engine provides functionality related to supplied liquidity.
    55  type Engine struct {
    56  	rm                             RiskModel
    57  	pm                             PriceMonitor
    58  	marketID                       string
    59  	horizon                        num.Decimal // projection horizon used in probability calculations
    60  	probabilityOfTradingTauScaling num.Decimal
    61  	minProbabilityOfTrading        num.Decimal
    62  	pot                            *probabilityOfTrading
    63  	potInitialised                 bool
    64  
    65  	getBestStaticPrices func() (num.Decimal, num.Decimal, error)
    66  	log                 *logging.Logger
    67  	positionFactor      num.Decimal
    68  }
    69  
    70  // NewEngine returns a reference to a new supplied liquidity calculation engine.
    71  func NewEngine(riskModel RiskModel, priceMonitor PriceMonitor, asset, marketID string, stateVarEngine StateVarEngine, log *logging.Logger, positionFactor num.Decimal) *Engine {
    72  	e := &Engine{
    73  		rm:                             riskModel,
    74  		pm:                             priceMonitor,
    75  		marketID:                       marketID,
    76  		horizon:                        riskModel.GetProjectionHorizon(),
    77  		probabilityOfTradingTauScaling: num.DecimalFromInt64(1), // this is the same as the default in the netparams
    78  		minProbabilityOfTrading:        defaultMinimumProbabilityOfTrading,
    79  		pot:                            &probabilityOfTrading{},
    80  		potInitialised:                 false,
    81  		log:                            log,
    82  		positionFactor:                 positionFactor,
    83  	}
    84  
    85  	stateVarEngine.RegisterStateVariable(asset, marketID, "probability_of_trading", probabilityOfTradingConverter{}, e.startCalcProbOfTrading, []statevar.EventType{statevar.EventTypeTimeTrigger, statevar.EventTypeAuctionEnded, statevar.EventTypeOpeningAuctionFirstUncrossingPrice}, e.updateProbabilities)
    86  	return e
    87  }
    88  
    89  func (e *Engine) UpdateMarketConfig(riskModel RiskModel, monitor PriceMonitor) {
    90  	e.rm = riskModel
    91  	e.pm = monitor
    92  	e.horizon = riskModel.GetProjectionHorizon()
    93  	e.potInitialised = false
    94  }
    95  
    96  func (e *Engine) SetGetStaticPricesFunc(f func() (num.Decimal, num.Decimal, error)) {
    97  	e.getBestStaticPrices = f
    98  }
    99  
   100  func (e *Engine) OnMinProbabilityOfTradingLPOrdersUpdate(v num.Decimal) {
   101  	e.minProbabilityOfTrading = v
   102  }
   103  
   104  func (e *Engine) OnProbabilityOfTradingTauScalingUpdate(v num.Decimal) {
   105  	e.probabilityOfTradingTauScaling = v
   106  }
   107  
   108  // CalculateLiquidityScore returns the current liquidity scores (volume-weighted probability of trading).
   109  func (e *Engine) CalculateLiquidityScore(
   110  	orders []*types.Order,
   111  	bestBid, bestAsk num.Decimal,
   112  	minLpPrice, maxLpPrice *num.Uint,
   113  ) num.Decimal {
   114  	minPMPrice, maxPMPrice := e.pm.GetValidPriceRange()
   115  
   116  	bLiq := num.DecimalZero()
   117  	sLiq := num.DecimalZero()
   118  	bSize := num.DecimalZero()
   119  	sSize := num.DecimalZero()
   120  	for _, o := range orders {
   121  		if o.Price.LT(minLpPrice) || o.Price.GT(maxLpPrice) {
   122  			continue
   123  		}
   124  		prob := num.DecimalZero()
   125  		// if order is outside of price monitoring bounds then probability is set to 0.
   126  		if o.Price.GTE(minPMPrice.Representation()) && o.Price.LTE(maxPMPrice.Representation()) {
   127  			prob = getProbabilityOfTrading(bestBid, bestAsk, minPMPrice.Original(), maxPMPrice.Original(), e.pot, o.Price.ToDecimal(), o.Side == types.SideBuy, e.minProbabilityOfTrading, OffsetOneDecimal)
   128  			if prob.LessThanOrEqual(e.minProbabilityOfTrading) {
   129  				prob = num.DecimalZero()
   130  			}
   131  		}
   132  		s := num.DecimalFromUint(num.NewUint(o.Remaining))
   133  		l := prob.Mul(s)
   134  		if o.Side == types.SideBuy {
   135  			bLiq = bLiq.Add(l)
   136  			bSize = bSize.Add(s)
   137  		}
   138  		if o.Side == types.SideSell {
   139  			sLiq = sLiq.Add(l)
   140  			sSize = sSize.Add(s)
   141  		}
   142  	}
   143  	// descale by total volume per side
   144  	if !bSize.IsZero() {
   145  		bLiq = bLiq.Div(bSize)
   146  	}
   147  	if !sSize.IsZero() {
   148  		sLiq = sLiq.Div(sSize)
   149  	}
   150  
   151  	// return the minimum of the two
   152  	if bLiq.LessThanOrEqual(sLiq) {
   153  		return bLiq
   154  	}
   155  	return sLiq
   156  }