code.vegaprotocol.io/vega@v0.79.0/core/monitor/auction.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 monitor
    17  
    18  import (
    19  	"context"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/events"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  )
    25  
    26  type AuctionState struct {
    27  	mode               types.MarketTradingMode // current trading mode
    28  	defMode            types.MarketTradingMode // default trading mode for market
    29  	trigger            types.AuctionTrigger    // Set to the value indicating what started the auction
    30  	begin              *time.Time              // optional setting auction start time (will be set if start flag is true)
    31  	end                *types.AuctionDuration  // will be set when in auction, defines parameters that end an auction period
    32  	start, stop        bool                    // flags to clarify whether we're entering or leaving auction
    33  	m                  *types.Market           // keep market definition handy, useful to end auctions when default is FBA
    34  	extension          *types.AuctionTrigger   // Set if the current auction was extended, reset after the event was created
    35  	extensionEventSent bool
    36  	maxDuration        *time.Duration
    37  }
    38  
    39  func NewAuctionState(mkt *types.Market, now time.Time) *AuctionState {
    40  	s := AuctionState{
    41  		mode:    types.MarketTradingModeOpeningAuction,
    42  		defMode: types.MarketTradingModeContinuous,
    43  		trigger: types.AuctionTriggerOpening,
    44  		begin:   &now,
    45  		start:   true,
    46  		m:       mkt,
    47  	}
    48  	// no opening auction
    49  	if mkt.OpeningAuction == nil {
    50  		s.mode = s.defMode
    51  		if s.mode == types.MarketTradingModeBatchAuction {
    52  			// @TODO set end params here (FBA is not yet implemented)
    53  			return &s
    54  		}
    55  		// no opening auction
    56  		s.begin = nil
    57  		s.start = false
    58  		s.trigger = types.AuctionTriggerUnspecified
    59  	} else {
    60  		s.end = mkt.OpeningAuction.DeepClone()
    61  	}
    62  	return &s
    63  }
    64  
    65  func (a *AuctionState) GetAuctionBegin() *time.Time {
    66  	if a.begin == nil {
    67  		return nil
    68  	}
    69  	cpy := *a.begin
    70  	return &cpy
    71  }
    72  
    73  func (a *AuctionState) GetAuctionEnd() *time.Time {
    74  	if a.begin == nil || a.end == nil {
    75  		return nil
    76  	}
    77  	cpy := *a.begin
    78  	cpy = cpy.Add(time.Duration(a.end.Duration) * time.Second)
    79  	return &cpy
    80  }
    81  
    82  func (a *AuctionState) StartLiquidityAuctionUnmetTarget(t time.Time, d *types.AuctionDuration) {
    83  	a.startLiquidityAuction(t, d, types.AuctionTriggerLiquidityTargetNotMet)
    84  }
    85  
    86  // startLiquidityAuction - set the state to start a liquidity triggered auction
    87  // @TODO these functions will be removed once the types are in proto.
    88  func (a *AuctionState) startLiquidityAuction(t time.Time, d *types.AuctionDuration, tigger types.AuctionTrigger) {
    89  	a.mode = types.MarketTradingModeMonitoringAuction
    90  	a.trigger = tigger
    91  	a.start = true
    92  	a.stop = false
    93  	a.begin = &t
    94  	a.end = d
    95  }
    96  
    97  // StartPriceAuction - set the state to start a price triggered auction
    98  // @TODO these functions will be removed once the types are in proto.
    99  func (a *AuctionState) StartPriceAuction(t time.Time, d *types.AuctionDuration) {
   100  	a.mode = types.MarketTradingModeMonitoringAuction
   101  	a.trigger = types.AuctionTriggerPrice
   102  	a.start = true
   103  	a.stop = false
   104  	a.begin = &t
   105  	a.end = d
   106  }
   107  
   108  func (a *AuctionState) StartLongBlockAuction(t time.Time, d int64) {
   109  	a.mode = types.MarketTradingModeLongBlockAuction
   110  	a.trigger = types.AuctionTriggerLongBlock
   111  	a.start = true
   112  	a.stop = false
   113  	a.begin = &t
   114  	a.end = &types.AuctionDuration{Duration: d}
   115  }
   116  
   117  func (a *AuctionState) StartAutomatedPurchaseAuction(t time.Time, d int64) {
   118  	a.mode = types.MarketTradingModeAutomatedPuchaseAuction
   119  	a.trigger = types.AuctionTriggerAutomatedPurchase
   120  	a.start = true
   121  	a.stop = false
   122  	a.begin = &t
   123  	a.end = &types.AuctionDuration{Duration: d}
   124  }
   125  
   126  func (a *AuctionState) StartGovernanceSuspensionAuction(t time.Time) {
   127  	a.mode = types.MarketTradingModeSuspendedViaGovernance
   128  	a.trigger = types.AuctionTriggerGovernanceSuspension
   129  	a.start = true
   130  	a.stop = false
   131  	a.begin = &t
   132  	a.end = &types.AuctionDuration{Duration: 0}
   133  }
   134  
   135  func (a *AuctionState) EndGovernanceSuspensionAuction() {
   136  	if a.trigger == types.AuctionTriggerGovernanceSuspension {
   137  		// if there governance was the trigger and there is no extension, reset the state.
   138  		if a.extension == nil {
   139  			a.mode = types.MarketTradingModeContinuous
   140  			a.trigger = types.AuctionTriggerUnspecified
   141  			a.start = false
   142  			a.stop = true
   143  			a.begin = nil
   144  			a.end = nil
   145  		} else {
   146  			// if we're leaving the governance auction which was the trigger but there was an extension trigger -
   147  			// make the extension trigger the trigger and set the mode to monitoring auction.
   148  			a.mode = types.MarketTradingModeMonitoringAuction
   149  			a.trigger = *a.extension
   150  			a.extension = nil
   151  		}
   152  	} else if a.ExtensionTrigger() == types.AuctionTriggerGovernanceSuspension {
   153  		// if governance suspension was the extension trigger - just reset it.
   154  		a.extension = nil
   155  	}
   156  }
   157  
   158  // StartOpeningAuction - set the state to start an opening auction (used for testing)
   159  // @TODO these functions will be removed once the types are in proto.
   160  func (a *AuctionState) StartOpeningAuction(t time.Time, d *types.AuctionDuration) {
   161  	a.mode = types.MarketTradingModeOpeningAuction
   162  	a.trigger = types.AuctionTriggerOpening
   163  	a.start = true
   164  	a.stop = false
   165  	a.begin = &t
   166  	a.end = d
   167  }
   168  
   169  // ExtendAuctionPrice - call from price monitoring to extend the auction
   170  // sets the extension trigger field accordingly.
   171  func (a *AuctionState) ExtendAuctionPrice(delta types.AuctionDuration) {
   172  	t := types.AuctionTriggerPrice
   173  	a.extension = &t
   174  	a.ExtendAuction(delta)
   175  }
   176  
   177  func (a *AuctionState) ExtendAuctionLongBlock(delta types.AuctionDuration) {
   178  	t := types.AuctionTriggerLongBlock
   179  	if a.trigger != t {
   180  		a.extension = &t
   181  	}
   182  	a.ExtendAuction(delta)
   183  }
   184  
   185  func (a *AuctionState) ExtendAuctionAutomatedPurchase(delta types.AuctionDuration) {
   186  	t := types.AuctionTriggerAutomatedPurchase
   187  	if a.trigger != t {
   188  		a.extension = &t
   189  	}
   190  	a.ExtendAuction(delta)
   191  }
   192  
   193  func (a *AuctionState) ExtendAuctionSuspension(delta types.AuctionDuration) {
   194  	t := types.AuctionTriggerGovernanceSuspension
   195  	a.extension = &t
   196  	a.ExtendAuction(delta)
   197  }
   198  
   199  // ExtendAuction extends the current auction.
   200  func (a *AuctionState) ExtendAuction(delta types.AuctionDuration) {
   201  	a.end.Duration += delta.Duration
   202  	a.end.Volume += delta.Volume
   203  	a.stop = false // the auction was supposed to stop, but we've extended it
   204  }
   205  
   206  // SetReadyToLeave is called by monitoring engines to mark if an auction period has expired.
   207  func (a *AuctionState) SetReadyToLeave() {
   208  	// we can't leave the auction if it was triggered by governance suspension
   209  	if a.trigger == types.AuctionTriggerGovernanceSuspension {
   210  		return
   211  	}
   212  	if a.maxDuration != nil {
   213  		a.maxDuration = nil
   214  	}
   215  	a.stop = true
   216  }
   217  
   218  // Duration returns a copy of the current auction duration object.
   219  func (a AuctionState) Duration() types.AuctionDuration {
   220  	if a.end == nil {
   221  		return types.AuctionDuration{}
   222  	}
   223  	return *a.end
   224  }
   225  
   226  // Start - returns time pointer of the start of the auction (nil if not in auction).
   227  func (a AuctionState) Start() time.Time {
   228  	if a.begin == nil {
   229  		return time.Time{} // zero time
   230  	}
   231  	return *a.begin
   232  }
   233  
   234  // ExpiresAt returns end as time -> if nil, the auction duration either isn't determined by time
   235  // or we're simply not in an auction.
   236  func (a AuctionState) ExpiresAt() *time.Time {
   237  	if a.begin == nil { // no start time == no end time
   238  		return nil
   239  	}
   240  	if a.end == nil || a.end.Duration == 0 { // not time limited
   241  		return nil
   242  	}
   243  	// add duration to start time, return
   244  	t := a.begin.Add(time.Duration(a.end.Duration) * time.Second)
   245  	return &t
   246  }
   247  
   248  // Mode returns current trading mode.
   249  func (a AuctionState) Mode() types.MarketTradingMode {
   250  	return a.mode
   251  }
   252  
   253  // Trigger returns what triggered an auction.
   254  func (a AuctionState) Trigger() types.AuctionTrigger {
   255  	return a.trigger
   256  }
   257  
   258  // ExtensionTrigger returns what extended an auction.
   259  func (a AuctionState) ExtensionTrigger() types.AuctionTrigger {
   260  	if a.extension == nil {
   261  		return types.AuctionTriggerUnspecified
   262  	}
   263  	return *a.extension
   264  }
   265  
   266  // InAuction returns bool if the market is in auction for any reason
   267  // Returns false if auction is triggered, but not yet started by market (execution).
   268  func (a AuctionState) InAuction() bool {
   269  	return !a.start && a.trigger != types.AuctionTriggerUnspecified
   270  }
   271  
   272  func (a AuctionState) IsOpeningAuction() bool {
   273  	return a.trigger == types.AuctionTriggerOpening
   274  }
   275  
   276  func (a AuctionState) IsPriceAuction() bool {
   277  	return a.trigger == types.AuctionTriggerPrice
   278  }
   279  
   280  func (a AuctionState) IsPriceExtension() bool {
   281  	return a.extension != nil && *a.extension == types.AuctionTriggerPrice
   282  }
   283  
   284  func (a AuctionState) IsFBA() bool {
   285  	return a.trigger == types.AuctionTriggerBatch
   286  }
   287  
   288  // IsMonitorAuction - quick way to determine whether or not we're in an auction triggered by a monitoring engine.
   289  func (a AuctionState) IsMonitorAuction() bool {
   290  	// FIXME(jeremy): the second part of the condition is to support
   291  	// the compatibility on 72 > 73 snapshots.
   292  
   293  	return a.trigger == types.AuctionTriggerPrice || a.trigger == types.AuctionTriggerLiquidityTargetNotMet || a.trigger == types.AuctionTriggerUnableToDeployLPOrders
   294  }
   295  
   296  func (a AuctionState) IsPAPAuction() bool {
   297  	return a.trigger == types.AuctionTriggerAutomatedPurchase
   298  }
   299  
   300  // CanLeave bool indicating whether auction should be closed or not, if true, we can still extend the auction
   301  // but when the market takes over (after monitoring engines), the auction will be closed.
   302  func (a AuctionState) CanLeave() bool {
   303  	return a.stop
   304  }
   305  
   306  // AuctionStart bool indicates something has already triggered an auction to start, we can skip other monitoring potentially
   307  // and we know to create an auction event.
   308  func (a AuctionState) AuctionStart() bool {
   309  	return a.start
   310  }
   311  
   312  // AuctionExtended - called to confirm we will not leave auction, returns the event to be sent
   313  // or nil if the auction wasn't extended.
   314  func (a *AuctionState) AuctionExtended(ctx context.Context, now time.Time) *events.Auction {
   315  	if a.extension == nil || a.extensionEventSent {
   316  		return nil
   317  	}
   318  	a.start = false
   319  	end := int64(0)
   320  	if a.begin == nil {
   321  		a.begin = &now
   322  	}
   323  	if a.end != nil && a.end.Duration > 0 {
   324  		end = a.begin.Add(time.Duration(a.end.Duration) * time.Second).UnixNano()
   325  	}
   326  	ext := *a.extension
   327  	// set extension flag to nil
   328  	a.extensionEventSent = true
   329  	return events.NewAuctionEvent(ctx, a.m.ID, false, a.begin.UnixNano(), end, a.trigger, ext)
   330  }
   331  
   332  // AuctionStarted is called by the execution package to set flags indicating the market has started the auction.
   333  func (a *AuctionState) AuctionStarted(ctx context.Context, now time.Time) *events.Auction {
   334  	a.start = false
   335  	end := int64(0)
   336  	// Either an auction was just started, or a market in opening auction passed the vote, the real opening auction starts now.
   337  	if a.begin == nil || (a.trigger == types.AuctionTriggerOpening && a.begin.Before(now)) {
   338  		a.begin = &now
   339  	}
   340  	if a.end != nil && a.end.Duration > 0 {
   341  		end = a.begin.Add(time.Duration(a.end.Duration) * time.Second).UnixNano()
   342  	}
   343  	return events.NewAuctionEvent(ctx, a.m.ID, false, a.begin.UnixNano(), end, a.trigger)
   344  }
   345  
   346  // Left is called by execution to update internal state indicating this auction was closed.
   347  func (a *AuctionState) Left(ctx context.Context, now time.Time) *events.Auction {
   348  	// the end-of-auction event
   349  	var start int64
   350  	if a.begin != nil {
   351  		start = a.begin.UnixNano()
   352  	}
   353  	evt := events.NewAuctionEvent(ctx, a.m.ID, true, start, now.UnixNano(), a.trigger)
   354  	a.start, a.stop = false, false
   355  	a.begin, a.end = nil, nil
   356  	a.trigger = types.AuctionTriggerUnspecified
   357  	a.extension = nil
   358  	a.mode = a.defMode
   359  	// default mode is auction, this is an FBA market
   360  	if a.mode == types.MarketTradingModeBatchAuction {
   361  		a.trigger = types.AuctionTriggerBatch
   362  	}
   363  	return evt
   364  }
   365  
   366  // UpdateMinDuration - see if we need to update the end value for current auction duration (if any)
   367  // if the auction duration increases, an auction event will be returned.
   368  func (a *AuctionState) UpdateMinDuration(ctx context.Context, d time.Duration) *events.Auction {
   369  	// oldExp is nil if we're not in auction
   370  	if oldExp := a.ExpiresAt(); oldExp != nil {
   371  		// calc new end for auction:
   372  		newMin := a.begin.Add(d)
   373  		// no need to check for nil, we already have
   374  		if newMin.After(*oldExp) {
   375  			a.end.Duration = int64(d / time.Second)
   376  			// this would increase the duration by delta new - old, effectively setting duration == new min. Instead, we can just assign new min duration.
   377  			// a.end.Duration += int64(newMin.Sub(*oldExp) / time.Second) // we have to divide by seconds as we're using seconds in AuctionDuration type
   378  			return events.NewAuctionEvent(ctx, a.m.ID, false, a.begin.UnixNano(), newMin.UnixNano(), a.trigger)
   379  		}
   380  	}
   381  	return nil
   382  }
   383  
   384  func (a *AuctionState) UpdateMaxDuration(_ context.Context, d time.Duration) {
   385  	if a.trigger == types.AuctionTriggerOpening {
   386  		a.maxDuration = &d
   387  	}
   388  }
   389  
   390  func (a *AuctionState) ExceededMaxOpening(now time.Time) bool {
   391  	if a.trigger != types.AuctionTriggerOpening || a.begin == nil || a.maxDuration == nil {
   392  		return false
   393  	}
   394  	minTo := now
   395  	if a.end != nil && a.end.Duration > 0 {
   396  		minTo = a.begin.Add(time.Duration(a.end.Duration) * time.Second)
   397  	}
   398  	validTo := a.begin.Add(*a.maxDuration)
   399  	// the market is invalid if it hasn't left auction before max duration
   400  	// or if it cannot leave before max duration allows it
   401  	return validTo.Before(now) || minTo.After(validTo)
   402  }