code.vegaprotocol.io/vega@v0.79.0/core/liquidity/supplied/statevar.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  	"sort"
    21  
    22  	"code.vegaprotocol.io/vega/core/types/statevar"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  	"code.vegaprotocol.io/vega/logging"
    25  )
    26  
    27  type probabilityOfTradingConverter struct{}
    28  
    29  type probabilityOfTrading struct {
    30  	bidOffset      []uint32      // represents offsets from best bid - not sent over the wire
    31  	bidProbability []num.Decimal // probability[0] = probability of the best bid, probabtility[len-1] = probability of the worst bid
    32  	askOffset      []uint32      // represents offsets from best ask - not sent over the wire
    33  	askProbability []num.Decimal // probability[0] = probability of the best ask, probabtility[len-1] = probability of the worst ask
    34  }
    35  
    36  var (
    37  	defaultInRangeProbabilityOfTrading = num.DecimalFromFloat(0.5)
    38  	defaultMinimumProbabilityOfTrading = num.DecimalFromFloat(1e-8)
    39  	tolerance                          = num.DecimalFromFloat(1e-6)
    40  	OffsetIncrement                    = uint32(1000) // the increment of offsets in the offset slice - 1/PriceIncrementFactor as integer
    41  	OffsetIncrementAsDecimal           = num.DecimalFromInt64(int64(OffsetIncrement))
    42  	OffsetOneDecimal                   = OffsetIncrementAsDecimal.Mul(OffsetIncrementAsDecimal)             // an offset of 100%
    43  	PriceIncrementFactor               = num.DecimalOne().Div(num.DecimalFromInt64(int64(OffsetIncrement))) // we calculate the probability of trading in increments of 0.001 from of the best bid/ask
    44  	maxDistanceWhenNoConsensusPct      = num.DecimalFromFloat(20)                                           // if there's no consensus yet and the price is within 20% of the best bid/ask it gets the default probability
    45  	maxDistanceWhenNoConsensusFactor   = maxDistanceWhenNoConsensusPct.Div(num.DecimalFromFloat(100))       // if there's no consensus yet and the price is within 0.2 of the best bid/ask it gets the default probability
    46  
    47  	maxAskMultiplier = num.DecimalFromFloat(6) // arbitrarily large factor on the best ask - 600%
    48  )
    49  
    50  func (probabilityOfTradingConverter) BundleToInterface(kvb *statevar.KeyValueBundle) statevar.StateVariableResult {
    51  	return &probabilityOfTrading{
    52  		bidProbability: kvb.KVT[0].Val.(*statevar.DecimalVector).Val,
    53  		askProbability: kvb.KVT[1].Val.(*statevar.DecimalVector).Val,
    54  	}
    55  }
    56  
    57  func (probabilityOfTradingConverter) InterfaceToBundle(res statevar.StateVariableResult) *statevar.KeyValueBundle {
    58  	value := res.(*probabilityOfTrading)
    59  	return &statevar.KeyValueBundle{
    60  		KVT: []statevar.KeyValueTol{
    61  			{Key: "bidProbability", Val: &statevar.DecimalVector{Val: value.bidProbability}, Tolerance: tolerance},
    62  			{Key: "askProbability", Val: &statevar.DecimalVector{Val: value.askProbability}, Tolerance: tolerance},
    63  		},
    64  	}
    65  }
    66  
    67  func (e *Engine) IsProbabilityOfTradingInitialised() bool {
    68  	return e.potInitialised
    69  }
    70  
    71  // startCalcPriceRanges kicks off the probability of trading calculation.
    72  func (e *Engine) startCalcProbOfTrading(eventID string, endOfCalcCallback statevar.FinaliseCalculation) {
    73  	tauScaled := e.horizon.Mul(e.probabilityOfTradingTauScaling)
    74  
    75  	// get the best bid and ask
    76  	bestBid, bestAsk, err := e.getBestStaticPrices()
    77  	if err != nil {
    78  		e.log.Error("failed to get static price for probability of trading state var", logging.String("error", err.Error()))
    79  		endOfCalcCallback.CalculationFinished(eventID, nil, err)
    80  		return
    81  	}
    82  
    83  	// calculate offsets and probabilities for the range
    84  	bidProbabilities := calculateBidRange(bestBid, PriceIncrementFactor, tauScaled, e.rm.ProbabilityOfTrading)
    85  	askProbabilities := calculateAskRange(bestAsk, PriceIncrementFactor, tauScaled, e.rm.ProbabilityOfTrading)
    86  
    87  	res := &probabilityOfTrading{
    88  		bidProbability: bidProbabilities,
    89  		askProbability: askProbabilities,
    90  	}
    91  	endOfCalcCallback.CalculationFinished(eventID, res, nil)
    92  }
    93  
    94  // calculateBidRange calculates the probabilities of price between bestBid and the worst bid
    95  // in increments of incrementFactor of the best bid and records the offsets and probabilities
    96  // such that offset 0 is stored in offsets[0] which corresponds to the best bid
    97  // and similarly probabilities[0] corresponds to best bid trading probability
    98  // whereas the last entry in offset equals to the maximum distance from bid for which
    99  // probability is calculated and is greater than the default minimum acceptable probability of trading.
   100  func calculateBidRange(bestBid num.Decimal, priceIncrementFactor, tauScaled num.Decimal, probabilityFunc func(num.Decimal, num.Decimal, num.Decimal, num.Decimal, num.Decimal, bool, bool) num.Decimal) []num.Decimal {
   101  	probabilities := []num.Decimal{}
   102  
   103  	p := bestBid
   104  	increment := priceIncrementFactor.Mul(bestBid)
   105  	for {
   106  		prob := probabilityFunc(bestBid, p, num.DecimalZero(), bestBid, tauScaled, true, true)
   107  		if prob.LessThanOrEqual(defaultMinimumProbabilityOfTrading) {
   108  			break
   109  		}
   110  		probabilities = append(probabilities, prob)
   111  		p = p.Sub(increment)
   112  	}
   113  	return probabilities
   114  }
   115  
   116  // calculateAskRange calculates the probabilities of price between bestAsk and the worst ask (maxAsk)
   117  // in increments of incrementFactor of the best ask and records the offsets and probabilities
   118  // such that offset 0 is stored in offsets[0] which corresponds to the best ask
   119  // and similarly probabilities[0] corresponds to best ask trading probability
   120  // whereas the last entry in offset equals to the maximum distance from ask for which
   121  // probability is calculated, and the last entry in probabilities corresponds to probability of trading
   122  // at the price implied by this offset from best ask.
   123  func calculateAskRange(bestAsk num.Decimal, priceIncrementFactor, tauScaled num.Decimal, probabilityFunc func(num.Decimal, num.Decimal, num.Decimal, num.Decimal, num.Decimal, bool, bool) num.Decimal) []num.Decimal {
   124  	probabilities := []num.Decimal{}
   125  
   126  	maxAsk := bestAsk.Mul(maxAskMultiplier)
   127  	increment := priceIncrementFactor.Mul(bestAsk)
   128  
   129  	p := bestAsk
   130  	for {
   131  		prob := probabilityFunc(bestAsk, p, bestAsk, maxAsk, tauScaled, false, true)
   132  		if prob.LessThanOrEqual(defaultMinimumProbabilityOfTrading) {
   133  			break
   134  		}
   135  		probabilities = append(probabilities, prob)
   136  		p = p.Add(increment)
   137  	}
   138  	return probabilities
   139  }
   140  
   141  // updatePriceBounds is called back from the state variable consensus engine when consensus is reached for the down/up factors and updates the price bounds.
   142  func (e *Engine) updateProbabilities(ctx context.Context, res statevar.StateVariableResult) error {
   143  	e.pot = res.(*probabilityOfTrading)
   144  
   145  	e.pot.bidOffset = make([]uint32, 0, len(e.pot.bidProbability))
   146  	for i := range e.pot.bidProbability {
   147  		e.pot.bidOffset = append(e.pot.bidOffset, uint32(i)*OffsetIncrement)
   148  	}
   149  
   150  	e.pot.askOffset = make([]uint32, 0, len(e.pot.bidProbability))
   151  	for i := range e.pot.askProbability {
   152  		e.pot.askOffset = append(e.pot.askOffset, uint32(i)*OffsetIncrement)
   153  	}
   154  
   155  	if e.log.GetLevel() <= logging.DebugLevel {
   156  		e.log.Debug("consensus reached for probability of trading", logging.String("market", e.marketID))
   157  	}
   158  
   159  	e.potInitialised = true
   160  	return nil
   161  }
   162  
   163  // getProbabilityOfTrading returns the probability of trading for the given price
   164  // if the price is a bid order that is better than the best bid or an ask order better than the best and ask it returns <defaultInRangeProbabilityOfTrading>
   165  // if the price is a bid worse than min or an ask worse than max it returns minProbabilityOfTrading
   166  // otherwise if we've not seen a consensus value and the price is within 10% the best (by side)
   167  // it returns <defaultProbability> else if there is not yet consensus value it returns <minProbabilityOfTrading>.
   168  // If there is consensus value and the price is worse than the worse price, we extrapolate using the last 2 price points
   169  // If the price is within the range, the corresponding probability implied by the offset is returned, scaled, with lower bound of <minProbabilityOfTrading>.
   170  func getProbabilityOfTrading(bestBid, bestAsk, minPrice, maxPrice num.Decimal, pot *probabilityOfTrading, price num.Decimal, isBid bool, minProbabilityOfTrading num.Decimal, offsetOne num.Decimal) num.Decimal {
   171  	if (isBid && price.GreaterThanOrEqual(bestBid)) || (!isBid && price.LessThanOrEqual(bestAsk)) {
   172  		return defaultInRangeProbabilityOfTrading
   173  	}
   174  
   175  	if isBid {
   176  		if price.LessThan(minPrice) {
   177  			return minProbabilityOfTrading
   178  		}
   179  		return getBidProbabilityOfTrading(bestBid, pot.bidOffset, pot.bidProbability, price, minProbabilityOfTrading, offsetOne)
   180  	}
   181  	if price.GreaterThan(maxPrice) {
   182  		return minProbabilityOfTrading
   183  	}
   184  	return getAskProbabilityOfTrading(bestAsk, pot.askOffset, pot.askProbability, price, minProbabilityOfTrading, offsetOne)
   185  }
   186  
   187  func getAskProbabilityOfTrading(bestAsk num.Decimal, offsets []uint32, probabilities []num.Decimal, price, minProbabilityOfTrading num.Decimal, offsetOne num.Decimal) num.Decimal {
   188  	// no consensus yet
   189  	if len(offsets) == 0 {
   190  		maxDistance := maxDistanceWhenNoConsensusFactor.Mul(bestAsk)
   191  		if bestAsk.Sub(price).Abs().LessThanOrEqual(maxDistance) {
   192  			return defaultInRangeProbabilityOfTrading
   193  		}
   194  		return minProbabilityOfTrading
   195  	}
   196  	offset := uint32(offsetOne.Mul(price.Sub(bestAsk).Div(bestAsk)).Floor().IntPart())
   197  
   198  	// if outside the range - extrapolate
   199  	if offset > offsets[len(offsets)-1] {
   200  		return lexterp(offsets, probabilities, offset, minProbabilityOfTrading)
   201  	}
   202  
   203  	idx := sort.Search(len(offsets), func(i int) bool {
   204  		return offset < offsets[i]
   205  	})
   206  	// linear interpolation
   207  	interpolatedProbability := linterp(offsets, probabilities, offset, idx, minProbabilityOfTrading)
   208  	return interpolatedProbability
   209  }
   210  
   211  func getBidProbabilityOfTrading(bestBid num.Decimal, offsets []uint32, probabilities []num.Decimal, price, minProbabilityOfTrading num.Decimal, offsetOne num.Decimal) num.Decimal {
   212  	// no consensus yet
   213  	if len(offsets) == 0 {
   214  		maxDistance := maxDistanceWhenNoConsensusFactor.Mul(bestBid)
   215  		if bestBid.Sub(price).Abs().LessThanOrEqual(maxDistance) {
   216  			return defaultInRangeProbabilityOfTrading
   217  		}
   218  		return minProbabilityOfTrading
   219  	}
   220  
   221  	offset := uint32(offsetOne.Mul(bestBid.Sub(price).Div(bestBid)).Floor().IntPart())
   222  
   223  	// if outside the range - extrapolate
   224  	if offset > offsets[len(offsets)-1] {
   225  		return lexterp(offsets, probabilities, offset, minProbabilityOfTrading)
   226  	}
   227  
   228  	idx := sort.Search(len(offsets), func(i int) bool {
   229  		return offset < offsets[i]
   230  	})
   231  	// linear interpolation
   232  	interpolatedProbability := linterp(offsets, probabilities, offset, idx, minProbabilityOfTrading)
   233  	return interpolatedProbability
   234  }
   235  
   236  func lexterp(offsets []uint32, probabilities []num.Decimal, offset uint32, minProbabilityOfTrading num.Decimal) num.Decimal {
   237  	if len(offsets) == 1 {
   238  		return minProbabilityOfTrading
   239  	}
   240  	last := offsets[len(offsets)-1]
   241  	last2 := offsets[len(offsets)-2]
   242  	probLast := probabilities[len(offsets)-1]
   243  	probLast2 := probabilities[len(offsets)-2]
   244  	slopeNum := num.DecimalFromInt64(int64(offset - last2))
   245  	slopeDenom := num.DecimalFromInt64(int64(last - last2))
   246  	slope := slopeNum.Div(slopeDenom)
   247  	prob := probLast2.Add(probLast.Sub(probLast2).Mul(slope))
   248  	scaled := rescaleProbability(prob)
   249  	prob = num.MinD(num.DecimalFromInt64(1), num.MaxD(minProbabilityOfTrading, scaled))
   250  	return prob
   251  }
   252  
   253  func linterp(offsets []uint32, probabilities []num.Decimal, priceOffset uint32, i int, minProbabilityOfTrading num.Decimal) num.Decimal {
   254  	if i >= len(probabilities) {
   255  		return num.MaxD(minProbabilityOfTrading, rescaleProbability(probabilities[len(probabilities)-1]))
   256  	}
   257  	prev := offsets[i-1]
   258  	size := offsets[i] - (prev)
   259  	ratio := num.DecimalFromInt64(int64(priceOffset - prev)).Div(num.DecimalFromInt64(int64(size)))
   260  	cRatio := num.DecimalFromInt64(1).Sub(ratio)
   261  	prob := ratio.Mul(probabilities[i]).Add(cRatio.Mul(probabilities[i-1]))
   262  	scaled := rescaleProbability(prob)
   263  	capped := num.MaxD(minProbabilityOfTrading, scaled)
   264  	return capped
   265  }
   266  
   267  // rescaleProbability rescales probability so that it's at most the value returned between bid and ask.
   268  func rescaleProbability(prob num.Decimal) num.Decimal {
   269  	return prob.Mul(defaultInRangeProbabilityOfTrading)
   270  }