code.vegaprotocol.io/vega@v0.79.0/core/risk/models/lognormal.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 models
    17  
    18  import (
    19  	"errors"
    20  	"math"
    21  
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  
    25  	"code.vegaprotocol.io/quant/interfaces"
    26  	pd "code.vegaprotocol.io/quant/pricedistribution"
    27  	"code.vegaprotocol.io/quant/riskmodelbs"
    28  )
    29  
    30  var ErrMissingLogNormalParameter = errors.New("missing log normal parameters")
    31  
    32  // LogNormal represent a future risk model.
    33  type LogNormal struct {
    34  	riskAversionParameter, tau num.Decimal
    35  	params                     riskmodelbs.ModelParamsBS
    36  	asset                      string
    37  	RiskFactorOverride         *types.RiskFactorOverride
    38  
    39  	distCache    interfaces.AnalyticalDistribution
    40  	cachePrice   num.Decimal
    41  	cacheHorizon num.Decimal
    42  }
    43  
    44  // NewBuiltinFutures instantiate a new builtin future.
    45  func NewBuiltinFutures(pf *types.LogNormalRiskModel, asset string) (*LogNormal, error) {
    46  	if pf.Params == nil {
    47  		return nil, ErrMissingLogNormalParameter
    48  	}
    49  	// the quant stuff really needs to be updated to use the same num types...
    50  	mu, _ := pf.Params.Mu.Float64()
    51  	r, _ := pf.Params.R.Float64()
    52  	sigma, _ := pf.Params.Sigma.Float64()
    53  
    54  	var override *types.RiskFactorOverride
    55  	if pf.RiskFactorOverride != nil {
    56  		override = &types.RiskFactorOverride{
    57  			Short: pf.RiskFactorOverride.Short,
    58  			Long:  pf.RiskFactorOverride.Long,
    59  		}
    60  	}
    61  
    62  	return &LogNormal{
    63  		riskAversionParameter: pf.RiskAversionParameter,
    64  		tau:                   pf.Tau,
    65  		cachePrice:            num.DecimalZero(),
    66  		params: riskmodelbs.ModelParamsBS{
    67  			Mu:    mu,
    68  			R:     r,
    69  			Sigma: sigma,
    70  		},
    71  		RiskFactorOverride: override,
    72  		asset:              asset,
    73  	}, nil
    74  }
    75  
    76  // CalculateRiskFactors calls the risk model in order to get
    77  // the new risk models.
    78  func (f *LogNormal) CalculateRiskFactors() *types.RiskFactor {
    79  	if f.RiskFactorOverride != nil {
    80  		return &types.RiskFactor{
    81  			Long:  f.RiskFactorOverride.Long,
    82  			Short: f.RiskFactorOverride.Short,
    83  		}
    84  	}
    85  
    86  	rav, _ := f.riskAversionParameter.Float64()
    87  	tau, _ := f.tau.Float64()
    88  	rawrf := riskmodelbs.RiskFactorsForward(rav, tau, f.params)
    89  	return &types.RiskFactor{
    90  		Long:  num.DecimalFromFloat(rawrf.Long),
    91  		Short: num.DecimalFromFloat(rawrf.Short),
    92  	}
    93  }
    94  
    95  // PriceRange returns the minimum and maximum price as implied by the model's probability distribution with horizon given by yearFraction (e.g. 0.5 for half a year) and probability level (e.g. 0.95 for 95%).
    96  func (f *LogNormal) PriceRange(currentP, yFrac, probabilityLevel num.Decimal) (num.Decimal, num.Decimal) {
    97  	dist := f.getDistribution(currentP, yFrac)
    98  	// damn you quant!
    99  	pl, _ := probabilityLevel.Float64()
   100  	min, max := pd.PriceRange(dist, pl)
   101  	return num.DecimalFromFloat(min), num.DecimalFromFloat(max)
   102  }
   103  
   104  // ProbabilityOfTrading of trading returns the probability of trading given current mark price, projection horizon expressed as year fraction, order price and side (isBid).
   105  // Additional arguments control optional truncation of probability density outside the [minPrice,maxPrice] range.
   106  func (f *LogNormal) ProbabilityOfTrading(currentP, orderP num.Decimal, minP, maxP num.Decimal, yFrac num.Decimal, isBid, applyMinMax bool) num.Decimal {
   107  	dist := f.getDistribution(currentP, yFrac)
   108  	min := math.Max(minP.InexactFloat64(), 0)
   109  	// still, quant uses floats
   110  	prob := pd.ProbabilityOfTrading(dist, orderP.InexactFloat64(), isBid, applyMinMax, min, maxP.InexactFloat64())
   111  	if math.IsNaN(prob) {
   112  		return num.DecimalZero()
   113  	}
   114  
   115  	return num.DecimalFromFloat(prob)
   116  }
   117  
   118  func (f *LogNormal) getDistribution(currentP num.Decimal, yFrac num.Decimal) interfaces.AnalyticalDistribution {
   119  	if f.distCache == nil || !f.cachePrice.Equal(currentP) || !f.cacheHorizon.Equal(yFrac) {
   120  		// quant still uses floats... sad
   121  		yf, _ := yFrac.Float64()
   122  		f.distCache = f.params.GetProbabilityDistribution(currentP.InexactFloat64(), yf)
   123  	}
   124  	return f.distCache
   125  }
   126  
   127  // GetProjectionHorizon returns the projection horizon used by the model for margin calculation pruposes.
   128  func (f *LogNormal) GetProjectionHorizon() num.Decimal {
   129  	return f.tau
   130  }
   131  
   132  func (f *LogNormal) DefaultRiskFactors() *types.RiskFactor {
   133  	if f.RiskFactorOverride != nil {
   134  		return &types.RiskFactor{
   135  			Long:  f.RiskFactorOverride.Long,
   136  			Short: f.RiskFactorOverride.Short,
   137  		}
   138  	}
   139  
   140  	return &types.RiskFactor{
   141  		Short: num.DecimalFromFloat(1),
   142  		Long:  num.DecimalFromFloat(1),
   143  	}
   144  }