code.vegaprotocol.io/vega@v0.79.0/core/liquidity/target/spot/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 spot
    17  
    18  import (
    19  	"errors"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  )
    25  
    26  var (
    27  	// ErrTimeSequence signals that time sequence is not in a non-decreasing order.
    28  	ErrTimeSequence = errors.New("received a time that's before the last received time")
    29  	// ErrNegativeScalingFactor indicates that a negative scaling factor was supplied to the engine.
    30  	ErrNegativeScalingFactor = errors.New("scaling factor can't be negative")
    31  )
    32  
    33  // Engine allows tracking price changes and verifying them against the theoretical levels implied by the RangeProvider (risk model).
    34  type Engine struct {
    35  	marketID string
    36  
    37  	tWindow time.Duration
    38  	sFactor num.Decimal
    39  
    40  	now               time.Time
    41  	scheduledTruncate time.Time
    42  	current           []uint64
    43  	previous          []timestampedTotalStake
    44  	max               timestampedTotalStake
    45  	positionFactor    num.Decimal
    46  }
    47  
    48  type timestampedTotalStake struct {
    49  	Time       time.Time
    50  	TotalStake uint64
    51  }
    52  
    53  // NewEngine returns a new instance of target stake calculation Engine.
    54  func NewEngine(parameters types.TargetStakeParameters, marketID string, positionFactor num.Decimal) *Engine {
    55  	return &Engine{
    56  		marketID:       marketID,
    57  		tWindow:        time.Duration(parameters.TimeWindow) * time.Second,
    58  		sFactor:        parameters.ScalingFactor,
    59  		positionFactor: positionFactor,
    60  	}
    61  }
    62  
    63  // UpdateTimeWindow updates the time windows used in target stake calculation.
    64  func (e *Engine) UpdateTimeWindow(tWindow time.Duration) {
    65  	e.tWindow = tWindow
    66  }
    67  
    68  // UpdateScalingFactor updates the scaling factor used in target stake calculation
    69  // if it's non-negative and returns an error otherwise.
    70  func (e *Engine) UpdateScalingFactor(sFactor num.Decimal) error {
    71  	if sFactor.IsNegative() {
    72  		return ErrNegativeScalingFactor
    73  	}
    74  	e.sFactor = sFactor
    75  	return nil
    76  }
    77  
    78  // RecordTotalStake records open interest history so that target stake can be calculated.
    79  func (e *Engine) RecordTotalStake(ts uint64, now time.Time) error {
    80  	if now.Before(e.now) {
    81  		return ErrTimeSequence
    82  	}
    83  
    84  	if ts >= e.max.TotalStake {
    85  		e.max = timestampedTotalStake{Time: now, TotalStake: ts}
    86  	}
    87  
    88  	if now.After(e.now) {
    89  		// get max from current before updating timestamp
    90  		e.previous = append(e.previous, e.getMaxFromCurrent())
    91  		e.current = make([]uint64, 0, len(e.current))
    92  		e.now = now
    93  	}
    94  	e.current = append(e.current, ts)
    95  	if e.now.After(e.scheduledTruncate) {
    96  		e.truncateHistory(e.minTime(now))
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // GetTargetStake returns target stake based current time.
   103  func (e *Engine) GetTargetStake(now time.Time) *num.Uint {
   104  	if minTime := e.minTime(now); minTime.After(e.max.Time) {
   105  		e.computeMaxTotalStake(minTime)
   106  	}
   107  
   108  	value, _ := num.UintFromDecimal(num.DecimalFromInt64(int64(e.max.TotalStake)).Mul(e.sFactor))
   109  	return value
   110  }
   111  
   112  func (e *Engine) UpdateParameters(parameters types.TargetStakeParameters) {
   113  	e.sFactor = parameters.ScalingFactor
   114  	e.tWindow = time.Duration(parameters.TimeWindow) * time.Second
   115  }
   116  
   117  func (e *Engine) getMaxFromCurrent() timestampedTotalStake {
   118  	if len(e.current) == 0 {
   119  		return timestampedTotalStake{Time: e.now, TotalStake: 0}
   120  	}
   121  	m := e.current[0]
   122  	for i := 1; i < len(e.current); i++ {
   123  		if e.current[i] > m {
   124  			m = e.current[i]
   125  		}
   126  	}
   127  	return timestampedTotalStake{Time: e.now, TotalStake: m}
   128  }
   129  
   130  func (e *Engine) computeMaxTotalStake(minTime time.Time) {
   131  	m := e.getMaxFromCurrent()
   132  	e.truncateHistory(minTime)
   133  	var j int
   134  	for i := 0; i < len(e.previous); i++ {
   135  		if e.previous[i].TotalStake > m.TotalStake {
   136  			m = e.previous[i]
   137  			j = i
   138  		}
   139  	}
   140  	e.max = m
   141  
   142  	// remove entries less than max as these won't ever be needed anyway
   143  	e.previous = e.previous[j:]
   144  }
   145  
   146  // minTime returns the lower bound of the sliding time window.
   147  func (e *Engine) minTime(now time.Time) time.Time {
   148  	return now.Add(-e.tWindow)
   149  }
   150  
   151  func (e *Engine) truncateHistory(minTime time.Time) {
   152  	var i int
   153  	for i = 0; i < len(e.previous); i++ {
   154  		if !e.previous[i].Time.Before(minTime) {
   155  			break
   156  		}
   157  	}
   158  	e.previous = e.previous[i:]
   159  	// Truncate at least every 2 time windows in case not called before to prevent excessive memory usage
   160  	e.scheduledTruncate = e.now.Add(2 * e.tWindow)
   161  }