code.vegaprotocol.io/vega@v0.79.0/core/execution/future/market.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 future 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "sort" 23 "strconv" 24 "sync" 25 "time" 26 27 "code.vegaprotocol.io/vega/core/assets" 28 "code.vegaprotocol.io/vega/core/collateral" 29 "code.vegaprotocol.io/vega/core/events" 30 "code.vegaprotocol.io/vega/core/execution/amm" 31 "code.vegaprotocol.io/vega/core/execution/common" 32 "code.vegaprotocol.io/vega/core/execution/liquidation" 33 "code.vegaprotocol.io/vega/core/execution/stoporders" 34 "code.vegaprotocol.io/vega/core/fee" 35 "code.vegaprotocol.io/vega/core/idgeneration" 36 liquiditytarget "code.vegaprotocol.io/vega/core/liquidity/target" 37 "code.vegaprotocol.io/vega/core/liquidity/v2" 38 "code.vegaprotocol.io/vega/core/markets" 39 "code.vegaprotocol.io/vega/core/matching" 40 "code.vegaprotocol.io/vega/core/metrics" 41 "code.vegaprotocol.io/vega/core/monitor" 42 "code.vegaprotocol.io/vega/core/monitor/price" 43 "code.vegaprotocol.io/vega/core/positions" 44 "code.vegaprotocol.io/vega/core/products" 45 "code.vegaprotocol.io/vega/core/risk" 46 "code.vegaprotocol.io/vega/core/settlement" 47 "code.vegaprotocol.io/vega/core/types" 48 "code.vegaprotocol.io/vega/core/types/statevar" 49 vegacontext "code.vegaprotocol.io/vega/libs/context" 50 vgcontext "code.vegaprotocol.io/vega/libs/context" 51 "code.vegaprotocol.io/vega/libs/crypto" 52 "code.vegaprotocol.io/vega/libs/num" 53 "code.vegaprotocol.io/vega/libs/ptr" 54 "code.vegaprotocol.io/vega/logging" 55 "code.vegaprotocol.io/vega/protos/vega" 56 vegapb "code.vegaprotocol.io/vega/protos/vega" 57 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 58 59 "golang.org/x/exp/maps" 60 ) 61 62 // TargetStakeCalculator interface. 63 type TargetStakeCalculator interface { 64 types.StateProvider 65 RecordOpenInterest(oi uint64, now time.Time) error 66 GetTargetStake(rf types.RiskFactor, now time.Time, markPrice *num.Uint) *num.Uint 67 GetTheoreticalTargetStake(rf types.RiskFactor, now time.Time, markPrice *num.Uint, trades []*types.Trade) *num.Uint 68 UpdateScalingFactor(sFactor num.Decimal) error 69 UpdateTimeWindow(tWindow time.Duration) 70 StopSnapshots() 71 UpdateParameters(types.TargetStakeParameters) 72 } 73 74 // Market represents an instance of a market in vega and is in charge of calling 75 // the engines in order to process all transactions. 76 type Market struct { 77 log *logging.Logger 78 idgen common.IDGenerator 79 80 mkt *types.Market 81 82 closingAt time.Time 83 timeService common.TimeService 84 85 mu sync.RWMutex 86 markPriceLock sync.RWMutex 87 88 lastTradedPrice *num.Uint 89 priceFactor num.Decimal 90 assetFactor num.Decimal 91 92 // own engines 93 matching *matching.CachedOrderBook 94 tradableInstrument *markets.TradableInstrument 95 risk *risk.Engine 96 position *positions.SnapshotEngine 97 settlement *settlement.SnapshotEngine 98 fee *fee.Engine 99 referralDiscountRewardService fee.ReferralDiscountRewardService 100 volumeDiscountService fee.VolumeDiscountService 101 volumeRebateService fee.VolumeRebateService 102 liquidity *common.MarketLiquidity 103 liquidityEngine common.LiquidityEngine 104 105 // deps engines 106 collateral common.Collateral 107 banking common.Banking 108 109 broker common.Broker 110 closed bool 111 finalFeesDistributed bool 112 113 parties map[string]struct{} 114 115 pMonitor common.PriceMonitor 116 117 tsCalc TargetStakeCalculator 118 119 as common.AuctionState 120 121 peggedOrders *common.PeggedOrders 122 expiringOrders *common.ExpiringOrders 123 124 // Store the previous price values so we can see what has changed 125 lastBestBidPrice *num.Uint 126 lastBestAskPrice *num.Uint 127 lastMidBuyPrice *num.Uint 128 lastMidSellPrice *num.Uint 129 130 bondPenaltyFactor num.Decimal 131 lastMarketValueProxy num.Decimal 132 marketValueWindowLength time.Duration 133 minMaintenanceMarginQuantumMultiplier num.Decimal 134 135 // Liquidity Fee 136 feeSplitter *common.FeeSplitter 137 equityShares *common.EquityShares 138 139 stateVarEngine common.StateVarEngine 140 marketActivityTracker *common.MarketActivityTracker 141 positionFactor num.Decimal // 10^pdp 142 assetDP uint32 143 144 settlementDataInMarket *num.Numeric 145 nextMTM time.Time 146 nextInternalCompositePriceCalc time.Time 147 mtmDelta time.Duration 148 internalCompositePriceFrequency time.Duration 149 150 settlementAsset string 151 succeeded bool 152 153 maxStopOrdersPerParties *num.Uint 154 stopOrders *stoporders.Pool 155 expiringStopOrders *common.ExpiringOrders 156 157 minDuration time.Duration 158 perp bool 159 160 stats *types.MarketStats 161 liquidation *liquidation.Engine // @TODO probably should be an interface for unit testing 162 163 // set to false when started 164 // we'll use it only once after an upgrade 165 // to make sure the migraton from the upgrade 166 // are applied properly 167 ensuredMigration73 bool 168 epoch types.Epoch 169 170 // party ID to isolated margin factor 171 partyMarginFactor map[string]num.Decimal 172 markPriceCalculator *common.CompositePriceCalculator 173 internalCompositePriceCalculator *common.CompositePriceCalculator 174 175 amm *amm.Engine 176 177 fCap *types.FutureCap 178 capMax *num.Uint 179 } 180 181 // NewMarket creates a new market using the market framework configuration and creates underlying engines. 182 func NewMarket( 183 ctx context.Context, 184 log *logging.Logger, 185 riskConfig risk.Config, 186 positionConfig positions.Config, 187 settlementConfig settlement.Config, 188 matchingConfig matching.Config, 189 feeConfig fee.Config, 190 liquidityConfig liquidity.Config, 191 collateralEngine common.Collateral, 192 oracleEngine products.OracleEngine, 193 mkt *types.Market, 194 timeService common.TimeService, 195 broker common.Broker, 196 auctionState *monitor.AuctionState, 197 stateVarEngine common.StateVarEngine, 198 marketActivityTracker *common.MarketActivityTracker, 199 assetDetails *assets.Asset, 200 peggedOrderNotify func(int64), 201 referralDiscountRewardService fee.ReferralDiscountRewardService, 202 volumeDiscountService fee.VolumeDiscountService, 203 volumeRebateService fee.VolumeRebateService, 204 banking common.Banking, 205 parties common.Parties, 206 ) (*Market, error) { 207 if len(mkt.ID) == 0 { 208 return nil, common.ErrEmptyMarketID 209 } 210 211 assetDecimals := assetDetails.DecimalPlaces() 212 positionFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(mkt.PositionDecimalPlaces)) 213 214 tradableInstrument, err := markets.NewTradableInstrument(ctx, log, mkt.TradableInstrument, mkt.ID, timeService, oracleEngine, broker, uint32(assetDecimals)) 215 if err != nil { 216 return nil, fmt.Errorf("unable to instantiate a new market: %w", err) 217 } 218 priceFactor := num.DecimalOne() 219 if exp := int(assetDecimals) - int(mkt.DecimalPlaces); exp != 0 { 220 priceFactor = num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(exp))) 221 } 222 assetFactor := num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(assetDecimals))) 223 224 asset := tradableInstrument.Instrument.Product.GetAsset() 225 positionEngine := positions.NewSnapshotEngine(log, positionConfig, mkt.ID, broker) 226 227 ammEngine := amm.New( 228 log, 229 broker, 230 collateralEngine, 231 mkt.GetID(), 232 asset, 233 positionEngine, 234 priceFactor, 235 positionFactor, 236 marketActivityTracker, 237 parties, 238 mkt.AllowedEmptyAmmLevels, 239 ) 240 241 // @TODO -> the raw auctionstate shouldn't be something exposed to the matching engine 242 // as far as matching goes: it's either an auction or not 243 book := matching.NewCachedOrderBook(log, matchingConfig, mkt.ID, auctionState.InAuction(), peggedOrderNotify) 244 book.SetOffbookSource(ammEngine) 245 246 riskEngine := risk.NewEngine(log, 247 riskConfig, 248 tradableInstrument.MarginCalculator, 249 tradableInstrument.RiskModel, 250 book, 251 auctionState, 252 timeService, 253 broker, 254 mkt.ID, 255 asset, 256 stateVarEngine, 257 positionFactor, 258 false, 259 nil, 260 mkt.LinearSlippageFactor, 261 mkt.QuadraticSlippageFactor, 262 ) 263 264 settleEngine := settlement.NewSnapshotEngine( 265 log, 266 settlementConfig, 267 tradableInstrument.Instrument.Product, 268 mkt.ID, 269 timeService, 270 broker, 271 positionFactor, 272 ) 273 274 feeEngine, err := fee.New(log, feeConfig, *mkt.Fees, asset, positionFactor) 275 if err != nil { 276 return nil, fmt.Errorf("unable to instantiate fee engine: %w", err) 277 } 278 279 tsCalc := liquiditytarget.NewSnapshotEngine(*mkt.LiquidityMonitoringParameters.TargetStakeParameters, positionEngine, mkt.ID, positionFactor) 280 281 pMonitor, err := price.NewMonitor(asset, mkt.ID, tradableInstrument.RiskModel, auctionState, mkt.PriceMonitoringSettings, stateVarEngine, log) 282 if err != nil { 283 return nil, fmt.Errorf("unable to instantiate price monitoring engine: %w", err) 284 } 285 286 now := timeService.GetTimeNow() 287 288 liquidityEngine := liquidity.NewSnapshotEngine( 289 liquidityConfig, log, timeService, broker, tradableInstrument.RiskModel, 290 pMonitor, book, auctionState, asset, mkt.ID, stateVarEngine, positionFactor, mkt.LiquiditySLAParams) 291 292 equityShares := common.NewEquityShares(num.DecimalZero()) 293 294 marketLiquidity := common.NewMarketLiquidity( 295 log, liquidityEngine, collateralEngine, broker, book, equityShares, marketActivityTracker, 296 feeEngine, common.FutureMarketType, mkt.ID, asset, priceFactor, mkt.LiquiditySLAParams.PriceRange, 297 ammEngine, 298 ) 299 300 // The market is initially created in a proposed state 301 mkt.State = types.MarketStateProposed 302 mkt.TradingMode = types.MarketTradingModeNoTrading 303 304 pending, open := auctionState.GetAuctionBegin(), auctionState.GetAuctionEnd() 305 // Populate the market timestamps 306 ts := &types.MarketTimestamps{ 307 Proposed: now.UnixNano(), 308 Pending: now.UnixNano(), 309 } 310 if pending != nil { 311 ts.Pending = pending.UnixNano() 312 } 313 if open != nil { 314 ts.Open = open.UnixNano() 315 } 316 317 mkt.MarketTimestamps = ts 318 // @TODO remove this once liquidation strategy is no longer optional 319 // consider mkt.LiquidationStrategy is currently still treated as optional, but we use 320 // mkt in the events we're sending to data-node, let's set the default strategy here 321 // and update the mkt object so the events will accurately reflect what this is being set to 322 if mkt.LiquidationStrategy == nil { 323 mkt.LiquidationStrategy = liquidation.GetLegacyStrat() 324 } 325 326 marketType := mkt.MarketType() 327 market := &Market{ 328 log: log, 329 idgen: nil, 330 mkt: mkt, 331 matching: book, 332 tradableInstrument: tradableInstrument, 333 risk: riskEngine, 334 position: positionEngine, 335 settlement: settleEngine, 336 collateral: collateralEngine, 337 timeService: timeService, 338 broker: broker, 339 fee: feeEngine, 340 liquidity: marketLiquidity, 341 liquidityEngine: liquidityEngine, // TODO karel - consider not having this 342 parties: map[string]struct{}{}, 343 as: auctionState, 344 pMonitor: pMonitor, 345 tsCalc: tsCalc, 346 peggedOrders: common.NewPeggedOrders(log, timeService), 347 expiringOrders: common.NewExpiringOrders(), 348 feeSplitter: common.NewFeeSplitter(), 349 equityShares: equityShares, 350 lastBestAskPrice: num.UintZero(), 351 lastMidSellPrice: num.UintZero(), 352 lastMidBuyPrice: num.UintZero(), 353 lastBestBidPrice: num.UintZero(), 354 stateVarEngine: stateVarEngine, 355 marketActivityTracker: marketActivityTracker, 356 priceFactor: priceFactor, 357 assetFactor: assetFactor, 358 positionFactor: positionFactor, 359 nextMTM: time.Time{}, // default to zero time 360 maxStopOrdersPerParties: num.UintZero(), 361 stopOrders: stoporders.New(log), 362 expiringStopOrders: common.NewExpiringOrders(), 363 perp: marketType == types.MarketTypePerp, 364 referralDiscountRewardService: referralDiscountRewardService, 365 volumeDiscountService: volumeDiscountService, 366 volumeRebateService: volumeRebateService, 367 partyMarginFactor: map[string]num.Decimal{}, 368 banking: banking, 369 markPriceCalculator: common.NewCompositePriceCalculator(ctx, mkt.MarkPriceConfiguration, oracleEngine, timeService), 370 amm: ammEngine, 371 } 372 373 le := liquidation.New(log, mkt.LiquidationStrategy, mkt.GetID(), broker, book, auctionState, timeService, positionEngine, pMonitor, market.amm) 374 market.liquidation = le 375 376 market.markPriceCalculator.SetOraclePriceScalingFunc(market.scaleOracleData) 377 if fCap := mkt.TradableInstrument.Instrument.Product.Cap(); fCap != nil { 378 market.fCap = fCap 379 market.capMax, _ = num.UintFromDecimal(fCap.MaxPrice.ToDecimal().Mul(priceFactor)) 380 market.markPriceCalculator.SetMaxPriceCap(market.capMax.Clone()) 381 } 382 383 if market.IsPerp() { 384 internalCompositePriceConfig := mkt.TradableInstrument.Instrument.GetPerps().InternalCompositePriceConfig 385 if internalCompositePriceConfig != nil { 386 market.internalCompositePriceCalculator = common.NewCompositePriceCalculator(ctx, internalCompositePriceConfig, oracleEngine, timeService) 387 market.internalCompositePriceCalculator.SetOraclePriceScalingFunc(market.scaleOracleData) 388 389 market.internalCompositePriceCalculator.NotifyOnDataSourcePropagation(market.dataSourcePropagation) 390 } 391 } 392 393 market.markPriceCalculator.NotifyOnDataSourcePropagation(market.dataSourcePropagation) 394 395 // now set AMM engine on liquidity market. 396 market.liquidity.SetAMM(market.amm) 397 398 assets, _ := mkt.GetAssets() 399 market.settlementAsset = assets[0] 400 401 liquidityEngine.SetGetStaticPricesFunc(market.getBestStaticPricesDecimal) 402 403 switch marketType { 404 case types.MarketTypeFuture: 405 market.tradableInstrument.Instrument.Product.NotifyOnTradingTerminated(market.tradingTerminated) 406 market.tradableInstrument.Instrument.Product.NotifyOnSettlementData(market.settlementData) 407 case types.MarketTypePerp: 408 market.tradableInstrument.Instrument.Product.NotifyOnSettlementData(market.settlementDataPerp) 409 market.tradableInstrument.Instrument.Product.NotifyOnDataSourcePropagation(market.productDataSourcePropagation) 410 case types.MarketTypeSpot: 411 default: 412 log.Panic("unexpected market type", logging.Int("type", int(marketType))) 413 } 414 market.assetDP = uint32(assetDecimals) 415 return market, nil 416 } 417 418 func (m *Market) OnEpochEvent(ctx context.Context, epoch types.Epoch) { 419 if m.closed { 420 return 421 } 422 423 switch epoch.Action { 424 case vegapb.EpochAction_EPOCH_ACTION_START: 425 m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams) 426 m.liquidity.OnEpochStart(ctx, m.timeService.GetTimeNow(), m.markPriceCalculator.GetPrice(), m.midPrice(), m.getTargetStake(), m.positionFactor) 427 m.epoch = epoch 428 case vegapb.EpochAction_EPOCH_ACTION_END: 429 // compute parties stats for the previous epoch 430 m.onEpochEndPartiesStats() 431 if !m.finalFeesDistributed { 432 m.liquidity.OnEpochEnd(ctx, m.timeService.GetTimeNow(), epoch) 433 } 434 435 m.banking.RegisterTradingFees(ctx, m.settlementAsset, m.fee.TotalTradingFeesPerParty()) 436 437 assetQuantum, _ := m.collateral.GetAssetQuantum(m.settlementAsset) 438 feesStats := m.fee.GetFeesStatsOnEpochEnd(assetQuantum) 439 feesStats.Market = m.GetID() 440 feesStats.EpochSeq = epoch.Seq 441 442 m.broker.Send(events.NewFeesStatsEvent(ctx, feesStats)) 443 } 444 445 m.updateLiquidityFee(ctx) 446 } 447 448 func (m *Market) ClosePosition(context.Context, string) bool { 449 return true 450 } 451 452 func (m *Market) OnEpochRestore(ctx context.Context, epoch types.Epoch) { 453 m.epoch = epoch 454 m.liquidityEngine.OnEpochRestore(epoch) 455 } 456 457 func (m *Market) IsOpeningAuction() bool { 458 return m.as.IsOpeningAuction() 459 } 460 461 func (m *Market) onEpochEndPartiesStats() { 462 if m.markPriceCalculator.GetPrice() == nil { 463 // no mark price yet, so no reason to calculate any of those 464 return 465 } 466 467 if m.stats == nil { 468 m.stats = &types.MarketStats{} 469 } 470 471 m.stats.PartiesOpenNotionalVolume = map[string]*num.Uint{} 472 m.stats.PartiesTotalTradeVolume = map[string]*num.Uint{} 473 474 assetQuantum, err := m.collateral.GetAssetQuantum(m.settlementAsset) 475 if err != nil { 476 m.log.Panic("couldn't get quantum for asset", 477 logging.MarketID(m.mkt.ID), 478 logging.AssetID(m.settlementAsset), 479 ) 480 } 481 482 // first get the open interest per party 483 partiesOpenInterest := m.position.GetPartiesLowestOpenInterestForEpoch() 484 for p, oi := range partiesOpenInterest { 485 // volume 486 openInterestVolume := num.UintZero().Mul(num.NewUint(oi), m.markPriceCalculator.GetPrice()) 487 // scale to position decimal 488 scaledOpenInterest := openInterestVolume.ToDecimal().Div(m.positionFactor) 489 // apply quantum 490 m.stats.PartiesOpenNotionalVolume[p], _ = num.UintFromDecimal( 491 scaledOpenInterest.Div(assetQuantum), 492 ) 493 } 494 495 // first get the open interest per party 496 partiesTradedVolume := m.position.GetPartiesTradedVolumeForEpoch() 497 for p, oi := range partiesTradedVolume { 498 // volume 499 tradedVolume := num.UintZero().Mul(num.NewUint(oi), m.markPriceCalculator.GetPrice()) 500 // scale to position decimal 501 scaledOpenInterest := tradedVolume.ToDecimal().Div(m.positionFactor) 502 // apply quantum 503 m.stats.PartiesTotalTradeVolume[p], _ = num.UintFromDecimal( 504 scaledOpenInterest.Div(assetQuantum), 505 ) 506 } 507 } 508 509 func (m *Market) BeginBlock(ctx context.Context) { 510 if m.ensuredMigration73 { 511 return 512 } 513 m.ensuredMigration73 = true 514 515 // TODO(jeremy): remove this after the 72 upgrade 516 oevents := []events.Event{} 517 for _, oid := range m.liquidityEngine.GetLegacyOrders() { 518 order, foundOnBook, err := m.getOrderByID(oid) 519 if err != nil { 520 continue // err here is ErrOrderNotFound 521 } 522 if !foundOnBook { 523 m.log.Panic("lp order was in the pegged order list?", logging.Order(order)) 524 } 525 526 cancellation, err := m.matching.CancelOrder(order) 527 if cancellation == nil || err != nil { 528 m.log.Panic("Failure after cancel order from matching engine", 529 logging.String("party-id", order.Party), 530 logging.String("order-id", oid), 531 logging.String("market", m.mkt.ID), 532 logging.Error(err)) 533 } 534 535 _ = m.position.UnregisterOrder(ctx, order) 536 order.Status = types.OrderStatusCancelled 537 oevents = append(oevents, events.NewOrderEvent(ctx, order)) 538 } 539 540 if len(oevents) > 0 { 541 m.broker.SendBatch(oevents) 542 } 543 544 // TODO(jeremy): This bit is here specifically to create account 545 // which should have been create with the normal process of 546 // submitting liquidity provisions for the market. 547 // should probably be removed in the near future (aft this release) 548 lpParties := maps.Keys(m.liquidityEngine.ProvisionsPerParty()) 549 sort.Strings(lpParties) 550 551 for _, p := range lpParties { 552 _, err := m.collateral.GetOrCreatePartyLiquidityFeeAccount( 553 ctx, p, m.GetID(), m.GetSettlementAsset()) 554 if err != nil { 555 m.log.Panic("couldn't create party liquidity fee account") 556 } 557 } 558 559 _, err := m.collateral.GetOrCreateLiquidityFeesBonusDistributionAccount(ctx, m.GetID(), m.GetSettlementAsset()) 560 if err != nil { 561 m.log.Panic("failed to get bonus distribution account", logging.Error(err)) 562 } 563 } 564 565 func (m *Market) GetAssets() []string { 566 return []string{m.settlementAsset} 567 } 568 569 // GetPartiesStats is called at the end of the epoch, only once to 570 // be sent to the activity streak engine. This is using the calculated 571 // at the end of the epoch based on the countrer in the position engine. 572 // This is never sent into a snapshot as it relies on the order the 573 // epoch callback are executed. We expect the market OnEpoch to be called 574 // first, and compute the data, then the activity tracker callback to be 575 // called next, and retrieve the data through this method. 576 // The stats are reseted before being returned. 577 func (m *Market) GetPartiesStats() (stats *types.MarketStats) { 578 stats, m.stats = m.stats, &types.MarketStats{} 579 580 return stats 581 } 582 583 func (m *Market) IsSucceeded() bool { 584 return m.succeeded 585 } 586 587 func (m *Market) IsPerp() bool { 588 return m.perp 589 } 590 591 func (m *Market) StopSnapshots() { 592 m.matching.StopSnapshots() 593 m.position.StopSnapshots() 594 m.liquidityEngine.StopSnapshots() 595 m.settlement.StopSnapshots() 596 m.tsCalc.StopSnapshots() 597 m.liquidation.StopSnapshots() 598 } 599 600 func (m *Market) Mkt() *types.Market { 601 return m.mkt 602 } 603 604 func (m *Market) GetEquityShares() *common.EquityShares { 605 return m.equityShares 606 } 607 608 func (m *Market) GetEquitySharesForParty(partyID string) num.Decimal { 609 primary := m.equityShares.SharesFromParty(partyID) 610 if sub, err := m.amm.GetAMMParty(partyID); err == nil { 611 return primary.Add(m.equityShares.SharesFromParty(sub)) 612 } 613 return primary 614 } 615 616 func (m *Market) ResetParentIDAndInsurancePoolFraction() { 617 m.mkt.ParentMarketID = "" 618 m.mkt.InsurancePoolFraction = num.DecimalZero() 619 } 620 621 func (m *Market) GetParentMarketID() string { 622 return m.mkt.ParentMarketID 623 } 624 625 func (m *Market) GetInsurancePoolFraction() num.Decimal { 626 return m.mkt.InsurancePoolFraction 627 } 628 629 func (m *Market) SetSucceeded() { 630 m.succeeded = true 631 } 632 633 func (m *Market) SetNextInternalCompositePriceCalc(tm time.Time) { 634 m.nextInternalCompositePriceCalc = tm 635 } 636 637 func (m *Market) SetNextMTM(tm time.Time) { 638 m.nextMTM = tm 639 } 640 641 func (m *Market) GetNextMTM() time.Time { 642 return m.nextMTM 643 } 644 645 func (m *Market) GetSettlementAsset() string { 646 return m.settlementAsset 647 } 648 649 func (m *Market) Update(ctx context.Context, config *types.Market, oracleEngine products.OracleEngine) error { 650 tickSizeChanged := config.TickSize.NEQ(m.mkt.TickSize) 651 652 config.TradingMode = m.mkt.TradingMode 653 config.State = m.mkt.State 654 config.MarketTimestamps = m.mkt.MarketTimestamps 655 recalcMargins := !config.TradableInstrument.RiskModel.Equal(m.mkt.TradableInstrument.RiskModel) 656 // update the liquidation strategy if required, ideally we want to use .LiquidationStrategy.EQ(), but that breaks the integration tests 657 // as the market config pointer is shared 658 if config.LiquidationStrategy != nil { 659 m.liquidation.Update(config.LiquidationStrategy) 660 } 661 m.mkt = config 662 assets, _ := config.GetAssets() 663 m.settlementAsset = assets[0] 664 665 if err := m.tradableInstrument.UpdateInstrument(ctx, m.log, m.mkt.TradableInstrument, m.GetID(), oracleEngine, m.broker); err != nil { 666 return err 667 } 668 m.risk.UpdateModel(m.stateVarEngine, m.tradableInstrument.MarginCalculator, m.tradableInstrument.RiskModel, m.mkt.LinearSlippageFactor, m.mkt.QuadraticSlippageFactor) 669 m.settlement.UpdateProduct(m.tradableInstrument.Instrument.Product) 670 m.tsCalc.UpdateParameters(*m.mkt.LiquidityMonitoringParameters.TargetStakeParameters) 671 m.pMonitor.UpdateSettings(m.tradableInstrument.RiskModel, m.mkt.PriceMonitoringSettings, m.as) 672 m.liquidity.UpdateMarketConfig(m.tradableInstrument.RiskModel, m.pMonitor) 673 m.amm.UpdateAllowedEmptyLevels(m.mkt.AllowedEmptyAmmLevels) 674 675 if err := m.markPriceCalculator.UpdateConfig(ctx, oracleEngine, m.mkt.MarkPriceConfiguration); err != nil { 676 m.markPriceCalculator.SetOraclePriceScalingFunc(m.scaleOracleData) 677 return err 678 } 679 if m.IsPerp() { 680 internalCompositePriceConfig := m.mkt.TradableInstrument.Instrument.GetPerps().InternalCompositePriceConfig 681 if internalCompositePriceConfig == nil && m.internalCompositePriceCalculator != nil { 682 // unsubscribe existing oracles if any 683 m.internalCompositePriceCalculator.UpdateConfig(ctx, oracleEngine, nil) 684 m.internalCompositePriceCalculator = nil 685 } else if m.internalCompositePriceCalculator != nil { 686 // there was previously a intenal composite price calculator 687 if err := m.internalCompositePriceCalculator.UpdateConfig(ctx, oracleEngine, internalCompositePriceConfig); err != nil { 688 m.internalCompositePriceCalculator.SetOraclePriceScalingFunc(m.scaleOracleData) 689 return err 690 } 691 } else if internalCompositePriceConfig != nil { 692 // it's a new index calculator 693 m.internalCompositePriceCalculator = common.NewCompositePriceCalculator(ctx, internalCompositePriceConfig, oracleEngine, m.timeService) 694 m.internalCompositePriceCalculator.SetOraclePriceScalingFunc(m.scaleOracleData) 695 } 696 } 697 698 // we should not need to rebind a replacement oracle here, the m.tradableInstrument.UpdateInstrument 699 // call handles the callbacks for us. We only need to check the market state and unbind if needed 700 switch m.mkt.State { 701 case types.MarketStateTradingTerminated: 702 if !m.perp { 703 m.tradableInstrument.Instrument.UnsubscribeTradingTerminated(ctx) 704 // never hurts to check margins on a terminated, but unsettled market 705 recalcMargins = true 706 } 707 case types.MarketStateSettled: 708 // market is settled, unsubscribe all 709 m.tradableInstrument.Instrument.Unsubscribe(ctx) 710 } 711 if tickSizeChanged { 712 peggedOrders := m.matching.GetActivePeggedOrderIDs() 713 peggedOrders = append(peggedOrders, m.peggedOrders.GetParkedIDs()...) 714 715 tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor)) 716 for _, po := range peggedOrders { 717 order, err := m.matching.GetOrderByID(po) 718 if err != nil { 719 order = m.peggedOrders.GetParkedByID(po) 720 if order == nil { 721 continue 722 } 723 } 724 offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor)) 725 if !num.UintZero().Mod(order.PeggedOrder.Offset, m.mkt.TickSize).IsZero() || 726 (order.PeggedOrder.Reference == types.PeggedReferenceMid && offsetInAsset.IsZero() && tickSizeInAsset.IsZero()) { 727 m.cancelOrder(ctx, order.Party, order.ID) 728 } 729 } 730 } 731 m.updateLiquidityFee(ctx) 732 // risk model hasn't changed -> return 733 if !recalcMargins { 734 return nil 735 } 736 // We know the risk model has been updated, so we have to recalculate margin requirements 737 m.recheckMargin(ctx, m.position.Positions()) 738 739 // update immediately during opening auction 740 if m.as.IsOpeningAuction() { 741 m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams) 742 } 743 744 return nil 745 } 746 747 func (m *Market) IntoType() types.Market { 748 return *m.mkt.DeepClone() 749 } 750 751 func (m *Market) Hash() []byte { 752 mID := logging.String("market-id", m.GetID()) 753 matchingHash := m.matching.Hash() 754 m.log.Debug("orderbook state hash", logging.Hash(matchingHash), mID) 755 756 positionHash := m.position.Hash() 757 m.log.Debug("positions state hash", logging.Hash(positionHash), mID) 758 759 return crypto.Hash(append(matchingHash, positionHash...)) 760 } 761 762 func (m *Market) GetMarketState() types.MarketState { 763 return m.mkt.State 764 } 765 766 // priceToMarketPrecision 767 // It should never return a nil pointer. 768 func (m *Market) priceToMarketPrecision(price *num.Uint) *num.Uint { 769 p, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) 770 return p 771 } 772 773 func (m *Market) midPrice() *num.Uint { 774 bestBidPrice, _, _ := m.matching.BestBidPriceAndVolume() 775 bestOfferPrice, _, _ := m.matching.BestOfferPriceAndVolume() 776 two := num.NewUint(2) 777 midPrice := num.UintZero() 778 if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() { 779 midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two) 780 } 781 return midPrice 782 } 783 784 func (m *Market) GetMarketData() types.MarketData { 785 bestBidPrice, bestBidVolume, _ := m.matching.BestBidPriceAndVolume() 786 bestOfferPrice, bestOfferVolume, _ := m.matching.BestOfferPriceAndVolume() 787 bestStaticBidPrice, bestStaticBidVolume, _ := m.getBestStaticBidPriceAndVolume() 788 bestStaticOfferPrice, bestStaticOfferVolume, _ := m.getBestStaticAskPriceAndVolume() 789 790 // Auction related values 791 indicativePrice := num.UintZero() 792 indicativeVolume := uint64(0) 793 var auctionStart, auctionEnd int64 794 if m.as.InAuction() { 795 indicativePrice, indicativeVolume, _ = m.matching.GetIndicativePriceAndVolume() 796 if t := m.as.Start(); !t.IsZero() { 797 auctionStart = t.UnixNano() 798 } 799 if t := m.as.ExpiresAt(); t != nil { 800 auctionEnd = t.UnixNano() 801 } 802 } 803 804 // If we do not have one of the best_* prices, leave the mid price as zero 805 two := num.NewUint(2) 806 midPrice := num.UintZero() 807 if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() { 808 midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two) 809 } 810 811 staticMidPrice := num.UintZero() 812 if !bestStaticBidPrice.IsZero() && !bestStaticOfferPrice.IsZero() { 813 staticMidPrice = staticMidPrice.Div(num.Sum(bestStaticBidPrice, bestStaticOfferPrice), two) 814 } 815 816 var targetStake string 817 if m.as.InAuction() { 818 targetStake = m.getTheoreticalTargetStake().String() 819 } else { 820 targetStake = m.getTargetStake().String() 821 } 822 bounds := m.pMonitor.GetBounds() 823 for _, b := range bounds { 824 b.MaxValidPrice = m.priceToMarketPrecision(b.MaxValidPrice) // effictively floors this 825 b.MinValidPrice = m.priceToMarketPrecision(b.MinValidPrice) 826 827 rp, _ := num.UintFromDecimal(b.ReferencePrice) 828 rp = m.priceToMarketPrecision(rp) 829 b.ReferencePrice = num.DecimalFromUint(rp) 830 831 if m.priceFactor.GreaterThan(num.DecimalOne()) { 832 b.MinValidPrice.AddSum(common.One) // ceil 833 } 834 } 835 mode := m.as.Mode() 836 if m.mkt.TradingMode == types.MarketTradingModeNoTrading { 837 mode = m.mkt.TradingMode 838 } 839 840 var internalCompositePrice *num.Uint 841 var nextInternalCompositePriceCalc int64 842 var internalCompositePriceType vega.CompositePriceType 843 var internalCompositePriceState *types.CompositePriceState 844 pd := m.tradableInstrument.Instrument.Product.GetData(m.timeService.GetTimeNow().UnixNano()) 845 if m.perp && pd != nil { 846 if m.internalCompositePriceCalculator != nil { 847 internalCompositePriceState = m.internalCompositePriceCalculator.GetData() 848 internalCompositePriceType = m.internalCompositePriceCalculator.GetConfig().CompositePriceType 849 internalCompositePrice = m.internalCompositePriceCalculator.GetPrice() 850 if internalCompositePrice == nil { 851 internalCompositePrice = num.UintZero() 852 } else { 853 internalCompositePrice = m.priceToMarketPrecision(internalCompositePrice) 854 } 855 nextInternalCompositePriceCalc = m.nextInternalCompositePriceCalc.UnixNano() 856 } else { 857 internalCompositePriceState = m.markPriceCalculator.GetData() 858 internalCompositePriceType = m.markPriceCalculator.GetConfig().CompositePriceType 859 internalCompositePrice = m.priceToMarketPrecision(m.getCurrentMarkPrice()) 860 nextInternalCompositePriceCalc = m.nextMTM.UnixNano() 861 } 862 perpData := pd.Data.(*types.PerpetualData) 863 perpData.InternalCompositePrice = internalCompositePrice 864 perpData.NextInternalCompositePriceCalc = nextInternalCompositePriceCalc 865 perpData.InternalCompositePriceType = internalCompositePriceType 866 perpData.InternalCompositePriceState = internalCompositePriceState 867 } 868 869 md := types.MarketData{ 870 Market: m.GetID(), 871 BestBidPrice: m.priceToMarketPrecision(bestBidPrice), 872 BestBidVolume: bestBidVolume, 873 BestOfferPrice: m.priceToMarketPrecision(bestOfferPrice), 874 BestOfferVolume: bestOfferVolume, 875 BestStaticBidPrice: m.priceToMarketPrecision(bestStaticBidPrice), 876 BestStaticBidVolume: bestStaticBidVolume, 877 BestStaticOfferPrice: m.priceToMarketPrecision(bestStaticOfferPrice), 878 BestStaticOfferVolume: bestStaticOfferVolume, 879 MidPrice: m.priceToMarketPrecision(midPrice), 880 StaticMidPrice: m.priceToMarketPrecision(staticMidPrice), 881 MarkPrice: m.priceToMarketPrecision(m.getCurrentMarkPrice()), 882 LastTradedPrice: m.priceToMarketPrecision(m.getLastTradedPrice()), 883 Timestamp: m.timeService.GetTimeNow().UnixNano(), 884 OpenInterest: m.position.GetOpenInterest(), 885 IndicativePrice: m.priceToMarketPrecision(indicativePrice), 886 IndicativeVolume: indicativeVolume, 887 AuctionStart: auctionStart, 888 AuctionEnd: auctionEnd, 889 MarketTradingMode: mode, 890 MarketState: m.mkt.State, 891 Trigger: m.as.Trigger(), 892 ExtensionTrigger: m.as.ExtensionTrigger(), 893 TargetStake: targetStake, 894 SuppliedStake: m.getSuppliedStake().String(), 895 PriceMonitoringBounds: bounds, 896 MarketValueProxy: m.lastMarketValueProxy.BigInt().String(), 897 LiquidityProviderFeeShare: m.equityShares.LpsToLiquidityProviderFeeShare(m.liquidityEngine.GetAverageLiquidityScores()), 898 LiquidityProviderSLA: m.liquidityEngine.LiquidityProviderSLAStats(m.timeService.GetTimeNow()), 899 NextMTM: m.nextMTM.UnixNano(), 900 MarketGrowth: m.equityShares.GetMarketGrowth(), 901 ProductData: pd, 902 NextNetClose: m.liquidation.GetNextCloseoutTS(), 903 MarkPriceType: m.markPriceCalculator.GetConfig().CompositePriceType, 904 MarkPriceState: m.markPriceCalculator.GetData(), 905 } 906 return md 907 } 908 909 // ReloadConf will trigger a reload of all the config settings in the market and all underlying engines 910 // this is required when hot-reloading any config changes, eg. logger level. 911 func (m *Market) ReloadConf( 912 matchingConfig matching.Config, 913 riskConfig risk.Config, 914 positionConfig positions.Config, 915 settlementConfig settlement.Config, 916 feeConfig fee.Config, 917 ) { 918 m.log.Info("reloading configuration") 919 m.matching.ReloadConf(matchingConfig) 920 m.risk.ReloadConf(riskConfig) 921 m.position.ReloadConf(positionConfig) 922 m.settlement.ReloadConf(settlementConfig) 923 m.fee.ReloadConf(feeConfig) 924 } 925 926 func (m *Market) Reject(ctx context.Context) error { 927 if !m.canReject() { 928 return common.ErrCannotRejectMarketNotInProposedState 929 } 930 931 // we closed all parties accounts 932 m.cleanupOnReject(ctx) 933 m.mkt.State = types.MarketStateRejected 934 m.mkt.TradingMode = types.MarketTradingModeNoTrading 935 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 936 937 return nil 938 } 939 940 func (m *Market) canReject() bool { 941 if m.mkt.State == types.MarketStateProposed { 942 return true 943 } 944 if len(m.mkt.ParentMarketID) == 0 { 945 return false 946 } 947 // parent market is set, market can be in pending state when it is rejected. 948 return m.mkt.State == types.MarketStatePending 949 } 950 951 func (m *Market) onTxProcessed() { 952 m.risk.FlushMarginLevelsEvents() 953 } 954 955 // CanLeaveOpeningAuction checks if the market can leave the opening auction based on whether floating point consensus has been reached on all 3 vars. 956 func (m *Market) CanLeaveOpeningAuction() bool { 957 boundFactorsInitialised := m.pMonitor.IsBoundFactorsInitialised() 958 potInitialised := m.liquidity.IsProbabilityOfTradingInitialised() 959 riskFactorsInitialised := m.risk.IsRiskFactorInitialised() 960 canLeave := boundFactorsInitialised && riskFactorsInitialised && potInitialised 961 if !canLeave { 962 m.log.Info("Cannot leave opening auction", logging.String("market", m.mkt.ID), logging.Bool("bound-factors-initialised", boundFactorsInitialised), logging.Bool("risk-factors-initialised", riskFactorsInitialised)) 963 } 964 return canLeave 965 } 966 967 func (m *Market) InheritParent(ctx context.Context, pstate *types.CPMarketState) { 968 // parent is in opening auction, do not inherit any state 969 if pstate.State == types.MarketStatePending { 970 return 971 } 972 // add the trade value from the parent 973 m.feeSplitter.SetTradeValue(pstate.LastTradeValue) 974 m.equityShares.InheritELS(pstate.Shares) 975 } 976 977 func (m *Market) RestoreELS(ctx context.Context, pstate *types.CPMarketState) { 978 m.equityShares.RestoreELS(pstate.Shares) 979 } 980 981 func (m *Market) RollbackInherit(ctx context.Context) { 982 // the InheritParent call has to be made before checking if the market can leave opening auction 983 // if the market did not leave opening auction, market state needs to be resored to what it was 984 // before the call to InheritParent was made. Market is still in opening auction, therefore 985 // feeSplitter trade value is zero, and equity shares are linear stake/vstake/ELS 986 // do make sure this call is not made when the market is active 987 if m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateProposed { 988 m.feeSplitter.SetTradeValue(num.UintZero()) 989 m.equityShares.RollbackParentELS() 990 } 991 } 992 993 func (m *Market) StartOpeningAuction(ctx context.Context) error { 994 if m.mkt.State != types.MarketStateProposed { 995 return common.ErrCannotStartOpeningAuctionForMarketNotInProposedState 996 } 997 998 defer m.onTxProcessed() 999 1000 // now we start the opening auction 1001 if m.as.AuctionStart() { 1002 // we are now in a pending state 1003 m.mkt.State = types.MarketStatePending 1004 // this should no longer be needed 1005 // m.mkt.MarketTimestamps.Pending = m.timeService.GetTimeNow().UnixNano() 1006 m.mkt.TradingMode = types.MarketTradingModeOpeningAuction 1007 m.enterAuction(ctx) 1008 } else { 1009 // TODO(): to be removed once we don't have market starting 1010 // without an opening auction - this is only used in unit tests 1011 // validation on the proposal ensures opening auction duration is always >= 1 (or whatever the min duration is) 1012 m.mkt.State = types.MarketStateActive 1013 m.mkt.TradingMode = types.MarketTradingModeContinuous 1014 } 1015 1016 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1017 return nil 1018 } 1019 1020 // GetID returns the id of the given market. 1021 func (m *Market) GetID() string { 1022 return m.mkt.ID 1023 } 1024 1025 func (m *Market) PostRestore(ctx context.Context) error { 1026 // tell the matching engine about the markets price factor so it can finish restoring orders 1027 m.matching.RestoreWithMarketPriceFactor(m.priceFactor) 1028 1029 // if loading from an old snapshot we're restoring positions using the position engine 1030 if m.marketActivityTracker.NeedsInitialisation(m.settlementAsset, m.mkt.ID) { 1031 for _, mp := range m.position.Positions() { 1032 if mp.Size() != 0 { 1033 m.marketActivityTracker.RestorePosition(m.settlementAsset, mp.Party(), m.mkt.ID, mp.Size(), mp.Price(), m.positionFactor) 1034 } 1035 } 1036 } 1037 m.marketActivityTracker.RestoreMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice()) 1038 1039 // Disposal slippage was set as part of this upgrade, send event to ensure datanode is updated. 1040 if vegacontext.InProgressUpgradeFromMultiple(ctx, "v0.75.8", "v0.75.7") { 1041 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1042 } 1043 1044 return nil 1045 } 1046 1047 // OnTick notifies the market of a new time event/update. 1048 // todo: make this a more generic function name e.g. OnTimeUpdateEvent 1049 func (m *Market) OnTick(ctx context.Context, t time.Time) bool { 1050 defer m.onTxProcessed() 1051 1052 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "OnTick") 1053 m.mu.Lock() 1054 defer m.mu.Unlock() 1055 1056 _, blockHash := vegacontext.TraceIDFromContext(ctx) 1057 // make deterministic ID for this market, concatenate 1058 // the block hash and the market ID 1059 m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID())) 1060 // and we call next ID on this directly just so we don't have an ID which have 1061 // a different from others, we basically burn the first ID. 1062 _ = m.idgen.NextID() 1063 defer func() { m.idgen = nil }() 1064 1065 if m.closed { 1066 return true 1067 } 1068 1069 // first we check if we should reduce the network position, then we expire orders 1070 if !m.closed && m.canTrade() { 1071 m.checkNetwork(ctx, t) 1072 expired := m.removeExpiredOrders(ctx, t.UnixNano()) 1073 metrics.OrderGaugeAdd(-len(expired), m.GetID()) 1074 confirmations := m.removeExpiredStopOrders(ctx, t.UnixNano(), m.idgen) 1075 1076 stopsExpired := 0 1077 for _, v := range confirmations { 1078 stopsExpired++ 1079 for _, v := range v.PassiveOrdersAffected { 1080 if v.Status != types.OrderStatusActive { 1081 stopsExpired++ 1082 } 1083 } 1084 } 1085 metrics.OrderGaugeAdd(-stopsExpired, m.GetID()) 1086 } 1087 1088 // some engines still needs to get updates: 1089 m.pMonitor.OnTimeUpdate(t) 1090 m.feeSplitter.SetCurrentTime(t) 1091 1092 // TODO(): This also assume that the market is not 1093 // being closed before the market is leaving 1094 // the opening auction, but settlement at expiry is 1095 // not even specced or implemented as of now... 1096 // if the state of the market is just PROPOSED, 1097 // we will just skip everything there as nothing apply. 1098 if m.mkt.State == types.MarketStateProposed { 1099 return false 1100 } 1101 1102 // if trading is terminated, we have nothing to do here. 1103 // we just need to wait for the settlementData to arrive through oracle 1104 if m.mkt.State == types.MarketStateTradingTerminated { 1105 return false 1106 } 1107 1108 m.liquidity.OnTick(ctx, t) 1109 m.amm.OnTick(ctx, t) 1110 1111 // check auction, if any. If we leave auction, MTM is performed in this call 1112 m.checkAuction(ctx, t, m.idgen) 1113 // check the position of the network, may place orders to close the network out 1114 timer.EngineTimeCounterAdd() 1115 1116 m.updateMarketValueProxy() 1117 m.updateLiquidityFee(ctx) 1118 m.broker.Send(events.NewMarketTick(ctx, m.mkt.ID, t)) 1119 return m.closed 1120 } 1121 1122 // BlockEnd notifies the market of the end of the block. 1123 func (m *Market) BlockEnd(ctx context.Context) { 1124 defer m.onTxProcessed() 1125 t := m.timeService.GetTimeNow() 1126 if m.mkt.State == types.MarketStateProposed || 1127 m.mkt.State == types.MarketStateCancelled || 1128 m.mkt.State == types.MarketStateRejected || 1129 m.mkt.State == types.MarketStateSettled { 1130 if (m.nextMTM.IsZero() || !m.nextMTM.After(t)) && !m.as.InAuction() { 1131 m.nextMTM = t.Add(m.mtmDelta) 1132 } 1133 return 1134 } 1135 1136 // MTM if enough time has elapsed, we are not in auction, and we have a non-zero mark price. 1137 // we MTM in leaveAuction before deploying LP orders like we did before, but we do update nextMTM there 1138 var tID string 1139 ctx, tID = vegacontext.TraceIDFromContext(ctx) 1140 m.idgen = idgeneration.New(tID + crypto.HashStrToHex("blockend"+m.GetID())) 1141 defer func() { 1142 m.idgen = nil 1143 }() 1144 1145 if !m.as.InAuction() { 1146 m.markPriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t.UnixNano(), m.matching) 1147 if m.internalCompositePriceCalculator != nil { 1148 m.internalCompositePriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t.UnixNano(), m.matching) 1149 } 1150 } 1151 1152 // if we do have a separate configuration for the intenal composite price and we have a new intenal composite price we push it to the perp 1153 if m.internalCompositePriceCalculator != nil && (m.nextInternalCompositePriceCalc.IsZero() || 1154 !m.nextInternalCompositePriceCalc.After(t) && 1155 !m.as.InAuction()) { 1156 prevInternalCompositePrice := m.internalCompositePriceCalculator.GetPrice() 1157 m.internalCompositePriceCalculator.CalculateMarkPrice( 1158 ctx, 1159 m.pMonitor, 1160 m.as, 1161 t.UnixNano(), 1162 m.matching, 1163 m.mtmDelta, 1164 m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, false, false) 1165 m.nextInternalCompositePriceCalc = t.Add(m.internalCompositePriceFrequency) 1166 if (prevInternalCompositePrice == nil || !m.internalCompositePriceCalculator.GetPrice().EQ(prevInternalCompositePrice) || m.settlement.HasTraded()) && 1167 !m.getCurrentInternalCompositePrice().IsZero() { 1168 m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentInternalCompositePrice(), m.timeService.GetTimeNow().UnixNano()) 1169 } 1170 } 1171 1172 // if it's time for mtm, let's do it 1173 if (m.nextMTM.IsZero() || !m.nextMTM.After(t)) && !m.as.InAuction() { 1174 prevMarkPrice := m.markPriceCalculator.GetPrice() 1175 m.markPriceLock.Lock() 1176 _, err := m.markPriceCalculator.CalculateMarkPrice( 1177 ctx, 1178 m.pMonitor, 1179 m.as, 1180 t.UnixNano(), 1181 m.matching, 1182 m.mtmDelta, 1183 m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, 1184 m.mkt.LinearSlippageFactor, 1185 m.risk.GetRiskFactors().Short, 1186 m.risk.GetRiskFactors().Long, 1187 true, false) 1188 m.markPriceLock.Unlock() 1189 m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice()) 1190 if err != nil { 1191 // start the monitoring auction if required 1192 if m.as.AuctionStart() { 1193 m.enterAuction(ctx) 1194 } 1195 } else { 1196 // if we don't have an alternative configuration (and schedule) for the mark price the we push the mark price to the perp as a new datapoint 1197 // on the standard mark price 1198 if m.internalCompositePriceCalculator == nil && m.perp && 1199 (prevMarkPrice == nil || !m.markPriceCalculator.GetPrice().EQ(prevMarkPrice) || m.settlement.HasTraded()) && 1200 !m.getCurrentMarkPrice().IsZero() { 1201 m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentMarkPrice(), m.timeService.GetTimeNow().UnixNano()) 1202 } 1203 } 1204 m.nextMTM = t.Add(m.mtmDelta) 1205 1206 // mark price mustn't be zero, except for capped futures, where a zero price may well be possible 1207 if !m.as.InAuction() && (prevMarkPrice == nil || !m.markPriceCalculator.GetPrice().EQ(prevMarkPrice) || m.settlement.HasTraded()) && 1208 (!m.getCurrentMarkPrice().IsZero() || m.capMax != nil) { 1209 if m.confirmMTM(ctx, false) { 1210 closedPositions := m.position.GetClosedPositions() 1211 if len(closedPositions) > 0 { 1212 m.releaseExcessMargin(ctx, closedPositions...) 1213 // also remove all stop orders 1214 m.removeAllStopOrders(ctx, closedPositions...) 1215 } 1216 } else { 1217 // do not increment the next MTM time to now + delta 1218 // this ensures we try to perform the MTM settlement ASAP. 1219 m.nextMTM = t 1220 } 1221 } 1222 } 1223 1224 m.releaseExcessMargin(ctx, m.position.Positions()...) 1225 // send position events 1226 m.position.FlushPositionEvents(ctx) 1227 1228 var markPriceCopy *num.Uint 1229 if m.markPriceCalculator.GetPrice() != nil { 1230 markPriceCopy = m.markPriceCalculator.GetPrice().Clone() 1231 } 1232 m.liquidity.EndBlock(markPriceCopy, m.midPrice(), m.positionFactor) 1233 1234 if !m.matching.CheckBook() { 1235 m.log.Panic("ontick book has orders pegged to nothing") 1236 } 1237 } 1238 1239 func (m *Market) removeAllStopOrders( 1240 ctx context.Context, 1241 positions ...events.MarketPosition, 1242 ) { 1243 evts := []events.Event{} 1244 1245 for _, v := range positions { 1246 sos, _ := m.stopOrders.Cancel(v.Party(), "") 1247 for _, so := range sos { 1248 if so.Expiry.Expires() { 1249 _ = m.expiringStopOrders.RemoveOrder(so.Expiry.ExpiresAt.UnixNano(), so.ID) 1250 } 1251 evts = append(evts, events.NewStopOrderEvent(ctx, so)) 1252 } 1253 } 1254 1255 if len(evts) > 0 { 1256 m.broker.SendBatch(evts) 1257 } 1258 } 1259 1260 func (m *Market) updateMarketValueProxy() { 1261 // if windows length is reached, reset fee splitter 1262 if mvwl := m.marketValueWindowLength; m.feeSplitter.Elapsed() > mvwl { 1263 // AvgTradeValue calculates the rolling average trade value to include the current window (which is ending) 1264 m.equityShares.AvgTradeValue(m.feeSplitter.AvgTradeValue()) 1265 // this increments the internal window counter 1266 m.feeSplitter.TimeWindowStart(m.timeService.GetTimeNow()) 1267 // m.equityShares.UpdateVirtualStake() // this should always set the vStake >= physical stake? 1268 } 1269 1270 // TODO karel - do we still need to calculate the market value proxy???? 1271 // these need to happen every block 1272 // but also when new LP is submitted just so we are sure we do 1273 // not have a mvp of 0 1274 // ts := m.liquidity.Stake 1275 // m.lastMarketValueProxy = m.feeSplitter.MarketValueProxy( 1276 // m.marketValueWindowLength, ts) 1277 } 1278 1279 func (m *Market) removeOrders(ctx context.Context) { 1280 // remove all order from the book 1281 // and send events with the stopped status 1282 orders := append(m.matching.Settled(), m.peggedOrders.Settled()...) 1283 orderEvents := make([]events.Event, 0, len(orders)) 1284 for _, v := range orders { 1285 orderEvents = append(orderEvents, events.NewOrderEvent(ctx, v)) 1286 } 1287 1288 m.broker.SendBatch(orderEvents) 1289 } 1290 1291 func (m *Market) cleanMarketWithState(ctx context.Context, mktState types.MarketState) error { 1292 parties := make([]string, 0, len(m.parties)) 1293 for k := range m.parties { 1294 parties = append(parties, k) 1295 } 1296 1297 // insurance pool has to be preserved in case a successor market leaves opening auction 1298 // the insurance pool must be preserved if a market is settled or was closed through governance 1299 keepInsurance := (mktState == types.MarketStateSettled || mktState == types.MarketStateClosed) && !m.succeeded 1300 sort.Strings(parties) 1301 clearMarketTransfers, err := m.collateral.ClearMarket(ctx, m.GetID(), m.settlementAsset, parties, keepInsurance) 1302 if err != nil { 1303 m.log.Error("Clear market error", 1304 logging.MarketID(m.GetID()), 1305 logging.Error(err)) 1306 return err 1307 } 1308 1309 // unregister state-variables 1310 m.stateVarEngine.UnregisterStateVariable(m.settlementAsset, m.mkt.ID) 1311 1312 if len(clearMarketTransfers) > 0 { 1313 m.broker.Send(events.NewLedgerMovements(ctx, clearMarketTransfers)) 1314 } 1315 1316 m.markPriceCalculator.Close(ctx) 1317 if m.internalCompositePriceCalculator != nil { 1318 m.internalCompositePriceCalculator.Close(ctx) 1319 } 1320 1321 if err := m.amm.MarketClosing(ctx); err != nil { 1322 return err 1323 } 1324 1325 m.mkt.State = mktState 1326 m.mkt.TradingMode = types.MarketTradingModeNoTrading 1327 m.mkt.MarketTimestamps.Close = m.timeService.GetTimeNow().UnixNano() 1328 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1329 1330 return nil 1331 } 1332 1333 func (m *Market) closeCancelledMarket(ctx context.Context) error { 1334 // we got here because trading was terminated, so we've already unsubscribed that oracle data source. 1335 m.tradableInstrument.Instrument.UnsubscribeSettlementData(ctx) 1336 1337 if err := m.cleanMarketWithState(ctx, types.MarketStateCancelled); err != nil { 1338 return err 1339 } 1340 1341 m.liquidity.StopAllLiquidityProvision(ctx) 1342 1343 m.closed = true 1344 1345 return nil 1346 } 1347 1348 func (m *Market) recordPositionActivity(t *types.Transfer) { 1349 if t == nil || t.Amount == nil || t.Amount.Amount == nil { 1350 return 1351 } 1352 amt := t.Amount.Amount.ToDecimal() 1353 if t.Type == types.TransferTypeMTMLoss || t.Type == types.TransferTypePerpFundingLoss { 1354 amt = t.Amount.Amount.ToDecimal().Mul(num.DecimalMinusOne()) 1355 } 1356 if t.Type == types.TransferTypeMTMWin || t.Type == types.TransferTypeMTMLoss || 1357 t.Type == types.TransferTypePerpFundingWin || t.Type == types.TransferTypePerpFundingLoss { 1358 m.marketActivityTracker.RecordM2M(m.settlementAsset, t.Owner, m.mkt.ID, amt.Div(m.assetFactor)) 1359 } 1360 if t.Type == types.TransferTypePerpFundingWin || t.Type == types.TransferTypePerpFundingLoss { 1361 m.marketActivityTracker.RecordFundingPayment(m.settlementAsset, t.Owner, m.mkt.ID, amt) 1362 } 1363 } 1364 1365 func (m *Market) closeMarket(ctx context.Context, t time.Time, finalState types.MarketState, settlementPriceInAsset *num.Uint) error { 1366 positions, round, err := m.settlement.Settle(t, settlementPriceInAsset) 1367 if err != nil { 1368 m.log.Error("Failed to get settle positions on market closed", 1369 logging.Error(err)) 1370 1371 return err 1372 } 1373 1374 for _, t := range positions { 1375 m.recordPositionActivity(t) 1376 } 1377 1378 transfers, err := m.collateral.FinalSettlement(ctx, m.GetID(), positions, round, m.useGeneralAccountForMarginSearch) 1379 if err != nil { 1380 m.log.Error("Failed to get ledger movements after settling closed market", 1381 logging.MarketID(m.GetID()), 1382 logging.Error(err)) 1383 return err 1384 } 1385 1386 m.tradableInstrument.Instrument.UnsubscribeSettlementData(ctx) 1387 // @TODO pass in correct context -> Previous or next block? 1388 // Which is most appropriate here? 1389 // this will be next block 1390 if len(transfers) > 0 { 1391 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 1392 } 1393 1394 // final distribution of liquidity fees 1395 if !m.finalFeesDistributed { 1396 if err := m.liquidity.AllocateFees(ctx); err != nil { 1397 m.log.Panic("failed to allocate liquidity provision fees", logging.Error(err)) 1398 } 1399 1400 m.liquidity.OnEpochEnd(ctx, t, m.epoch) 1401 m.finalFeesDistributed = true 1402 } 1403 1404 err = m.cleanMarketWithState(ctx, finalState) 1405 if err != nil { 1406 return err 1407 } 1408 1409 m.removeOrders(ctx) 1410 1411 m.liquidity.StopAllLiquidityProvision(ctx) 1412 1413 return nil 1414 } 1415 1416 func (m *Market) unregisterAndReject(ctx context.Context, order *types.Order, err error) error { 1417 // in case the order was reduce only 1418 order.ClearUpExtraRemaining() 1419 1420 _ = m.position.UnregisterOrder(ctx, order) 1421 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 1422 order.Status = types.OrderStatusRejected 1423 if oerr, ok := types.IsOrderError(err); ok { 1424 // the order wasn't invalid, so stopped is a better status, rather than rejected. 1425 if types.IsStoppingOrder(oerr) { 1426 order.Status = types.OrderStatusStopped 1427 } 1428 order.Reason = oerr 1429 } else { 1430 // should not happened but still... 1431 order.Reason = types.OrderErrorInternalError 1432 } 1433 m.broker.Send(events.NewOrderEvent(ctx, order)) 1434 if m.log.GetLevel() == logging.DebugLevel { 1435 m.log.Debug("Failure after submitting order to matching engine", 1436 logging.Order(*order), 1437 logging.Error(err)) 1438 } 1439 return err 1440 } 1441 1442 func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) { 1443 if m.as.InAuction() { 1444 return num.UintZero(), common.ErrCannotRepriceDuringAuction 1445 } 1446 1447 var ( 1448 err error 1449 price *num.Uint 1450 ) 1451 1452 switch order.PeggedOrder.Reference { 1453 case types.PeggedReferenceMid: 1454 price, err = m.getStaticMidPrice(order.Side) 1455 case types.PeggedReferenceBestBid: 1456 price, err = m.getBestStaticBidPrice() 1457 case types.PeggedReferenceBestAsk: 1458 price, err = m.getBestStaticAskPrice() 1459 } 1460 if err != nil { 1461 return num.UintZero(), common.ErrUnableToReprice 1462 } 1463 1464 // we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly 1465 inMarketD := price.ToDecimal().Div(m.priceFactor) 1466 1467 // if there are any decimals (because its an AMM price) then rounding down on a sell with 0 offset could cause a crossed book if the spread is small 1468 if order.PeggedOrder.Reference == types.PeggedReferenceBestAsk { 1469 inMarketD = inMarketD.Ceil() 1470 } 1471 priceInMarket, _ := num.UintFromDecimal(inMarketD) 1472 1473 // if the pegged offset is zero and the reference price is non-tick size (from an AMM) then we have to move it so it 1474 // is otherwise the pegged will cross. 1475 if order.PeggedOrder.Offset.IsZero() { 1476 if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() { 1477 if order.Side == types.SideBuy { 1478 priceInMarket.Sub(priceInMarket, mod) 1479 } else { 1480 d := num.UintOne().Sub(m.mkt.TickSize, mod) 1481 priceInMarket.AddSum(d) 1482 } 1483 } 1484 } 1485 1486 if order.Side == types.SideSell { 1487 priceInMarket.AddSum(order.PeggedOrder.Offset) 1488 1489 // this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size 1490 // but this can never cross the mid by construction as the the minimum offset is 1 tick size and all prices must be 1491 // whole multiples of tick size. 1492 if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() { 1493 priceInMarket.Sub(priceInMarket, mod) 1494 } 1495 price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) 1496 1497 if m.capMax != nil { 1498 upper := num.UintZero().Sub(m.capMax, num.UintOne()) 1499 price = num.Min(price, upper) 1500 } 1501 if price.IsZero() { 1502 price = num.UintOne() 1503 } 1504 return price, nil 1505 } 1506 1507 if priceInMarket.LTE(order.PeggedOrder.Offset) { 1508 return num.UintZero(), common.ErrUnableToReprice 1509 } 1510 1511 priceInMarket.Sub(priceInMarket, order.PeggedOrder.Offset) 1512 if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() { 1513 priceInMarket = num.UintZero().Sub(priceInMarket.AddSum(m.mkt.TickSize), mod) 1514 } 1515 price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) 1516 1517 if m.capMax != nil { 1518 upper := num.UintZero().Sub(m.capMax, num.UintOne()) 1519 price = num.Min(price, upper) 1520 } 1521 if price.IsZero() { 1522 price = num.UintOne() 1523 } 1524 return price, nil 1525 } 1526 1527 // Reprice a pegged order. This only updates the price on the order. 1528 func (m *Market) repricePeggedOrder(order *types.Order) error { 1529 // Work out the new price of the order 1530 price, err := m.getNewPeggedPrice(order) 1531 if err != nil { 1532 return err 1533 } 1534 order.OriginalPrice, _ = num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) // set original price in market precision 1535 order.Price = price 1536 return nil 1537 } 1538 1539 func (m *Market) parkAllPeggedOrders(ctx context.Context) []*types.Order { 1540 toParkIDs := m.matching.GetActivePeggedOrderIDs() 1541 1542 parked := make([]*types.Order, 0, len(toParkIDs)) 1543 for _, order := range toParkIDs { 1544 parked = append(parked, m.parkOrder(ctx, order)) 1545 } 1546 return parked 1547 } 1548 1549 func (m *Market) uncrossOrderAtAuctionEnd(ctx context.Context) { 1550 if !m.as.InAuction() || m.as.IsOpeningAuction() { 1551 return 1552 } 1553 m.uncrossOnLeaveAuction(ctx) 1554 } 1555 1556 func (m *Market) EnterLongBlockAuction(ctx context.Context, duration int64) { 1557 if !m.canTrade() { 1558 return 1559 } 1560 // markets that are suspended via governance should be unaffected by long block auctions. 1561 if m.mkt.TradingMode == types.MarketTradingModeSuspendedViaGovernance { 1562 return 1563 } 1564 1565 if m.as.InAuction() { 1566 // auction remaining: 1567 now := m.timeService.GetTimeNow() 1568 aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second) 1569 if aRemaining >= duration { 1570 return 1571 } 1572 m.as.ExtendAuctionLongBlock(types.AuctionDuration{ 1573 Duration: duration - aRemaining, 1574 }) 1575 if evt := m.as.AuctionExtended(ctx, now); evt != nil { 1576 m.broker.Send(evt) 1577 } 1578 } else { 1579 m.as.StartLongBlockAuction(m.timeService.GetTimeNow(), duration) 1580 m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true) 1581 m.mkt.TradingMode = types.MarketTradingModeLongBlockAuction 1582 m.mkt.State = types.MarketStateSuspended 1583 m.enterAuction(ctx) 1584 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1585 } 1586 } 1587 1588 func (m *Market) UpdateMarketState(ctx context.Context, changes *types.MarketStateUpdateConfiguration) error { 1589 _, blockHash := vegacontext.TraceIDFromContext(ctx) 1590 // make deterministic ID for this market, concatenate 1591 // the block hash and the market ID 1592 m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID())) 1593 // and we call next ID on this directly just so we don't have an ID which have 1594 // a different from others, we basically burn the first ID. 1595 _ = m.idgen.NextID() 1596 defer func() { m.idgen = nil }() 1597 if changes.UpdateType == types.MarketStateUpdateTypeTerminate { 1598 final := types.MarketStateClosed 1599 if m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateProposed { 1600 final = types.MarketStateCancelled 1601 } 1602 // terminate and settle data (either last traded price for perp, or settlement data provided via governance 1603 settlement, _ := num.UintFromDecimal(changes.SettlementPrice.ToDecimal().Mul(m.priceFactor)) 1604 if !m.validateSettlementData(settlement) { 1605 // final settlement is not valid/impossible 1606 return common.ErrSettlementDataOutOfRange 1607 } 1608 // in case we're in auction, uncross 1609 m.uncrossOrderAtAuctionEnd(ctx) 1610 m.tradingTerminatedWithFinalState(ctx, final, settlement) 1611 } else if changes.UpdateType == types.MarketStateUpdateTypeSuspend { 1612 m.mkt.State = types.MarketStateSuspendedViaGovernance 1613 m.mkt.TradingMode = types.MarketTradingModeSuspendedViaGovernance 1614 if m.as.InAuction() { 1615 m.as.ExtendAuctionSuspension(types.AuctionDuration{Duration: int64(m.minDuration.Seconds())}) 1616 evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()) 1617 if evt != nil { 1618 m.broker.Send(evt) 1619 } 1620 } else { 1621 m.as.StartGovernanceSuspensionAuction(m.timeService.GetTimeNow()) 1622 m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true) 1623 m.enterAuction(ctx) 1624 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1625 } 1626 } else if changes.UpdateType == types.MarketStateUpdateTypeResume && m.mkt.State == types.MarketStateSuspendedViaGovernance { 1627 if m.as.GetState().Trigger == types.AuctionTriggerGovernanceSuspension && m.as.GetState().Extension == types.AuctionTriggerUnspecified { 1628 m.as.EndGovernanceSuspensionAuction() 1629 m.leaveAuction(ctx, m.timeService.GetTimeNow()) 1630 } else { 1631 m.as.EndGovernanceSuspensionAuction() 1632 if m.as.GetState().Trigger == types.AuctionTriggerOpening { 1633 m.mkt.State = types.MarketStatePending 1634 m.mkt.TradingMode = types.MarketTradingModeOpeningAuction 1635 } else { 1636 m.mkt.State = types.MarketStateSuspended 1637 m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction 1638 } 1639 m.checkAuction(ctx, m.timeService.GetTimeNow(), m.idgen) 1640 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1641 } 1642 } 1643 return nil 1644 } 1645 1646 // EnterAuction : Prepare the order book to be run as an auction. 1647 func (m *Market) enterAuction(ctx context.Context) { 1648 // Change market type to auction 1649 ordersToCancel := m.matching.EnterAuction() 1650 1651 // Move into auction mode to prevent pegged order repricing 1652 event := m.as.AuctionStarted(ctx, m.timeService.GetTimeNow()) 1653 1654 // Cancel all the orders that were invalid 1655 for _, order := range ordersToCancel { 1656 _, err := m.cancelOrder(ctx, order.Party, order.ID) 1657 if err != nil { 1658 m.log.Debug("error cancelling order when entering auction", 1659 logging.MarketID(m.GetID()), 1660 logging.OrderID(order.ID), 1661 logging.Error(err)) 1662 } 1663 } 1664 1665 // now update all special orders 1666 m.enterAuctionSpecialOrders(ctx) 1667 1668 // Send an event bus update 1669 m.broker.Send(event) 1670 1671 if m.as.InAuction() && m.as.IsPriceAuction() { 1672 m.mkt.State = types.MarketStateSuspended 1673 m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction 1674 m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true) 1675 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1676 } 1677 } 1678 1679 func (m *Market) uncrossOnLeaveAuction(ctx context.Context) ([]*types.OrderConfirmation, []*types.Order) { 1680 uncrossedOrders, ordersToCancel, err := m.matching.LeaveAuction(m.timeService.GetTimeNow()) 1681 if err != nil { 1682 m.log.Error("Error leaving auction", logging.Error(err)) 1683 } 1684 1685 // Process each confirmation & apply fee calculations to each trade 1686 evts := make([]events.Event, 0, len(uncrossedOrders)) 1687 for _, uncrossedOrder := range uncrossedOrders { 1688 // handle fees first 1689 fees, err := m.calcFees(uncrossedOrder.Trades) 1690 if err != nil { 1691 // @TODO this ought to be an event 1692 m.log.Error("Unable to calculate fees to order", 1693 logging.String("OrderID", uncrossedOrder.Order.ID)) 1694 } else { 1695 if fees != nil { 1696 err = m.applyFees(ctx, uncrossedOrder.Order, fees) 1697 if err != nil { 1698 // @TODO this ought to be an event 1699 m.log.Error("Unable to apply fees to order", 1700 logging.String("OrderID", uncrossedOrder.Order.ID)) 1701 } 1702 } 1703 } 1704 1705 // if the uncrossed order was generated by an AMM then register its position as if it was submitted 1706 order := uncrossedOrder.Order 1707 if order.GeneratedOffbook { 1708 cpy := order.Clone() 1709 cpy.Remaining = cpy.Size // remaining will be 0 since it has traded, so we copy it back to its full size to register 1710 m.position.RegisterOrder(ctx, cpy) 1711 } 1712 1713 // then do the confirmation 1714 m.handleConfirmation(ctx, uncrossedOrder, nil) 1715 1716 if uncrossedOrder.Order.Remaining == 0 { 1717 uncrossedOrder.Order.Status = types.OrderStatusFilled 1718 } 1719 evts = append(evts, events.NewOrderEvent(ctx, uncrossedOrder.Order)) 1720 } 1721 1722 // send order events in a single batch, it's more efficient 1723 m.broker.SendBatch(evts) 1724 1725 // after auction uncrossing we can relax the price requirement and release some excess order margin if any was placed during an auction. 1726 for k, d := range m.partyMarginFactor { 1727 partyPos, _ := m.position.GetPositionByPartyID(k) 1728 if partyPos != nil && (partyPos.Buy() != 0 || partyPos.Sell() != 0) { 1729 marketObservable, mpos, increment, _, _, orders, err := m.getIsolatedMarginContext(partyPos, nil) 1730 if err != nil { 1731 continue 1732 } 1733 r := m.risk.ReleaseExcessMarginAfterAuctionUncrossing(ctx, mpos, marketObservable, increment, d, orders) 1734 if r != nil && r.Transfer() != nil { 1735 m.transferMargins(ctx, []events.Risk{r}, nil) 1736 } 1737 } 1738 } 1739 1740 return uncrossedOrders, ordersToCancel 1741 } 1742 1743 // OnOpeningAuctionFirstUncrossingPrice is triggered when the opening auction sees an uncrossing price for the first time and emits 1744 // an event to the state variable engine. 1745 func (m *Market) OnOpeningAuctionFirstUncrossingPrice() { 1746 m.log.Info("OnOpeningAuctionFirstUncrossingPrice event fired", logging.String("market", m.mkt.ID)) 1747 m.stateVarEngine.ReadyForTimeTrigger(m.settlementAsset, m.mkt.ID) 1748 m.stateVarEngine.NewEvent(m.settlementAsset, m.mkt.ID, statevar.EventTypeOpeningAuctionFirstUncrossingPrice) 1749 } 1750 1751 // OnAuctionEnded is called whenever an auction is ended and emits an event to the state var engine. 1752 func (m *Market) OnAuctionEnded() { 1753 m.log.Info("OnAuctionEnded event fired", logging.String("market", m.mkt.ID)) 1754 m.stateVarEngine.NewEvent(m.settlementAsset, m.mkt.ID, statevar.EventTypeAuctionEnded) 1755 } 1756 1757 // leaveAuction : Return the orderbook and market to continuous trading. 1758 func (m *Market) leaveAuction(ctx context.Context, now time.Time) { 1759 defer func() { 1760 if !m.as.InAuction() && (m.mkt.State == types.MarketStateSuspended || m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateSuspendedViaGovernance) { 1761 if m.mkt.State == types.MarketStatePending { 1762 // the market is now properly open, 1763 // so set the timestamp to when the opening auction actually ended 1764 m.mkt.MarketTimestamps.Open = now.UnixNano() 1765 } 1766 1767 m.mkt.State = types.MarketStateActive 1768 // this probably should get the default trading mode from the market definition. 1769 m.mkt.TradingMode = types.MarketTradingModeContinuous 1770 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1771 1772 m.updateLiquidityFee(ctx) 1773 m.OnAuctionEnded() 1774 } 1775 }() 1776 1777 uncrossedOrders, ordersToCancel := m.uncrossOnLeaveAuction(ctx) 1778 // will hold all orders which have been updated by the uncrossing 1779 // or which were cancelled at end of auction 1780 updatedOrders := []*types.Order{} 1781 1782 // Process each order we have to cancel 1783 for _, order := range ordersToCancel { 1784 conf, err := m.cancelOrder(ctx, order.Party, order.ID) 1785 // it is possible for a party in isolated margin that their orders have been stopped when uncrossed 1786 // due to having insufficient order margin so we don't need to panic in this case 1787 if (m.getMarginMode(order.Party) == types.MarginModeCrossMargin && err == common.ErrOrderNotFound) || (err != nil && err != common.ErrOrderNotFound) { 1788 m.log.Panic("Failed to cancel order", 1789 logging.Error(err), 1790 logging.String("OrderID", order.ID)) 1791 } 1792 if err == nil { 1793 updatedOrders = append(updatedOrders, conf.Order) 1794 } 1795 } 1796 1797 wasOpeningAuction := m.IsOpeningAuction() 1798 1799 // update auction state, so we know what the new tradeMode ought to be 1800 endEvt := m.as.Left(ctx, now) 1801 // we tell the perp that we've left auction, we might re-enter just a bit down but thats fine as 1802 // we will at least keep the in/out orders in sync 1803 m.tradableInstrument.Instrument.UpdateAuctionState(ctx, false) 1804 1805 for _, uncrossedOrder := range uncrossedOrders { 1806 updatedOrders = append(updatedOrders, uncrossedOrder.Order) 1807 updatedOrders = append( 1808 updatedOrders, uncrossedOrder.PassiveOrdersAffected...) 1809 } 1810 t := m.timeService.GetTimeNow().UnixNano() 1811 1812 m.markPriceCalculator.SetBookPriceAtTimeT(m.lastTradedPrice, t) 1813 m.markPriceLock.Lock() 1814 if _, err := m.markPriceCalculator.CalculateMarkPrice( 1815 ctx, 1816 m.pMonitor, 1817 m.as, 1818 t, 1819 m.matching, 1820 m.mtmDelta, 1821 m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, 1822 m.mkt.LinearSlippageFactor, 1823 m.risk.GetRiskFactors().Short, 1824 m.risk.GetRiskFactors().Long, 1825 true, 1826 wasOpeningAuction); err != nil { 1827 if evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()); evt != nil { 1828 m.broker.Send(evt) 1829 } 1830 1831 // start the monitoring auction if required 1832 if m.as.AuctionStart() { 1833 m.enterAuction(ctx) 1834 } 1835 } 1836 m.markPriceLock.Unlock() 1837 if wasOpeningAuction && !m.as.IsOpeningAuction() && m.getCurrentMarkPrice().IsZero() { 1838 m.markPriceCalculator.OverridePrice(m.lastTradedPrice) 1839 m.pMonitor.ResetPriceHistory(m.lastTradedPrice) 1840 } 1841 m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice()) 1842 if m.perp { 1843 if m.internalCompositePriceCalculator != nil { 1844 m.internalCompositePriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t, m.matching) 1845 m.internalCompositePriceCalculator.CalculateMarkPrice( 1846 ctx, 1847 m.pMonitor, 1848 m.as, 1849 t, 1850 m.matching, 1851 m.internalCompositePriceFrequency, 1852 m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, 1853 m.mkt.LinearSlippageFactor, 1854 m.risk.GetRiskFactors().Short, 1855 m.risk.GetRiskFactors().Long, 1856 false, false) 1857 1858 if wasOpeningAuction && (m.getCurrentInternalCompositePrice().IsZero()) { 1859 m.internalCompositePriceCalculator.OverridePrice(m.lastTradedPrice) 1860 } 1861 m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentInternalCompositePrice(), m.timeService.GetTimeNow().UnixNano()) 1862 } else { 1863 m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentMarkPrice(), m.timeService.GetTimeNow().UnixNano()) 1864 } 1865 } 1866 1867 m.checkForReferenceMoves(ctx, updatedOrders, true) 1868 1869 m.checkBondBalance(ctx) 1870 1871 if !m.as.InAuction() { 1872 // only send the auction-left event if we actually *left* the auction. 1873 m.broker.Send(endEvt) 1874 // now that we've left the auction and all the orders have been unparked, 1875 // we can mark all positions using the margin calculation method appropriate 1876 // for non-auction mode and carry out any closeouts if need be 1877 if m.confirmMTM(ctx, false) { 1878 // set next MTM 1879 m.nextMTM = m.timeService.GetTimeNow().Add(m.mtmDelta) 1880 } 1881 // we have just left auction, check the network position, dispose of volume if possible 1882 m.checkNetwork(ctx, now) 1883 } 1884 } 1885 1886 func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err error) { 1887 defer func() { 1888 if err != nil { 1889 order.Status = types.OrderStatusRejected 1890 m.broker.Send(events.NewOrderEvent(ctx, order)) 1891 } 1892 }() 1893 1894 // Check we are allowed to handle this order type with the current market status 1895 isAuction := m.as.InAuction() 1896 if isAuction && order.TimeInForce == types.OrderTimeInForceGFN { 1897 order.Status = types.OrderStatusRejected 1898 order.Reason = types.OrderErrorCannotSendGFNOrderDuringAnAuction 1899 return common.ErrGFNOrderReceivedAuctionTrading 1900 } 1901 1902 if isAuction && order.TimeInForce == types.OrderTimeInForceIOC { 1903 order.Reason = types.OrderErrorCannotSendIOCOrderDuringAuction 1904 return common.ErrIOCOrderReceivedAuctionTrading 1905 } 1906 1907 if isAuction && order.TimeInForce == types.OrderTimeInForceFOK { 1908 order.Reason = types.OrderErrorCannotSendFOKOrderDurinAuction 1909 return common.ErrFOKOrderReceivedAuctionTrading 1910 } 1911 1912 if !isAuction && order.TimeInForce == types.OrderTimeInForceGFA { 1913 order.Reason = types.OrderErrorGFAOrderDuringContinuousTrading 1914 return common.ErrGFAOrderReceivedDuringContinuousTrading 1915 } 1916 1917 // Check the expiry time is valid 1918 if order.ExpiresAt > 0 && order.ExpiresAt < order.CreatedAt { 1919 order.Reason = types.OrderErrorInvalidExpirationDatetime 1920 return common.ErrInvalidExpiresAtTime 1921 } 1922 1923 if m.closed { 1924 // adding order to the buffer first 1925 order.Reason = types.OrderErrorMarketClosed 1926 return common.ErrMarketClosed 1927 } 1928 1929 if order.Type == types.OrderTypeNetwork { 1930 order.Reason = types.OrderErrorInvalidType 1931 return common.ErrInvalidOrderType 1932 } 1933 1934 // Validate market 1935 if order.MarketID != m.mkt.ID { 1936 // adding order to the buffer first 1937 order.Reason = types.OrderErrorInvalidMarketID 1938 if m.log.GetLevel() == logging.DebugLevel { 1939 m.log.Debug("Market ID mismatch", 1940 logging.Order(*order), 1941 logging.String("market", m.mkt.ID)) 1942 } 1943 return types.ErrInvalidMarketID 1944 } 1945 1946 // Validate pegged orders 1947 if order.PeggedOrder != nil { 1948 if m.getMarginMode(order.Party) != types.MarginModeCrossMargin { 1949 return types.ErrPeggedOrdersNotAllowedInIsolatedMargin 1950 } 1951 if reason := order.ValidatePeggedOrder(); reason != types.OrderErrorUnspecified { 1952 order.Reason = reason 1953 if m.log.GetLevel() == logging.DebugLevel { 1954 m.log.Debug("Failed to validate pegged order details", 1955 logging.Order(*order), 1956 logging.String("market", m.mkt.ID)) 1957 } 1958 return reason 1959 } 1960 if order.PeggedOrder.Reference == types.PeggedReferenceMid { 1961 offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor)) 1962 tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor)) 1963 if offsetInAsset.IsZero() && tickSizeInAsset.IsZero() { 1964 return fmt.Errorf("invalid offset - pegged mid will cross") 1965 } 1966 } 1967 return m.validateTickSize(order.PeggedOrder.Offset) 1968 } 1969 1970 if order.OriginalPrice != nil { 1971 return m.validateTickSize(order.OriginalPrice) 1972 } 1973 1974 return nil 1975 } 1976 1977 // validateOrder checks that the order parameters are valid for the market. 1978 // NB: price in market, tickSize in market decimals. 1979 func (m *Market) validateTickSize(price *num.Uint) error { 1980 d := num.UintZero().Mod(price, m.mkt.TickSize) 1981 if !d.IsZero() { 1982 return types.ErrOrderNotInTickSize 1983 } 1984 return nil 1985 } 1986 1987 func (m *Market) validateAccounts(ctx context.Context, order *types.Order) error { 1988 if !m.collateral.HasGeneralAccount(order.Party, m.settlementAsset) { 1989 // adding order to the buffer first 1990 order.Status = types.OrderStatusRejected 1991 order.Reason = types.OrderErrorInsufficientAssetBalance 1992 m.broker.Send(events.NewOrderEvent(ctx, order)) 1993 1994 // party should be created before even trying to post order 1995 return common.ErrPartyInsufficientAssetBalance 1996 } 1997 1998 // ensure party have a general account, and margin account is / can be created 1999 _, err := m.collateral.CreatePartyMarginAccount(ctx, order.Party, order.MarketID, m.settlementAsset) 2000 if err != nil { 2001 m.log.Error("Margin account verification failed", 2002 logging.String("party-id", order.Party), 2003 logging.String("market-id", m.GetID()), 2004 logging.String("asset", m.settlementAsset), 2005 ) 2006 // adding order to the buffer first 2007 order.Status = types.OrderStatusRejected 2008 order.Reason = types.OrderErrorMissingGeneralAccount 2009 m.broker.Send(events.NewOrderEvent(ctx, order)) 2010 return common.ErrMissingGeneralAccountForParty 2011 } 2012 2013 // from this point we know the party have a margin account 2014 // we had it to the list of parties. 2015 if m.addParty(order.Party) { 2016 // First time seeing the party, we report his margin mode. 2017 m.emitPartyMarginModeUpdated(ctx, order.Party, m.getMarginMode(order.Party), m.getMarginFactor(order.Party)) 2018 } 2019 return nil 2020 } 2021 2022 func (m *Market) releaseMarginExcess(ctx context.Context, partyID string) { 2023 // if this position went 0 2024 pos, ok := m.position.GetPositionByPartyID(partyID) 2025 if !ok { 2026 // the party has closed their position and it's been removed from the 2027 // position engine. Let's just create an empty one, so it can be cleared 2028 // down the line. 2029 pos = positions.NewMarketPosition(partyID) 2030 } 2031 m.releaseExcessMargin(ctx, pos) 2032 } 2033 2034 // releaseExcessMargin does what releaseMarginExcess does. Added this function to be able to release 2035 // all excess margin on MTM without having to call the latter by iterating all positions, and then 2036 // fetching said position again my party. 2037 func (m *Market) releaseExcessMargin(ctx context.Context, positions ...events.MarketPosition) { 2038 evts := make([]events.Event, 0, len(positions)) 2039 mEvts := make([]events.Event, 0, len(positions)) 2040 mktID := m.GetID() 2041 // base margin event. We don't care about the uint values being pointers here 2042 // this is only used to create an event, which converts this to proto. 2043 marginEvt := types.MarginLevels{ 2044 MaintenanceMargin: num.UintZero(), 2045 SearchLevel: num.UintZero(), 2046 InitialMargin: num.UintZero(), 2047 CollateralReleaseLevel: num.UintZero(), 2048 OrderMargin: num.UintZero(), 2049 MarketID: mktID, 2050 Asset: m.settlementAsset, 2051 Timestamp: m.timeService.GetTimeNow().UnixNano(), 2052 } 2053 for _, pos := range positions { 2054 party := pos.Party() 2055 // if the party still have a position in the settlement engine, 2056 // do not remove them for now 2057 if m.settlement.HasPosition(party) { 2058 continue 2059 } 2060 2061 // now check if all buy/sell/size are 0 2062 if pos.Buy() != 0 || pos.Sell() != 0 || pos.Size() != 0 { 2063 // position is not 0, nothing to release surely 2064 continue 2065 } 2066 2067 // If no error is returned, the party either had a zero balance, or no margin balance left. 2068 // Either way their margin levels are zero, so we need to emit an event saying as much. 2069 transfers, err := m.collateral.ClearPartyMarginAccount( 2070 ctx, party, mktID, m.settlementAsset) 2071 if err != nil { 2072 m.log.Error("unable to clear party margin account", logging.Error(err)) 2073 continue 2074 } 2075 marginEvt.Party = party 2076 marginEvt.MarginFactor = m.getMarginFactor(party) 2077 marginEvt.MarginMode = m.getMarginMode(party) 2078 mEvts = append(mEvts, events.NewMarginLevelsEvent(ctx, marginEvt)) 2079 2080 if transfers != nil { 2081 evts = append(evts, events.NewLedgerMovements( 2082 ctx, []*types.LedgerMovement{transfers}), 2083 ) 2084 } 2085 if marginEvt.MarginMode == types.MarginModeIsolatedMargin { 2086 transfers, err = m.collateral.ClearPartyOrderMarginAccount( 2087 ctx, party, mktID, m.settlementAsset) 2088 if err != nil { 2089 m.log.Error("unable to clear party order margin account", logging.Error(err)) 2090 continue 2091 } 2092 if transfers != nil { 2093 evts = append(evts, events.NewLedgerMovements( 2094 ctx, []*types.LedgerMovement{transfers}), 2095 ) 2096 } 2097 } 2098 // we can delete the party from the map here 2099 // unless the party is an LP 2100 if !m.liquidityEngine.IsLiquidityProvider(party) { 2101 delete(m.parties, party) 2102 } 2103 } 2104 if len(evts) > 0 { 2105 m.broker.SendBatch(evts) 2106 } 2107 if len(mEvts) > 0 { 2108 m.broker.SendBatch(mEvts) 2109 } 2110 } 2111 2112 func rejectStopOrders(rejectionReason types.StopOrderRejectionReason, orders ...*types.StopOrder) { 2113 for _, o := range orders { 2114 if o != nil { 2115 o.Status = types.StopOrderStatusRejected 2116 o.RejectionReason = ptr.From(rejectionReason) 2117 } 2118 } 2119 } 2120 2121 func (m *Market) SubmitStopOrdersWithIDGeneratorAndOrderIDs( 2122 ctx context.Context, 2123 submission *types.StopOrdersSubmission, 2124 party string, 2125 idgen common.IDGenerator, 2126 fallsBelowID, risesAboveID *string, 2127 ) (*types.OrderConfirmation, error) { 2128 m.idgen = idgen 2129 defer func() { m.idgen = nil }() 2130 2131 fallsBelow, risesAbove := submission.IntoStopOrders( 2132 party, ptr.UnBox(fallsBelowID), ptr.UnBox(risesAboveID), m.timeService.GetTimeNow()) 2133 2134 defer func() { 2135 evts := []events.Event{} 2136 if fallsBelow != nil { 2137 evts = append(evts, events.NewStopOrderEvent(ctx, fallsBelow)) 2138 } 2139 if risesAbove != nil { 2140 evts = append(evts, events.NewStopOrderEvent(ctx, risesAbove)) 2141 } 2142 2143 if len(evts) > 0 { 2144 m.broker.SendBatch(evts) 2145 } 2146 }() 2147 2148 if m.IsOpeningAuction() { 2149 rejectStopOrders(types.StopOrderRejectionNotAllowedDuringOpeningAuction, fallsBelow, risesAbove) 2150 return nil, common.ErrStopOrderNotAllowedDuringOpeningAuction 2151 } 2152 2153 if !m.canTrade() { 2154 rejectStopOrders(types.StopOrderRejectionTradingNotAllowed, fallsBelow, risesAbove) 2155 return nil, common.ErrTradingNotAllowed 2156 } 2157 2158 now := m.timeService.GetTimeNow() 2159 orderCnt := 0 2160 if fallsBelow != nil { 2161 if fallsBelow.Expiry.Expires() && fallsBelow.Expiry.ExpiresAt.Before(now) { 2162 rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove) 2163 return nil, common.ErrStopOrderExpiryInThePast 2164 } 2165 if !fallsBelow.OrderSubmission.ReduceOnly { 2166 rejectStopOrders(types.StopOrderRejectionMustBeReduceOnly, fallsBelow, risesAbove) 2167 return nil, common.ErrStopOrderMustBeReduceOnly 2168 } 2169 orderCnt++ 2170 } 2171 if risesAbove != nil { 2172 if risesAbove.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Before(now) { 2173 rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove) 2174 return nil, common.ErrStopOrderExpiryInThePast 2175 } 2176 if !risesAbove.OrderSubmission.ReduceOnly { 2177 rejectStopOrders(types.StopOrderRejectionMustBeReduceOnly, fallsBelow, risesAbove) 2178 return nil, common.ErrStopOrderMustBeReduceOnly 2179 } 2180 orderCnt++ 2181 } 2182 2183 if risesAbove != nil && fallsBelow != nil { 2184 if risesAbove.Expiry.Expires() && fallsBelow.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Compare(*fallsBelow.Expiry.ExpiresAt) == 0 { 2185 rejectStopOrders(types.StopOrderRejectionOCONotAllowedSameExpiryTime, fallsBelow, risesAbove) 2186 return nil, common.ErrStopOrderNotAllowedSameExpiry 2187 } 2188 } 2189 2190 // now check if that party hasn't exceeded the max amount per market 2191 if m.stopOrders.CountForParty(party)+uint64(orderCnt) > m.maxStopOrdersPerParties.Uint64() { 2192 rejectStopOrders(types.StopOrderRejectionMaxStopOrdersPerPartyReached, fallsBelow, risesAbove) 2193 return nil, common.ErrMaxStopOrdersPerPartyReached 2194 } 2195 2196 // now check for the parties position 2197 if positions := m.position.GetPositionsByParty(party); len(positions) > 1 { 2198 m.log.Panic("only one position expected", logging.Int("got", len(positions))) 2199 } else if len(positions) < 1 { 2200 rejectStopOrders(types.StopOrderRejectionNotAllowedWithoutAPosition, fallsBelow, risesAbove) 2201 return nil, common.ErrStopOrderSubmissionNotAllowedWithoutExistingPosition 2202 } 2203 2204 fallsBelowTriggered, risesAboveTriggered := m.stopOrderWouldTriggerAtSubmission(fallsBelow), 2205 m.stopOrderWouldTriggerAtSubmission(risesAbove) 2206 triggered := fallsBelowTriggered || risesAboveTriggered 2207 2208 // if the stop order links to a position, see if we are scaling the size 2209 if fallsBelow != nil && fallsBelow.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition { 2210 if fallsBelow.SizeOverrideValue != nil { 2211 if fallsBelow.SizeOverrideValue.PercentageSize.LessThanOrEqual(num.DecimalFromFloat(0.0)) || 2212 fallsBelow.SizeOverrideValue.PercentageSize.GreaterThan(num.DecimalFromFloat(1.0)) { 2213 rejectStopOrders(types.StopOrderRejectionLinkedPercentageInvalid, fallsBelow, risesAbove) 2214 return nil, common.ErrStopOrderSizeOverridePercentageInvalid 2215 } 2216 } 2217 } 2218 2219 if risesAbove != nil && risesAbove.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition { 2220 if risesAbove.SizeOverrideValue != nil { 2221 if risesAbove.SizeOverrideValue.PercentageSize.LessThanOrEqual(num.DecimalFromFloat(0.0)) || 2222 risesAbove.SizeOverrideValue.PercentageSize.GreaterThan(num.DecimalFromFloat(1.0)) { 2223 rejectStopOrders(types.StopOrderRejectionLinkedPercentageInvalid, fallsBelow, risesAbove) 2224 return nil, common.ErrStopOrderSizeOverridePercentageInvalid 2225 } 2226 } 2227 } 2228 2229 // if we are in an auction 2230 // or no order is triggered 2231 // let's just submit it straight away 2232 if m.as.InAuction() || !triggered { 2233 m.poolStopOrders(fallsBelow, risesAbove) 2234 return nil, nil 2235 } 2236 2237 var confirmation *types.OrderConfirmation 2238 var err error 2239 // now would the order get trigger straight away? 2240 switch { 2241 case fallsBelowTriggered: 2242 fallsBelow.Status = types.StopOrderStatusTriggered 2243 if risesAbove != nil { 2244 risesAbove.Status = types.StopOrderStatusStopped 2245 } 2246 fallsBelow.OrderID = idgen.NextID() 2247 confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID( 2248 ctx, fallsBelow.OrderSubmission, party, idgen, fallsBelow.OrderID, true, 2249 ) 2250 if err != nil && confirmation != nil { 2251 fallsBelow.OrderID = confirmation.Order.ID 2252 } 2253 case risesAboveTriggered: 2254 risesAbove.Status = types.StopOrderStatusTriggered 2255 if fallsBelow != nil { 2256 fallsBelow.Status = types.StopOrderStatusStopped 2257 } 2258 risesAbove.OrderID = idgen.NextID() 2259 confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID( 2260 ctx, risesAbove.OrderSubmission, party, idgen, risesAbove.OrderID, true, 2261 ) 2262 if err != nil && confirmation != nil { 2263 risesAbove.OrderID = confirmation.Order.ID 2264 } 2265 } 2266 2267 return confirmation, err 2268 } 2269 2270 func (m *Market) poolStopOrders( 2271 fallsBelow, risesAbove *types.StopOrder, 2272 ) { 2273 if fallsBelow != nil { 2274 m.stopOrders.Insert(fallsBelow) 2275 if fallsBelow.Expiry.Expires() { 2276 m.expiringStopOrders.Insert(fallsBelow.ID, fallsBelow.Expiry.ExpiresAt.UnixNano()) 2277 } 2278 } 2279 if risesAbove != nil { 2280 m.stopOrders.Insert(risesAbove) 2281 if risesAbove.Expiry.Expires() { 2282 m.expiringStopOrders.Insert(risesAbove.ID, risesAbove.Expiry.ExpiresAt.UnixNano()) 2283 } 2284 } 2285 } 2286 2287 func (m *Market) stopOrderWouldTriggerAtSubmission( 2288 stopOrder *types.StopOrder, 2289 ) bool { 2290 if m.lastTradedPrice == nil || stopOrder == nil || stopOrder.Trigger.IsTrailingPercentOffset() { 2291 return false 2292 } 2293 2294 lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice()) 2295 2296 switch stopOrder.Trigger.Direction { 2297 case types.StopOrderTriggerDirectionFallsBelow: 2298 if lastTradedPrice.LTE(stopOrder.Trigger.Price()) { 2299 return true 2300 } 2301 case types.StopOrderTriggerDirectionRisesAbove: 2302 if lastTradedPrice.GTE(stopOrder.Trigger.Price()) { 2303 return true 2304 } 2305 } 2306 return false 2307 } 2308 2309 func (m *Market) triggerStopOrders( 2310 ctx context.Context, 2311 idgen common.IDGenerator, 2312 ) []*types.OrderConfirmation { 2313 if m.lastTradedPrice == nil { 2314 return nil 2315 } 2316 lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice()) 2317 triggered, cancelled := m.stopOrders.PriceUpdated(lastTradedPrice) 2318 2319 // See if there are any linked orders that are the wrong direction 2320 cancelled = append(cancelled, m.stopOrders.CheckDirection(m.position)...) 2321 2322 if len(triggered) <= 0 && len(cancelled) <= 0 { 2323 return nil 2324 } 2325 2326 now := m.timeService.GetTimeNow() 2327 // remove from expiring orders + set updatedAt 2328 for _, v := range append(triggered, cancelled...) { 2329 v.UpdatedAt = now 2330 if v.Expiry.Expires() { 2331 m.expiringStopOrders.RemoveOrder(v.Expiry.ExpiresAt.UnixNano(), v.ID) 2332 } 2333 } 2334 2335 evts := make([]events.Event, 0, len(cancelled)) 2336 for _, v := range cancelled { 2337 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 2338 } 2339 2340 m.broker.SendBatch(evts) 2341 2342 if len(triggered) <= 0 { 2343 return nil 2344 } 2345 2346 confirmations := m.submitStopOrders(ctx, triggered, types.StopOrderStatusTriggered, idgen) 2347 2348 return append(m.triggerStopOrders(ctx, idgen), confirmations...) 2349 } 2350 2351 // SubmitOrder submits the given order. 2352 func (m *Market) SubmitOrder( 2353 ctx context.Context, 2354 orderSubmission *types.OrderSubmission, 2355 party string, 2356 deterministicID string, 2357 ) (oc *types.OrderConfirmation, _ error) { 2358 idgen := idgeneration.New(deterministicID) 2359 return m.SubmitOrderWithIDGeneratorAndOrderID( 2360 ctx, orderSubmission, party, idgen, idgen.NextID(), true, 2361 ) 2362 } 2363 2364 // SubmitOrder submits the given order. 2365 func (m *Market) SubmitOrderWithIDGeneratorAndOrderID( 2366 ctx context.Context, 2367 orderSubmission *types.OrderSubmission, 2368 party string, 2369 idgen common.IDGenerator, 2370 orderID string, 2371 checkForTriggers bool, 2372 ) (oc *types.OrderConfirmation, _ error) { 2373 defer m.onTxProcessed() 2374 2375 m.idgen = idgen 2376 defer func() { m.idgen = nil }() 2377 2378 defer func() { 2379 if !checkForTriggers { 2380 return 2381 } 2382 2383 m.triggerStopOrders(ctx, idgen) 2384 }() 2385 order := orderSubmission.IntoOrder(party) 2386 order.CreatedAt = m.timeService.GetTimeNow().UnixNano() 2387 order.ID = orderID 2388 if order.Price != nil { 2389 order.OriginalPrice = order.Price.Clone() 2390 order.Price, _ = num.UintFromDecimal(order.Price.ToDecimal().Mul(m.priceFactor)) 2391 if order.Type == types.OrderTypeLimit && order.PeggedOrder == nil && order.Price.IsZero() { 2392 // limit orders need to be priced > 0 2393 order.Status = types.OrderStatusRejected 2394 order.Reason = types.OrderErrorPriceNotInTickSize // @TODO add new error 2395 m.broker.Send(events.NewOrderEvent(ctx, order)) 2396 return nil, common.ErrInvalidOrderPrice 2397 } 2398 } 2399 // check max price in case of capped market 2400 if m.capMax != nil && order.Price != nil && order.Price.GTE(m.capMax) { 2401 order.Status = types.OrderStatusRejected 2402 order.Reason = types.OrderErrorPriceLTEMaxPrice 2403 m.broker.Send(events.NewOrderEvent(ctx, order)) 2404 return nil, common.ErrInvalidOrderPrice 2405 } 2406 2407 if !m.canTrade() { 2408 order.Status = types.OrderStatusRejected 2409 order.Reason = types.OrderErrorMarketClosed 2410 m.broker.Send(events.NewOrderEvent(ctx, order)) 2411 return nil, common.ErrTradingNotAllowed 2412 } 2413 2414 conf, orderUpdates, err := m.submitOrder(ctx, order) 2415 if err != nil { 2416 return nil, err 2417 } 2418 2419 allUpdatedOrders := append( 2420 []*types.Order{conf.Order}, conf.PassiveOrdersAffected...) 2421 allUpdatedOrders = append(allUpdatedOrders, orderUpdates...) 2422 2423 if !m.as.InAuction() { 2424 m.checkForReferenceMoves(ctx, allUpdatedOrders, false) 2425 } 2426 2427 return conf, nil 2428 } 2429 2430 func (m *Market) submitOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) { 2431 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "SubmitOrder") 2432 orderValidity := "invalid" 2433 defer func() { 2434 timer.EngineTimeCounterAdd() 2435 metrics.OrderCounterInc(m.mkt.ID, orderValidity) 2436 }() 2437 2438 // set those at the beginning as even rejected order get through the buffers 2439 order.Version = common.InitialOrderVersion 2440 order.Status = types.OrderStatusActive 2441 2442 if err := m.validateOrder(ctx, order); err != nil { 2443 return nil, nil, err 2444 } 2445 2446 if err := m.validateAccounts(ctx, order); err != nil { 2447 return nil, nil, err 2448 } 2449 2450 if err := m.position.ValidateOrder(order); err != nil { 2451 return nil, nil, err 2452 } 2453 2454 // Now that validation is handled, call the code to place the order 2455 orderConf, orderUpdates, err := m.submitValidatedOrder(ctx, order) 2456 if err == nil { 2457 orderValidity = "valid" 2458 } 2459 2460 if order.PeggedOrder != nil && order.IsFinished() { 2461 // remove the pegged order from anywhere 2462 m.removePeggedOrder(order) 2463 } 2464 2465 // insert an expiring order if it's either in the book 2466 // or in the parked list 2467 if order.IsExpireable() && !order.IsFinished() { 2468 m.expiringOrders.Insert(order.ID, order.ExpiresAt) 2469 } 2470 2471 return orderConf, orderUpdates, err 2472 } 2473 2474 func (m *Market) submitValidatedOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) { 2475 isPegged := order.PeggedOrder != nil 2476 if isPegged { 2477 order.Status = types.OrderStatusParked 2478 order.Reason = types.OrderErrorUnspecified 2479 2480 if m.as.InAuction() { 2481 order.SetIcebergPeaks() 2482 2483 m.peggedOrders.Park(order) 2484 // If we are in an auction, we don't insert this order into the book 2485 // Maybe should return an orderConfirmation with order state PARKED 2486 m.broker.Send(events.NewOrderEvent(ctx, order)) 2487 return &types.OrderConfirmation{Order: order}, nil, nil 2488 } 2489 // Reprice 2490 err := m.repricePeggedOrder(order) 2491 if err != nil { 2492 order.SetIcebergPeaks() 2493 m.peggedOrders.Park(order) 2494 m.broker.Send(events.NewOrderEvent(ctx, order)) 2495 return &types.OrderConfirmation{Order: order}, nil, nil // nolint 2496 } 2497 } 2498 2499 // Register order as potential positions 2500 pos := m.position.RegisterOrder(ctx, order) 2501 2502 // in case we have an IOC order, that would work but need to be stopped because 2503 // it'd be flipping the position of the party 2504 // first check if we have a reduce only order and make sure it can go through 2505 if order.ReduceOnly { 2506 reduce, extraSize := pos.OrderReducesOnlyExposure(order) 2507 // if we are not reducing, or if the position flips on a FOK, we short-circuit here. 2508 // in the case of a IOC, the order will be stopped once we reach 0 2509 if !reduce || (order.TimeInForce == types.OrderTimeInForceFOK && extraSize > 0) { 2510 return nil, nil, m.unregisterAndReject( 2511 ctx, order, types.ErrReduceOnlyOrderWouldNotReducePosition) 2512 } 2513 // keep track of the eventual reduce only size 2514 order.ReduceOnlyAdjustRemaining(extraSize) 2515 } 2516 marginMode := m.getMarginMode(order.Party) 2517 2518 // Perform check and allocate margin unless the order is (partially) closing the party position 2519 // NB: this is only done at this point for cross margin mode 2520 if marginMode == types.MarginModeCrossMargin && !order.ReduceOnly && !pos.OrderReducesExposure(order) { 2521 if err := m.checkMarginForOrder(ctx, pos, order); err != nil { 2522 if m.log.GetLevel() <= logging.DebugLevel { 2523 m.log.Debug("Unable to check/add margin for party", 2524 logging.Order(*order), logging.Error(err)) 2525 } 2526 _ = m.unregisterAndReject( 2527 ctx, order, types.OrderErrorMarginCheckFailed) 2528 return nil, nil, common.ErrMarginCheckFailed 2529 } 2530 } 2531 2532 // from here we may have assigned some margin. 2533 // we add the check to roll it back in case we have a 0 positions after this 2534 defer m.releaseMarginExcess(ctx, order.Party) 2535 2536 // If we are not in an opening auction, apply fees 2537 var trades []*types.Trade 2538 var fees events.FeesTransfer 2539 // we're not in auction (not opening, not any other auction 2540 if !m.as.InAuction() { 2541 // first we call the order book to evaluate auction triggers and get the list of trades 2542 var err error 2543 trades, err = m.checkPriceAndGetTrades(ctx, order) 2544 if err != nil { 2545 return nil, nil, m.unregisterAndReject(ctx, order, err) 2546 } 2547 2548 // try to apply fees on the trade 2549 fees, err = m.calcFees(trades) 2550 if err != nil { 2551 return nil, nil, m.unregisterAndReject(ctx, order, err) 2552 } 2553 } 2554 2555 // if an auction was trigger, and we are a pegged order 2556 // or a liquidity order, let's return now. 2557 if m.as.InAuction() && isPegged { 2558 if isPegged { 2559 m.peggedOrders.Park(order) 2560 } 2561 // parking the order, needs to unregister it first 2562 _ = m.position.UnregisterOrder(ctx, order) 2563 m.broker.Send(events.NewOrderEvent(ctx, order)) 2564 return &types.OrderConfirmation{Order: order}, nil, nil 2565 } 2566 2567 order.Status = types.OrderStatusActive 2568 2569 var aggressorFee *num.Uint 2570 if fees != nil { 2571 aggressorFee = fees.TotalFeesAmountPerParty()[order.Party] 2572 } 2573 2574 // NB: this is the position with the trades included and the order sizes updated to remaining!!! 2575 // NB: this is not touching the actual position from the position engine but is all done on a clone, so that 2576 // in handle confirmation this will be done as per normal. 2577 posWithTrades := pos.UpdateInPlaceOnTrades(m.log, order.Side, trades, order) 2578 // First, check whether the order will trade, either fully or in part, immediately upon entry. If so: 2579 // If the trade would increase the party's position, the required additional funds as specified in the Increasing Position section will be calculated. 2580 // The total expected margin balance (current plus new funds) will then be compared to the maintenance margin for the expected position, 2581 // if the margin balance would be less than maintenance, instead reject the order in it's entirety. 2582 // If the margin will be greater than the maintenance margin their general account will be checked for sufficient funds. 2583 // If they have sufficient, that amount will be moved into their margin account and the immediately matching portion of the order will trade. 2584 // If they do not have sufficient, the order will be rejected in it's entirety for not meeting margin requirements. 2585 // If the trade would decrease the party's position, that portion will trade and margin will be released as in the Decreasing Position. 2586 // If the order is not persistent this is the end, if it is persistent any portion of the order which 2587 // has not traded in step 1 will move to being placed on the order book. 2588 if len(trades) > 0 { 2589 if marginMode == types.MarginModeIsolatedMargin { 2590 // check that the party can cover the trade AND the fees 2591 if err := m.updateIsolatedMarginOnAggressor(ctx, posWithTrades, order, trades, false, aggressorFee); err != nil { 2592 if m.log.GetLevel() <= logging.DebugLevel { 2593 m.log.Debug("Unable to check/add immediate trade margin for party", 2594 logging.Order(*order), logging.Error(err)) 2595 } 2596 _ = m.position.UnregisterOrder(ctx, order) 2597 return nil, nil, common.ErrMarginCheckFailed 2598 } 2599 } else if aggressorFee != nil { 2600 if err := m.collateral.PartyCanCoverFees(m.settlementAsset, m.mkt.ID, order.Party, aggressorFee); err != nil { 2601 m.log.Error("insufficient funds to cover fees", logging.Order(order), logging.Error(err)) 2602 m.unregisterAndReject(ctx, order, types.OrderErrorInsufficientFundsToPayFees) 2603 return nil, nil, err 2604 } 2605 } 2606 if order.Type == types.OrderTypeMarket && marginMode == types.MarginModeCrossMargin && !order.ReduceOnly && !pos.OrderReducesExposure(order) { 2607 if err := m.checkMarginForOrder(ctx, posWithTrades, order); err != nil { 2608 if m.log.GetLevel() <= logging.DebugLevel { 2609 m.log.Debug("Unable to check/add margin for party", 2610 logging.Order(*order), logging.Error(err)) 2611 } 2612 _ = m.unregisterAndReject( 2613 ctx, order, types.OrderErrorMarginCheckFailed) 2614 return nil, nil, common.ErrMarginCheckFailed 2615 } 2616 } 2617 } 2618 2619 // Send the aggressive order into matching engine 2620 confirmation, err := m.matching.SubmitOrder(order) 2621 if err != nil { 2622 return nil, nil, m.unregisterAndReject(ctx, order, err) 2623 } 2624 2625 // this is no op for non reduce-only orders 2626 order.ClearUpExtraRemaining() 2627 2628 // this means our reduce-only order (IOC) have been stopped 2629 // from trading to the point it would flip the position, 2630 // and successfully reduced the position to 0. 2631 // set the status to Stopped then. 2632 if order.ReduceOnly && order.Remaining > 0 { 2633 order.Status = types.OrderStatusStopped 2634 } 2635 2636 // if the order is not staying in the book, then we remove it 2637 // from the potential positions 2638 if order.IsFinished() && order.Remaining > 0 { 2639 _ = m.position.UnregisterOrder(ctx, order) 2640 } 2641 2642 // we replace the trades in the confirmation with the one we got initially 2643 // the contains the fees information 2644 confirmation.Trades = trades 2645 2646 if marginMode == types.MarginModeIsolatedMargin && order.Status == types.OrderStatusActive && order.TrueRemaining() > 0 { 2647 // now we need to check if the party has sufficient funds to cover the order margin for the remaining size 2648 // if not the remaining order is cancelled. 2649 // if successful the required order margin are transferred to the order margin account. 2650 if err := m.updateIsolatedMarginOnOrder(ctx, posWithTrades, order); err != nil { 2651 if m.log.GetLevel() <= logging.DebugLevel { 2652 m.log.Debug("Unable to check/add margin for party", 2653 logging.Order(*order), logging.Error(err)) 2654 } 2655 _ = m.unregisterAndReject( 2656 ctx, order, types.OrderErrorMarginCheckFailed) 2657 m.matching.RemoveOrder(order.ID) 2658 if len(trades) > 0 { 2659 if err = m.applyFees(ctx, order, fees); err != nil { 2660 m.log.Panic("failed to apply fees on order", logging.Order(order), logging.String("aggressor total fees", fees.TotalFeesAmountPerParty()[order.ID].String()), logging.Error(err)) 2661 } 2662 // if there were trades we need to return the confirmation so the trades can be handled 2663 // otherwise they were just removed from the book for the passive side and gone 2664 orderUpdates := m.handleConfirmation(ctx, confirmation, nil) 2665 return confirmation, orderUpdates, common.ErrMarginCheckFailed 2666 } 2667 return nil, nil, common.ErrMarginCheckFailed 2668 } 2669 } 2670 2671 if fees != nil { 2672 if err = m.applyFees(ctx, order, fees); err != nil { 2673 m.log.Panic("failed to apply fees on order", logging.Order(order), logging.String("aggressor total fees", fees.TotalFeesAmountPerParty()[order.ID].String()), logging.Error(err)) 2674 } 2675 } 2676 2677 // Send out the order update here as handling the confirmation message 2678 // below might trigger an action that can change the order details. 2679 m.broker.Send(events.NewOrderEvent(ctx, order)) 2680 2681 orderUpdates := m.handleConfirmation(ctx, confirmation, nil) 2682 return confirmation, orderUpdates, nil 2683 } 2684 2685 func (m *Market) checkPriceAndGetTrades(ctx context.Context, order *types.Order) ([]*types.Trade, error) { 2686 trades, err := m.matching.GetTrades(order) 2687 if err != nil { 2688 return nil, err 2689 } 2690 2691 if order.PostOnly && len(trades) > 0 { 2692 return nil, types.OrderErrorPostOnlyOrderWouldTrade 2693 } 2694 2695 persistent := true 2696 switch order.TimeInForce { 2697 case types.OrderTimeInForceFOK, types.OrderTimeInForceGFN, types.OrderTimeInForceIOC: 2698 persistent = false 2699 } 2700 2701 for _, t := range trades { 2702 if m.pMonitor.CheckPrice(ctx, m.as, t.Price, persistent, false) { 2703 return nil, types.OrderErrorNonPersistentOrderOutOfPriceBounds 2704 } 2705 if evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()); evt != nil { 2706 m.broker.Send(evt) 2707 } 2708 // start the monitoring auction if required? 2709 if m.as.AuctionStart() { 2710 m.enterAuction(ctx) 2711 return nil, nil 2712 } 2713 } 2714 2715 return trades, nil 2716 } 2717 2718 // addParty returns true if the party is new to the market, false otherwise. 2719 func (m *Market) addParty(party string) bool { 2720 _, registered := m.parties[party] 2721 if !registered { 2722 m.parties[party] = struct{}{} 2723 } 2724 return !registered 2725 } 2726 2727 func (m *Market) calcFees(trades []*types.Trade) (events.FeesTransfer, error) { 2728 // if we have some trades, let's try to get the fees 2729 2730 if len(trades) <= 0 || m.as.IsOpeningAuction() { 2731 return nil, nil 2732 } 2733 2734 // first we get the fees for these trades 2735 var ( 2736 fees events.FeesTransfer 2737 err error 2738 ) 2739 2740 if !m.as.InAuction() { 2741 fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 2742 } else if m.as.IsMonitorAuction() { 2743 // we are in auction mode 2744 fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 2745 } else if m.as.IsFBA() { 2746 fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 2747 } 2748 2749 if err != nil { 2750 return nil, err 2751 } 2752 return fees, nil 2753 } 2754 2755 func (m *Market) applyFees(ctx context.Context, order *types.Order, fees events.FeesTransfer) error { 2756 var transfers []*types.LedgerMovement 2757 var err error 2758 2759 if !m.as.InAuction() { 2760 transfers, err = m.collateral.TransferFeesContinuousTrading(ctx, m.GetID(), m.settlementAsset, fees) 2761 } else if m.as.IsMonitorAuction() { 2762 // @TODO handle this properly 2763 transfers, err = m.collateral.TransferFees(ctx, m.GetID(), m.settlementAsset, fees) 2764 } else if m.as.IsFBA() { 2765 // @TODO implement transfer for auction types 2766 transfers, err = m.collateral.TransferFees(ctx, m.GetID(), m.settlementAsset, fees) 2767 } 2768 2769 if err != nil { 2770 m.log.Error("unable to transfer fees for trades", 2771 logging.String("order-id", order.ID), 2772 logging.String("market-id", m.GetID()), 2773 logging.Error(err)) 2774 return types.OrderErrorInsufficientFundsToPayFees 2775 } 2776 2777 // send transfers through the broker 2778 if len(transfers) > 0 { 2779 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 2780 } 2781 2782 m.marketActivityTracker.UpdateFeesFromTransfers(m.settlementAsset, m.GetID(), fees.Transfers()) 2783 2784 return nil 2785 } 2786 2787 func (m *Market) handleConfirmationPassiveOrders( 2788 ctx context.Context, 2789 conf *types.OrderConfirmation, 2790 ) { 2791 if conf.PassiveOrdersAffected != nil { 2792 evts := make([]events.Event, 0, len(conf.PassiveOrdersAffected)) 2793 2794 // Insert or update passive orders siting on the book 2795 for _, order := range conf.PassiveOrdersAffected { 2796 // if the order was generated by an offbook source such as an AMM we need to register the order here 2797 // since it was never registered as an incoming order. 2798 if order.GeneratedOffbook { 2799 cpy := order.Clone() 2800 cpy.Remaining = cpy.Size // remaining will be 0 since it has traded, so we copy it back to its full size to register 2801 m.position.RegisterOrder(ctx, cpy) 2802 } 2803 2804 // set the `updatedAt` value as these orders have changed 2805 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 2806 evts = append(evts, events.NewOrderEvent(ctx, order)) 2807 2808 // If the order is a pegged order and is complete we must remove it from the pegged list 2809 if order.PeggedOrder != nil { 2810 if order.Remaining == 0 || order.Status != types.OrderStatusActive { 2811 m.removePeggedOrder(order) 2812 } 2813 } 2814 2815 // remove the order from the expiring list 2816 // if it was a GTT order 2817 if order.IsExpireable() && order.IsFinished() { 2818 m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID) 2819 } 2820 } 2821 2822 m.broker.SendBatch(evts) 2823 } 2824 } 2825 2826 func decreasedPosition(p1, p2 events.MarketPosition) int64 { 2827 // was long, still long (or 0) 2828 if p1.Size() > 0 && p2.Size() >= 0 && p2.Size() < p1.Size() { 2829 return p1.Size() - p2.Size() 2830 } 2831 // was short, still short (or 0) 2832 if p1.Size() < 0 && p2.Size() <= 0 && p2.Size() > p1.Size() { 2833 return p2.Size() - p1.Size() 2834 } 2835 // position changed side 2836 if p1.Size()*p2.Size() < 0 { 2837 if p1.Size() > 0 { 2838 return p1.Size() 2839 } 2840 return -p1.Size() 2841 } 2842 return 0 2843 } 2844 2845 func (m *Market) handleConfirmation(ctx context.Context, conf *types.OrderConfirmation, tradeT *types.TradeType) []*types.Order { 2846 // When re-submitting liquidity order, it happen that the pricing is putting 2847 // the order at a price which makes it uncross straight away. 2848 // then triggering this handleConfirmation flow, etc. 2849 // Although the order is considered aggressive, and we never expect in the flow 2850 // for an aggressive order to be pegged, so we never remove them from the pegged 2851 // list. All this impact the float of EnterAuction, which if triggered from there 2852 // will try to park all pegged orders, including this order which have never been 2853 // removed from the pegged list. We add this check to make sure that if the 2854 // aggressive order is pegged, we then do remove it from the list. 2855 if conf.Order.PeggedOrder != nil { 2856 if conf.Order.Remaining == 0 || conf.Order.Status != types.OrderStatusActive { 2857 m.removePeggedOrder(conf.Order) 2858 } 2859 } 2860 2861 m.handleConfirmationPassiveOrders(ctx, conf) 2862 orderUpdates := make([]*types.Order, 0, len(conf.PassiveOrdersAffected)+1) 2863 orderUpdates = append(orderUpdates, conf.Order) 2864 orderUpdates = append(orderUpdates, conf.PassiveOrdersAffected...) 2865 2866 if len(conf.Trades) == 0 { 2867 return orderUpdates 2868 } 2869 m.setLastTradedPrice(conf.Trades[len(conf.Trades)-1]) 2870 2871 // Insert all trades resulted from the executed order 2872 tradeEvts := make([]events.Event, 0, len(conf.Trades)) 2873 tradedValue, _ := num.UintFromDecimal( 2874 conf.TradedValue().ToDecimal().Div(m.positionFactor)) 2875 for idx, trade := range conf.Trades { 2876 trade.SetIDs(m.idgen.NextID(), conf.Order, conf.PassiveOrdersAffected[idx]) 2877 if tradeT != nil { 2878 trade.Type = *tradeT 2879 } else { 2880 m.markPriceCalculator.NewTrade(trade) 2881 if m.internalCompositePriceCalculator != nil { 2882 m.internalCompositePriceCalculator.NewTrade(trade) 2883 } 2884 } 2885 2886 tradeEvts = append(tradeEvts, events.NewTradeEvent(ctx, *trade)) 2887 notionalTraded, _ := num.UintFromDecimal(num.UintZero().Mul(num.NewUint(trade.Size), trade.Price).ToDecimal().Div(m.positionFactor)) 2888 m.marketActivityTracker.RecordNotionalTraded(m.settlementAsset, m.mkt.ID, notionalTraded) 2889 2890 preTradePositions := m.position.GetPositionsByParty(trade.Buyer, trade.Seller) 2891 for i, mp := range m.position.Update(ctx, trade, conf.PassiveOrdersAffected[idx], conf.Order) { 2892 m.marketActivityTracker.RecordPosition(m.settlementAsset, mp.Party(), m.mkt.ID, mp.Size(), trade.Price, m.positionFactor, m.timeService.GetTimeNow()) 2893 if closedPosition := decreasedPosition(preTradePositions[i], mp); closedPosition > 0 { 2894 var reaslisedPosition num.Decimal 2895 if preTradePositions[i].Size() > 0 { 2896 // a party **reduces** their **LONG** position 2897 // (trade price - average entry price) * position delta$$ 2898 reaslisedPosition = trade.Price.ToDecimal().Sub(preTradePositions[i].AverageEntryPrice().ToDecimal()).Mul(num.DecimalFromInt64(closedPosition)).Div(m.positionFactor) 2899 } else { 2900 // a party **reduces** their **SHORT** position 2901 // (average entry price - trade price) * position delta$$ 2902 reaslisedPosition = preTradePositions[i].AverageEntryPrice().ToDecimal().Sub(trade.Price.ToDecimal()).Mul(num.DecimalFromInt64(closedPosition)).Div(m.positionFactor) 2903 } 2904 m.marketActivityTracker.RecordRealisedPosition(m.GetSettlementAsset(), mp.Party(), m.mkt.ID, reaslisedPosition) 2905 } 2906 } 2907 // if the passive party is in isolated margin we need to update the margin on the position change 2908 if m.getMarginMode(conf.PassiveOrdersAffected[idx].Party) == types.MarginModeIsolatedMargin { 2909 pos, _ := m.position.GetPositionByPartyID(conf.PassiveOrdersAffected[idx].Party) 2910 err := m.updateIsolatedMarginsOnPositionChange(ctx, pos, conf.PassiveOrdersAffected[idx], trade) 2911 if err != nil { 2912 // if the evaluation after the position update means the party has insufficient funds, all of their orders need to be stopped 2913 // but first we need to transfer the margins from the order margin account. 2914 if err == risk.ErrInsufficientFundsForMaintenanceMargin { 2915 m.handleIsolatedMarginInsufficientOrderMargin(ctx, conf.PassiveOrdersAffected[idx].Party) 2916 } 2917 m.log.Error("failed to update isolated margin on position change", logging.Error(err)) 2918 } 2919 } 2920 // if we're uncrossing an auction then we need to do this also for parties with isolated margin on the "aggressive" side 2921 if m.as.InAuction() { 2922 aggressor := conf.Order.Party 2923 if m.getMarginMode(aggressor) == types.MarginModeIsolatedMargin { 2924 aggressorOrder := conf.Order 2925 pos, _ := m.position.GetPositionByPartyID(aggressor) 2926 err := m.updateIsolatedMarginsOnPositionChange(ctx, pos, aggressorOrder, trade) 2927 if err != nil { 2928 m.log.Error("failed to update isolated margin on position change", logging.Error(err)) 2929 if err == risk.ErrInsufficientFundsForMaintenanceMargin { 2930 m.handleIsolatedMarginInsufficientOrderMargin(ctx, aggressor) 2931 } 2932 } 2933 } 2934 } 2935 // Record open interest change 2936 if err := m.tsCalc.RecordOpenInterest(m.position.GetOpenInterest(), m.timeService.GetTimeNow()); err != nil { 2937 m.log.Debug("unable record open interest", 2938 logging.String("market-id", m.GetID()), 2939 logging.Error(err)) 2940 } 2941 // add trade to settlement engine for correct MTM settlement of individual trades 2942 m.settlement.AddTrade(trade) 2943 } 2944 if !m.as.InAuction() { 2945 aggressor := conf.Order.Party 2946 if quantum, err := m.collateral.GetAssetQuantum(m.settlementAsset); err == nil && !quantum.IsZero() { 2947 n, _ := num.UintFromDecimal(tradedValue.ToDecimal().Div(quantum)) 2948 m.marketActivityTracker.RecordNotionalTakerVolume(m.mkt.ID, aggressor, n) 2949 } 2950 } 2951 m.feeSplitter.AddTradeValue(tradedValue) 2952 m.marketActivityTracker.AddValueTraded(m.settlementAsset, m.mkt.ID, tradedValue) 2953 m.broker.SendBatch(tradeEvts) 2954 2955 // check reference moves if we have order updates, and we are not in an auction (or leaving an auction) 2956 // we handle reference moves in confirmMTM when leaving an auction already 2957 if len(orderUpdates) > 0 && !m.as.CanLeave() && !m.as.InAuction() { 2958 m.checkForReferenceMoves( 2959 ctx, orderUpdates, false) 2960 } 2961 2962 return orderUpdates 2963 } 2964 2965 // confirmMTM returns false if the MTM settlement was skipped due to price cap. 2966 func (m *Market) confirmMTM(ctx context.Context, skipMargin bool) bool { 2967 // now let's get the transfers for MTM settlement 2968 mp := m.getCurrentMarkPrice() 2969 // if this is a capped market with a max price, skip MTM until the mark price is within the [0,max_price] range. 2970 if m.capMax != nil && m.capMax.LT(mp) { 2971 return false 2972 } 2973 m.liquidation.UpdateMarkPrice(mp.Clone()) 2974 evts := m.position.UpdateMarkPrice(mp) 2975 settle := m.settlement.SettleMTM(ctx, mp, evts) 2976 2977 for _, t := range settle { 2978 m.recordPositionActivity(t.Transfer()) 2979 } 2980 2981 // Only process collateral and risk once per order, not for every trade 2982 margins, isolatedMarginPartiesToClose := m.collateralAndRisk(ctx, settle) 2983 orderUpdates := m.handleRiskEvts(ctx, margins, isolatedMarginPartiesToClose) 2984 2985 // orders updated -> check reference moves 2986 // force check 2987 m.checkForReferenceMoves(ctx, orderUpdates, false) 2988 2989 if !skipMargin { 2990 // release excess margin for all positions 2991 m.recheckMargin(ctx, m.position.Positions()) 2992 } 2993 2994 // tell the AMM engine we've MTM'd so any closing pool's can be cancelled 2995 m.amm.OnMTM(ctx) 2996 return true 2997 } 2998 2999 func (m *Market) handleRiskEvts(ctx context.Context, margins []events.Risk, isolatedMargin []events.Risk) []*types.Order { 3000 if len(margins) == 0 { 3001 return nil 3002 } 3003 isolatedForCloseout := m.collateral.IsolatedMarginUpdate(isolatedMargin) 3004 transfers, closed, bondPenalties, err := m.collateral.MarginUpdate(ctx, m.GetID(), margins) 3005 if err != nil { 3006 m.log.Error("margin update had issues", logging.Error(err)) 3007 } 3008 if err == nil && len(transfers) > 0 { 3009 evt := events.NewLedgerMovements(ctx, transfers) 3010 m.broker.Send(evt) 3011 } 3012 if len(bondPenalties) > 0 { 3013 transfers, err := m.bondSlashing(ctx, bondPenalties...) 3014 if err != nil { 3015 m.log.Error("Failed to perform bond slashing", 3016 logging.Error(err)) 3017 } 3018 // if bond slashing occurred then amounts in "closed" will not be accurate 3019 if len(transfers) > 0 { 3020 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 3021 closedRecalculated := make([]events.Margin, 0, len(closed)) 3022 for _, c := range closed { 3023 if pos, ok := m.position.GetPositionByPartyID(c.Party()); ok { 3024 margin, err := m.collateral.GetPartyMargin(pos, m.settlementAsset, m.mkt.ID) 3025 if err != nil { 3026 m.log.Error("couldn't get party margin", 3027 logging.PartyID(c.Party()), 3028 logging.Error(err)) 3029 // keep old value if we weren't able to recalculate 3030 closedRecalculated = append(closedRecalculated, c) 3031 continue 3032 } 3033 closedRecalculated = append(closedRecalculated, margin) 3034 } 3035 } 3036 closed = closedRecalculated 3037 } 3038 } 3039 3040 closed = append(closed, isolatedForCloseout...) 3041 if len(closed) == 0 { 3042 m.updateLiquidityFee(ctx) 3043 return nil 3044 } 3045 var orderUpdates []*types.Order 3046 upd := m.resolveClosedOutParties(ctx, closed) 3047 if len(upd) > 0 { 3048 orderUpdates = append(orderUpdates, upd...) 3049 } 3050 3051 m.updateLiquidityFee(ctx) 3052 return orderUpdates 3053 } 3054 3055 // updateLiquidityFee computes the current LiquidityProvision fee and updates 3056 // the fee engine. 3057 func (m *Market) updateLiquidityFee(ctx context.Context) { 3058 provisions := m.liquidityEngine.ProvisionsPerParty().Clone() 3059 for party, pool := range m.amm.GetAMMPoolsBySubAccount() { 3060 provisions[party] = &types.LiquidityProvision{ 3061 Party: party, 3062 CommitmentAmount: pool.CommitmentAmount(), 3063 Fee: pool.LiquidityFee(), 3064 } 3065 } 3066 3067 var fee num.Decimal 3068 switch m.mkt.Fees.LiquidityFeeSettings.Method { 3069 case types.LiquidityFeeMethodConstant: 3070 if len(provisions) != 0 { 3071 fee = m.mkt.Fees.LiquidityFeeSettings.FeeConstant 3072 } 3073 case types.LiquidityFeeMethodMarginalCost: 3074 fee = provisions.FeeForTarget(m.getTargetStake()) 3075 case types.LiquidityFeeMethodWeightedAverage: 3076 fee = provisions.FeeForWeightedAverage() 3077 default: 3078 m.log.Panic("unknown liquidity fee method") 3079 } 3080 3081 if !fee.Equals(m.getLiquidityFee()) { 3082 m.fee.SetLiquidityFee(fee) 3083 m.setLiquidityFee(fee) 3084 m.broker.Send( 3085 events.NewMarketUpdatedEvent(ctx, *m.mkt), 3086 ) 3087 } 3088 } 3089 3090 func (m *Market) setLiquidityFee(fee num.Decimal) { 3091 m.mkt.Fees.Factors.LiquidityFee = fee 3092 } 3093 3094 func (m *Market) getLiquidityFee() num.Decimal { 3095 return m.mkt.Fees.Factors.LiquidityFee 3096 } 3097 3098 // resolveClosedOutParties - the parties with the given market position who haven't got sufficient collateral 3099 // need to be closed out -> the network buys/sells the open volume, and trades with the rest of the network 3100 // this flow is similar to the SubmitOrder bit where trades are made, with fewer checks (e.g. no MTM settlement, no risk checks) 3101 // pass in the order which caused parties to be distressed. 3102 func (m *Market) resolveClosedOutParties(ctx context.Context, distressedMarginEvts []events.Margin) []*types.Order { 3103 if len(distressedMarginEvts) == 0 { 3104 return nil 3105 } 3106 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "resolveClosedOutParties") 3107 defer timer.EngineTimeCounterAdd() 3108 3109 now := m.timeService.GetTimeNow() 3110 // this is going to be run after the closed out routines 3111 // are finished, in order to notify the liquidity engine of 3112 // any changes in the book 3113 orderUpdates := []*types.Order{} 3114 3115 distressedPos := make([]events.MarketPosition, 0, len(distressedMarginEvts)) 3116 for _, v := range distressedMarginEvts { 3117 if m.log.GetLevel() == logging.DebugLevel { 3118 m.log.Debug("closing out party", 3119 logging.PartyID(v.Party()), 3120 logging.MarketID(m.GetID())) 3121 } 3122 // we're not removing orders for isolated margin closed out parties 3123 if m.getMarginMode(v.Party()) == types.MarginModeCrossMargin { 3124 distressedPos = append(distressedPos, v) 3125 } 3126 } 3127 3128 rmorders, err := m.matching.RemoveDistressedOrders(distressedPos) 3129 if err != nil { 3130 m.log.Panic("Failed to remove distressed parties from the orderbook", 3131 logging.Error(err), 3132 ) 3133 } 3134 3135 mktID := m.GetID() 3136 // push rm orders into buf 3137 // and remove the orders from the positions engine 3138 evts := []events.Event{} 3139 for _, o := range rmorders { 3140 if o.IsExpireable() { 3141 m.expiringOrders.RemoveOrder(o.ExpiresAt, o.ID) 3142 } 3143 if o.PeggedOrder != nil { 3144 m.removePeggedOrder(o) 3145 } 3146 o.UpdatedAt = now.UnixNano() 3147 evts = append(evts, events.NewOrderEvent(ctx, o)) 3148 _ = m.position.UnregisterOrder(ctx, o) 3149 } 3150 3151 // add the orders remove from the book to the orders 3152 // to be sent to the liquidity engine 3153 orderUpdates = append(orderUpdates, rmorders...) 3154 3155 // now we also remove ALL parked order for the different parties 3156 for _, v := range distressedPos { 3157 orders, oevts := m.peggedOrders.RemoveAllForParty( 3158 ctx, v.Party(), types.OrderStatusStopped) 3159 3160 for _, v := range orders { 3161 m.expiringOrders.RemoveOrder(v.ExpiresAt, v.ID) 3162 } 3163 3164 // add all pegged orders too to the orderUpdates 3165 orderUpdates = append(orderUpdates, orders...) 3166 // add all events to evts list 3167 evts = append(evts, oevts...) 3168 } 3169 3170 // send all orders which got stopped through the event bus 3171 m.broker.SendBatch(evts) 3172 3173 closed := distressedMarginEvts // default behaviour (ie if rmorders is empty) is to closed out all distressed positions we started out with 3174 3175 // we need to check margin requirements again, it's possible for parties to no longer be distressed now that their orders have been removed 3176 if len(rmorders) != 0 { 3177 var okPos []events.Margin // need to declare this because we want to reassign closed 3178 // now that we closed orders, let's run the risk engine again 3179 // so it'll separate the positions still in distress from the 3180 // which have acceptable margins 3181 increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano()) 3182 var pr *num.Uint 3183 if m.capMax != nil && m.fCap.FullyCollateralised { 3184 pr = m.capMax.Clone() 3185 } else { 3186 pr = m.lastTradedPrice.Clone() 3187 } 3188 okPos, closed = m.risk.ExpectMargins(distressedMarginEvts, pr, increment, m.getAuctionPrice()) 3189 3190 parties := make([]string, 0, len(okPos)) 3191 for _, v := range okPos { 3192 parties = append(parties, v.Party()) 3193 } 3194 if m.log.IsDebug() { 3195 for _, pID := range parties { 3196 m.log.Debug("previously distressed party have now an acceptable margin", 3197 logging.String("market-id", mktID), 3198 logging.String("party-id", pID)) 3199 } 3200 } 3201 if len(parties) > 0 { 3202 // emit event indicating we had to close orders, but parties were not distressed anymore after doing so. 3203 m.broker.Send(events.NewDistressedOrdersEvent(ctx, mktID, parties)) 3204 } 3205 } 3206 3207 // if no position are meant to be closed, just return now. 3208 if len(closed) <= 0 { 3209 return orderUpdates 3210 } 3211 3212 currentMP := m.getCurrentMarkPrice() 3213 mCmp := m.priceToMarketPrecision(currentMP) 3214 closedMPs, closedParties, _ := m.liquidation.ClearDistressedParties(ctx, m.idgen, closed, currentMP, mCmp) 3215 dp, sp := m.position.MarkDistressed(closedParties) 3216 if len(dp) > 0 || len(sp) > 0 { 3217 m.broker.Send(events.NewDistressedPositionsEvent(ctx, m.GetID(), dp, sp)) 3218 } 3219 m.finalizePartiesCloseOut(ctx, closed, closedMPs) 3220 m.zeroOutNetwork(ctx, closedParties) 3221 return orderUpdates 3222 } 3223 3224 func (m *Market) finalizePartiesCloseOut( 3225 ctx context.Context, 3226 closed []events.Margin, 3227 closedMPs []events.MarketPosition, 3228 ) { 3229 // remove accounts, positions and return 3230 // from settlement engine first 3231 m.settlement.RemoveDistressed(ctx, closed) 3232 // then from positions 3233 toRemoveFromPosition := []events.MarketPosition{} 3234 for _, mp := range closedMPs { 3235 if m.getMarginMode(mp.Party()) == types.MarginModeCrossMargin || (mp.Buy() == 0 && mp.Sell() == 0) { 3236 toRemoveFromPosition = append(toRemoveFromPosition, mp) 3237 } 3238 var reaslisedPosition num.Decimal 3239 if mp.Size() > 0 { 3240 // a party **closed out** on their **LONG** position 3241 // (trade price - average entry price) * position delta$$ 3242 reaslisedPosition = m.getCurrentMarkPrice().ToDecimal().Sub(mp.AverageEntryPrice().ToDecimal()).Mul(num.DecimalFromInt64(mp.Size())).Div(m.positionFactor) 3243 } else { 3244 // a party **closed out** their **SHORT** position 3245 // (average entry price - trade price) * position delta$$ 3246 reaslisedPosition = mp.AverageEntryPrice().ToDecimal().Sub(m.getCurrentMarkPrice().ToDecimal()).Mul(num.DecimalFromInt64(-mp.Size())).Div(m.positionFactor) 3247 } 3248 m.marketActivityTracker.RecordRealisedPosition(m.settlementAsset, mp.Party(), m.mkt.ID, reaslisedPosition) 3249 } 3250 m.position.RemoveDistressed(toRemoveFromPosition) 3251 // but we want to update the market activity tracker on their 0 position for all of the closed parties 3252 for _, mp := range closedMPs { 3253 // record the updated closed out party's position 3254 m.marketActivityTracker.RecordPosition(m.settlementAsset, mp.Party(), m.mkt.ID, 0, mp.Price(), m.positionFactor, m.timeService.GetTimeNow()) 3255 } 3256 3257 // if the distressed party was an AMM we need to stop it AMM-ing 3258 m.amm.RemoveDistressed(ctx, closedMPs) 3259 3260 // finally remove from collateral (moving funds where needed) 3261 movements, err := m.collateral.RemoveDistressed( 3262 ctx, closedMPs, m.GetID(), m.settlementAsset, m.useGeneralAccountForMarginSearch) 3263 if err != nil { 3264 m.log.Panic( 3265 "Failed to remove distressed accounts cleanly", 3266 logging.Error(err)) 3267 } 3268 3269 if len(movements.Entries) > 0 { 3270 m.broker.Send( 3271 events.NewLedgerMovements( 3272 ctx, []*types.LedgerMovement{movements}), 3273 ) 3274 } 3275 3276 for _, mp := range closedMPs { 3277 if m.getMarginMode(mp.Party()) == types.MarginModeIsolatedMargin || (mp.Buy() != 0 && mp.Sell() != 0) { 3278 pp, _ := m.position.GetPositionByPartyID(mp.Party()) 3279 if pp == nil { 3280 continue 3281 } 3282 marketObservable, evt, increment, _, marginFactor, orders, err := m.getIsolatedMarginContext(pp, nil) 3283 if err != nil { 3284 m.log.Panic("failed to get isolated margin context") 3285 } 3286 _, err = m.risk.CheckMarginInvariants(ctx, evt, marketObservable, increment, orders, marginFactor) 3287 if err == risk.ErrInsufficientFundsForOrderMargin { 3288 m.log.Debug("party in isolated margin mode has insufficient order margin", logging.String("party", mp.Party())) 3289 m.handleIsolatedMarginInsufficientOrderMargin(ctx, mp.Party()) 3290 } 3291 } 3292 } 3293 } 3294 3295 func (m *Market) zeroOutNetwork(ctx context.Context, parties []string) { 3296 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "zeroOutNetwork") 3297 defer timer.EngineTimeCounterAdd() 3298 3299 // ensure an original price is set 3300 marketID := m.GetID() 3301 now := m.timeService.GetTimeNow().UnixNano() 3302 3303 evts := make([]events.Event, 0, len(parties)) 3304 3305 marginLevels := types.MarginLevels{ 3306 MarketID: marketID, 3307 Asset: m.settlementAsset, 3308 Timestamp: now, 3309 } 3310 for _, p := range parties { 3311 marginLevels.Party = p 3312 marginLevels.MarginMode = m.getMarginMode(p) 3313 marginLevels.MarginFactor = m.getMarginFactor(p) 3314 if marginLevels.MarginMode == types.MarginModeIsolatedMargin { 3315 // for isolated margin closed out for position, there may still be a valid order margin 3316 marginLevels.OrderMargin = m.risk.CalcOrderMarginsForClosedOutParty(m.matching.GetOrdersPerParty(p), marginLevels.MarginFactor) 3317 } 3318 evts = append(evts, events.NewMarginLevelsEvent(ctx, marginLevels)) 3319 } 3320 if len(evts) > 0 { 3321 m.broker.SendBatch(evts) 3322 } 3323 } 3324 3325 func (m *Market) recheckMargin(ctx context.Context, pos []events.MarketPosition) { 3326 posCrossMargin := make([]events.MarketPosition, 0, len(pos)) 3327 3328 for _, mp := range pos { 3329 if m.getMarginMode(mp.Party()) == types.MarginModeCrossMargin { 3330 posCrossMargin = append(posCrossMargin, mp) 3331 } 3332 } 3333 risk := m.updateMargin(ctx, posCrossMargin) 3334 if len(risk) == 0 { 3335 return 3336 } 3337 // now transfer margins, ignore closed because we're only recalculating for non-distressed parties. 3338 m.transferRecheckMargins(ctx, risk) 3339 } 3340 3341 func (m *Market) checkMarginForOrder(ctx context.Context, pos *positions.MarketPosition, order *types.Order) error { 3342 risk, closed, err := m.calcMargins(ctx, pos, order) 3343 // margin error 3344 if err != nil { 3345 return err 3346 } 3347 3348 // margins calculated, set about tranferring funds. At this point, if closed is not empty, those parties are distressed 3349 // the risk slice are risk events, that we must use to transfer funds 3350 return m.transferMargins(ctx, risk, closed) 3351 } 3352 3353 // updateIsolatedMarginOnAggressor is called when a new or amended order is matched immediately upon submission. 3354 // it checks that new margin requirements can be satisfied and if so transfers the margin from the general account to the margin account. 3355 func (m *Market) updateIsolatedMarginOnAggressor(ctx context.Context, pos *positions.MarketPosition, order *types.Order, trades []*types.Trade, isAmend bool, fees *num.Uint) error { 3356 marketObservable, mpos, increment, _, marginFactor, orders, err := m.getIsolatedMarginContext(pos, order) 3357 if err != nil { 3358 return err 3359 } 3360 3361 totalTrades := uint64(0) 3362 for _, t := range trades { 3363 totalTrades += t.Size 3364 } 3365 3366 clonedOrders := make([]*types.Order, 0, len(orders)) 3367 found := false 3368 for _, o := range orders { 3369 if o.ID == order.ID { 3370 clonedOrder := order.Clone() 3371 clonedOrder.Remaining -= totalTrades 3372 if clonedOrder.Remaining > 0 { 3373 clonedOrders = append(clonedOrders, clonedOrder) 3374 } 3375 found = true 3376 } else { 3377 clonedOrders = append(clonedOrders, o) 3378 } 3379 } 3380 if !found { 3381 clonedOrder := order.Clone() 3382 clonedOrder.Remaining -= totalTrades 3383 if clonedOrder.Remaining > 0 { 3384 clonedOrders = append(clonedOrders, clonedOrder) 3385 } 3386 } 3387 3388 aggressorFee := num.UintZero() 3389 if fees != nil { 3390 aggressorFee = fees.Clone() 3391 } 3392 risk, err := m.risk.UpdateIsolatedMarginOnAggressor(ctx, mpos, marketObservable, increment, clonedOrders, trades, marginFactor, order.Side, isAmend, aggressorFee) 3393 if err != nil { 3394 return err 3395 } 3396 if risk == nil { 3397 return nil 3398 } 3399 return m.transferMargins(ctx, risk, nil) 3400 } 3401 3402 func (m *Market) updateIsolatedMarginOnOrderCancel(ctx context.Context, mpos *positions.MarketPosition, order *types.Order) error { 3403 marketObservable, pos, increment, auctionPrice, marginFactor, orders, err := m.getIsolatedMarginContext(mpos, order) 3404 if err != nil { 3405 return err 3406 } 3407 risk, err := m.risk.UpdateIsolatedMarginOnOrderCancel(ctx, pos, orders, marketObservable, auctionPrice, increment, marginFactor) 3408 if err != nil { 3409 return err 3410 } 3411 if risk == nil { 3412 return nil 3413 } 3414 return m.transferMargins(ctx, []events.Risk{risk}, nil) 3415 } 3416 3417 func (m *Market) updateIsolatedMarginOnOrder(ctx context.Context, mpos *positions.MarketPosition, order *types.Order) error { 3418 marketObservable, pos, increment, auctionPrice, marginFactor, orders, err := m.getIsolatedMarginContext(mpos, order) 3419 if err != nil { 3420 return err 3421 } 3422 risk, err := m.risk.UpdateIsolatedMarginOnOrder(ctx, pos, orders, marketObservable, auctionPrice, increment, marginFactor) 3423 if err != nil { 3424 return err 3425 } 3426 if risk == nil { 3427 return nil 3428 } 3429 return m.transferMargins(ctx, []events.Risk{risk}, nil) 3430 } 3431 3432 func (m *Market) checkMarginForAmendOrder(ctx context.Context, existingOrder *types.Order, amendedOrder *types.Order) error { 3433 origPos, ok := m.position.GetPositionByPartyID(existingOrder.Party) 3434 if !ok { 3435 m.log.Panic("could not get position for party", logging.PartyID(existingOrder.Party)) 3436 } 3437 3438 pos := origPos.Clone() 3439 3440 // if order was park we have nothing to do here 3441 if existingOrder.Status != types.OrderStatusParked { 3442 pos.UnregisterOrder(m.log, existingOrder) 3443 } 3444 3445 pos.RegisterOrder(m.log, amendedOrder) 3446 3447 // we are just checking here if we can pass the margin calls. 3448 _, _, err := m.calcMargins(ctx, pos, amendedOrder) 3449 return err 3450 } 3451 3452 func (m *Market) setLastTradedPrice(trade *types.Trade) { 3453 m.lastTradedPrice = trade.Price.Clone() 3454 } 3455 3456 // this function handles moving money after settle MTM + risk margin updates 3457 // but does not move the money between party accounts (ie not to/from margin accounts after risk). 3458 func (m *Market) collateralAndRisk(ctx context.Context, settle []events.Transfer) ([]events.Risk, []events.Risk) { 3459 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "collateralAndRisk") 3460 defer timer.EngineTimeCounterAdd() 3461 evts, response, err := m.collateral.MarkToMarket(ctx, m.GetID(), settle, m.settlementAsset, m.useGeneralAccountForMarginSearch) 3462 if err != nil { 3463 m.log.Error( 3464 "Failed to process mark to market settlement (collateral)", 3465 logging.Error(err), 3466 ) 3467 return nil, nil 3468 } 3469 // sending response to buffer 3470 if len(response) > 0 { 3471 m.broker.Send(events.NewLedgerMovements(ctx, response)) 3472 } 3473 3474 // let risk engine do its thing here - it returns a slice of money that needs 3475 // to be moved to and from margin accounts 3476 increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano()) 3477 3478 // split to cross and isolated margins to handle separately 3479 crossEvts := make([]events.Margin, 0, len(evts)) 3480 isolatedEvts := make([]events.Margin, 0, len(evts)) 3481 for _, evt := range evts { 3482 if m.getMarginMode(evt.Party()) == types.MarginModeCrossMargin { 3483 crossEvts = append(crossEvts, evt) 3484 } else { 3485 isolatedEvts = append(isolatedEvts, evt) 3486 } 3487 } 3488 3489 crossRiskUpdates := m.risk.UpdateMarginsOnSettlement(ctx, crossEvts, m.getCurrentMarkPriceForMargin(), increment, m.getAuctionPrice()) 3490 isolatedMarginPartiesToClose := []events.Risk{} 3491 for _, evt := range isolatedEvts { 3492 mrgns, err := m.risk.CheckMarginInvariants(ctx, evt, m.getMarketObservable(nil), increment, m.matching.GetOrdersPerParty(evt.Party()), m.getMarginFactor(evt.Party())) 3493 if err == risk.ErrInsufficientFundsForMaintenanceMargin { 3494 m.log.Debug("party in isolated margin mode has insufficient margin", logging.String("party", evt.Party())) 3495 isolatedMarginPartiesToClose = append(isolatedMarginPartiesToClose, mrgns) 3496 } 3497 } 3498 3499 // if len(crossRiskUpdates) == 0 { 3500 // return nil, isolatedMarginPartiesToClose 3501 // } 3502 return crossRiskUpdates, isolatedMarginPartiesToClose 3503 } 3504 3505 func (m *Market) CancelAllStopOrders(ctx context.Context, partyID string) error { 3506 if !m.canTrade() { 3507 return common.ErrTradingNotAllowed 3508 } 3509 3510 stopOrders, err := m.stopOrders.Cancel(partyID, "") 3511 if err != nil { 3512 return err 3513 } 3514 3515 m.removeCancelledExpiringStopOrders(stopOrders) 3516 3517 evts := make([]events.Event, 0, len(stopOrders)) 3518 for _, v := range stopOrders { 3519 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 3520 } 3521 3522 m.broker.SendBatch(evts) 3523 3524 return nil 3525 } 3526 3527 func (m *Market) CancelAllOrders(ctx context.Context, partyID string) ([]*types.OrderCancellationConfirmation, error) { 3528 defer m.onTxProcessed() 3529 3530 if !m.canTrade() { 3531 return nil, common.ErrTradingNotAllowed 3532 } 3533 3534 // get all order for this party in the book 3535 orders := m.matching.GetOrdersPerParty(partyID) 3536 3537 // add all orders being eventually parked 3538 orders = append(orders, m.peggedOrders.GetAllParkedForParty(partyID)...) 3539 3540 // just an early exit, there's just no orders... 3541 if len(orders) <= 0 { 3542 return nil, nil 3543 } 3544 3545 // now we eventually dedup them 3546 uniq := map[string]*types.Order{} 3547 for _, v := range orders { 3548 uniq[v.ID] = v 3549 } 3550 3551 // put them back in the slice, and sort them 3552 orders = make([]*types.Order, 0, len(uniq)) 3553 for _, v := range uniq { 3554 orders = append(orders, v) 3555 } 3556 sort.Slice(orders, func(i, j int) bool { 3557 return orders[i].ID < orders[j].ID 3558 }) 3559 3560 cancellations := make([]*types.OrderCancellationConfirmation, 0, len(orders)) 3561 orderIDs := make([]string, 0, len(orders)) 3562 3563 // now iterate over all orders and cancel one by one. 3564 cancelledOrders := make([]*types.Order, 0, len(orders)) 3565 for _, order := range orders { 3566 cancellation, err := m.cancelOrderInBatch(ctx, partyID, order.ID) 3567 if err != nil { 3568 return nil, err 3569 } 3570 orderIDs = append(orderIDs, order.ID) 3571 cancellations = append(cancellations, cancellation) 3572 cancelledOrders = append(cancelledOrders, cancellation.Order) 3573 } 3574 cancelEvt := events.NewCancelledOrdersEvent(ctx, m.GetID(), partyID, orderIDs...) 3575 m.broker.Send(cancelEvt) 3576 // we have just cancelled all orders, release excess margin 3577 m.releaseMarginExcess(ctx, partyID) 3578 3579 m.checkForReferenceMoves(ctx, cancelledOrders, false) 3580 3581 return cancellations, nil 3582 } 3583 3584 func (m *Market) CancelOrder( 3585 ctx context.Context, 3586 partyID, orderID string, deterministicID string, 3587 ) (oc *types.OrderCancellationConfirmation, _ error) { 3588 idgen := idgeneration.New(deterministicID) 3589 return m.CancelOrderWithIDGenerator(ctx, partyID, orderID, idgen) 3590 } 3591 3592 func (m *Market) CancelOrderWithIDGenerator( 3593 ctx context.Context, 3594 partyID, orderID string, 3595 idgen common.IDGenerator, 3596 ) (oc *types.OrderCancellationConfirmation, _ error) { 3597 defer m.onTxProcessed() 3598 3599 m.idgen = idgen 3600 defer func() { m.idgen = nil }() 3601 3602 if !m.canTrade() { 3603 return nil, common.ErrTradingNotAllowed 3604 } 3605 3606 conf, err := m.cancelOrder(ctx, partyID, orderID) 3607 if err != nil { 3608 return conf, err 3609 } 3610 3611 if !m.as.InAuction() { 3612 m.checkForReferenceMoves(ctx, []*types.Order{conf.Order}, false) 3613 } 3614 3615 return conf, nil 3616 } 3617 3618 func (m *Market) CancelStopOrder( 3619 ctx context.Context, 3620 partyID, orderID string, 3621 ) error { 3622 if !m.canTrade() { 3623 return common.ErrTradingNotAllowed 3624 } 3625 3626 stopOrders, err := m.stopOrders.Cancel(partyID, orderID) 3627 if err != nil || len(stopOrders) <= 0 { // could return just an empty slice 3628 return err 3629 } 3630 3631 m.removeCancelledExpiringStopOrders(stopOrders) 3632 3633 evts := []events.Event{events.NewStopOrderEvent(ctx, stopOrders[0])} 3634 if len(stopOrders) > 1 { 3635 evts = append(evts, events.NewStopOrderEvent(ctx, stopOrders[1])) 3636 } 3637 3638 m.broker.SendBatch(evts) 3639 3640 return nil 3641 } 3642 3643 func (m *Market) removeCancelledExpiringStopOrders( 3644 stopOrders []*types.StopOrder, 3645 ) { 3646 for _, o := range stopOrders { 3647 if o.Expiry.Expires() { 3648 m.expiringStopOrders.RemoveOrder(o.Expiry.ExpiresAt.UnixNano(), o.ID) 3649 } 3650 } 3651 } 3652 3653 func (m *Market) cancelOrderInBatch(ctx context.Context, partyID string, orderID string) (*types.OrderCancellationConfirmation, error) { 3654 return m.cancelSingleOrder(ctx, partyID, orderID, true) 3655 } 3656 3657 // CancelOrder cancels the given order. 3658 func (m *Market) cancelOrder(ctx context.Context, partyID, orderID string) (*types.OrderCancellationConfirmation, error) { 3659 return m.cancelSingleOrder(ctx, partyID, orderID, false) 3660 } 3661 3662 func (m *Market) cancelSingleOrder(ctx context.Context, partyID, orderID string, isBatch bool) (*types.OrderCancellationConfirmation, error) { 3663 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "CancelOrder") 3664 defer timer.EngineTimeCounterAdd() 3665 3666 if m.closed { 3667 return nil, common.ErrMarketClosed 3668 } 3669 3670 order, foundOnBook, err := m.getOrderByID(orderID) 3671 if err != nil { 3672 return nil, err 3673 } 3674 3675 // Only allow the original order creator to cancel their order 3676 if order.Party != partyID { 3677 if m.log.GetLevel() == logging.DebugLevel { 3678 m.log.Debug("Party ID mismatch", 3679 logging.String("party-id", partyID), 3680 logging.String("order-id", orderID), 3681 logging.String("market", m.mkt.ID)) 3682 } 3683 return nil, types.ErrInvalidPartyID 3684 } 3685 3686 if !isBatch { 3687 defer m.releaseMarginExcess(ctx, partyID) 3688 } 3689 3690 if foundOnBook { 3691 cancellation, err := m.matching.CancelOrder(order) 3692 if cancellation == nil || err != nil { 3693 if m.log.GetLevel() == logging.DebugLevel { 3694 m.log.Debug("Failure after cancel order from matching engine", 3695 logging.String("party-id", partyID), 3696 logging.String("order-id", orderID), 3697 logging.String("market", m.mkt.ID), 3698 logging.Error(err)) 3699 } 3700 return nil, err 3701 } 3702 _ = m.position.UnregisterOrder(ctx, order) 3703 } 3704 3705 if order.IsExpireable() { 3706 m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID) 3707 } 3708 3709 // If this is a pegged order, remove from pegged and parked lists 3710 if order.PeggedOrder != nil { 3711 m.removePeggedOrder(order) 3712 order.Status = types.OrderStatusCancelled 3713 } 3714 3715 // Publish the changed order details 3716 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 3717 if !isBatch { 3718 m.broker.Send(events.NewCancelledOrdersEvent(ctx, order.MarketID, order.Party, order.ID)) 3719 } 3720 3721 // if the order was found in the book and we're in isolated margin we need to update the 3722 // order margin 3723 if foundOnBook && m.getMarginMode(partyID) == types.MarginModeIsolatedMargin { 3724 pos, _ := m.position.GetPositionByPartyID(partyID) 3725 // it might be that we place orders before an auction, then during an auction we're trying to cancel the order - if we have still other order 3726 // they will definitely have insufficient order margin but that's ok, either they will be fine when uncrossing the auction 3727 // or will get cancelled then, no need to punish the party and cancel them at this point. Therefore this can either release funds 3728 // from the order account or error which we ignore. 3729 m.updateIsolatedMarginOnOrderCancel(ctx, pos, order) 3730 } 3731 3732 return &types.OrderCancellationConfirmation{Order: order}, nil 3733 } 3734 3735 // parkOrder removes the given order from the orderbook 3736 // parkOrder will panic if it encounters errors, which means that it reached an 3737 // invalid state. 3738 func (m *Market) parkOrder(ctx context.Context, orderID string) *types.Order { 3739 order, err := m.matching.RemoveOrder(orderID) 3740 if err != nil { 3741 m.log.Panic("Failure to remove order from matching engine", 3742 logging.OrderID(orderID), 3743 logging.Error(err)) 3744 } 3745 3746 _ = m.position.UnregisterOrder(ctx, order) 3747 m.peggedOrders.Park(order) 3748 m.broker.Send(events.NewOrderEvent(ctx, order)) 3749 m.releaseMarginExcess(ctx, order.Party) 3750 return order 3751 } 3752 3753 // AmendOrder amend an existing order from the order book. 3754 func (m *Market) AmendOrder( 3755 ctx context.Context, 3756 orderAmendment *types.OrderAmendment, 3757 party string, 3758 deterministicID string, 3759 ) (oc *types.OrderConfirmation, _ error, 3760 ) { 3761 idgen := idgeneration.New(deterministicID) 3762 return m.AmendOrderWithIDGenerator(ctx, orderAmendment, party, idgen) 3763 } 3764 3765 // handleIsolatedMarginInsufficientOrderMargin stops all party orders 3766 // Upon position/order update if there are insufficient funds in the order margin, all open orders are stopped and margin re-evaluated. 3767 func (m *Market) handleIsolatedMarginInsufficientOrderMargin(ctx context.Context, party string) { 3768 orders := m.matching.GetOrdersPerParty(party) 3769 for _, o := range orders { 3770 if !o.IsFinished() { 3771 m.matching.RemoveOrder(o.ID) 3772 m.unregisterAndReject(ctx, o, types.OrderErrorIsolatedMarginCheckFailed) 3773 } 3774 // TODO is there anywhere else that this order needs to be removed from? 3775 } 3776 pos, _ := m.position.GetPositionByPartyID(party) 3777 if err := m.updateIsolatedMarginOnOrder(ctx, pos, nil); err != nil { 3778 m.log.Panic("failed to release margin for party with insufficient order margin", logging.String("party", party)) 3779 } 3780 } 3781 3782 func (m *Market) AmendOrderWithIDGenerator( 3783 ctx context.Context, 3784 orderAmendment *types.OrderAmendment, 3785 party string, 3786 idgen common.IDGenerator, 3787 ) (oc *types.OrderConfirmation, _ error, 3788 ) { 3789 defer m.onTxProcessed() 3790 3791 m.idgen = idgen 3792 defer func() { m.idgen = nil }() 3793 3794 defer func() { 3795 m.triggerStopOrders(ctx, idgen) 3796 }() 3797 3798 if !m.canTrade() { 3799 return nil, common.ErrTradingNotAllowed 3800 } 3801 3802 conf, updatedOrders, err := m.amendOrder(ctx, orderAmendment, party) 3803 if err != nil { 3804 m.log.Error("failed to amend order", logging.String("marketID", orderAmendment.MarketID), logging.String("orderID", orderAmendment.OrderID), logging.Error(err)) 3805 if m.getMarginMode(party) == types.MarginModeIsolatedMargin && err == common.ErrMarginCheckFailed { 3806 m.handleIsolatedMarginInsufficientOrderMargin(ctx, party) 3807 } 3808 return nil, err 3809 } 3810 3811 allUpdatedOrders := append( 3812 []*types.Order{conf.Order}, 3813 conf.PassiveOrdersAffected..., 3814 ) 3815 allUpdatedOrders = append( 3816 allUpdatedOrders, 3817 updatedOrders..., 3818 ) 3819 3820 if !m.as.InAuction() { 3821 m.checkForReferenceMoves(ctx, allUpdatedOrders, false) 3822 } 3823 3824 return conf, nil 3825 } 3826 3827 func (m *Market) findOrderAndEnsureOwnership( 3828 orderID, partyID, marketID string, 3829 ) (exitingOrder *types.Order, foundOnBook bool, err error) { 3830 // Try and locate the existing order specified on the 3831 // order book in the matching engine for this market 3832 existingOrder, foundOnBook, err := m.getOrderByID(orderID) 3833 if err != nil { 3834 if m.log.GetLevel() == logging.DebugLevel { 3835 m.log.Debug("Invalid order ID", 3836 logging.OrderID(orderID), 3837 logging.PartyID(partyID), 3838 logging.MarketID(marketID), 3839 logging.Error(err)) 3840 } 3841 return nil, false, types.ErrInvalidOrderID 3842 } 3843 3844 // We can only amend this order if we created it 3845 if existingOrder.Party != partyID { 3846 if m.log.GetLevel() == logging.DebugLevel { 3847 m.log.Debug("Invalid party ID", 3848 logging.String("original party id:", existingOrder.Party), 3849 logging.PartyID(partyID), 3850 ) 3851 } 3852 return nil, false, types.ErrInvalidPartyID 3853 } 3854 3855 // Validate Market 3856 if existingOrder.MarketID != marketID { 3857 // we should never reach this point 3858 m.log.Panic("Market ID mismatch", 3859 logging.MarketID(m.mkt.ID), 3860 logging.Order(*existingOrder), 3861 logging.Error(types.ErrInvalidMarketID), 3862 ) 3863 } 3864 3865 return existingOrder, foundOnBook, err 3866 } 3867 3868 func (m *Market) amendOrder( 3869 ctx context.Context, 3870 orderAmendment *types.OrderAmendment, 3871 party string, 3872 ) (cnf *types.OrderConfirmation, orderUpdates []*types.Order, returnedErr error) { 3873 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "AmendOrder") 3874 defer timer.EngineTimeCounterAdd() 3875 3876 // Verify that the market is not closed 3877 if m.closed { 3878 return nil, nil, common.ErrMarketClosed 3879 } 3880 3881 existingOrder, foundOnBook, err := m.findOrderAndEnsureOwnership( 3882 orderAmendment.OrderID, party, m.GetID()) 3883 if err != nil { 3884 return nil, nil, err 3885 } 3886 3887 if err := m.validateOrderAmendment(existingOrder, orderAmendment); err != nil { 3888 return nil, nil, err 3889 } 3890 3891 amendedOrder, err := existingOrder.ApplyOrderAmendment(orderAmendment, m.timeService.GetTimeNow().UnixNano(), m.priceFactor) 3892 if err != nil { 3893 return nil, nil, err 3894 } 3895 3896 if err := m.checkOrderAmendForSpam(amendedOrder); err != nil { 3897 return nil, nil, err 3898 } 3899 3900 if orderAmendment.Price != nil && amendedOrder.OriginalPrice != nil { 3901 if err = m.validateTickSize(amendedOrder.OriginalPrice); err != nil { 3902 return nil, nil, err 3903 } 3904 } 3905 3906 if err := m.position.ValidateAmendOrder(existingOrder, amendedOrder); err != nil { 3907 return nil, nil, err 3908 } 3909 3910 // We do this first, just in case the party would also have 3911 // change the expiry, and that would have been catched by 3912 // the follow up checks, so we do not insert a non-existing 3913 // order in the expiring orders 3914 // if remaining is reduces <= 0, then order is cancelled 3915 if amendedOrder.Remaining <= 0 { 3916 confirm, err := m.cancelOrder( 3917 ctx, existingOrder.Party, existingOrder.ID) 3918 if err != nil { 3919 return nil, nil, err 3920 } 3921 return &types.OrderConfirmation{ 3922 Order: confirm.Order, 3923 }, nil, nil 3924 } 3925 3926 // If we have a pegged order that is no longer expiring, we need to remove it 3927 var ( 3928 needToRemoveExpiry, needToAddExpiry bool 3929 expiresAt int64 3930 ) 3931 3932 defer func() { 3933 // no errors, amend most likely happened properly 3934 if returnedErr == nil { 3935 if needToRemoveExpiry { 3936 m.expiringOrders.RemoveOrder(expiresAt, existingOrder.ID) 3937 } 3938 // need to make sure the order haven't been matched with the 3939 // amend, consuming the remain volume as well or we would 3940 // add an order while it's not needed to the expiring list 3941 if needToAddExpiry && cnf != nil && !cnf.Order.IsFinished() { 3942 m.expiringOrders.Insert(amendedOrder.ID, amendedOrder.ExpiresAt) 3943 } 3944 } 3945 }() 3946 3947 // if we are amending from GTT to GTC, flag ready to remove from expiry list 3948 if existingOrder.IsExpireable() && !amendedOrder.IsExpireable() { 3949 // We no longer need to handle the expiry 3950 needToRemoveExpiry = true 3951 expiresAt = existingOrder.ExpiresAt 3952 } 3953 3954 // if we are amending from GTC to GTT, flag ready to add to expiry list 3955 if !existingOrder.IsExpireable() && amendedOrder.IsExpireable() { 3956 // We need to handle the expiry 3957 needToAddExpiry = true 3958 } 3959 3960 // if both where expireable but we changed the duration 3961 // then we need to remove, then reinsert... 3962 if existingOrder.IsExpireable() && amendedOrder.IsExpireable() && 3963 existingOrder.ExpiresAt != amendedOrder.ExpiresAt { 3964 // Still expiring but needs to be updated in the expiring 3965 // orders pool 3966 needToRemoveExpiry = true 3967 needToAddExpiry = true 3968 expiresAt = existingOrder.ExpiresAt 3969 } 3970 3971 // if expiration has changed and is before the original creation time, reject this amend 3972 if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < existingOrder.CreatedAt { 3973 if m.log.GetLevel() == logging.DebugLevel { 3974 m.log.Debug("Amended expiry before original creation time", 3975 logging.Int64("existing-created-at", existingOrder.CreatedAt), 3976 logging.Int64("amended-expires-at", amendedOrder.ExpiresAt), 3977 logging.Order(*existingOrder)) 3978 } 3979 return nil, nil, types.ErrInvalidExpirationDatetime 3980 } 3981 3982 // if expiration has changed and is not 0, and is before currentTime 3983 // then we expire the order 3984 if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < amendedOrder.UpdatedAt { 3985 needToAddExpiry = false 3986 // remove the order from the expiring 3987 // at this point the order is still referenced at the time of expiry of the existingOrder 3988 if existingOrder.IsExpireable() { 3989 m.expiringOrders.RemoveOrder(existingOrder.ExpiresAt, amendedOrder.ID) 3990 } 3991 3992 // Update the existing message in place before we cancel it 3993 if foundOnBook { 3994 // Do not amend in place, the amend could be something 3995 // not supported for an amend in place, and not pass 3996 // the validation of the order book 3997 cancellation, err := m.matching.CancelOrder(existingOrder) 3998 if cancellation == nil || err != nil { 3999 m.log.Panic("Failure to cancel order from matching engine", 4000 logging.String("party-id", amendedOrder.Party), 4001 logging.String("order-id", amendedOrder.ID), 4002 logging.String("market", m.mkt.ID), 4003 logging.Error(err)) 4004 } 4005 4006 // unregister the existing order 4007 _ = m.position.UnregisterOrder(ctx, existingOrder) 4008 } 4009 4010 // Update the order in our stores (will be marked as cancelled) 4011 // set the proper status 4012 amendedOrder.Status = types.OrderStatusExpired 4013 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 4014 m.removePeggedOrder(amendedOrder) 4015 4016 return &types.OrderConfirmation{ 4017 Order: amendedOrder, 4018 }, nil, nil 4019 } 4020 4021 if existingOrder.PeggedOrder != nil { 4022 // Amend in place during an auction 4023 if m.as.InAuction() { 4024 ret := m.orderAmendWhenParked(amendedOrder) 4025 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 4026 return ret, nil, nil 4027 } 4028 err := m.repricePeggedOrder(amendedOrder) 4029 if err != nil { 4030 // Failed to reprice so we have to park the order 4031 if amendedOrder.Status != types.OrderStatusParked { 4032 // If we are live then park 4033 m.parkOrder(ctx, existingOrder.ID) 4034 } 4035 ret := m.orderAmendWhenParked(amendedOrder) 4036 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 4037 return ret, nil, nil 4038 } 4039 // We got a new valid price, if we are parked we need to unpark 4040 if amendedOrder.Status == types.OrderStatusParked { 4041 // is we cann pass the margin calls, then do nothing 4042 if err := m.checkMarginForAmendOrder(ctx, existingOrder, amendedOrder); err != nil { 4043 return nil, nil, err 4044 } 4045 4046 // we were parked, need to unpark 4047 m.peggedOrders.Unpark(amendedOrder.ID) 4048 return m.submitValidatedOrder(ctx, amendedOrder) 4049 } 4050 } 4051 4052 priceShift := amendedOrder.Price.NEQ(existingOrder.Price) 4053 sizeIncrease := amendedOrder.Size > existingOrder.Size 4054 sizeDecrease := amendedOrder.Size < existingOrder.Size 4055 expiryChange := amendedOrder.ExpiresAt != existingOrder.ExpiresAt 4056 timeInForceChange := amendedOrder.TimeInForce != existingOrder.TimeInForce 4057 4058 // If nothing changed, amend in place to update updatedAt and version number 4059 if !priceShift && !sizeIncrease && !sizeDecrease && !expiryChange && !timeInForceChange { 4060 ret := m.orderAmendInPlace(existingOrder, amendedOrder) 4061 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 4062 return ret, nil, nil 4063 } 4064 4065 // Update potential new position after the amend 4066 pos := m.position.AmendOrder(ctx, existingOrder, amendedOrder) 4067 4068 // Perform check and allocate margin if price or order size is increased 4069 // ignore rollback return here, as if we amend it means the order 4070 // is already on the book, not rollback will be needed, the margin 4071 // will be updated later on for sure. 4072 4073 // always update margin, even for price/size decrease 4074 if m.getMarginMode(party) == types.MarginModeCrossMargin { 4075 if err = m.checkMarginForOrder(ctx, pos, amendedOrder); err != nil { 4076 // Undo the position registering 4077 _ = m.position.AmendOrder(ctx, amendedOrder, existingOrder) 4078 4079 if m.log.GetLevel() == logging.DebugLevel { 4080 m.log.Debug("Unable to check/add margin for party", 4081 logging.String("market-id", m.GetID()), 4082 logging.Error(err)) 4083 } 4084 return nil, nil, common.ErrMarginCheckFailed 4085 } 4086 } 4087 4088 icebergSizeIncrease := false 4089 if amendedOrder.IcebergOrder != nil && sizeIncrease { 4090 // iceberg orders size changes can always be done in-place because they either: 4091 // 1) decrease the size, which is already done in-place for all orders 4092 // 2) increase the size, which only increases the reserved remaining and not the "active" remaining of the iceberg 4093 // so we set an icebergSizeIncrease to skip the cancel-replace flow. 4094 sizeIncrease = false 4095 icebergSizeIncrease = true 4096 } 4097 4098 // if increase in size or change in price 4099 // ---> DO atomic cancel and submit 4100 if priceShift || sizeIncrease { 4101 return m.orderCancelReplace(ctx, existingOrder, amendedOrder) 4102 } 4103 4104 // if decrease in size or change in expiration date 4105 // ---> DO amend in place in matching engine 4106 if expiryChange || sizeDecrease || timeInForceChange || icebergSizeIncrease { 4107 ret := m.orderAmendInPlace(existingOrder, amendedOrder) 4108 if sizeDecrease { 4109 if m.getMarginMode(party) == types.MarginModeCrossMargin { 4110 // ensure we release excess if party reduced the size of their order 4111 m.recheckMargin(ctx, m.position.GetPositionsByParty(amendedOrder.Party)) 4112 } 4113 } 4114 if m.getMarginMode(party) == types.MarginModeIsolatedMargin { 4115 pos, _ := m.position.GetPositionByPartyID(amendedOrder.Party) 4116 if err := m.updateIsolatedMarginOnOrder(ctx, pos, amendedOrder); err == risk.ErrInsufficientFundsForMarginInGeneralAccount { 4117 m.log.Error("party has insufficient margin to cover the order change, going to cancel all orders for the party") 4118 // in this case we're now with the position with the amended order and the order book with the amended 4119 // order which is consistent and will lead to the successful cancellation of all orders for the party. 4120 // the reason we can't amend back here in place is that while decreasing size etc can be done in place 4121 // increasing size (which is the inverse amend) cannot be done in place so we shouldn't attempt it. 4122 return nil, nil, common.ErrMarginCheckFailed 4123 } 4124 } 4125 4126 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 4127 return ret, nil, nil 4128 } 4129 4130 // we should never reach this point as amendment was validated before 4131 // and every kind should have been handled down here. 4132 m.log.Panic( 4133 "invalid amend did not match any amendment combination", 4134 logging.String("amended-order", amendedOrder.String()), 4135 logging.String("existing-order", amendedOrder.String()), 4136 ) 4137 4138 return nil, nil, types.ErrEditNotAllowed 4139 } 4140 4141 func (m *Market) validateOrderAmendment( 4142 order *types.Order, 4143 amendment *types.OrderAmendment, 4144 ) error { 4145 if err := amendment.Validate(); err != nil { 4146 return err 4147 } 4148 // check TIME_IN_FORCE and expiry 4149 if amendment.TimeInForce == types.OrderTimeInForceGTT { 4150 // if expiresAt is before or equal to created at 4151 // we return an error, we know ExpiresAt is set because of amendment.Validate 4152 if *amendment.ExpiresAt <= order.CreatedAt { 4153 return types.OrderErrorExpiryAtBeforeCreatedAt 4154 } 4155 } 4156 4157 if (amendment.TimeInForce == types.OrderTimeInForceGFN || 4158 amendment.TimeInForce == types.OrderTimeInForceGFA) && 4159 amendment.TimeInForce != order.TimeInForce { 4160 // We cannot amend to a GFA/GFN orders 4161 return types.OrderErrorCannotAmendToGFAOrGFN 4162 } 4163 4164 if (order.TimeInForce == types.OrderTimeInForceGFN || 4165 order.TimeInForce == types.OrderTimeInForceGFA) && 4166 (amendment.TimeInForce != order.TimeInForce && 4167 amendment.TimeInForce != types.OrderTimeInForceUnspecified) { 4168 // We cannot amend from a GFA/GFN orders 4169 return types.OrderErrorCannotAmendFromGFAOrGFN 4170 } 4171 if order.PeggedOrder == nil { 4172 // We cannot change a pegged orders details on a non pegged order 4173 if amendment.PeggedOffset != nil || 4174 amendment.PeggedReference != types.PeggedReferenceUnspecified { 4175 return types.OrderErrorCannotAmendPeggedOrderDetailsOnNonPeggedOrder 4176 } 4177 } else if amendment.Price != nil { 4178 // We cannot change the price on a pegged order 4179 return types.OrderErrorUnableToAmendPriceOnPeggedOrder 4180 } 4181 return nil 4182 } 4183 4184 func (m *Market) orderCancelReplace( 4185 ctx context.Context, 4186 existingOrder, newOrder *types.Order, 4187 ) (conf *types.OrderConfirmation, orders []*types.Order, err error) { 4188 var fees events.FeesTransfer 4189 4190 defer func() { 4191 if err != nil && !(err == common.ErrMarginCheckFailed && conf != nil) { 4192 // if an error happens, the order never hit the book, so we can 4193 // just rollback the position size 4194 _ = m.position.AmendOrder(ctx, newOrder, existingOrder) 4195 // we have an error, but a non-nil confirmation object meaning we updated the book, 4196 // but the amend was rejected because of the margin check, we have to restore the book 4197 // to its original state 4198 return 4199 } 4200 if fees != nil { 4201 if feeErr := m.applyFees(ctx, newOrder, fees); feeErr != nil { 4202 m.log.Panic("orderCancelReplace failed to apply fees on order", logging.Order(newOrder), logging.String("aggressor total fees", fees.TotalFeesAmountPerParty()[newOrder.ID].String()), logging.Error(feeErr)) 4203 } 4204 } 4205 orders = m.handleConfirmation(ctx, conf, nil) 4206 m.broker.Send(events.NewOrderEvent(ctx, conf.Order)) 4207 }() 4208 4209 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderCancelReplace") 4210 defer timer.EngineTimeCounterAdd() 4211 4212 // make sure the order is on the book, this was done by canceling the order initially, but that could 4213 // trigger an auction in some cases. 4214 if o, err := m.matching.GetOrderByID(existingOrder.ID); err != nil || o == nil { 4215 m.log.Panic("Can't CancelReplace, the original order was not found", 4216 logging.OrderWithTag(*existingOrder, "existing-order"), 4217 logging.Error(err)) 4218 } 4219 // cancel-replace amend during auction is quite simple at this point 4220 if m.as.InAuction() { 4221 conf, err := m.matching.ReplaceOrder(existingOrder, newOrder) 4222 if err != nil { 4223 m.log.Panic("unable to submit order", logging.Error(err)) 4224 } 4225 if newOrder.PeggedOrder != nil { 4226 m.log.Panic("should never reach this point") 4227 } 4228 4229 if m.getMarginMode(newOrder.Party) == types.MarginModeIsolatedMargin { 4230 pos, _ := m.position.GetPositionByPartyID(newOrder.Party) 4231 if err := m.updateIsolatedMarginOnOrder(ctx, pos, newOrder); err != nil { 4232 // existing order have status cancelled so we need to get it back to active so it can be stopped properly 4233 existingOrder.Status = newOrder.Status 4234 m.matching.ReplaceOrder(newOrder, existingOrder) 4235 if m.log.GetLevel() <= logging.DebugLevel { 4236 m.log.Debug("Unable to check/add margin for party", 4237 logging.Order(*newOrder), logging.Error(err)) 4238 } 4239 return nil, nil, common.ErrMarginCheckFailed 4240 } 4241 } 4242 return conf, nil, nil 4243 } 4244 4245 // if its an iceberg order with a price change and it is being submitted aggressively 4246 // set the visible remaining to the full size 4247 if newOrder.IcebergOrder != nil { 4248 newOrder.Remaining += newOrder.IcebergOrder.ReservedRemaining 4249 newOrder.IcebergOrder.ReservedRemaining = 0 4250 } 4251 4252 // first we call the order book to evaluate auction triggers and get the list of trades 4253 trades, err := m.checkPriceAndGetTrades(ctx, newOrder) 4254 if err != nil { 4255 return nil, nil, errors.New("couldn't insert order in book") 4256 } 4257 4258 // try to apply fees on the trade 4259 if fees, err = m.calcFees(trades); err != nil { 4260 return nil, nil, errors.New("could not calculate fees for order") 4261 } 4262 4263 var aggressorFee *num.Uint 4264 if fees != nil { 4265 aggressorFee = fees.TotalFeesAmountPerParty()[newOrder.Party] 4266 } 4267 4268 marginMode := m.getMarginMode(newOrder.Party) 4269 pos, _ := m.position.GetPositionByPartyID(newOrder.Party) 4270 posWithTrades := pos 4271 if len(trades) > 0 { 4272 if marginMode == types.MarginModeIsolatedMargin { 4273 posWithTrades = pos.UpdateInPlaceOnTrades(m.log, newOrder.Side, trades, newOrder) 4274 if err = m.updateIsolatedMarginOnAggressor(ctx, posWithTrades, newOrder, trades, true, aggressorFee); err != nil { 4275 if m.log.GetLevel() <= logging.DebugLevel { 4276 m.log.Debug("Unable to check/add immediate trade margin for party", 4277 logging.Order(*newOrder), logging.Error(err)) 4278 } 4279 return nil, nil, common.ErrMarginCheckFailed 4280 } 4281 } else if aggressorFee != nil { 4282 if err := m.collateral.PartyCanCoverFees(m.settlementAsset, m.mkt.ID, newOrder.Party, aggressorFee); err != nil { 4283 m.log.Error("insufficient funds to cover fees", logging.Order(newOrder), logging.Error(err)) 4284 return nil, nil, err 4285 } 4286 } 4287 } 4288 4289 // "hot-swap" of the orders 4290 conf, err = m.matching.ReplaceOrder(existingOrder, newOrder) 4291 if err != nil { 4292 m.log.Panic("unable to submit order", logging.Error(err)) 4293 } 4294 4295 // replace the trades in the confirmation to have 4296 // the ones with the fees embedded 4297 conf.Trades = trades 4298 if marginMode == types.MarginModeIsolatedMargin { 4299 if err = m.updateIsolatedMarginOnOrder(ctx, posWithTrades, newOrder); err != nil { 4300 if m.log.GetLevel() <= logging.DebugLevel { 4301 m.log.Debug("Unable to check/add margin for party", 4302 logging.Order(*newOrder), logging.Error(err)) 4303 } 4304 return conf, nil, common.ErrMarginCheckFailed 4305 } 4306 } 4307 4308 // if the order is not staying in the book, then we remove it 4309 // from the potential positions 4310 if conf.Order.IsFinished() && conf.Order.Remaining > 0 { 4311 _ = m.position.UnregisterOrder(ctx, conf.Order) 4312 } 4313 4314 return conf, orders, nil 4315 } 4316 4317 func (m *Market) orderAmendInPlace( 4318 originalOrder, amendOrder *types.Order, 4319 ) *types.OrderConfirmation { 4320 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderAmendInPlace") 4321 defer timer.EngineTimeCounterAdd() 4322 4323 err := m.matching.AmendOrder(originalOrder, amendOrder) 4324 if err != nil { 4325 // panic here, no good reason for a failure at this point 4326 m.log.Panic("Failure after amend order from matching engine (amend-in-place)", 4327 logging.OrderWithTag(*amendOrder, "new-order"), 4328 logging.OrderWithTag(*originalOrder, "old-order"), 4329 logging.Error(err)) 4330 } 4331 4332 return &types.OrderConfirmation{ 4333 Order: amendOrder, 4334 } 4335 } 4336 4337 func (m *Market) orderAmendWhenParked(amendOrder *types.Order) *types.OrderConfirmation { 4338 amendOrder.Status = types.OrderStatusParked 4339 amendOrder.Price = num.UintZero() 4340 amendOrder.OriginalPrice = num.UintZero() 4341 m.peggedOrders.AmendParked(amendOrder) 4342 4343 return &types.OrderConfirmation{ 4344 Order: amendOrder, 4345 } 4346 } 4347 4348 // submitStopOrders gets a status as parameter. 4349 // this function is used on trigger but also on submission 4350 // at expiry, so just filters out with a parameter. 4351 func (m *Market) submitStopOrders( 4352 ctx context.Context, 4353 stopOrders []*types.StopOrder, 4354 status types.StopOrderStatus, 4355 idgen common.IDGenerator, 4356 ) []*types.OrderConfirmation { 4357 confirmations := []*types.OrderConfirmation{} 4358 evts := make([]events.Event, 0, len(stopOrders)) 4359 toDelete := []*types.Order{} 4360 4361 // might contain both the triggered orders and the expired OCO 4362 for _, v := range stopOrders { 4363 if v.Status == status { 4364 if v.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition { 4365 // Update the order size to match that of the party's position 4366 var pos int64 4367 if position, ok := m.position.GetPositionByPartyID(v.Party); ok { 4368 pos = position.Size() 4369 } 4370 4371 // Scale this size if required 4372 scaledPos := num.DecimalFromInt64(pos) 4373 if v.SizeOverrideValue != nil { 4374 scaledPos = scaledPos.Mul(v.SizeOverrideValue.PercentageSize) 4375 scaledPos = scaledPos.Round(0) 4376 } 4377 orderSize := scaledPos.IntPart() 4378 4379 if orderSize == 0 { 4380 // Nothing to do 4381 m.log.Error("position is flat so no order required", 4382 logging.StopOrderSubmission(v)) 4383 continue 4384 } else if orderSize > 0 { 4385 // We are long so need to sell 4386 if v.OrderSubmission.Side != types.SideSell { 4387 // Don't send an order as we are the wrong direction 4388 m.log.Error("triggered order is the wrong side to flatten position", 4389 logging.StopOrderSubmission(v)) 4390 continue 4391 } 4392 v.OrderSubmission.Size = uint64(orderSize) 4393 } else { 4394 // We are short so need to buy 4395 if v.OrderSubmission.Side != types.SideBuy { 4396 // Don't send an order as we are the wrong direction 4397 m.log.Error("triggered order is the wrong side to flatten position", 4398 logging.StopOrderSubmission(v)) 4399 continue 4400 } 4401 v.OrderSubmission.Size = uint64(-orderSize) 4402 } 4403 } 4404 4405 conf, err := m.SubmitOrderWithIDGeneratorAndOrderID( 4406 ctx, v.OrderSubmission, v.Party, idgen, idgen.NextID(), false, 4407 ) 4408 if err != nil { 4409 // not much we can do at that point, let's log the error and move on? 4410 m.log.Error("could not submit stop order", 4411 logging.StopOrderSubmission(v), 4412 logging.Error(err)) 4413 } 4414 if err == nil && conf != nil { 4415 v.OrderID = conf.Order.ID 4416 confirmations = append(confirmations, conf) 4417 } 4418 } 4419 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 4420 } 4421 4422 // Remove any referenced orders 4423 for _, order := range toDelete { 4424 m.CancelOrder(ctx, order.Party, order.ID, order.ID) 4425 } 4426 4427 m.broker.SendBatch(evts) 4428 4429 return confirmations 4430 } 4431 4432 // removeExpiredOrders remove all expired orders from the order book 4433 // and also any pegged orders that are parked. 4434 func (m *Market) removeExpiredStopOrders( 4435 ctx context.Context, timestamp int64, idgen common.IDGenerator, 4436 ) []*types.OrderConfirmation { 4437 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredStopOrders") 4438 defer timer.EngineTimeCounterAdd() 4439 4440 toExpire := m.expiringStopOrders.Expire(timestamp) 4441 stopOrders := m.stopOrders.RemoveExpired(toExpire) 4442 4443 // ensure any OCO orders are also expire 4444 toExpireSet := map[string]struct{}{} 4445 for _, v := range toExpire { 4446 toExpireSet[v] = struct{}{} 4447 } 4448 4449 for _, so := range stopOrders { 4450 if _, ok := toExpireSet[so.ID]; !ok { 4451 if so.Expiry.Expires() { 4452 m.expiringStopOrders.RemoveOrder(so.Expiry.ExpiresAt.UnixNano(), so.ID) 4453 } 4454 } 4455 } 4456 4457 updatedAt := m.timeService.GetTimeNow() 4458 4459 if m.as.InAuction() { 4460 m.removeExpiredStopOrdersInAuction(ctx, updatedAt, stopOrders) 4461 return nil 4462 } 4463 4464 return m.removeExpiredStopOrdersInContinuous(ctx, updatedAt, stopOrders, idgen) 4465 } 4466 4467 func (m *Market) removeExpiredStopOrdersInAuction( 4468 ctx context.Context, 4469 updatedAt time.Time, 4470 stopOrders []*types.StopOrder, 4471 ) { 4472 evts := []events.Event{} 4473 for _, v := range stopOrders { 4474 v.UpdatedAt = updatedAt 4475 v.Status = types.StopOrderStatusExpired 4476 // nothing to do, can send the event now 4477 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 4478 } 4479 4480 m.broker.SendBatch(evts) 4481 } 4482 4483 func (m *Market) removeExpiredStopOrdersInContinuous( 4484 ctx context.Context, 4485 updatedAt time.Time, 4486 stopOrders []*types.StopOrder, 4487 idgen common.IDGenerator, 4488 ) []*types.OrderConfirmation { 4489 evts := []events.Event{} 4490 filteredOCO := []*types.StopOrder{} 4491 for _, v := range stopOrders { 4492 v.UpdatedAt = updatedAt 4493 if v.Status == types.StopOrderStatusExpired && v.Expiry.Expires() && *v.Expiry.ExpiryStrategy == types.StopOrderExpiryStrategySubmit { 4494 filteredOCO = append(filteredOCO, v) 4495 continue 4496 } 4497 // nothing to do, can send the event now 4498 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 4499 } 4500 4501 m.broker.SendBatch(evts) 4502 4503 return m.submitStopOrders(ctx, filteredOCO, types.StopOrderStatusExpired, idgen) 4504 } 4505 4506 // removeExpiredOrders remove all expired orders from the order book 4507 // and also any pegged orders that are parked. 4508 func (m *Market) removeExpiredOrders( 4509 ctx context.Context, timestamp int64, 4510 ) []*types.Order { 4511 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredOrders") 4512 defer timer.EngineTimeCounterAdd() 4513 4514 expired := []*types.Order{} 4515 toExp := m.expiringOrders.Expire(timestamp) 4516 if len(toExp) == 0 { 4517 return expired 4518 } 4519 ids := make([]string, 0, len(toExp)) 4520 for _, orderID := range toExp { 4521 var order *types.Order 4522 // The pegged expiry orders are copies and do not reflect the 4523 // current state of the order, therefore we look it up 4524 originalOrder, foundOnBook, err := m.getOrderByID(orderID) 4525 if err != nil { 4526 // nothing to do there. 4527 continue 4528 } 4529 // assign to the order the order from the book 4530 // so we get the most recent version from the book 4531 // to continue with 4532 order = originalOrder 4533 4534 // if the order was on the book basically 4535 // either a pegged + non parked 4536 // or a non-pegged order 4537 if foundOnBook { 4538 pos := m.position.UnregisterOrder(ctx, order) 4539 m.matching.DeleteOrder(order) 4540 if m.getMarginMode(order.Party) == types.MarginModeIsolatedMargin { 4541 err := m.updateIsolatedMarginOnOrder(ctx, pos, order) 4542 if err != nil { 4543 m.log.Panic("failed to recalculate isolated margin after order cancellation", logging.String("party", order.Party), logging.Order(order)) 4544 } 4545 } 4546 } 4547 4548 // if this was a pegged order 4549 // remove from the pegged / parked list 4550 if order.PeggedOrder != nil { 4551 m.removePeggedOrder(order) 4552 } 4553 4554 // now we add to the list of expired orders 4555 // and assign the appropriate status 4556 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 4557 order.Status = types.OrderStatusExpired 4558 expired = append(expired, order) 4559 ids = append(ids, orderID) 4560 } 4561 if len(ids) > 0 { 4562 m.broker.Send(events.NewExpiredOrdersEvent(ctx, m.mkt.ID, ids)) 4563 } 4564 4565 // If we have removed an expired order, do we need to reprice any 4566 // or maybe notify the liquidity engine 4567 if len(expired) > 0 && !m.as.InAuction() { 4568 m.checkForReferenceMoves(ctx, expired, false) 4569 } 4570 4571 return expired 4572 } 4573 4574 func (m *Market) getBestStaticAskPrice() (*num.Uint, error) { 4575 return m.matching.GetBestStaticAskPrice() 4576 } 4577 4578 func (m *Market) getBestStaticAskPriceAndVolume() (*num.Uint, uint64, error) { 4579 return m.matching.GetBestStaticAskPriceAndVolume() 4580 } 4581 4582 func (m *Market) getBestStaticBidPrice() (*num.Uint, error) { 4583 return m.matching.GetBestStaticBidPrice() 4584 } 4585 4586 func (m *Market) getBestStaticBidPriceAndVolume() (*num.Uint, uint64, error) { 4587 return m.matching.GetBestStaticBidPriceAndVolume() 4588 } 4589 4590 func (m *Market) getBestStaticPricesDecimal() (bid, ask num.Decimal, err error) { 4591 ask = num.DecimalZero() 4592 ubid, err := m.getBestStaticBidPrice() 4593 if err != nil { 4594 bid = num.DecimalZero() 4595 return 4596 } 4597 bid = ubid.ToDecimal() 4598 uask, err := m.getBestStaticAskPrice() 4599 if err != nil { 4600 ask = num.DecimalZero() 4601 return 4602 } 4603 ask = uask.ToDecimal() 4604 return 4605 } 4606 4607 func (m *Market) getStaticMidPrice(side types.Side) (*num.Uint, error) { 4608 bid, err := m.matching.GetBestStaticBidPrice() 4609 if err != nil { 4610 return num.UintZero(), err 4611 } 4612 ask, err := m.matching.GetBestStaticAskPrice() 4613 if err != nil { 4614 return num.UintZero(), err 4615 } 4616 mid := num.UintZero() 4617 one := num.NewUint(1) 4618 two := num.Sum(one, one) 4619 one, _ = num.UintFromDecimal(one.ToDecimal().Mul(m.priceFactor)) 4620 if side == types.SideBuy { 4621 mid = mid.Div(num.Sum(bid, ask, one), two) 4622 } else { 4623 mid = mid.Div(num.Sum(bid, ask), two) 4624 } 4625 4626 return mid, nil 4627 } 4628 4629 // removePeggedOrder looks through the pegged and parked list 4630 // and removes the matching order if found. 4631 func (m *Market) removePeggedOrder(order *types.Order) { 4632 // remove if order was expiring 4633 m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID) 4634 // unpark will remove the order from the pegged orders data structure 4635 m.peggedOrders.Unpark(order.ID) 4636 } 4637 4638 // getOrderBy looks for the order in the order book and in the list 4639 // of pegged orders in the market. Returns the order if found, a bool 4640 // representing if the order was found on the order book and any error code. 4641 func (m *Market) getOrderByID(orderID string) (*types.Order, bool, error) { 4642 order, err := m.matching.GetOrderByID(orderID) 4643 if err == nil { 4644 return order, true, nil 4645 } 4646 4647 // The pegged order list contains all the pegged orders in the system 4648 // whether they are parked or live. Check this list of a matching order 4649 if o := m.peggedOrders.GetParkedByID(orderID); o != nil { 4650 return o, false, nil 4651 } 4652 4653 // We couldn't find it 4654 return nil, false, common.ErrOrderNotFound 4655 } 4656 4657 func (m *Market) getTheoreticalTargetStake() *num.Uint { 4658 rf := m.risk.GetRiskFactors() 4659 4660 // Ignoring the error as GetTheoreticalTargetStake handles trades==nil and len(trades)==0 4661 trades, _ := m.matching.GetIndicativeTrades() 4662 4663 return m.tsCalc.GetTheoreticalTargetStake( 4664 *rf, m.timeService.GetTimeNow(), m.getReferencePrice(), trades) 4665 } 4666 4667 func (m *Market) getTargetStake() *num.Uint { 4668 return m.tsCalc.GetTargetStake(*m.risk.GetRiskFactors(), m.timeService.GetTimeNow(), m.getCurrentMarkPrice()) 4669 } 4670 4671 func (m *Market) getSuppliedStake() *num.Uint { 4672 return m.liquidityEngine.CalculateSuppliedStake() 4673 } 4674 4675 func (m *Market) tradingTerminated(ctx context.Context, tt bool) { 4676 targetState := types.MarketStateSettled 4677 if m.mkt.State == types.MarketStatePending { 4678 targetState = types.MarketStateCancelled 4679 } 4680 m.tradingTerminatedWithFinalState(ctx, targetState, nil) 4681 } 4682 4683 func (m *Market) tradingTerminatedWithFinalState(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) { 4684 m.mu.Lock() 4685 defer m.mu.Unlock() 4686 m.terminateMarket(ctx, finalState, settlementDataInAsset) 4687 } 4688 4689 func (m *Market) terminateMarket(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) { 4690 // ignore trading termination while the governance proposal hasn't been enacted 4691 if m.mkt.State == types.MarketStateProposed { 4692 m.log.Debug("market must not terminated before its enactment time", logging.MarketID(m.GetID())) 4693 return 4694 } 4695 4696 if finalState != types.MarketStateCancelled { 4697 // we're either going to set state to trading terminated 4698 // or we'll be performing the final settlement (setting market status to settled) 4699 // in both cases, we want to MTM any pending trades 4700 // TODO @zohar do we need to keep this check? mark price 4701 // can change even if there were no trades 4702 if m.settlement.HasTraded() { 4703 // we need the ID-gen 4704 _, blockHash := vegacontext.TraceIDFromContext(ctx) 4705 m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex("finalmtm"+m.GetID())) 4706 defer func() { 4707 m.idgen = nil 4708 }() 4709 // we have trades, and the market has been closed. Perform MTM sequence now so the final settlement 4710 // works as expected. 4711 m.markPriceLock.Lock() 4712 m.markPriceCalculator.CalculateMarkPrice( 4713 ctx, 4714 m.pMonitor, 4715 m.as, 4716 m.timeService.GetTimeNow().UnixNano(), 4717 m.matching, 4718 m.mtmDelta, 4719 m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, 4720 m.mkt.LinearSlippageFactor, 4721 m.risk.GetRiskFactors().Short, 4722 m.risk.GetRiskFactors().Long, 4723 false, 4724 false) 4725 m.markPriceLock.Unlock() 4726 m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice()) 4727 4728 if m.internalCompositePriceCalculator != nil { 4729 m.internalCompositePriceCalculator.CalculateMarkPrice( 4730 ctx, 4731 m.pMonitor, 4732 m.as, 4733 m.timeService.GetTimeNow().UnixNano(), 4734 m.matching, 4735 m.internalCompositePriceFrequency, 4736 m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, 4737 m.mkt.LinearSlippageFactor, 4738 m.risk.GetRiskFactors().Short, 4739 m.risk.GetRiskFactors().Long, 4740 false, 4741 false) 4742 } 4743 4744 if m.perp { 4745 // if perp and we have an intenal composite price (direct or by mark price), feed it to the perp before the mark to market 4746 if m.internalCompositePriceCalculator != nil { 4747 if internalCompositePrice := m.getCurrentInternalCompositePrice(); !internalCompositePrice.IsZero() { 4748 m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, internalCompositePrice, m.timeService.GetTimeNow().UnixNano()) 4749 } 4750 } else { 4751 if internalCompositePrice := m.getCurrentMarkPrice(); !internalCompositePrice.IsZero() { 4752 m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, internalCompositePrice, m.timeService.GetTimeNow().UnixNano()) 4753 } 4754 } 4755 } 4756 4757 // send market data event with the updated mark price 4758 m.broker.Send(events.NewMarketDataEvent(ctx, m.GetMarketData())) 4759 m.confirmMTM(ctx, true) 4760 } 4761 m.mkt.State = types.MarketStateTradingTerminated 4762 m.mkt.TradingMode = types.MarketTradingModeNoTrading 4763 m.tradableInstrument.Instrument.Product.UnsubscribeTradingTerminated(ctx) 4764 4765 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 4766 var err error 4767 if settlementDataInAsset != nil && m.validateSettlementData(settlementDataInAsset) { 4768 m.settlementDataWithLock(ctx, finalState, settlementDataInAsset) 4769 } else if m.settlementDataInMarket != nil { 4770 // because we need to be able to perform the MTM settlement, only update market state now 4771 settlementDataInAsset, err = m.tradableInstrument.Instrument.Product.ScaleSettlementDataToDecimalPlaces(m.settlementDataInMarket, m.assetDP) 4772 if err != nil { 4773 m.log.Error(err.Error()) 4774 return 4775 } 4776 if !m.validateSettlementData(settlementDataInAsset) { 4777 m.log.Warn("invalid settlement data", logging.MarketID(m.GetID())) 4778 m.settlementDataInMarket = nil 4779 return 4780 } 4781 m.settlementDataWithLock(ctx, finalState, settlementDataInAsset) 4782 } else { 4783 m.log.Debug("no settlement data", logging.MarketID(m.GetID())) 4784 } 4785 return 4786 } 4787 4788 m.tradableInstrument.Instrument.Product.UnsubscribeTradingTerminated(ctx) 4789 4790 parties := maps.Keys(m.parties) 4791 sort.Strings(parties) 4792 for _, party := range parties { 4793 _, err := m.CancelAllOrders(ctx, party) 4794 if err != nil { 4795 m.log.Debug("could not cancel orders for party", logging.PartyID(party), logging.Error(err)) 4796 panic(err) 4797 } 4798 } 4799 err := m.closeCancelledMarket(ctx) 4800 if err != nil { 4801 m.log.Debug("could not close market", logging.MarketID(m.GetID())) 4802 return 4803 } 4804 } 4805 4806 func (m *Market) scaleOracleData(ctx context.Context, price *num.Numeric, dp int64) *num.Uint { 4807 if price == nil { 4808 return nil 4809 } 4810 4811 if !price.SupportDecimalPlaces(int64(m.assetDP)) { 4812 return nil 4813 } 4814 4815 p, err := price.ScaleTo(dp, int64(m.assetDP)) 4816 if err != nil { 4817 m.log.Error(err.Error()) 4818 return nil 4819 } 4820 return p 4821 } 4822 4823 func (m *Market) settlementData(ctx context.Context, settlementData *num.Numeric) { 4824 m.mu.Lock() 4825 defer m.mu.Unlock() 4826 4827 m.settlementDataInMarket = settlementData 4828 settlementDataInAsset, err := m.tradableInstrument.Instrument.Product.ScaleSettlementDataToDecimalPlaces(m.settlementDataInMarket, m.assetDP) 4829 if err != nil { 4830 m.log.Error(err.Error()) 4831 return 4832 } 4833 4834 // validate the settlement data 4835 if !m.validateSettlementData(settlementDataInAsset) { 4836 m.log.Warn("settlement data for capped market is invalid", logging.MarketID(m.GetID())) 4837 // reset settlement data, it's not valid 4838 m.settlementDataInMarket = nil 4839 return 4840 } 4841 m.settlementDataWithLock(ctx, types.MarketStateSettled, settlementDataInAsset) 4842 } 4843 4844 func (m *Market) settlementDataPerp(ctx context.Context, settlementData *num.Numeric) { 4845 // if the market state for a perp is trading terminated then we have come in through goverannce 4846 // and will already have the market lock 4847 if m.mkt.State != types.MarketStateTradingTerminated { 4848 m.mu.Lock() 4849 defer m.mu.Unlock() 4850 } 4851 4852 _, blockHash := vegacontext.TraceIDFromContext(ctx) 4853 m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex("perpsettlement"+m.GetID())) 4854 defer func() { 4855 m.idgen = nil 4856 }() 4857 4858 // take all positions, get funding transfers 4859 sdi := settlementData.Int() 4860 if !settlementData.IsInt() && settlementData.Decimal() != nil { 4861 sdi = num.NewInt(settlementData.Decimal().IntPart()) 4862 } 4863 if sdi == nil { 4864 return 4865 } 4866 4867 transfers, round := m.settlement.SettleFundingPeriod(ctx, m.position.Positions(), settlementData.Int()) 4868 if len(transfers) == 0 { 4869 m.log.Debug("Failed to get settle positions for funding period") 4870 return 4871 } 4872 4873 for _, t := range transfers { 4874 m.recordPositionActivity(t.Transfer()) 4875 } 4876 m.broker.Send(events.NewFundingPaymentsEvent(ctx, m.mkt.ID, m.tradableInstrument.Instrument.Product.GetCurrentPeriod(), transfers)) 4877 4878 margins, ledgerMovements, err := m.collateral.PerpsFundingSettlement(ctx, m.GetID(), transfers, m.settlementAsset, round, m.useGeneralAccountForMarginSearch) 4879 if err != nil { 4880 m.log.Error("Failed to get ledger movements when performing the funding settlement", 4881 logging.MarketID(m.GetID()), 4882 logging.Error(err)) 4883 return 4884 } 4885 4886 if len(ledgerMovements) > 0 { 4887 m.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements)) 4888 } 4889 // no margin events, no margin stuff to check 4890 if len(margins) == 0 { 4891 return 4892 } 4893 4894 // split to cross and isolated margins to handle separately 4895 crossEvts := make([]events.Margin, 0, len(margins)) 4896 isolatedEvts := make([]events.Margin, 0, len(margins)) 4897 for _, evt := range margins { 4898 if m.getMarginMode(evt.Party()) == types.MarginModeCrossMargin { 4899 crossEvts = append(crossEvts, evt) 4900 } else { 4901 isolatedEvts = append(isolatedEvts, evt) 4902 } 4903 } 4904 4905 // check margin balances 4906 increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano()) 4907 riskUpdates := m.risk.UpdateMarginsOnSettlement(ctx, crossEvts, m.getCurrentMarkPriceForMargin(), increment, m.getAuctionPrice()) 4908 isolatedMarginPartiesToClose := []events.Risk{} 4909 for _, evt := range isolatedEvts { 4910 mrgns, err := m.risk.CheckMarginInvariants(ctx, evt, m.getMarketObservable(nil), increment, m.matching.GetOrdersPerParty(evt.Party()), m.getMarginFactor(evt.Party())) 4911 if err == risk.ErrInsufficientFundsForMaintenanceMargin { 4912 m.log.Debug("party in isolated margin mode has insufficient margin", logging.String("party", evt.Party())) 4913 isolatedMarginPartiesToClose = append(isolatedMarginPartiesToClose, mrgns) 4914 } 4915 } 4916 4917 // no margin accounts need updating... 4918 if len(riskUpdates)+len(isolatedMarginPartiesToClose) == 0 { 4919 return 4920 } 4921 // update margins, close-out any positions that don't have the required margin 4922 orderUpdates := m.handleRiskEvts(ctx, riskUpdates, isolatedMarginPartiesToClose) 4923 m.checkForReferenceMoves(ctx, orderUpdates, false) 4924 } 4925 4926 func (m *Market) ValidateSettlementData(data *num.Uint) bool { 4927 // convert to asset precision 4928 settlement, _ := num.UintFromDecimal(data.ToDecimal().Mul(m.priceFactor)) 4929 return m.validateSettlementData(settlement) 4930 } 4931 4932 func (m *Market) validateSettlementData(data *num.Uint) bool { 4933 if m.closed { 4934 return false 4935 } 4936 // not capped, accept the data 4937 if m.fCap == nil { 4938 return true 4939 } 4940 // data > max 4941 if m.capMax.LT(data) { 4942 return false 4943 } 4944 // binary capped market: reject if data is not zero and not == max price. 4945 if m.fCap.Binary && !data.IsZero() && !data.EQ(m.capMax) { 4946 return false 4947 } 4948 return true 4949 } 4950 4951 // NB this must be called with the lock already acquired. 4952 func (m *Market) settlementDataWithLock(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) { 4953 if m.closed { 4954 return 4955 } 4956 if m.capMax != nil && m.capMax.LT(settlementDataInAsset) { 4957 return 4958 } 4959 if m.fCap != nil && m.fCap.Binary { 4960 // binary settlement is either 0 or max price: 4961 if !settlementDataInAsset.IsZero() && !settlementDataInAsset.EQ(m.capMax) { 4962 // not zero, not max price -> no final settlement can occur yet 4963 return 4964 } 4965 } 4966 4967 if m.mkt.State == types.MarketStateTradingTerminated && settlementDataInAsset != nil { 4968 err := m.closeMarket(ctx, m.timeService.GetTimeNow(), finalState, settlementDataInAsset) 4969 if err != nil { 4970 m.log.Error("could not close market", logging.Error(err)) 4971 } 4972 m.closed = m.mkt.State == finalState 4973 4974 // mark price should be updated here 4975 if settlementDataInAsset != nil { 4976 m.lastTradedPrice = settlementDataInAsset.Clone() 4977 // the settlement price is the final mark price 4978 m.markPriceCalculator.OverridePrice(m.lastTradedPrice) 4979 if m.internalCompositePriceCalculator != nil { 4980 m.internalCompositePriceCalculator.OverridePrice(m.lastTradedPrice) 4981 } 4982 } 4983 4984 // send the market data with all updated stuff 4985 m.broker.Send(events.NewMarketDataEvent(ctx, m.GetMarketData())) 4986 m.broker.Send(events.NewMarketSettled(ctx, m.GetID(), m.timeService.GetTimeNow().UnixNano(), m.lastTradedPrice, m.positionFactor)) 4987 } 4988 } 4989 4990 func (m *Market) canTrade() bool { 4991 return m.mkt.State == types.MarketStateActive || 4992 m.mkt.State == types.MarketStatePending || 4993 m.mkt.State == types.MarketStateSuspended || 4994 m.mkt.State == types.MarketStateSuspendedViaGovernance 4995 } 4996 4997 // cleanupOnReject remove all resources created while the 4998 // market was on PREPARED state. 4999 // we'll need to remove all accounts related to the market 5000 // all margin accounts for this market 5001 // all bond accounts for this market too. 5002 // at this point no fees would have been collected or anything 5003 // like this. 5004 func (m *Market) cleanupOnReject(ctx context.Context) { 5005 m.tradableInstrument.Instrument.Unsubscribe(ctx) 5006 5007 m.markPriceCalculator.Close(ctx) 5008 if m.internalCompositePriceCalculator != nil { 5009 m.internalCompositePriceCalculator.Close(ctx) 5010 } 5011 5012 // get the list of all parties in this market 5013 parties := make([]string, 0, len(m.parties)) 5014 for k := range m.parties { 5015 parties = append(parties, k) 5016 } 5017 5018 m.liquidity.StopAllLiquidityProvision(ctx) 5019 5020 // cancel all pending orders 5021 orders := m.matching.Settled() 5022 // stop all parkedPeggedOrders 5023 parkedPeggedOrders := m.peggedOrders.Settled() 5024 5025 evts := make([]events.Event, 0, len(orders)+len(parkedPeggedOrders)) 5026 for _, o := range append(orders, parkedPeggedOrders...) { 5027 evts = append(evts, events.NewOrderEvent(ctx, o)) 5028 } 5029 if len(evts) > 0 { 5030 m.broker.SendBatch(evts) 5031 } 5032 5033 // now we do stop orders 5034 stopOrders := m.stopOrders.Settled() 5035 evts = make([]events.Event, 0, len(stopOrders)) 5036 for _, o := range stopOrders { 5037 evts = append(evts, events.NewStopOrderEvent(ctx, o)) 5038 } 5039 if len(evts) > 0 { 5040 m.broker.SendBatch(evts) 5041 } 5042 5043 // release margin balance 5044 tresps, err := m.collateral.ClearMarket(ctx, m.GetID(), m.settlementAsset, parties, false) 5045 if err != nil { 5046 m.log.Panic("unable to cleanup a rejected market", 5047 logging.String("market-id", m.GetID()), 5048 logging.Error(err)) 5049 return 5050 } 5051 5052 m.stateVarEngine.UnregisterStateVariable(m.settlementAsset, m.mkt.ID) 5053 5054 // then send the responses 5055 if len(tresps) > 0 { 5056 m.broker.Send(events.NewLedgerMovements(ctx, tresps)) 5057 } 5058 } 5059 5060 // GetTotalOrderBookLevelCount returns the total number of levels in the order book. 5061 func (m *Market) GetTotalOrderBookLevelCount() uint64 { 5062 return m.matching.GetOrderBookLevelCount() 5063 } 5064 5065 // GetTotalPeggedOrderCount returns the total number of pegged orders. 5066 func (m *Market) GetTotalPeggedOrderCount() uint64 { 5067 return m.matching.GetPeggedOrdersCount() 5068 } 5069 5070 // GetTotalStopOrderCount returns the total number of stop orders. 5071 func (m *Market) GetTotalStopOrderCount() uint64 { 5072 return m.stopOrders.GetStopOrderCount() 5073 } 5074 5075 // GetTotalOpenPositionCount returns the total number of open positions. 5076 func (m *Market) GetTotalOpenPositionCount() uint64 { 5077 return m.position.GetOpenPositionCount() 5078 } 5079 5080 // getMarketObservable returns current mark price once market is out of opening auction, during opening auction the indicative uncrossing price is returned. 5081 func (m *Market) getMarketObservable(fallbackPrice *num.Uint) *num.Uint { 5082 // this is used for margin calculations, so if there's a max price, return that. 5083 if m.capMax != nil && m.fCap.FullyCollateralised { 5084 return m.capMax.Clone() 5085 } 5086 // during opening auction we don't have a last traded price, so we use the indicative price instead 5087 if m.as.IsOpeningAuction() { 5088 if ip := m.matching.GetIndicativePrice(); !ip.IsZero() { 5089 return ip 5090 } 5091 // we don't have an indicative price yet so we use the supplied price 5092 return fallbackPrice 5093 } 5094 return m.getCurrentMarkPrice() 5095 } 5096 5097 // Mark price gets returned when market is not in auction, otherwise indicative uncrossing price gets returned. 5098 func (m *Market) getReferencePrice() *num.Uint { 5099 if !m.as.InAuction() { 5100 return m.getCurrentMarkPrice() 5101 } 5102 ip := m.matching.GetIndicativePrice() // can be zero 5103 if ip.IsZero() { 5104 return m.getCurrentMarkPrice() 5105 } 5106 return ip 5107 } 5108 5109 func (m *Market) getCurrentInternalCompositePrice() *num.Uint { 5110 if !m.perp || m.internalCompositePriceCalculator == nil { 5111 m.log.Panic("trying to get current internal composite price in a market with no intenal composite price configuration or not a perp market") 5112 } 5113 if m.internalCompositePriceCalculator.GetPrice() == nil { 5114 return num.UintZero() 5115 } 5116 return m.internalCompositePriceCalculator.GetPrice().Clone() 5117 } 5118 5119 // getCurrentMarkPriceForMargin is the same as getCurrentMarkPrice, but adds a check for capped futures. 5120 // if this is called on a future with a max price, then the max price will be returned. This is useful for margin checks. 5121 func (m *Market) getCurrentMarkPriceForMargin() *num.Uint { 5122 if m.capMax != nil && m.fCap.FullyCollateralised { 5123 return m.capMax.Clone() 5124 } 5125 return m.getCurrentMarkPrice() 5126 } 5127 5128 func (m *Market) getCurrentMarkPrice() *num.Uint { 5129 m.markPriceLock.RLock() 5130 defer m.markPriceLock.RUnlock() 5131 if m.markPriceCalculator.GetPrice() == nil { 5132 return num.UintZero() 5133 } 5134 return m.markPriceCalculator.GetPrice().Clone() 5135 } 5136 5137 func (m *Market) getLastTradedPrice() *num.Uint { 5138 if m.lastTradedPrice == nil { 5139 return num.UintZero() 5140 } 5141 return m.lastTradedPrice.Clone() 5142 } 5143 5144 func (m *Market) GetAssetForProposerBonus() string { 5145 return m.settlementAsset 5146 } 5147 5148 func (m *Market) GetMarketCounters() *types.MarketCounters { 5149 return &types.MarketCounters{ 5150 StopOrderCounter: m.GetTotalStopOrderCount(), 5151 PeggedOrderCounter: m.GetTotalPeggedOrderCount(), 5152 OrderbookLevelCount: m.GetTotalOrderBookLevelCount(), 5153 PositionCount: m.GetTotalOpenPositionCount(), 5154 } 5155 } 5156 5157 func (m *Market) GetRiskFactors() *types.RiskFactor { 5158 return m.risk.GetRiskFactors() 5159 } 5160 5161 func (m *Market) UpdateMarginMode(ctx context.Context, party string, marginMode types.MarginMode, marginFactor num.Decimal) error { 5162 if m.fCap != nil && m.fCap.FullyCollateralised { 5163 return common.ErrIsolatedMarginFullyCollateralised 5164 } 5165 if err := m.switchMarginMode(ctx, party, marginMode, marginFactor); err != nil { 5166 return err 5167 } 5168 5169 m.emitPartyMarginModeUpdated(ctx, party, marginMode, marginFactor) 5170 5171 return nil 5172 } 5173 5174 func (m *Market) getMarginMode(party string) types.MarginMode { 5175 marginFactor, ok := m.partyMarginFactor[party] 5176 if !ok || marginFactor.IsZero() { 5177 return types.MarginModeCrossMargin 5178 } 5179 return types.MarginModeIsolatedMargin 5180 } 5181 5182 func (m *Market) useGeneralAccountForMarginSearch(party string) bool { 5183 return m.getMarginMode(party) == types.MarginModeCrossMargin 5184 } 5185 5186 func (m *Market) getMarginFactor(party string) num.Decimal { 5187 marginFactor, ok := m.partyMarginFactor[party] 5188 if !ok || marginFactor.IsZero() { 5189 return num.DecimalZero() 5190 } 5191 return marginFactor 5192 } 5193 5194 // switchMarginMode handles a switch between margin modes and/or changes to the margin factor. 5195 // When switching to isolated margin mode, the following steps will be taken: 5196 // 1. For any active position, calculate average entry price * abs(position) * margin factor. 5197 // Calculate the amount of funds which will be added to, or subtracted from, the general account in order to do this. 5198 // If additional funds must be added which are not available, reject the transaction immediately. 5199 // 2. For any active orders, calculate the quantity limit price * remaining size * margin factor which needs to be placed in the 5200 // order margin account. Add this amount to the difference calculated in step 1. If this amount is less than or equal to the 5201 // amount in the general account, perform the transfers (first move funds into/out of margin account, then move funds into 5202 // the order margin account). If there are insufficient funds, reject the transaction. 5203 // 3. Move account to isolated margin mode on this market 5204 // 5205 // When switching from isolated margin mode to cross margin mode, the following steps will be taken: 5206 // 1. Any funds in the order margin account will be moved to the margin account. 5207 // 2. At this point trading can continue with the account switched to the cross margining account type. 5208 // If there are excess funds in the margin account they will be freed at the next margin release cycle. 5209 func (m *Market) switchMarginMode(ctx context.Context, party string, marginMode types.MarginMode, marginFactor num.Decimal) error { 5210 defer m.onTxProcessed() 5211 if marginMode == m.getMarginMode(party) && marginFactor.Equal(m.getMarginFactor(party)) { 5212 return nil 5213 } 5214 _ = m.addParty(party) 5215 5216 pos, ok := m.position.GetPositionByPartyID(party) 5217 if !ok { 5218 pos = positions.NewMarketPosition(party) 5219 } 5220 5221 margins, err := m.collateral.GetPartyMargin(pos, m.settlementAsset, m.GetID()) 5222 if err == collateral.ErrPartyAccountsMissing { 5223 _, err = m.collateral.CreatePartyMarginAccount(ctx, party, m.mkt.ID, m.settlementAsset) 5224 if err != nil { 5225 return err 5226 } 5227 margins, err = m.collateral.GetPartyMargin(pos, m.settlementAsset, m.GetID()) 5228 if err != nil { 5229 return err 5230 } 5231 } else if err != nil { 5232 return err 5233 } 5234 5235 marketObservable := m.getMarketObservable(nil) 5236 if marketObservable == nil { 5237 return fmt.Errorf("no market observable price") 5238 } 5239 increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano()) 5240 var auctionPrice *num.Uint 5241 if m.as.InAuction() { 5242 auctionPrice = marketObservable 5243 markPrice := m.getCurrentMarkPriceForMargin() 5244 if markPrice != nil && marketObservable.LT(markPrice) { 5245 auctionPrice = markPrice 5246 } 5247 } 5248 // switching to isolated or changing the margin factor 5249 if marginMode == types.MarginModeIsolatedMargin { 5250 risk, err := m.risk.SwitchToIsolatedMargin(ctx, margins, marketObservable, increment, m.matching.GetOrdersPerParty(party), marginFactor, auctionPrice) 5251 if err != nil { 5252 return err 5253 } 5254 // ensure we have an order margin account set up 5255 m.collateral.GetOrCreatePartyOrderMarginAccount(ctx, party, m.mkt.ID, m.settlementAsset) 5256 if len(risk) > 0 { 5257 for _, r := range risk { 5258 err = m.transferMargins(ctx, []events.Risk{r}, nil) 5259 if err != nil { 5260 return err 5261 } 5262 } 5263 } 5264 m.partyMarginFactor[party] = marginFactor 5265 // cancel pegged orders 5266 ordersAndParkedPegged := append(m.matching.GetOrdersPerParty(party), m.getPartyParkedPeggedOrders(party)...) 5267 for _, o := range ordersAndParkedPegged { 5268 if o.PeggedOrder != nil { 5269 m.cancelOrder(ctx, o.Party, o.ID) 5270 } 5271 } 5272 return nil 5273 } else { 5274 // switching from isolated margin to cross margin 5275 // 1. Any funds in the order margin account will be moved to the margin account. 5276 // 2. At this point trading can continue with the account switched to the cross margining account type. If there are excess funds in the margin account they will be freed at the next margin release cycle. 5277 risk := m.risk.SwitchFromIsolatedMargin(ctx, margins, marketObservable, increment, m.getAuctionPrice()) 5278 err = m.transferMargins(ctx, []events.Risk{risk}, nil) 5279 if err != nil { 5280 return err 5281 } 5282 delete(m.partyMarginFactor, party) 5283 return nil 5284 } 5285 } 5286 5287 func (m *Market) getPartyParkedPeggedOrders(party string) []*types.Order { 5288 partyParkedPegged := []*types.Order{} 5289 p := m.peggedOrders.Parked() 5290 for _, o := range p { 5291 if o.Party == party { 5292 partyParkedPegged = append(partyParkedPegged, o) 5293 } 5294 } 5295 return partyParkedPegged 5296 } 5297 5298 func (m *Market) emitPartyMarginModeUpdated(ctx context.Context, party string, mode types.MarginMode, factor num.Decimal) { 5299 e := &eventspb.PartyMarginModeUpdated{ 5300 MarketId: m.mkt.ID, 5301 PartyId: party, 5302 MarginMode: mode, 5303 AtEpoch: m.epoch.Seq, 5304 } 5305 5306 if mode == types.MarginModeIsolatedMargin { 5307 e.MarginFactor = ptr.From(factor.String()) 5308 } 5309 5310 m.broker.Send(events.NewPartyMarginModeUpdatedEvent(ctx, e)) 5311 } 5312 5313 func (m *Market) checkOrderForSpam(side types.Side, orderPrice *num.Uint, orderSize uint64, peggedOrder *types.PeggedOrder, orderType vegapb.Order_Type, quantumMultiplier num.Decimal) error { 5314 rf := num.DecimalOne() 5315 5316 factor := m.mkt.LinearSlippageFactor 5317 if m.risk.IsRiskFactorInitialised() { 5318 if side == types.SideBuy { 5319 rf = m.risk.GetRiskFactors().Long 5320 } else { 5321 rf = m.risk.GetRiskFactors().Short 5322 } 5323 } 5324 var price *num.Uint 5325 if peggedOrder != nil || orderType == vega.Order_TYPE_MARKET { 5326 priceInMarket, _ := num.UintFromDecimal(m.getCurrentMarkPrice().ToDecimal().Div(m.priceFactor)) 5327 offset := num.UintZero() 5328 if peggedOrder != nil { 5329 offset = peggedOrder.Offset 5330 } 5331 if side == types.SideBuy { 5332 priceInMarket.AddSum(offset) 5333 } else { 5334 priceInMarket = priceInMarket.Sub(priceInMarket, offset) 5335 } 5336 price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) 5337 } else { 5338 price, _ = num.UintFromDecimal(orderPrice.ToDecimal().Mul(m.priceFactor)) 5339 } 5340 5341 margins := num.UintZero().Mul(price, num.NewUint(orderSize)).ToDecimal().Div(m.positionFactor) 5342 5343 assetQuantum, err := m.collateral.GetAssetQuantum(m.settlementAsset) 5344 if err != nil { 5345 return err 5346 } 5347 value := margins.Mul(rf.Add(factor)) 5348 required := quantumMultiplier.Mul(assetQuantum) 5349 if value.LessThan(required) { 5350 return fmt.Errorf(fmt.Sprintf("order value (%s) is less than minimum maintenance margin for spam (%s)", value.String(), required.String())) 5351 } 5352 return nil 5353 } 5354 5355 func (m *Market) checkOrderAmendForSpam(order *types.Order) error { 5356 return m.checkOrderForSpam( 5357 order.Side, 5358 order.Price, 5359 order.Size, 5360 order.PeggedOrder, 5361 order.Type, 5362 m.minMaintenanceMarginQuantumMultiplier) 5363 } 5364 5365 func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmission, party string, quantumMultiplier num.Decimal) error { 5366 return m.checkOrderForSpam( 5367 orderSubmission.Side, 5368 orderSubmission.Price, 5369 orderSubmission.Size, 5370 orderSubmission.PeggedOrder, 5371 orderSubmission.Type, 5372 quantumMultiplier) 5373 } 5374 5375 func (m *Market) GetFillPrice(volume uint64, side types.Side) (*num.Uint, error) { 5376 return m.matching.GetFillPrice(volume, side) 5377 } 5378 5379 func (m *Market) getRebasingOrder( 5380 price *num.Uint, // the best bid/ask of the market 5381 side types.Side, // side the pool needs to trade as 5382 slippage num.Decimal, 5383 pool *amm.Pool, 5384 ) (*types.Order, error) { 5385 var volume uint64 5386 fairPrice := pool.FairPrice() 5387 oneTick, _ := num.UintFromDecimal(m.priceFactor) 5388 oneTick = num.Max(num.UintOne(), oneTick) 5389 5390 var until *num.Uint 5391 switch side { 5392 case types.SideBuy: 5393 until = fairPrice 5394 slipped := price.ToDecimal().Mul(num.DecimalOne().Add(slippage)) 5395 if stopAt, overflow := num.UintFromDecimal(slipped); !overflow { 5396 until = num.Min(until, stopAt) 5397 } 5398 case types.SideSell: 5399 until = fairPrice 5400 slipped := price.ToDecimal().Mul(num.DecimalOne().Sub(slippage)).Ceil() 5401 if stopAt, overflow := num.UintFromDecimal(slipped); !overflow { 5402 until = num.Max(until, stopAt) 5403 } 5404 } 5405 5406 Walk: 5407 for { 5408 // get the tradable volume necessary to move the AMM's position from fair-price -> price 5409 required := pool.TradableVolumeForPrice(types.OtherSide(side), price) 5410 5411 // AMM is close enough to the target that is has no volume between, so we do not need to rebase 5412 if required == 0 { 5413 return nil, nil 5414 } 5415 5416 if volume == 0 { 5417 volume = required 5418 } 5419 5420 // get the volume available to trade from both the orderbook and other AMM's 5421 ammVolume := m.amm.GetVolumeAtPrice(price, side) 5422 orderVolume := m.matching.GetVolumeAtPrice(price, types.OtherSide(side)) 5423 5424 // there is enough volume to trade at this level, create the order that the AMM needs to submit 5425 if required < orderVolume+ammVolume { 5426 originalPrice, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) 5427 return &types.Order{ 5428 ID: m.idgen.NextID(), 5429 MarketID: m.GetID(), 5430 Party: pool.AMMParty, 5431 Side: side, 5432 Price: price, 5433 OriginalPrice: originalPrice, 5434 Size: volume, 5435 Remaining: volume, 5436 TimeInForce: types.OrderTimeInForceIOC, 5437 Type: types.OrderTypeLimit, 5438 CreatedAt: m.timeService.GetTimeNow().UnixNano(), 5439 Status: types.OrderStatusActive, 5440 Reference: "amm-rebase" + pool.AMMParty, 5441 }, nil 5442 } 5443 5444 volume = required 5445 switch side { 5446 case types.SideBuy: 5447 // this function will walk the price forwards through price levels until we hit the AMM's fair price or slippage price 5448 // i.e 100 -> 101 -> 102 5449 price = num.UintZero().Add(price, oneTick) 5450 if price.GTE(until) { 5451 break Walk 5452 } 5453 case types.SideSell: 5454 // this function will walk the price backwards through price levels until we hit the AMM's fair price or slippage price 5455 // i.e 100 -> 99 -> 98 5456 price = num.UintZero().Sub(price, oneTick) 5457 if price.LTE(until) { 5458 break Walk 5459 } 5460 } 5461 } 5462 5463 return nil, common.ErrAMMCannotRebase 5464 } 5465 5466 // needsRebase returns whether an AMM at fair-price needs to submit a rebasing order given the current spread on the market. 5467 func (m *Market) needsRebase(pool *amm.Pool) (bool, types.Side, *num.Uint) { 5468 if pool.IsPending() { 5469 return false, types.SideUnspecified, nil 5470 } 5471 5472 if m.as.InAuction() { 5473 return false, types.SideUnspecified, nil 5474 } 5475 5476 fairPrice := pool.FairPrice() 5477 5478 ask, err := m.matching.GetBestAskPrice() 5479 if err == nil && fairPrice.GT(ask) { 5480 return true, types.SideBuy, ask 5481 } 5482 5483 bid, err := m.matching.GetBestBidPrice() 5484 if err == nil && fairPrice.LT(bid) { 5485 return true, types.SideSell, bid 5486 } 5487 return false, types.SideUnspecified, nil 5488 } 5489 5490 func VerifyAMMBounds(params *types.ConcentratedLiquidityParameters, cap *num.Uint, priceFactor num.Decimal) error { 5491 var lower, upper, base *num.Uint 5492 if params.DataSourceID == nil { 5493 base, _ = num.UintFromDecimal(params.Base.ToDecimal().Mul(priceFactor)) 5494 if cap != nil && base.GTE(cap) { 5495 return common.ErrAMMBoundsOutsidePriceCap 5496 } 5497 } 5498 5499 if params.LowerBound != nil { 5500 lower, _ = num.UintFromDecimal(params.LowerBound.ToDecimal().Mul(priceFactor)) 5501 if base != nil && lower.GTE(base) { 5502 return fmt.Errorf("base (%s) as factored by market and asset decimals must be greater than lower bound (%s)", base.String(), lower.String()) 5503 } 5504 5505 if cap != nil && lower.GTE(cap) { 5506 return common.ErrAMMBoundsOutsidePriceCap 5507 } 5508 } 5509 5510 if params.UpperBound != nil { 5511 upper, _ = num.UintFromDecimal(params.UpperBound.ToDecimal().Mul(priceFactor)) 5512 if base != nil && base.GTE(upper) { 5513 return fmt.Errorf("upper bound (%s) as factored by market and asset decimals must be greater than base (%s)", upper.String(), base.String()) 5514 } 5515 5516 if cap != nil && upper.GTE(cap) { 5517 return common.ErrAMMBoundsOutsidePriceCap 5518 } 5519 } 5520 5521 if lower != nil && upper != nil && lower.GTE(upper) { 5522 return fmt.Errorf("upper bound (%s) as factored by market and asset decimals must be greater than lower (%s)", upper.String(), lower.String()) 5523 } 5524 5525 return nil 5526 } 5527 5528 // productDataSourcePropagation is a call back for whenever the product receives a data-point used to drive any settlement data 5529 // we want to hijack it to use as the base point for AMM's. 5530 func (m *Market) productDataSourcePropagation(ctx context.Context, assetPrice *num.Uint) { 5531 if perp := m.mkt.TradableInstrument.Instrument.GetPerps(); perp != nil { 5532 m.dataSourcePropagation(ctx, perp.DataSourceSpecForSettlementData.ID, assetPrice) 5533 } 5534 } 5535 5536 // dataSourcePropagation is a call back for whenever a data-point used to drive any settlement data 5537 // we want to hijack it to use as the base point for AMM's. 5538 func (m *Market) dataSourcePropagation(ctx context.Context, dataSourceID string, assetPrice *num.Uint) { 5539 _, blockHash := vgcontext.TraceIDFromContext(ctx) 5540 5541 // we want to update any AMM's which have this id as their base price source 5542 pools := m.amm.GetDataSourcedAMMs(dataSourceID) 5543 5544 // now one buy one lets update, treating it like an amend 5545 for i, p := range pools { 5546 params := p.Parameters.Clone() 5547 5548 // scale to market DP 5549 params.Base, _ = num.UintFromDecimal(assetPrice.ToDecimal().Div(m.priceFactor)) 5550 5551 // if base price hasn't moved enough we won't update 5552 if !p.IsPending() && p.MinimumPriceChangeTrigger.IsPositive() { 5553 d := p.Parameters.Base.ToDecimal().Div(params.Base.ToDecimal()).Sub(num.DecimalOne()).Abs() 5554 if d.LessThan(p.MinimumPriceChangeTrigger) { 5555 m.log.Info("not rebasing pool, price change too small", 5556 logging.String("market-id", m.mkt.GetID()), 5557 logging.String("amm-party-id", p.AMMParty), 5558 logging.String("base", p.Parameters.Base.String()), 5559 logging.String("new base", params.Base.String()), 5560 ) 5561 continue 5562 } 5563 } 5564 5565 params.DataSourceID = nil 5566 if err := VerifyAMMBounds(params, m.capMax, m.priceFactor); err != nil { 5567 m.log.Error("unable to update AMM base price from data source", logging.Error(err), logging.String("amm-party", p.AMMParty)) 5568 continue 5569 } 5570 5571 params.DataSourceID = p.Parameters.DataSourceID 5572 5573 // lets treat the update as an amend 5574 amend := &types.AmendAMM{ 5575 AMMBaseCommand: types.AMMBaseCommand{ 5576 Party: p.Owner(), 5577 MarketID: m.mkt.GetID(), 5578 SlippageTolerance: p.SlippageTolerance, 5579 ProposedFee: p.ProposedFee, 5580 MinimumPriceChangeTrigger: p.MinimumPriceChangeTrigger, 5581 }, 5582 CommitmentAmount: nil, 5583 Parameters: params, 5584 } 5585 deterministicID := blockHash + crypto.HashStrToHex("amm-oracle-rebase"+m.mkt.ID+strconv.FormatInt(int64(i), 10)) 5586 err := m.AmendAMM(ctx, amend, deterministicID) 5587 if err != nil { 5588 m.log.Error("unable to update AMM base price from data source", logging.Error(err), logging.String("amm-party", p.AMMParty)) 5589 } 5590 } 5591 } 5592 5593 func (m *Market) SubmitAMM(ctx context.Context, submit *types.SubmitAMM, deterministicID string) error { 5594 if !m.canTrade() { 5595 return common.ErrTradingNotAllowed 5596 } 5597 5598 m.idgen = idgeneration.New(deterministicID) 5599 defer func() { m.idgen = nil }() 5600 5601 // create the AMM curves but do not confirm it with the engine 5602 var order *types.Order 5603 if err := VerifyAMMBounds(submit.Parameters, m.capMax, m.priceFactor); err != nil { 5604 return err 5605 } 5606 5607 pool, err := m.amm.Create(ctx, submit, m.idgen.NextID(), m.risk.GetRiskFactors(), m.risk.GetScalingFactors(), m.risk.GetSlippage()) 5608 if err != nil { 5609 return err 5610 } 5611 5612 // create a rebasing order if the AMM needs it i.e its base if not within best-bid/best-ask 5613 if ok, side, quote := m.needsRebase(pool); ok { 5614 order, err = m.getRebasingOrder(quote, side, submit.SlippageTolerance, pool) 5615 if err != nil { 5616 m.broker.Send( 5617 events.NewAMMPoolEvent( 5618 ctx, pool.Owner(), m.GetID(), pool.AMMParty, pool.ID, 5619 pool.CommitmentAmount(), pool.Parameters, 5620 types.AMMPoolStatusRejected, types.AMMStatusReasonCannotRebase, 5621 pool.ProposedFee, nil, nil, num.DecimalZero(), 5622 ), 5623 ) 5624 return err 5625 } 5626 } 5627 5628 _, err = m.amm.UpdateSubAccountBalance(ctx, submit.Party, pool.AMMParty, submit.CommitmentAmount) 5629 if err != nil { 5630 m.broker.Send( 5631 events.NewAMMPoolEvent( 5632 ctx, submit.Party, m.GetID(), pool.AMMParty, pool.ID, 5633 submit.CommitmentAmount, submit.Parameters, 5634 types.AMMPoolStatusRejected, types.AMMStatusReasonCannotFillCommitment, 5635 pool.ProposedFee, nil, nil, num.DecimalZero(), 5636 ), 5637 ) 5638 return err 5639 } 5640 5641 // if a rebase is not necessary we're done, just confirm with the amm-engine 5642 if order == nil { 5643 m.amm.Confirm(ctx, pool) 5644 m.matching.UpdateAMM(pool.AMMParty) 5645 m.checkForReferenceMoves(ctx, nil, false) 5646 return nil 5647 } 5648 5649 if conf, _, err := m.submitValidatedOrder(ctx, order); err != nil || len(conf.Trades) == 0 { 5650 m.log.Error("failed to submit rebasing order", 5651 logging.Order(order), 5652 logging.Error(err), 5653 ) 5654 ledgerMovements, _, _ := m.collateral.SubAccountRelease(ctx, submit.Party, pool.AMMParty, m.GetSettlementAsset(), m.GetID(), nil) 5655 m.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements)) 5656 m.broker.Send( 5657 events.NewAMMPoolEvent( 5658 ctx, submit.Party, m.GetID(), pool.AMMParty, pool.ID, 5659 submit.CommitmentAmount, submit.Parameters, 5660 types.AMMPoolStatusRejected, types.AMMStatusReasonCannotRebase, 5661 pool.ProposedFee, nil, nil, num.DecimalZero(), 5662 ), 5663 ) 5664 return err 5665 } 5666 5667 // rebase successful so confirm the pool with the engine 5668 m.amm.Confirm(ctx, pool) 5669 // now tell the matching engine something new has appeared incase it needs to update its auction IPV cache 5670 m.matching.UpdateAMM(pool.AMMParty) 5671 m.checkForReferenceMoves(ctx, nil, false) 5672 return nil 5673 } 5674 5675 func (m *Market) AmendAMM(ctx context.Context, amend *types.AmendAMM, deterministicID string) error { 5676 if !m.canTrade() { 5677 return common.ErrTradingNotAllowed 5678 } 5679 5680 m.idgen = idgeneration.New(deterministicID) 5681 defer func() { m.idgen = nil }() 5682 5683 if amend.Parameters != nil { 5684 if err := VerifyAMMBounds(amend.Parameters, m.capMax, m.priceFactor); err != nil { 5685 return err 5686 } 5687 } 5688 5689 // get an amended AMM and the existing AMM 5690 pool, existing, err := m.amm.Amend(ctx, amend, m.risk.GetRiskFactors(), m.risk.GetScalingFactors(), m.risk.GetSlippage()) 5691 if err != nil { 5692 return err 5693 } 5694 // if we failed to rebase the amended pool be sure to reinstante the old one 5695 defer func() { 5696 if err != nil { 5697 m.amm.Confirm(ctx, existing) 5698 } 5699 }() 5700 5701 var order *types.Order 5702 if ok, side, quote := m.needsRebase(pool); ok { 5703 order, err = m.getRebasingOrder(quote, side, amend.SlippageTolerance, pool) 5704 if err != nil { 5705 return err 5706 } 5707 } 5708 5709 // update commitment ready for rebasing 5710 var prevCommitment *num.Uint 5711 if amend.CommitmentAmount != nil { 5712 prevCommitment, err = m.amm.UpdateSubAccountBalance(ctx, pool.Owner(), pool.AMMParty, amend.CommitmentAmount) 5713 if err != nil { 5714 return err 5715 } 5716 } 5717 5718 if order == nil { 5719 m.amm.Confirm(ctx, pool) 5720 m.matching.UpdateAMM(pool.AMMParty) 5721 m.checkForReferenceMoves(ctx, nil, false) 5722 return nil 5723 } 5724 5725 conf, _, err := m.submitValidatedOrder(ctx, order) 5726 if err != nil || len(conf.Trades) == 0 { 5727 m.log.Error("failed to submit rebasing order", 5728 logging.Order(order), 5729 logging.Error(err), 5730 ) 5731 if amend.CommitmentAmount != nil { 5732 if _, err := m.amm.UpdateSubAccountBalance(ctx, pool.Owner(), pool.AMMParty, prevCommitment); err != nil { 5733 m.log.Panic("unable to restore AMM balances after failed amend", logging.Error(err)) 5734 } 5735 } 5736 err = common.ErrAMMCannotRebase // set it to err so that the defer runs 5737 return err 5738 } 5739 5740 m.amm.Confirm(ctx, pool) 5741 m.matching.UpdateAMM(pool.AMMParty) 5742 m.checkForReferenceMoves(ctx, nil, false) 5743 return nil 5744 } 5745 5746 func (m *Market) CancelAMM(ctx context.Context, cancel *types.CancelAMM, deterministicID string) error { 5747 if !m.canTrade() { 5748 return common.ErrTradingNotAllowed 5749 } 5750 5751 ammParty, err := m.amm.GetAMMParty(cancel.Party) 5752 if err != nil { 5753 return err 5754 } 5755 5756 closeout, err := m.amm.CancelAMM(ctx, cancel) 5757 if err != nil { 5758 return err 5759 } 5760 5761 // tell matching incase it needs to remove the AMM's contribution to the IPV cache 5762 m.matching.UpdateAMM(ammParty) 5763 5764 // rejig any pegged orders that might need re-pricing now an AMM is not longer there, or is no longer quoting one side 5765 m.checkForReferenceMoves(ctx, nil, false) 5766 5767 if closeout == nil { 5768 return nil 5769 } 5770 5771 // pool is closed but now its position needs to be closed out 5772 m.idgen = idgeneration.New(deterministicID) 5773 defer func() { m.idgen = nil }() 5774 m.resolveClosedOutParties(ctx, []events.Margin{closeout}) 5775 return nil 5776 }