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 }