code.vegaprotocol.io/vega@v0.79.0/core/risk/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 risk 17 18 import ( 19 "context" 20 "errors" 21 "sort" 22 "sync" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/types" 27 "code.vegaprotocol.io/vega/core/types/statevar" 28 "code.vegaprotocol.io/vega/libs/num" 29 "code.vegaprotocol.io/vega/logging" 30 31 "golang.org/x/exp/maps" 32 ) 33 34 //go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/risk Orderbook,AuctionState,TimeService,StateVarEngine,Model 35 36 var ( 37 ErrInsufficientFundsForInitialMargin = errors.New("insufficient funds for initial margin") 38 ErrInsufficientFundsForMaintenanceMargin = errors.New("insufficient funds for maintenance margin") 39 ErrInsufficientFundsForOrderMargin = errors.New("insufficient funds for order margin") 40 ErrInsufficientFundsForMarginInGeneralAccount = errors.New("insufficient funds to cover margin in general margin") 41 ErrRiskFactorsNotAvailableForAsset = errors.New("risk factors not available for the specified asset") 42 ErrInsufficientFundsToCoverTradeFees = errors.New("insufficient funds to cover fees") 43 ) 44 45 const RiskFactorStateVarName = "risk-factors" 46 47 // Orderbook represent an abstraction over the orderbook. 48 type Orderbook interface { 49 GetIndicativePrice() *num.Uint 50 } 51 52 // AuctionState represents the current auction state of the market, previously we got this information from the matching engine, but really... that's not its job. 53 type AuctionState interface { 54 InAuction() bool 55 CanLeave() bool 56 } 57 58 // TimeService. 59 type TimeService interface { 60 GetTimeNow() time.Time 61 } 62 63 // Broker the event bus broker. 64 type Broker interface { 65 Send(events.Event) 66 SendBatch([]events.Event) 67 } 68 69 type StateVarEngine interface { 70 RegisterStateVariable(asset, market, name string, converter statevar.Converter, startCalculation func(string, statevar.FinaliseCalculation), trigger []statevar.EventType, result func(context.Context, statevar.StateVariableResult) error) error 71 NewEvent(asset, market string, eventType statevar.EventType) 72 } 73 74 type marginChange struct { 75 events.Margin // previous event that caused this change 76 transfer *types.Transfer 77 margins *types.MarginLevels 78 } 79 80 // Engine is the risk engine. 81 type Engine struct { 82 Config 83 marginCalculator *types.MarginCalculator 84 scalingFactorsUint *scalingFactorsUint 85 log *logging.Logger 86 cfgMu sync.RWMutex 87 model Model 88 factors *types.RiskFactor 89 waiting bool 90 ob Orderbook 91 as AuctionState 92 timeSvc TimeService 93 broker Broker 94 riskFactorsInitialised bool 95 mktID string 96 asset string 97 positionFactor num.Decimal 98 linearSlippageFactor num.Decimal 99 quadraticSlippageFactor num.Decimal 100 101 // a map of margin levels events to be send 102 // should be flushed after the processing of every transaction 103 // partyId -> MarginLevelsEvent 104 marginLevelsUpdates map[string]*events.MarginLevels 105 updateMarginLevels func(...*events.MarginLevels) 106 } 107 108 // NewEngine instantiate a new risk engine. 109 func NewEngine(log *logging.Logger, 110 config Config, 111 marginCalculator *types.MarginCalculator, 112 model Model, 113 ob Orderbook, 114 as AuctionState, 115 timeSvc TimeService, 116 broker Broker, 117 mktID string, 118 asset string, 119 stateVarEngine StateVarEngine, 120 positionFactor num.Decimal, 121 riskFactorsInitialised bool, 122 initialisedRiskFactors *types.RiskFactor, // if restored from snapshot, will be nil otherwise 123 linearSlippageFactor num.Decimal, 124 quadraticSlippageFactor num.Decimal, 125 ) *Engine { 126 // setup logger 127 log = log.Named(namedLogger) 128 log.SetLevel(config.Level.Get()) 129 130 sfUint := scalingFactorsUintFromDecimals(marginCalculator.ScalingFactors) 131 e := &Engine{ 132 log: log, 133 Config: config, 134 marginCalculator: marginCalculator, 135 model: model, 136 waiting: false, 137 ob: ob, 138 as: as, 139 timeSvc: timeSvc, 140 broker: broker, 141 mktID: mktID, 142 asset: asset, 143 scalingFactorsUint: sfUint, 144 factors: model.DefaultRiskFactors(), 145 riskFactorsInitialised: riskFactorsInitialised, 146 positionFactor: positionFactor, 147 linearSlippageFactor: linearSlippageFactor, 148 quadraticSlippageFactor: quadraticSlippageFactor, 149 marginLevelsUpdates: map[string]*events.MarginLevels{}, 150 } 151 stateVarEngine.RegisterStateVariable(asset, mktID, RiskFactorStateVarName, FactorConverter{}, e.startRiskFactorsCalculation, []statevar.EventType{statevar.EventTypeMarketEnactment, statevar.EventTypeMarketUpdated}, e.updateRiskFactor) 152 153 if initialisedRiskFactors != nil { 154 e.cfgMu.Lock() 155 e.factors = initialisedRiskFactors 156 e.cfgMu.Unlock() 157 // we've restored from snapshot, we don't need want to trigger a MarketEnactment event 158 } else { 159 // trigger the calculation of risk factors for the market 160 stateVarEngine.NewEvent(asset, mktID, statevar.EventTypeMarketEnactment) 161 } 162 163 e.updateMarginLevels = e.bufferMarginLevels 164 if e.StreamMarginLevelsVerbose { 165 e.updateMarginLevels = e.sendMarginLevels 166 } 167 168 return e 169 } 170 171 func (e *Engine) FlushMarginLevelsEvents() { 172 if e.StreamMarginLevelsVerbose || len(e.marginLevelsUpdates) <= 0 { 173 return 174 } 175 176 e.sendBufferedMarginLevels() 177 } 178 179 func (e *Engine) sendBufferedMarginLevels() { 180 parties := maps.Keys(e.marginLevelsUpdates) 181 sort.Strings(parties) 182 evts := make([]events.Event, 0, len(parties)) 183 184 for _, v := range parties { 185 evts = append(evts, e.marginLevelsUpdates[v]) 186 } 187 188 e.broker.SendBatch(evts) 189 e.marginLevelsUpdates = make(map[string]*events.MarginLevels, len(e.marginLevelsUpdates)) 190 } 191 192 func (e *Engine) sendMarginLevels(m ...*events.MarginLevels) { 193 evts := make([]events.Event, 0, len(m)) 194 for _, ml := range m { 195 evts = append(evts, ml) 196 } 197 198 e.broker.SendBatch(evts) 199 } 200 201 func (e *Engine) bufferMarginLevels(mls ...*events.MarginLevels) { 202 for _, m := range mls { 203 e.marginLevelsUpdates[m.PartyID()] = m 204 } 205 } 206 207 func (e *Engine) OnMarginScalingFactorsUpdate(sf *types.ScalingFactors) error { 208 if sf.CollateralRelease.LessThan(sf.InitialMargin) || sf.InitialMargin.LessThanOrEqual(sf.SearchLevel) { 209 return errors.New("incompatible margins scaling factors") 210 } 211 212 e.marginCalculator.ScalingFactors = sf 213 e.scalingFactorsUint = scalingFactorsUintFromDecimals(sf) 214 return nil 215 } 216 217 func (e *Engine) UpdateModel( 218 stateVarEngine StateVarEngine, 219 calculator *types.MarginCalculator, 220 model Model, 221 linearSlippageFactor num.Decimal, 222 quadraticSlippageFactor num.Decimal, 223 ) { 224 e.scalingFactorsUint = scalingFactorsUintFromDecimals(calculator.ScalingFactors) 225 e.cfgMu.Lock() 226 e.factors = model.DefaultRiskFactors() 227 e.cfgMu.Unlock() 228 e.model = model 229 e.linearSlippageFactor = linearSlippageFactor 230 e.quadraticSlippageFactor = quadraticSlippageFactor 231 stateVarEngine.NewEvent(e.asset, e.mktID, statevar.EventTypeMarketUpdated) 232 } 233 234 // ReloadConf update the internal configuration of the risk engine. 235 func (e *Engine) ReloadConf(cfg Config) { 236 e.log.Info("reloading configuration") 237 if e.log.GetLevel() != cfg.Level.Get() { 238 e.log.Info("updating log level", 239 logging.String("old", e.log.GetLevel().String()), 240 logging.String("new", cfg.Level.String()), 241 ) 242 e.log.SetLevel(cfg.Level.Get()) 243 } 244 245 e.cfgMu.Lock() 246 e.Config = cfg 247 e.cfgMu.Unlock() 248 } 249 250 // GetRiskFactors returns risk factors per specified asset. 251 func (e *Engine) GetRiskFactors() *types.RiskFactor { 252 e.cfgMu.RLock() 253 defer e.cfgMu.RUnlock() 254 return e.factors 255 } 256 257 func (e *Engine) GetScalingFactors() *types.ScalingFactors { 258 return e.marginCalculator.ScalingFactors 259 } 260 261 func (e *Engine) GetSlippage() num.Decimal { 262 return e.linearSlippageFactor 263 } 264 265 func (e *Engine) UpdateMarginAuction(ctx context.Context, evts []events.Margin, price *num.Uint, increment num.Decimal, auctionPrice *num.Uint) ([]events.Risk, []events.Margin) { 266 if len(evts) == 0 { 267 return nil, nil 268 } 269 revts := make([]events.Risk, 0, len(evts)) 270 // parties with insufficient margin to meet required level, return the event passed as arg 271 low := []events.Margin{} 272 eventBatch := make([]*events.MarginLevels, 0, len(evts)) 273 // for now, we can assume a single asset for all events 274 rFactors := *e.factors 275 nowTS := e.timeSvc.GetTimeNow().UnixNano() 276 for _, evt := range evts { 277 levels := e.calculateMargins(evt, price, rFactors, true, true, increment, auctionPrice) 278 if levels == nil { 279 continue 280 } 281 282 levels.Party = evt.Party() 283 levels.Asset = e.asset // This is assuming there's a single asset at play here 284 levels.Timestamp = nowTS 285 levels.MarketID = e.mktID 286 287 curMargin := evt.MarginBalance() 288 if num.Sum(curMargin, evt.GeneralBalance()).LT(levels.InitialMargin) { 289 low = append(low, evt) 290 continue 291 } 292 eventBatch = append(eventBatch, events.NewMarginLevelsEvent(ctx, *levels)) 293 // party has sufficient margin, no need to transfer funds 294 if curMargin.GTE(levels.InitialMargin) { 295 continue 296 } 297 minAmount := num.UintZero() 298 if levels.MaintenanceMargin.GT(curMargin) { 299 minAmount.Sub(levels.MaintenanceMargin, curMargin) 300 } 301 amt := num.UintZero().Sub(levels.InitialMargin, curMargin) // we know curBalace is less than initial 302 t := &types.Transfer{ 303 Owner: evt.Party(), 304 Type: types.TransferTypeMarginLow, 305 Amount: &types.FinancialAmount{ 306 Asset: e.asset, 307 Amount: amt, 308 }, 309 MinAmount: minAmount, 310 } 311 revts = append(revts, &marginChange{ 312 Margin: evt, 313 transfer: t, 314 margins: levels, 315 }) 316 } 317 e.updateMarginLevels(eventBatch...) 318 return revts, low 319 } 320 321 // UpdateMarginOnNewOrder calculate the new margin requirement for a single order 322 // this is intended to be used when a new order is created in order to ensure the 323 // party margin account is at least at the InitialMargin level before the order is added to the book. 324 func (e *Engine) UpdateMarginOnNewOrder(ctx context.Context, evt events.Margin, markPrice *num.Uint, increment num.Decimal, auctionPrice *num.Uint) (events.Risk, events.Margin, error) { 325 if evt == nil { 326 return nil, nil, nil 327 } 328 auction := e.as.InAuction() && !e.as.CanLeave() 329 margins := e.calculateMargins(evt, markPrice, *e.factors, true, auction, increment, auctionPrice) 330 331 // no margins updates, nothing to do then 332 if margins == nil { 333 return nil, nil, nil 334 } 335 336 // update other fields for the margins 337 margins.Party = evt.Party() 338 margins.Asset = evt.Asset() 339 margins.Timestamp = e.timeSvc.GetTimeNow().UnixNano() 340 margins.MarketID = e.mktID 341 342 curMarginBalance := evt.MarginBalance() 343 344 if num.Sum(curMarginBalance, evt.GeneralBalance()).LT(margins.InitialMargin) { 345 // there's not enough monies in the accounts of the party 346 // and the order does not reduce party's exposure, 347 // we break from here. The minimum requirement is INITIAL. 348 return nil, nil, ErrInsufficientFundsForInitialMargin 349 } 350 351 // propagate margins levels to the buffer 352 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 353 354 // margins are sufficient, nothing to update 355 if curMarginBalance.GTE(margins.InitialMargin) { 356 return nil, nil, nil 357 } 358 359 minAmount := num.UintZero() 360 if margins.MaintenanceMargin.GT(curMarginBalance) { 361 minAmount.Sub(margins.MaintenanceMargin, curMarginBalance) 362 } 363 364 // margin is < that InitialMargin so we create a transfer request to top it up. 365 trnsfr := &types.Transfer{ 366 Owner: evt.Party(), 367 Type: types.TransferTypeMarginLow, 368 Amount: &types.FinancialAmount{ 369 Asset: evt.Asset(), 370 Amount: num.UintZero().Sub(margins.InitialMargin, curMarginBalance), 371 }, 372 MinAmount: minAmount, // minimal amount == maintenance 373 } 374 375 change := &marginChange{ 376 Margin: evt, 377 transfer: trnsfr, 378 margins: margins, 379 } 380 // we don't have enough in general + margin accounts to cover initial margin level, so we'll be dipping into our bond account 381 // we have to return the margin event to signal that 382 nonBondFunds := num.Sum(curMarginBalance, evt.GeneralBalance()) 383 nonBondFunds.Sub(nonBondFunds, evt.BondBalance()) 384 if nonBondFunds.LT(margins.InitialMargin) { 385 return change, evt, nil 386 } 387 return change, nil, nil 388 } 389 390 // UpdateMarginsOnSettlement ensure the margin requirement over all positions. 391 // margins updates are based on the following requirement 392 // 393 // --------------------------------------------------------------------------------------- 394 // 395 // | 1 | SearchLevel < CurMargin < InitialMargin | nothing to do / no risk for the network | 396 // | 2 | CurMargin < SearchLevel | set margin to InitialLevel | 397 // | 3 | CurMargin > ReleaseLevel | release up to the InitialLevel | 398 // 399 // --------------------------------------------------------------------------------------- 400 // 401 // In the case where the CurMargin is smaller to the MaintenanceLevel after trying to 402 // move monies later, we'll need to close out the party but that cannot be figured out 403 // now only in later when we try to move monies from the general account. 404 func (e *Engine) UpdateMarginsOnSettlement(ctx context.Context, evts []events.Margin, markPrice *num.Uint, increment num.Decimal, auctionPrice *num.Uint) []events.Risk { 405 ret := make([]events.Risk, 0, len(evts)) 406 now := e.timeSvc.GetTimeNow().UnixNano() 407 408 // var err error 409 // this will keep going until we've closed this channel 410 // this can be the result of an error, or being "finished" 411 for _, evt := range evts { 412 // before we do anything, see if the position is 0 now, but the margin balance is still set 413 // in which case the only response is to release the margin balance. 414 if evt.Size() == 0 && evt.Buy() == 0 && evt.Sell() == 0 && !evt.MarginBalance().IsZero() { 415 amt := evt.MarginBalance() 416 trnsfr := &types.Transfer{ 417 Owner: evt.Party(), 418 Type: types.TransferTypeMarginHigh, 419 Amount: &types.FinancialAmount{ 420 Asset: evt.Asset(), 421 Amount: amt, 422 }, 423 MinAmount: amt.Clone(), 424 } 425 margins := types.MarginLevels{ 426 MaintenanceMargin: num.UintZero(), 427 SearchLevel: num.UintZero(), 428 InitialMargin: num.UintZero(), 429 CollateralReleaseLevel: num.UintZero(), 430 OrderMargin: num.UintZero(), 431 Party: evt.Party(), 432 MarketID: evt.MarketID(), 433 Asset: evt.Asset(), 434 Timestamp: now, 435 MarginMode: types.MarginModeCrossMargin, 436 MarginFactor: num.DecimalZero(), 437 } 438 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, margins)) 439 ret = append(ret, &marginChange{ 440 Margin: evt, 441 transfer: trnsfr, 442 margins: &margins, 443 }) 444 continue 445 } 446 // channel is closed, and we've got a nil interface 447 auction := e.as.InAuction() && !e.as.CanLeave() 448 margins := e.calculateMargins(evt, markPrice, *e.factors, true, auction, increment, auctionPrice) 449 450 // no margins updates, nothing to do then 451 if margins == nil { 452 continue 453 } 454 455 // update other fields for the margins 456 margins.Timestamp = now 457 margins.MarketID = e.mktID 458 margins.Party = evt.Party() 459 margins.Asset = evt.Asset() 460 461 if e.log.GetLevel() == logging.DebugLevel { 462 e.log.Debug("margins calculated on settlement", 463 logging.String("party-id", evt.Party()), 464 logging.String("market-id", evt.MarketID()), 465 logging.Reflect("margins", *margins), 466 ) 467 } 468 469 curMargin := evt.MarginBalance() 470 // case 1 -> nothing to do margins are sufficient 471 if curMargin.GTE(margins.SearchLevel) && curMargin.LT(margins.CollateralReleaseLevel) { 472 // propagate margins then continue 473 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 474 continue 475 } 476 477 var trnsfr *types.Transfer 478 minAmount := num.UintZero() 479 // if we don't even have margin levels, the minimum amount must be non-zero. 480 if curMargin.LT(margins.MaintenanceMargin) { 481 minAmount.Sub(margins.MaintenanceMargin, curMargin) 482 } 483 // minAmount := num.UintZero().Sub(margins.MaintenanceMargin, curMargin) 484 // case 2 -> not enough margin 485 if curMargin.LT(margins.SearchLevel) { 486 // then the rest is common if we are before or after MaintenanceLevel, 487 // we try to reach the InitialMargin level 488 trnsfr = &types.Transfer{ 489 Owner: evt.Party(), 490 Type: types.TransferTypeMarginLow, 491 Amount: &types.FinancialAmount{ 492 Asset: evt.Asset(), 493 Amount: num.UintZero().Sub(margins.InitialMargin, curMargin), 494 }, 495 MinAmount: minAmount, 496 } 497 } else { // case 3 -> release some collateral 498 // collateral not relased in auction 499 if e.as.InAuction() && !e.as.CanLeave() { 500 // propagate margins then continue 501 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 502 continue 503 } 504 trnsfr = &types.Transfer{ 505 Owner: evt.Party(), 506 Type: types.TransferTypeMarginHigh, 507 Amount: &types.FinancialAmount{ 508 Asset: evt.Asset(), 509 Amount: num.UintZero().Sub(curMargin, margins.InitialMargin), 510 }, 511 MinAmount: num.UintZero().Sub(curMargin, margins.CollateralReleaseLevel), 512 } 513 } 514 515 // propage margins to the buffers 516 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 517 518 risk := &marginChange{ 519 Margin: evt, 520 transfer: trnsfr, 521 margins: margins, 522 } 523 ret = append(ret, risk) 524 } 525 return ret 526 } 527 528 // ExpectMargins is used in the case some parties are in a distressed positions 529 // in this situation we will only check if the party margin is > to the maintenance margin. 530 func (e *Engine) ExpectMargins(evts []events.Margin, markPrice *num.Uint, increment num.Decimal, auctionPrice *num.Uint) (okMargins []events.Margin, distressedPositions []events.Margin) { 531 okMargins = make([]events.Margin, 0, len(evts)/2) 532 distressedPositions = make([]events.Margin, 0, len(evts)/2) 533 auction := e.as.InAuction() && !e.as.CanLeave() 534 for _, evt := range evts { 535 margins := e.calculateMargins(evt, markPrice, *e.factors, false, auction, increment, auctionPrice) 536 // no margins updates, nothing to do then 537 if margins == nil { 538 okMargins = append(okMargins, evt) 539 continue 540 } 541 if e.log.GetLevel() == logging.DebugLevel { 542 e.log.Debug("margins calculated", 543 logging.String("party-id", evt.Party()), 544 logging.String("market-id", evt.MarketID()), 545 logging.Reflect("margins", *margins), 546 ) 547 } 548 549 curMargin := evt.MarginBalance() 550 if curMargin.GT(margins.MaintenanceMargin) { 551 okMargins = append(okMargins, evt) 552 } else { 553 distressedPositions = append(distressedPositions, evt) 554 } 555 } 556 557 return okMargins, distressedPositions 558 } 559 560 func (m marginChange) Amount() *num.Uint { 561 if m.transfer == nil { 562 return nil 563 } 564 return m.transfer.Amount.Amount.Clone() 565 } 566 567 // Transfer - it's actually part of the embedded interface already, but we have to mask it, because this type contains another transfer. 568 func (m marginChange) Transfer() *types.Transfer { 569 return m.transfer 570 } 571 572 func (m marginChange) MarginLevels() *types.MarginLevels { 573 return m.margins 574 }