code.vegaprotocol.io/vega@v0.79.0/core/execution/spot/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 spot 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "sort" 23 "sync" 24 "time" 25 26 "code.vegaprotocol.io/vega/core/assets" 27 "code.vegaprotocol.io/vega/core/events" 28 "code.vegaprotocol.io/vega/core/execution/common" 29 "code.vegaprotocol.io/vega/core/execution/stoporders" 30 "code.vegaprotocol.io/vega/core/fee" 31 "code.vegaprotocol.io/vega/core/idgeneration" 32 liquiditytarget "code.vegaprotocol.io/vega/core/liquidity/target/spot" 33 "code.vegaprotocol.io/vega/core/liquidity/v2" 34 "code.vegaprotocol.io/vega/core/matching" 35 "code.vegaprotocol.io/vega/core/metrics" 36 "code.vegaprotocol.io/vega/core/monitor/price" 37 "code.vegaprotocol.io/vega/core/risk" 38 "code.vegaprotocol.io/vega/core/types" 39 "code.vegaprotocol.io/vega/core/types/statevar" 40 vegacontext "code.vegaprotocol.io/vega/libs/context" 41 "code.vegaprotocol.io/vega/libs/crypto" 42 "code.vegaprotocol.io/vega/libs/num" 43 "code.vegaprotocol.io/vega/libs/ptr" 44 "code.vegaprotocol.io/vega/logging" 45 "code.vegaprotocol.io/vega/protos/vega" 46 ) 47 48 const ( 49 BaseAssetIndex = 0 50 QuoteAssetIndex = 1 51 ) 52 53 type TargetStakeCalculator interface { 54 types.StateProvider 55 RecordTotalStake(oi uint64, now time.Time) error 56 GetTargetStake(now time.Time) *num.Uint 57 UpdateScalingFactor(sFactor num.Decimal) error 58 UpdateTimeWindow(tWindow time.Duration) 59 StopSnapshots() 60 UpdateParameters(types.TargetStakeParameters) 61 } 62 63 // Market represents an instance of a market in vega and is in charge of calling the engines in order to process all transactions. 64 type Market struct { 65 log *logging.Logger 66 idgen common.IDGenerator 67 68 mkt *types.Market 69 70 closingAt time.Time 71 timeService common.TimeService 72 73 mu sync.RWMutex 74 markPriceLock sync.RWMutex 75 76 lastTradedPrice *num.Uint 77 markPrice *num.Uint 78 priceFactor num.Decimal 79 quoteAssetDP uint32 80 81 // own engines 82 matching *matching.CachedOrderBook 83 fee *fee.Engine 84 referralDiscountRewardService fee.ReferralDiscountRewardService 85 volumeDiscountService fee.VolumeDiscountService 86 volumeRebateService fee.VolumeRebateService 87 liquidity *common.MarketLiquidity 88 liquidityEngine common.LiquidityEngine 89 90 // deps engines 91 collateral common.Collateral 92 banking common.Banking 93 94 broker common.Broker 95 closed bool 96 finalFeesDistributed bool 97 98 parties map[string]struct{} 99 100 pMonitor common.PriceMonitor 101 102 tsCalc TargetStakeCalculator 103 104 as common.AuctionState 105 106 peggedOrders *common.PeggedOrders 107 expiringOrders *common.ExpiringOrders 108 109 // Store the previous price values so we can see what has changed 110 lastBestBidPrice *num.Uint 111 lastBestAskPrice *num.Uint 112 lastMidBuyPrice *num.Uint 113 lastMidSellPrice *num.Uint 114 115 lastMarketValueProxy num.Decimal 116 marketValueWindowLength time.Duration 117 minHoldingQuantumMultiplier num.Decimal 118 119 // Liquidity Fee 120 feeSplitter *common.FeeSplitter 121 lastEquityShareDistributed time.Time 122 equityShares *common.EquityShares 123 minLPStakeQuantumMultiple num.Decimal 124 125 stateVarEngine common.StateVarEngine 126 marketActivityTracker *common.MarketActivityTracker 127 baseFactor num.Decimal // 10^(baseDP-pdp) 128 positionFactor num.Decimal // 10^pdp 129 130 orderHoldingTracker *HoldingAccountTracker 131 132 nextMTM time.Time 133 mtmDelta time.Duration 134 hasTraded bool 135 baseAsset string 136 quoteAsset string 137 138 maxStopOrdersPerParties *num.Uint 139 stopOrders *stoporders.Pool 140 expiringStopOrders *common.ExpiringOrders 141 142 minDuration time.Duration 143 epoch types.Epoch 144 145 pap *ProtocolAutomatedPurchase 146 allowedSellers map[string]struct{} 147 } 148 149 // NewMarket creates a new market using the market framework configuration and creates underlying engines. 150 func NewMarket( 151 log *logging.Logger, 152 matchingConfig matching.Config, 153 feeConfig fee.Config, 154 liquidityConfig liquidity.Config, 155 collateralEngine common.Collateral, 156 mkt *types.Market, 157 timeService common.TimeService, 158 broker common.Broker, 159 as common.AuctionState, 160 stateVarEngine common.StateVarEngine, 161 marketActivityTracker *common.MarketActivityTracker, 162 baseAssetDetails *assets.Asset, 163 quoteAssetDetails *assets.Asset, 164 peggedOrderNotify func(int64), 165 referralDiscountRewardService fee.ReferralDiscountRewardService, 166 volumeDiscountService fee.VolumeDiscountService, 167 volumeRebateService fee.VolumeRebateService, 168 banking common.Banking, 169 ) (*Market, error) { 170 if len(mkt.ID) == 0 { 171 return nil, common.ErrEmptyMarketID 172 } 173 174 positionFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(mkt.PositionDecimalPlaces)) 175 priceFactor := num.DecimalOne() 176 if exp := int(quoteAssetDetails.DecimalPlaces()) - int(mkt.DecimalPlaces); exp != 0 { 177 priceFactor = num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(exp))) 178 } 179 baseFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(int64(baseAssetDetails.DecimalPlaces()) - mkt.PositionDecimalPlaces)) 180 book := matching.NewCachedOrderBook(log, matchingConfig, mkt.ID, as.InAuction(), peggedOrderNotify) 181 assets, err := mkt.GetAssets() 182 if err != nil { 183 return nil, err 184 } 185 186 if len(assets) != 2 { 187 return nil, fmt.Errorf("expecting base asset and quote asset for spot market") 188 } 189 190 baseAsset := assets[BaseAssetIndex] 191 quoteAsset := assets[QuoteAssetIndex] 192 feeEngine, err := fee.New(log, feeConfig, *mkt.Fees, quoteAsset, positionFactor) 193 if err != nil { 194 return nil, fmt.Errorf("unable to instantiate fee engine: %w", err) 195 } 196 197 tsCalc := liquiditytarget.NewSnapshotEngine(*mkt.LiquidityMonitoringParameters.TargetStakeParameters, mkt.ID, positionFactor) 198 riskModel, err := risk.NewModel(mkt.TradableInstrument.RiskModel, quoteAsset) 199 if err != nil { 200 return nil, fmt.Errorf("unable to instantiate risk model: %w", err) 201 } 202 pMonitor, err := price.NewMonitor(quoteAsset, mkt.ID, riskModel, as, mkt.PriceMonitoringSettings, stateVarEngine, log) 203 if err != nil { 204 return nil, fmt.Errorf("unable to instantiate price monitoring engine: %w", err) 205 } 206 207 now := timeService.GetTimeNow() 208 209 // The market is initially created in a proposed state 210 mkt.State = types.MarketStateProposed 211 mkt.TradingMode = types.MarketTradingModeNoTrading 212 213 // Populate the market timestamps 214 ts := &types.MarketTimestamps{ 215 Proposed: now.UnixNano(), 216 Pending: now.UnixNano(), 217 } 218 219 if mkt.OpeningAuction != nil { 220 ts.Open = now.Add(time.Duration(mkt.OpeningAuction.Duration)).UnixNano() 221 } else { 222 ts.Open = now.UnixNano() 223 } 224 225 mkt.MarketTimestamps = ts 226 liquidity := liquidity.NewSnapshotEngine(liquidityConfig, log, timeService, broker, riskModel, pMonitor, book, as, quoteAsset, mkt.ID, stateVarEngine, positionFactor, mkt.LiquiditySLAParams) 227 els := common.NewEquityShares(num.DecimalZero()) 228 // @TODO pass in AMM 229 marketLiquidity := common.NewMarketLiquidity(log, liquidity, collateralEngine, broker, book, els, marketActivityTracker, feeEngine, common.SpotMarketType, mkt.ID, quoteAsset, priceFactor, mkt.LiquiditySLAParams.PriceRange, nil) 230 231 allowedSellers := map[string]struct{}{} 232 for _, v := range mkt.AllowedSellers { 233 allowedSellers[v] = struct{}{} 234 } 235 236 market := &Market{ 237 log: log, 238 idgen: nil, 239 mkt: mkt, 240 matching: book, 241 collateral: collateralEngine, 242 timeService: timeService, 243 broker: broker, 244 fee: feeEngine, 245 referralDiscountRewardService: referralDiscountRewardService, 246 volumeDiscountService: volumeDiscountService, 247 volumeRebateService: volumeRebateService, 248 parties: map[string]struct{}{}, 249 as: as, 250 pMonitor: pMonitor, 251 liquidity: marketLiquidity, 252 liquidityEngine: liquidity, 253 tsCalc: tsCalc, 254 peggedOrders: common.NewPeggedOrders(log, timeService), 255 expiringOrders: common.NewExpiringOrders(), 256 feeSplitter: common.NewFeeSplitter(), 257 equityShares: els, 258 lastBestAskPrice: num.UintZero(), 259 lastMidSellPrice: num.UintZero(), 260 lastMidBuyPrice: num.UintZero(), 261 lastBestBidPrice: num.UintZero(), 262 stateVarEngine: stateVarEngine, 263 marketActivityTracker: marketActivityTracker, 264 priceFactor: priceFactor, 265 baseFactor: baseFactor, 266 minLPStakeQuantumMultiple: num.MustDecimalFromString("1"), 267 positionFactor: positionFactor, 268 baseAsset: baseAsset, 269 quoteAsset: quoteAsset, 270 orderHoldingTracker: NewHoldingAccountTracker(mkt.ID, log, collateralEngine), 271 nextMTM: time.Time{}, // default to zero time 272 maxStopOrdersPerParties: num.UintZero(), 273 stopOrders: stoporders.New(log), 274 expiringStopOrders: common.NewExpiringOrders(), 275 banking: banking, 276 allowedSellers: allowedSellers, 277 } 278 liquidity.SetGetStaticPricesFunc(market.getBestStaticPricesDecimal) 279 280 market.quoteAssetDP = uint32(quoteAssetDetails.DecimalPlaces()) 281 return market, nil 282 } 283 284 func (m *Market) GetAssets() []string { 285 return []string{m.baseAsset, m.quoteAsset} 286 } 287 288 func (m *Market) IsOpeningAuction() bool { 289 return m.as.IsOpeningAuction() 290 } 291 292 func (m *Market) getParties() []string { 293 parties := make([]string, 0, len(m.parties)) 294 for k := range m.parties { 295 parties = append(parties, k) 296 } 297 sort.Strings(parties) 298 return parties 299 } 300 301 func (m *Market) GetPartiesStats() *types.MarketStats { 302 return &types.MarketStats{} 303 } 304 305 func (m *Market) Update(ctx context.Context, config *types.Market) error { 306 tickSizeChanged := config.TickSize.NEQ(m.mkt.TickSize) 307 config.TradingMode = m.mkt.TradingMode 308 config.State = m.mkt.State 309 config.MarketTimestamps = m.mkt.MarketTimestamps 310 m.mkt = config 311 312 m.tsCalc.UpdateParameters(*config.LiquidityMonitoringParameters.TargetStakeParameters) 313 riskModel, err := risk.NewModel(config.TradableInstrument.RiskModel, m.quoteAsset) 314 if err != nil { 315 return err 316 } 317 m.pMonitor.UpdateSettings(riskModel, m.mkt.PriceMonitoringSettings, m.as) 318 m.liquidity.UpdateMarketConfig(riskModel, m.pMonitor) 319 m.updateLiquidityFee(ctx) 320 321 clear(m.allowedSellers) 322 for _, v := range config.AllowedSellers { 323 m.allowedSellers[v] = struct{}{} 324 } 325 326 if tickSizeChanged { 327 tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor)) 328 peggedOrders := m.matching.GetActivePeggedOrderIDs() 329 peggedOrders = append(peggedOrders, m.peggedOrders.GetParkedIDs()...) 330 for _, po := range peggedOrders { 331 order, err := m.matching.GetOrderByID(po) 332 if err != nil { 333 order = m.peggedOrders.GetParkedByID(po) 334 if order == nil { 335 continue 336 } 337 } 338 offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor)) 339 if !num.UintZero().Mod(order.PeggedOrder.Offset, m.mkt.TickSize).IsZero() || 340 (order.PeggedOrder.Reference == types.PeggedReferenceMid && offsetInAsset.IsZero() && tickSizeInAsset.IsZero()) { 341 m.cancelOrder(ctx, order.Party, order.ID) 342 } 343 } 344 } 345 346 // update immediately during opening auction 347 if m.as.IsOpeningAuction() { 348 m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams) 349 } 350 351 return nil 352 } 353 354 func (m *Market) GetEquityShares() *common.EquityShares { 355 return m.equityShares 356 } 357 358 func (m *Market) GetEquitySharesForParty(partyID string) num.Decimal { 359 primary := m.equityShares.SharesFromParty(partyID) 360 // AMM for spot has not been implemented yet 361 // if sub, err := m.amm.GetAMMParty(partyID); err == nil { 362 // return primary.Add(m.equityShares.SharesFromParty(sub)) 363 // } 364 return primary 365 } 366 367 func (m *Market) SetNextMTM(tm time.Time) { 368 m.nextMTM = tm 369 } 370 371 func (m *Market) GetNextMTM() time.Time { 372 return m.nextMTM 373 } 374 375 func (m *Market) midPrice() *num.Uint { 376 bestBidPrice, _, _ := m.matching.BestBidPriceAndVolume() 377 bestOfferPrice, _, _ := m.matching.BestOfferPriceAndVolume() 378 two := num.NewUint(2) 379 midPrice := num.UintZero() 380 if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() { 381 midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two) 382 } 383 return midPrice 384 } 385 386 func (m *Market) IntoType() types.Market { 387 return *m.mkt.DeepClone() 388 } 389 390 func (m *Market) Hash() []byte { 391 mID := logging.String("market-id", m.GetID()) 392 matchingHash := m.matching.Hash() 393 m.log.Debug("orderbook state hash", logging.Hash(matchingHash), mID) 394 return matchingHash 395 } 396 397 func (m *Market) GetMarketState() types.MarketState { 398 return m.mkt.State 399 } 400 401 func (m *Market) priceToMarketPrecision(price *num.Uint) *num.Uint { 402 p, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) 403 return p 404 } 405 406 func (m *Market) GetMarketData() types.MarketData { 407 bestBidPrice, bestBidVolume, _ := m.matching.BestBidPriceAndVolume() 408 bestOfferPrice, bestOfferVolume, _ := m.matching.BestOfferPriceAndVolume() 409 bestStaticBidPrice, bestStaticBidVolume, _ := m.getBestStaticBidPriceAndVolume() 410 bestStaticOfferPrice, bestStaticOfferVolume, _ := m.getBestStaticAskPriceAndVolume() 411 412 // Auction related values 413 indicativePrice := num.UintZero() 414 indicativeVolume := uint64(0) 415 var auctionStart, auctionEnd int64 416 if m.as.InAuction() { 417 indicativePrice, indicativeVolume, _ = m.matching.GetIndicativePriceAndVolume() 418 if t := m.as.Start(); !t.IsZero() { 419 auctionStart = t.UnixNano() 420 } 421 if t := m.as.ExpiresAt(); t != nil { 422 auctionEnd = t.UnixNano() 423 } 424 } 425 426 // If we do not have one of the best_* prices, leave the mid price as zero 427 two := num.NewUint(2) 428 midPrice := num.UintZero() 429 if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() { 430 midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two) 431 } 432 433 staticMidPrice := num.UintZero() 434 if !bestStaticBidPrice.IsZero() && !bestStaticOfferPrice.IsZero() { 435 staticMidPrice = staticMidPrice.Div(num.Sum(bestStaticBidPrice, bestStaticOfferPrice), two) 436 } 437 438 targetStake := m.getTargetStake().String() 439 bounds := m.pMonitor.GetBounds() 440 for _, b := range bounds { 441 b.MaxValidPrice = m.priceToMarketPrecision(b.MaxValidPrice) // effictively floors this 442 b.MinValidPrice = m.priceToMarketPrecision(b.MinValidPrice) 443 444 rp, _ := num.UintFromDecimal(b.ReferencePrice) 445 rp = m.priceToMarketPrecision(rp) 446 b.ReferencePrice = num.DecimalFromUint(rp) 447 448 if m.priceFactor.GreaterThan(num.DecimalOne()) { 449 b.MinValidPrice.AddSum(common.One) // ceil 450 } 451 } 452 mode := m.as.Mode() 453 if m.mkt.TradingMode == types.MarketTradingModeNoTrading { 454 mode = m.mkt.TradingMode 455 } 456 457 var papState *vega.ProtocolAutomatedPurchaseData 458 if m.pap != nil { 459 var activeOrder *string 460 if len(m.pap.activeOrder) > 0 { 461 activeOrder = &m.pap.activeOrder 462 } 463 papState = &vega.ProtocolAutomatedPurchaseData{ 464 Id: m.pap.ID, 465 OrderId: activeOrder, 466 } 467 } 468 469 return types.MarketData{ 470 Market: m.GetID(), 471 BestBidPrice: m.priceToMarketPrecision(bestBidPrice), 472 BestBidVolume: bestBidVolume, 473 BestOfferPrice: m.priceToMarketPrecision(bestOfferPrice), 474 BestOfferVolume: bestOfferVolume, 475 BestStaticBidPrice: m.priceToMarketPrecision(bestStaticBidPrice), 476 BestStaticBidVolume: bestStaticBidVolume, 477 BestStaticOfferPrice: m.priceToMarketPrecision(bestStaticOfferPrice), 478 BestStaticOfferVolume: bestStaticOfferVolume, 479 NextMTM: m.nextMTM.UnixNano(), 480 MidPrice: m.priceToMarketPrecision(midPrice), 481 StaticMidPrice: m.priceToMarketPrecision(staticMidPrice), 482 MarkPrice: m.priceToMarketPrecision(m.getCurrentMarkPrice()), 483 LastTradedPrice: m.priceToMarketPrecision(m.getLastTradedPrice()), 484 Timestamp: m.timeService.GetTimeNow().UnixNano(), 485 IndicativePrice: m.priceToMarketPrecision(indicativePrice), 486 IndicativeVolume: indicativeVolume, 487 AuctionStart: auctionStart, 488 AuctionEnd: auctionEnd, 489 MarketTradingMode: mode, 490 MarketState: m.mkt.State, 491 Trigger: m.as.Trigger(), 492 ExtensionTrigger: m.as.ExtensionTrigger(), 493 TargetStake: targetStake, 494 SuppliedStake: m.getSuppliedStake().String(), 495 PriceMonitoringBounds: bounds, 496 MarketValueProxy: m.lastMarketValueProxy.BigInt().String(), 497 LiquidityProviderFeeShare: m.equityShares.LpsToLiquidityProviderFeeShare(m.liquidity.GetAverageLiquidityScores()), 498 LiquidityProviderSLA: m.liquidityEngine.LiquidityProviderSLAStats(m.timeService.GetTimeNow()), 499 PAPState: papState, 500 } 501 } 502 503 func (m *Market) uncrossOnLeaveAuction(ctx context.Context) ([]*types.OrderConfirmation, []*types.Order) { 504 uncrossedOrders, ordersToCancel, err := m.matching.LeaveAuction(m.timeService.GetTimeNow()) 505 if err != nil { 506 m.log.Error("Error leaving auction", logging.Error(err)) 507 } 508 evts := make([]events.Event, 0, len(uncrossedOrders)) 509 for _, uncrossedOrder := range uncrossedOrders { 510 m.handleConfirmation(ctx, uncrossedOrder) 511 if uncrossedOrder.Order.Remaining == 0 { 512 uncrossedOrder.Order.Status = types.OrderStatusFilled 513 } 514 evts = append(evts, events.NewOrderEvent(ctx, uncrossedOrder.Order)) 515 } 516 517 for _, uncrossedOrder := range uncrossedOrders { 518 m.handleConfirmationPassiveOrders(ctx, uncrossedOrder) 519 } 520 521 // send order events in a single batch, it's more efficient 522 m.broker.SendBatch(evts) 523 for _, otc := range ordersToCancel { 524 if otc.Party == types.NetworkParty { 525 m.papOrderProcessingEnded(otc.ID) 526 } 527 } 528 for _, otc := range uncrossedOrders { 529 if otc.Order.ID == types.NetworkParty { 530 m.papOrderProcessingEnded(otc.Order.ID) 531 } 532 for _, t := range otc.Trades { 533 if t.Seller == types.NetworkParty { 534 m.papOrderProcessingEnded(t.SellOrder) 535 } else if t.Buyer == types.NetworkParty { 536 m.papOrderProcessingEnded(t.BuyOrder) 537 } 538 } 539 } 540 541 return uncrossedOrders, ordersToCancel 542 } 543 544 func (m *Market) uncrossOrderAtAuctionEnd(ctx context.Context) { 545 if !m.as.InAuction() || m.as.IsOpeningAuction() { 546 return 547 } 548 m.uncrossOnLeaveAuction(ctx) 549 } 550 551 func (m *Market) EnterLongBlockAuction(ctx context.Context, duration int64) { 552 if !m.canTrade() { 553 return 554 } 555 556 // markets in governance auction are unaffected by long block auctions. 557 if m.mkt.TradingMode == types.MarketTradingModeSuspendedViaGovernance { 558 return 559 } 560 561 if m.as.InAuction() { 562 now := m.timeService.GetTimeNow() 563 aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second) 564 if aRemaining >= duration { 565 return 566 } 567 m.as.ExtendAuctionLongBlock(types.AuctionDuration{ 568 Duration: duration - aRemaining, 569 }) 570 if evt := m.as.AuctionExtended(ctx, now); evt != nil { 571 m.broker.Send(evt) 572 } 573 } else { 574 m.as.StartLongBlockAuction(m.timeService.GetTimeNow(), duration) 575 m.mkt.TradingMode = types.MarketTradingModeLongBlockAuction 576 m.mkt.State = types.MarketStateSuspended 577 m.enterAuction(ctx) 578 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 579 } 580 } 581 582 func (m *Market) UpdateMarketState(ctx context.Context, changes *types.MarketStateUpdateConfiguration) error { 583 _, blockHash := vegacontext.TraceIDFromContext(ctx) 584 // make deterministic ID for this market, concatenate 585 // the block hash and the market ID 586 m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID())) 587 // and we call next ID on this directly just so we don't have an ID which have 588 // a different from others, we basically burn the first ID. 589 _ = m.idgen.NextID() 590 defer func() { m.idgen = nil }() 591 if changes.UpdateType == types.MarketStateUpdateTypeTerminate { 592 m.uncrossOrderAtAuctionEnd(ctx) 593 // terminate and settle 594 m.closeSpotMarket(ctx) 595 } else if changes.UpdateType == types.MarketStateUpdateTypeSuspend { 596 m.mkt.State = types.MarketStateSuspendedViaGovernance 597 m.mkt.TradingMode = types.MarketTradingModeSuspendedViaGovernance 598 if m.as.InAuction() { 599 m.as.ExtendAuctionSuspension(types.AuctionDuration{Duration: int64(m.minDuration.Seconds())}) 600 evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()) 601 if evt != nil { 602 m.broker.Send(evt) 603 } 604 } else { 605 m.as.StartGovernanceSuspensionAuction(m.timeService.GetTimeNow()) 606 m.enterAuction(ctx) 607 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 608 } 609 } else if changes.UpdateType == types.MarketStateUpdateTypeResume && m.mkt.State == types.MarketStateSuspendedViaGovernance { 610 if m.as.GetState().Trigger == types.AuctionTriggerGovernanceSuspension && m.as.GetState().Extension == types.AuctionTriggerUnspecified { 611 m.as.EndGovernanceSuspensionAuction() 612 m.leaveAuction(ctx, m.timeService.GetTimeNow()) 613 } else { 614 m.as.EndGovernanceSuspensionAuction() 615 if m.as.GetState().Trigger == types.AuctionTriggerOpening { 616 m.mkt.State = types.MarketStatePending 617 m.mkt.TradingMode = types.MarketTradingModeOpeningAuction 618 } else { 619 m.mkt.State = types.MarketStateSuspended 620 m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction 621 } 622 m.checkAuction(ctx, m.timeService.GetTimeNow(), m.idgen) 623 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 624 } 625 } 626 return nil 627 } 628 629 // ReloadConf will trigger a reload of all the config settings in the market and all underlying engines 630 // this is required when hot-reloading any config changes, eg. logger level. 631 func (m *Market) ReloadConf(matchingConfig matching.Config, feeConfig fee.Config) { 632 m.log.Info("reloading configuration") 633 m.matching.ReloadConf(matchingConfig) 634 m.fee.ReloadConf(feeConfig) 635 } 636 637 func (m *Market) GetAssetForProposerBonus() string { 638 return m.quoteAsset 639 } 640 641 // Reject a market if the market state allow. 642 func (m *Market) Reject(ctx context.Context) error { 643 if m.mkt.State != types.MarketStateProposed { 644 return common.ErrCannotRejectMarketNotInProposedState 645 } 646 647 // we closed all parties accounts 648 m.cleanupOnReject(ctx) 649 m.mkt.State = types.MarketStateRejected 650 m.mkt.TradingMode = types.MarketTradingModeNoTrading 651 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 652 return nil 653 } 654 655 // CanLeaveOpeningAuction checks if the market can leave the opening auction based on whether floating point consensus has been reached on all 2 vars. 656 func (m *Market) CanLeaveOpeningAuction() bool { 657 boundFactorsInitialised := m.pMonitor.IsBoundFactorsInitialised() 658 potInitialised := m.liquidity.IsProbabilityOfTradingInitialised() 659 660 canLeave := boundFactorsInitialised && potInitialised 661 if !canLeave { 662 m.log.Info("Cannot leave opening auction", logging.String("market", m.mkt.ID), logging.Bool("bound-factors-initialised", boundFactorsInitialised), logging.Bool("pot-initialised", potInitialised)) 663 } 664 return canLeave 665 } 666 667 // StartOpeningAuction kicks off opening auction. 668 func (m *Market) StartOpeningAuction(ctx context.Context) error { 669 if m.mkt.State != types.MarketStateProposed { 670 return common.ErrCannotStartOpeningAuctionForMarketNotInProposedState 671 } 672 673 // now we start the opening auction 674 if m.as.AuctionStart() { 675 // we are now in a pending state 676 m.mkt.State = types.MarketStatePending 677 m.mkt.TradingMode = types.MarketTradingModeOpeningAuction 678 m.enterAuction(ctx) 679 } else { 680 m.mkt.State = types.MarketStateActive 681 m.mkt.TradingMode = types.MarketTradingModeContinuous 682 } 683 684 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 685 return nil 686 } 687 688 // GetID returns the id of the given market. 689 func (m *Market) GetID() string { 690 return m.mkt.ID 691 } 692 693 // PostRestore restores market price in orders after snapshot reload. 694 func (m *Market) PostRestore(ctx context.Context) error { 695 // tell the matching engine about the markets price factor so it can finish restoring orders 696 m.matching.RestoreWithMarketPriceFactor(m.priceFactor) 697 return nil 698 } 699 700 // OnTick notifies the market of a new time event/update. 701 func (m *Market) OnTick(ctx context.Context, t time.Time) bool { 702 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "OnTick") 703 m.mu.Lock() 704 defer m.mu.Unlock() 705 706 _, blockHash := vegacontext.TraceIDFromContext(ctx) 707 // make deterministic ID for this market, concatenate 708 // the block hash and the market ID 709 m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID())) 710 // and we call next ID on this directly just so we don't have an ID which have 711 // a different from others, we basically burn the first ID. 712 _ = m.idgen.NextID() 713 defer func() { m.idgen = nil }() 714 715 if m.closed { 716 return true 717 } 718 719 m.checkPAP(ctx) 720 721 // first we expire orders 722 if !m.closed && m.canTrade() { 723 expired := m.removeExpiredOrders(ctx, t.UnixNano()) 724 metrics.OrderGaugeAdd(-len(expired), m.GetID()) 725 confirmations := m.removeExpiredStopOrders(ctx, t.UnixNano(), m.idgen) 726 727 stopsExpired := 0 728 for _, v := range confirmations { 729 stopsExpired++ 730 for _, v := range v.PassiveOrdersAffected { 731 if v.Status != types.OrderStatusActive { 732 stopsExpired++ 733 } 734 } 735 } 736 metrics.OrderGaugeAdd(-stopsExpired, m.GetID()) 737 } 738 739 // some engines still needs to get updates: 740 m.pMonitor.OnTimeUpdate(t) 741 m.feeSplitter.SetCurrentTime(t) 742 743 if m.mkt.State == types.MarketStateProposed { 744 return false 745 } 746 747 m.checkAuction(ctx, t, m.idgen) 748 timer.EngineTimeCounterAdd() 749 m.updateMarketValueProxy() 750 m.updateLiquidityFee(ctx) 751 m.liquidity.OnTick(ctx, t) 752 m.broker.Send(events.NewMarketTick(ctx, m.mkt.ID, t)) 753 return m.closed 754 } 755 756 // BlockEnd notifies the market of the end of the block. 757 func (m *Market) BlockEnd(ctx context.Context) { 758 t := m.timeService.GetTimeNow() 759 if m.mkt.State == types.MarketStateProposed || 760 m.mkt.State == types.MarketStateCancelled || 761 m.mkt.State == types.MarketStateRejected || 762 m.mkt.State == types.MarketStateSettled { 763 if (m.nextMTM.IsZero() || !m.nextMTM.After(t)) && !m.as.InAuction() { 764 m.nextMTM = t.Add(m.mtmDelta) 765 } 766 return 767 } 768 // simplified version of updating mark price every MTM interval 769 mp := m.getLastTradedPrice() 770 if !m.hasTraded && m.markPrice != nil { 771 // no trades happened, make sure we're just using the current mark price 772 mp = m.markPrice.Clone() 773 } 774 775 if !mp.IsZero() && !m.as.InAuction() && (m.nextMTM.IsZero() || !m.nextMTM.After(t)) { 776 m.pMonitor.CheckPrice(ctx, m.as, mp, true, true) 777 if !m.as.InAuction() && !m.as.AuctionStart() { 778 m.markPriceLock.Lock() 779 m.markPrice = mp 780 m.markPriceLock.Unlock() 781 m.lastTradedPrice = mp.Clone() 782 m.hasTraded = false 783 } 784 m.nextMTM = t.Add(m.mtmDelta) 785 } 786 m.tsCalc.RecordTotalStake(m.liquidity.CalculateSuppliedStake().Uint64(), m.timeService.GetTimeNow()) 787 m.liquidity.EndBlock(m.markPrice, m.midPrice(), m.positionFactor) 788 } 789 790 func (m *Market) updateMarketValueProxy() { 791 // if windows length is reached, reset fee splitter 792 if mvwl := m.marketValueWindowLength; m.feeSplitter.Elapsed() > mvwl { 793 // AvgTradeValue calculates the rolling average trade value to include the current window (which is ending) 794 m.equityShares.AvgTradeValue(m.feeSplitter.AvgTradeValue()) 795 // this increments the internal window counter 796 m.feeSplitter.TimeWindowStart(m.timeService.GetTimeNow()) 797 // m.equityShares.UpdateVirtualStake() // this should always set the vStake >= physical stake? 798 } 799 800 // these need to happen every block 801 // but also when new LP is submitted just so we are sure we do 802 // not have a mvp of 0 803 ts := m.liquidity.CalculateSuppliedStake() 804 m.lastMarketValueProxy = m.feeSplitter.MarketValueProxy( 805 m.marketValueWindowLength, ts) 806 } 807 808 // removeOrders removes orders from the book when the market is stopped. 809 func (m *Market) removeOrders(ctx context.Context) { 810 // remove all order from the book 811 // and send events with the stopped status 812 orders := append(m.matching.Settled(), m.peggedOrders.Settled()...) 813 orderEvents := make([]events.Event, 0, len(orders)) 814 for _, v := range orders { 815 orderEvents = append(orderEvents, events.NewOrderEvent(ctx, v)) 816 // release any locked funds for the order from the holding account 817 m.releaseOrderFromHoldingAccount(ctx, v.ID, v.Party, v.Side) 818 } 819 m.broker.SendBatch(orderEvents) 820 } 821 822 // cleanMarketWithState clears the collateral state of the market and clears up state vars and sets the terminated state of the market 823 // NB: should it actually go to settled?. 824 func (m *Market) cleanMarketWithState(ctx context.Context, mktState types.MarketState) error { 825 clearMarketTransfers, err := m.collateral.ClearSpotMarket(ctx, m.GetID(), m.quoteAsset, m.getParties()) 826 if err != nil { 827 m.log.Error("Clear market error", 828 logging.MarketID(m.GetID()), 829 logging.Error(err)) 830 return err 831 } 832 833 m.stateVarEngine.UnregisterStateVariable(m.quoteAsset, m.mkt.ID) 834 if len(clearMarketTransfers) > 0 { 835 m.broker.Send(events.NewLedgerMovements(ctx, clearMarketTransfers)) 836 } 837 838 m.mkt.State = mktState 839 m.mkt.TradingMode = types.MarketTradingModeNoTrading 840 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 841 m.closed = true 842 return nil 843 } 844 845 // closeCancelledMarket cleans up after a cancelled market. 846 func (m *Market) closeCancelledMarket(ctx context.Context) error { 847 if err := m.cleanMarketWithState(ctx, types.MarketStateCancelled); err != nil { 848 return err 849 } 850 851 m.liquidity.StopAllLiquidityProvision(ctx) 852 m.closed = true 853 return nil 854 } 855 856 // closeMarket 857 // NB: this is currently called immediately from terminate trading. 858 func (m *Market) closeMarket(ctx context.Context) error { 859 // final distribution of liquidity fees 860 m.liquidity.OnMarketClosed(ctx, m.timeService.GetTimeNow()) 861 // final distribution of liquidity fees 862 if !m.finalFeesDistributed { 863 if err := m.liquidity.AllocateFees(ctx); err != nil { 864 m.log.Panic("failed to allocate liquidity provision fees", logging.Error(err)) 865 } 866 867 m.liquidity.OnEpochEnd(ctx, m.timeService.GetTimeNow(), m.epoch) 868 m.finalFeesDistributed = true 869 } 870 err := m.cleanMarketWithState(ctx, types.MarketStateClosed) 871 if err != nil { 872 return err 873 } 874 875 m.removeOrders(ctx) 876 m.liquidity.StopAllLiquidityProvision(ctx) 877 return nil 878 } 879 880 // unregisterAndReject - the order didn't go to the book therefore there's no need to release funds from the holding account. 881 func (m *Market) unregisterAndReject(ctx context.Context, order *types.Order, err error) error { 882 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 883 order.Status = types.OrderStatusRejected 884 if oerr, ok := types.IsOrderError(err); ok { 885 // the order wasn't invalid, so stopped is a better status, rather than rejected. 886 if types.IsStoppingOrder(oerr) { 887 order.Status = types.OrderStatusStopped 888 } 889 order.Reason = oerr 890 } else { 891 // should not happened but still... 892 order.Reason = types.OrderErrorInternalError 893 } 894 m.broker.Send(events.NewOrderEvent(ctx, order)) 895 if m.log.GetLevel() == logging.DebugLevel { 896 m.log.Debug("Failure after submitting order to matching engine", 897 logging.Order(*order), 898 logging.Error(err)) 899 } 900 return err 901 } 902 903 // getNewPeggedPrice calculates pegged price based on the pegged reference and current prices. 904 func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) { 905 if m.as.InAuction() { 906 return num.UintZero(), common.ErrCannotRepriceDuringAuction 907 } 908 909 var ( 910 err error 911 price *num.Uint 912 ) 913 914 switch order.PeggedOrder.Reference { 915 case types.PeggedReferenceMid: 916 price, err = m.getStaticMidPrice(order.Side) 917 case types.PeggedReferenceBestBid: 918 price, err = m.getBestStaticBidPrice() 919 case types.PeggedReferenceBestAsk: 920 price, err = m.getBestStaticAskPrice() 921 } 922 if err != nil { 923 return num.UintZero(), common.ErrUnableToReprice 924 } 925 926 // we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly 927 priceInMarket, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) 928 if order.Side == types.SideSell { 929 priceInMarket.AddSum(order.PeggedOrder.Offset) 930 // this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size 931 // but this can never cross the mid by construction as the the minimum offset is 1 tick size and all prices must be 932 // whole multiples of tick size. 933 if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() { 934 priceInMarket.Sub(priceInMarket, mod) 935 } 936 price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) 937 return price, nil 938 } 939 940 if priceInMarket.LTE(order.PeggedOrder.Offset) { 941 return num.UintZero(), common.ErrUnableToReprice 942 } 943 944 priceInMarket.Sub(priceInMarket, order.PeggedOrder.Offset) 945 if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() { 946 priceInMarket = num.UintZero().Sub(priceInMarket.AddSum(m.mkt.TickSize), mod) 947 } 948 price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) 949 950 return price, nil 951 } 952 953 // Reprice a pegged order. This only updates the price on the order. 954 func (m *Market) repricePeggedOrder(order *types.Order) error { 955 // Work out the new price of the order 956 price, err := m.getNewPeggedPrice(order) 957 if err != nil { 958 return err 959 } 960 order.OriginalPrice, _ = num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) // set original price in market precision 961 order.Price = price 962 return nil 963 } 964 965 // parkAllPeggedOrders parks all pegged orders. 966 func (m *Market) parkAllPeggedOrders(ctx context.Context) { 967 toParkIDs := m.matching.GetActivePeggedOrderIDs() 968 for _, order := range toParkIDs { 969 m.parkOrder(ctx, order) 970 } 971 } 972 973 // EnterAuction : Prepare the order book to be run as an auction. 974 // when entering an auction we need to make sure there's sufficient funds in the holding account to cover the potential trade + fees. 975 // If there isn't, the order must be cancelled. 976 func (m *Market) enterAuction(ctx context.Context) { 977 // Change market type to auction 978 ordersToCancel := m.matching.EnterAuction() 979 980 // Move into auction mode to prevent pegged order repricing 981 event := m.as.AuctionStarted(ctx, m.timeService.GetTimeNow()) 982 983 // Cancel all the orders that were invalid 984 for _, order := range ordersToCancel { 985 _, err := m.cancelOrder(ctx, order.Party, order.ID) 986 if err != nil { 987 m.log.Debug("error cancelling order when entering auction", 988 logging.MarketID(m.GetID()), 989 logging.OrderID(order.ID), 990 logging.Error(err)) 991 } 992 } 993 994 // now update all special orders 995 m.enterAuctionSpecialOrders(ctx) 996 997 // now that all orders that don't fit in auctions have been cancelled, process necessary transfer of fees from the general account of the 998 // buyers to the holding account. Orders with insufficient cover of buyer or where the quantity to be delivered to the seller does not cover 999 // for the due fees during auction are cancelled here. 1000 m.processFeesTransfersOnEnterAuction(ctx) 1001 1002 // Send an event bus update 1003 m.broker.Send(event) 1004 1005 if m.as.InAuction() && m.as.IsPriceAuction() { 1006 m.mkt.State = types.MarketStateSuspended 1007 m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction 1008 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1009 } 1010 } 1011 1012 // OnOpeningAuctionFirstUncrossingPrice is triggered when the opening auction sees an uncrossing price for the first time and emits 1013 // an event to the state variable engine. 1014 func (m *Market) OnOpeningAuctionFirstUncrossingPrice() { 1015 m.log.Info("OnOpeningAuctionFirstUncrossingPrice event fired", logging.String("market", m.mkt.ID)) 1016 m.stateVarEngine.ReadyForTimeTrigger(m.quoteAsset, m.mkt.ID) 1017 m.stateVarEngine.NewEvent(m.quoteAsset, m.mkt.ID, statevar.EventTypeOpeningAuctionFirstUncrossingPrice) 1018 } 1019 1020 // OnAuctionEnded is called whenever an auction is ended and emits an event to the state var engine. 1021 func (m *Market) OnAuctionEnded() { 1022 m.log.Info("OnAuctionEnded event fired", logging.String("market", m.mkt.ID)) 1023 m.stateVarEngine.NewEvent(m.quoteAsset, m.mkt.ID, statevar.EventTypeAuctionEnded) 1024 } 1025 1026 // leaveAuction : Return the orderbook and market to continuous trading. 1027 func (m *Market) leaveAuction(ctx context.Context, now time.Time) { 1028 defer func() { 1029 if !m.as.InAuction() && (m.mkt.State == types.MarketStateSuspended || m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateSuspendedViaGovernance) { 1030 if m.mkt.State == types.MarketStatePending { 1031 // the market is now properly open, 1032 // so set the timestamp to when the opening auction actually ended 1033 m.mkt.MarketTimestamps.Open = now.UnixNano() 1034 } 1035 if m.mkt.TradingMode != types.MarketTradingModeOpeningAuction { 1036 // if we're leaving a price monitoring auction we can release the fees funds locked for the duration of the auction for any uncrossed orders 1037 m.processFeesReleaseOnLeaveAuction(ctx) 1038 } 1039 m.mkt.State = types.MarketStateActive 1040 m.mkt.TradingMode = types.MarketTradingModeContinuous 1041 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 1042 m.updateLiquidityFee(ctx) 1043 m.OnAuctionEnded() 1044 } 1045 }() 1046 1047 // Check here that the orders are still valid in terms of holding amounts 1048 m.checkFeeTransfersWhileInAuction(ctx) 1049 1050 _, ordersToCancel := m.uncrossOnLeaveAuction(ctx) 1051 1052 // Process each order we have to cancel 1053 for _, order := range ordersToCancel { 1054 _, err := m.cancelOrder(ctx, order.Party, order.ID) 1055 if err != nil { 1056 m.log.Panic("Failed to cancel order", 1057 logging.Error(err), 1058 logging.String("OrderID", order.ID)) 1059 } 1060 } 1061 1062 // update auction state, so we know what the new tradeMode ought to be 1063 endEvt := m.as.Left(ctx, now) 1064 1065 previousMarkPrice := m.getCurrentMarkPrice() 1066 // set the mark price here so that margins checks for special orders use the correct value 1067 m.markPriceLock.Lock() 1068 m.markPrice = m.getLastTradedPrice() 1069 m.markPriceLock.Unlock() 1070 1071 m.checkForReferenceMoves(ctx, true) 1072 if !m.as.InAuction() { 1073 // only send the auction-left event if we actually *left* the auction. 1074 m.broker.Send(endEvt) 1075 m.nextMTM = m.timeService.GetTimeNow().Add(m.mtmDelta) 1076 } else { 1077 // revert to old mark price if we're not leaving the auction after all 1078 m.markPriceLock.Lock() 1079 m.markPrice = previousMarkPrice 1080 m.markPriceLock.Unlock() 1081 } 1082 } 1083 1084 // validateOrder checks that the order parameters are valid for the market. 1085 // NB: price in market, tickSize in market decimals. 1086 func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err error) { 1087 defer func() { 1088 if err != nil { 1089 order.Status = types.OrderStatusRejected 1090 m.broker.Send(events.NewOrderEvent(ctx, order)) 1091 } 1092 }() 1093 1094 // Check we are allowed to handle this order type with the current market status 1095 isAuction := m.as.InAuction() 1096 if isAuction && order.TimeInForce == types.OrderTimeInForceGFN { 1097 order.Status = types.OrderStatusRejected 1098 order.Reason = types.OrderErrorCannotSendGFNOrderDuringAnAuction 1099 return common.ErrGFNOrderReceivedAuctionTrading 1100 } 1101 1102 if isAuction && order.TimeInForce == types.OrderTimeInForceIOC { 1103 order.Reason = types.OrderErrorCannotSendIOCOrderDuringAuction 1104 return common.ErrIOCOrderReceivedAuctionTrading 1105 } 1106 1107 if isAuction && order.TimeInForce == types.OrderTimeInForceFOK { 1108 order.Reason = types.OrderErrorCannotSendFOKOrderDurinAuction 1109 return common.ErrFOKOrderReceivedAuctionTrading 1110 } 1111 1112 if !isAuction && order.TimeInForce == types.OrderTimeInForceGFA { 1113 order.Reason = types.OrderErrorGFAOrderDuringContinuousTrading 1114 return common.ErrGFAOrderReceivedDuringContinuousTrading 1115 } 1116 1117 // Check the expiry time is valid 1118 if order.ExpiresAt > 0 && order.ExpiresAt < order.CreatedAt { 1119 order.Reason = types.OrderErrorInvalidExpirationDatetime 1120 return common.ErrInvalidExpiresAtTime 1121 } 1122 1123 if m.closed { 1124 // adding order to the buffer first 1125 order.Reason = types.OrderErrorMarketClosed 1126 return common.ErrMarketClosed 1127 } 1128 1129 if order.Type == types.OrderTypeNetwork { 1130 order.Reason = types.OrderErrorInvalidType 1131 return common.ErrInvalidOrderType 1132 } 1133 1134 // Validate market 1135 if order.MarketID != m.mkt.ID { 1136 // adding order to the buffer first 1137 order.Reason = types.OrderErrorInvalidMarketID 1138 if m.log.GetLevel() == logging.DebugLevel { 1139 m.log.Debug("Market ID mismatch", 1140 logging.Order(*order), 1141 logging.String("market", m.mkt.ID)) 1142 } 1143 return types.ErrInvalidMarketID 1144 } 1145 1146 // Validate pegged orders 1147 if order.PeggedOrder != nil { 1148 if reason := order.ValidatePeggedOrder(); reason != types.OrderErrorUnspecified { 1149 order.Reason = reason 1150 if m.log.GetLevel() == logging.DebugLevel { 1151 m.log.Debug("Failed to validate pegged order details", 1152 logging.Order(*order), 1153 logging.String("market", m.mkt.ID)) 1154 } 1155 return reason 1156 } 1157 if order.PeggedOrder.Reference == types.PeggedReferenceMid { 1158 offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor)) 1159 tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor)) 1160 if offsetInAsset.IsZero() && tickSizeInAsset.IsZero() { 1161 return fmt.Errorf("invalid offset - pegged mid will cross") 1162 } 1163 } 1164 return m.validateTickSize(order.PeggedOrder.Offset) 1165 } 1166 1167 if order.OriginalPrice != nil { 1168 return m.validateTickSize(order.OriginalPrice) 1169 } 1170 1171 return nil 1172 } 1173 1174 func (m *Market) validateTickSize(price *num.Uint) error { 1175 d := num.UintZero().Mod(price, m.mkt.TickSize) 1176 if !d.IsZero() { 1177 return types.ErrOrderNotInTickSize 1178 } 1179 return nil 1180 } 1181 1182 // validateAccounts checks that the party has the required accounts and that they have sufficient funds in the account to cover for the trade and 1183 // any fees due. 1184 func (m *Market) validateAccounts(ctx context.Context, order *types.Order) error { 1185 if order.Party == types.NetworkParty { 1186 return nil 1187 } 1188 if (order.Side == types.SideBuy && !m.collateral.HasGeneralAccount(order.Party, m.quoteAsset)) || 1189 (order.Side == types.SideSell && !m.collateral.HasGeneralAccount(order.Party, m.baseAsset)) { 1190 // adding order to the buffer first 1191 order.Status = types.OrderStatusRejected 1192 order.Reason = types.OrderErrorInsufficientAssetBalance 1193 m.broker.Send(events.NewOrderEvent(ctx, order)) 1194 1195 // party should be created before even trying to post order 1196 return common.ErrPartyInsufficientAssetBalance 1197 } 1198 1199 price := order.Price 1200 // pegged order would not have a price at this point so unless we're in auction we need to get a price for it first 1201 if order.PeggedOrder != nil && !m.as.InAuction() { 1202 p, err := m.getNewPeggedPrice(order) 1203 if err != nil { 1204 return err 1205 } 1206 price = p 1207 } 1208 // if the order is not pegged or it is pegged and we're not in an auction, check the party has sufficient balance 1209 if order.PeggedOrder == nil || !m.as.InAuction() { 1210 accType := types.AccountTypeGeneral 1211 if order.Party == types.NetworkParty { 1212 var err error 1213 accType, _, err = m.getACcountTypesForPAP() 1214 if err != nil { 1215 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", order.ID)) 1216 } 1217 } 1218 if err := m.checkSufficientFunds(order.Party, order.Side, price, order.TrueRemaining(), order.PeggedOrder != nil, accType); err != nil { 1219 return err 1220 } 1221 } 1222 1223 // from this point we know the party have the necessary accounts and balances 1224 // we had it to the list of parties. 1225 m.addParty(order.Party) 1226 return nil 1227 } 1228 1229 func rejectStopOrders(rejectionReason types.StopOrderRejectionReason, orders ...*types.StopOrder) { 1230 for _, o := range orders { 1231 if o != nil { 1232 o.Status = types.StopOrderStatusRejected 1233 o.RejectionReason = ptr.From(rejectionReason) 1234 } 1235 } 1236 } 1237 1238 func (m *Market) SubmitStopOrdersWithIDGeneratorAndOrderIDs( 1239 ctx context.Context, 1240 submission *types.StopOrdersSubmission, 1241 party string, 1242 idgen common.IDGenerator, 1243 fallsBelowID, risesAboveID *string, 1244 ) (*types.OrderConfirmation, error) { 1245 m.idgen = idgen 1246 defer func() { m.idgen = nil }() 1247 1248 fallsBelow, risesAbove := submission.IntoStopOrders( 1249 party, ptr.UnBox(fallsBelowID), ptr.UnBox(risesAboveID), m.timeService.GetTimeNow()) 1250 1251 defer func() { 1252 evts := []events.Event{} 1253 if fallsBelow != nil { 1254 evts = append(evts, events.NewStopOrderEvent(ctx, fallsBelow)) 1255 } 1256 if risesAbove != nil { 1257 evts = append(evts, events.NewStopOrderEvent(ctx, risesAbove)) 1258 } 1259 1260 if len(evts) > 0 { 1261 m.broker.SendBatch(evts) 1262 } 1263 }() 1264 1265 if m.IsOpeningAuction() { 1266 rejectStopOrders(types.StopOrderRejectionNotAllowedDuringOpeningAuction, fallsBelow, risesAbove) 1267 return nil, common.ErrStopOrderNotAllowedDuringOpeningAuction 1268 } 1269 1270 if !m.canTrade() { 1271 rejectStopOrders(types.StopOrderRejectionTradingNotAllowed, fallsBelow, risesAbove) 1272 return nil, common.ErrTradingNotAllowed 1273 } 1274 1275 if fallsBelow != nil && fallsBelow.OrderSubmission != nil && !m.canSubmitMaybeSell(fallsBelow.Party, fallsBelow.OrderSubmission.Side) { 1276 rejectStopOrders(types.StopOrderRejectionSellOrderNotAllowed, fallsBelow, risesAbove) 1277 return nil, common.ErrSellOrderNotAllowed 1278 } 1279 1280 if risesAbove != nil && risesAbove.OrderSubmission != nil && !m.canSubmitMaybeSell(risesAbove.Party, risesAbove.OrderSubmission.Side) { 1281 rejectStopOrders(types.StopOrderRejectionSellOrderNotAllowed, risesAbove, risesAbove) 1282 return nil, common.ErrSellOrderNotAllowed 1283 } 1284 1285 now := m.timeService.GetTimeNow() 1286 orderCnt := 0 1287 if fallsBelow != nil { 1288 if fallsBelow.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition { 1289 rejectStopOrders(types.StopOrderRejectionSizeOverrideUnsupportedForSpot, fallsBelow, risesAbove) 1290 return nil, common.ErrStopOrderSizeOverrideNotSupportedForSpots 1291 } 1292 if fallsBelow.Expiry.Expires() && fallsBelow.Expiry.ExpiresAt.Before(now) { 1293 rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove) 1294 return nil, common.ErrStopOrderExpiryInThePast 1295 } 1296 if fallsBelow.OrderSubmission.Side == types.SideBuy && !m.collateral.HasGeneralAccount(party, m.quoteAsset) { 1297 rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove) 1298 return nil, common.ErrStopOrderSideNotClosingThePosition 1299 } 1300 if !m.collateral.HasGeneralAccount(party, m.baseAsset) { 1301 rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove) 1302 return nil, common.ErrStopOrderSideNotClosingThePosition 1303 } 1304 orderCnt++ 1305 } 1306 if risesAbove != nil { 1307 if risesAbove.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition { 1308 rejectStopOrders(types.StopOrderRejectionSizeOverrideUnsupportedForSpot, fallsBelow, risesAbove) 1309 return nil, common.ErrStopOrderSizeOverrideNotSupportedForSpots 1310 } 1311 if risesAbove.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Before(now) { 1312 rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove) 1313 return nil, common.ErrStopOrderExpiryInThePast 1314 } 1315 if risesAbove.OrderSubmission.Side == types.SideBuy && !m.collateral.HasGeneralAccount(party, m.quoteAsset) { 1316 rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove) 1317 return nil, common.ErrStopOrderSideNotClosingThePosition 1318 } 1319 if !m.collateral.HasGeneralAccount(party, m.baseAsset) { 1320 rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove) 1321 return nil, common.ErrStopOrderSideNotClosingThePosition 1322 } 1323 orderCnt++ 1324 } 1325 1326 if risesAbove != nil && fallsBelow != nil { 1327 if risesAbove.Expiry.Expires() && fallsBelow.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Compare(*fallsBelow.Expiry.ExpiresAt) == 0 { 1328 rejectStopOrders(types.StopOrderRejectionOCONotAllowedSameExpiryTime, fallsBelow, risesAbove) 1329 return nil, common.ErrStopOrderNotAllowedSameExpiry 1330 } 1331 } 1332 1333 // now check if that party hasn't exceeded the max amount per market 1334 if m.stopOrders.CountForParty(party)+uint64(orderCnt) > m.maxStopOrdersPerParties.Uint64() { 1335 rejectStopOrders(types.StopOrderRejectionMaxStopOrdersPerPartyReached, fallsBelow, risesAbove) 1336 return nil, common.ErrMaxStopOrdersPerPartyReached 1337 } 1338 1339 fallsBelowTriggered, risesAboveTriggered := m.stopOrderWouldTriggerAtSubmission(fallsBelow), m.stopOrderWouldTriggerAtSubmission(risesAbove) 1340 triggered := fallsBelowTriggered || risesAboveTriggered 1341 1342 // if we are in an auction 1343 // or no order is triggered 1344 // let's just submit it straight away 1345 if m.as.InAuction() || !triggered { 1346 m.poolStopOrders(fallsBelow, risesAbove) 1347 return nil, nil 1348 } 1349 1350 var confirmation *types.OrderConfirmation 1351 var err error 1352 // now would the order get trigger straight away? 1353 switch { 1354 case fallsBelowTriggered: 1355 fallsBelow.Status = types.StopOrderStatusTriggered 1356 if risesAbove != nil { 1357 risesAbove.Status = types.StopOrderStatusStopped 1358 } 1359 fallsBelow.OrderID = idgen.NextID() 1360 confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID( 1361 ctx, fallsBelow.OrderSubmission, party, idgen, fallsBelow.OrderID, true, 1362 ) 1363 if err != nil && confirmation != nil { 1364 fallsBelow.OrderID = confirmation.Order.ID 1365 } 1366 case risesAboveTriggered: 1367 risesAbove.Status = types.StopOrderStatusTriggered 1368 if fallsBelow != nil { 1369 fallsBelow.Status = types.StopOrderStatusStopped 1370 } 1371 risesAbove.OrderID = idgen.NextID() 1372 confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID( 1373 ctx, risesAbove.OrderSubmission, party, idgen, risesAbove.OrderID, true, 1374 ) 1375 if err != nil && confirmation != nil { 1376 risesAbove.OrderID = confirmation.Order.ID 1377 } 1378 } 1379 1380 return confirmation, err 1381 } 1382 1383 func (m *Market) poolStopOrders( 1384 fallsBelow, risesAbove *types.StopOrder, 1385 ) { 1386 if fallsBelow != nil { 1387 m.stopOrders.Insert(fallsBelow) 1388 if fallsBelow.Expiry.Expires() { 1389 m.expiringStopOrders.Insert(fallsBelow.ID, fallsBelow.Expiry.ExpiresAt.UnixNano()) 1390 } 1391 } 1392 if risesAbove != nil { 1393 m.stopOrders.Insert(risesAbove) 1394 if risesAbove.Expiry.Expires() { 1395 m.expiringStopOrders.Insert(risesAbove.ID, risesAbove.Expiry.ExpiresAt.UnixNano()) 1396 } 1397 } 1398 } 1399 1400 func (m *Market) stopOrderWouldTriggerAtSubmission( 1401 stopOrder *types.StopOrder, 1402 ) bool { 1403 if m.lastTradedPrice == nil || stopOrder == nil || stopOrder.Trigger.IsTrailingPercentOffset() { 1404 return false 1405 } 1406 1407 lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice()) 1408 1409 switch stopOrder.Trigger.Direction { 1410 case types.StopOrderTriggerDirectionFallsBelow: 1411 if lastTradedPrice.LTE(stopOrder.Trigger.Price()) { 1412 return true 1413 } 1414 case types.StopOrderTriggerDirectionRisesAbove: 1415 if lastTradedPrice.GTE(stopOrder.Trigger.Price()) { 1416 return true 1417 } 1418 } 1419 return false 1420 } 1421 1422 func (m *Market) triggerStopOrders( 1423 ctx context.Context, 1424 idgen common.IDGenerator, 1425 ) []*types.OrderConfirmation { 1426 if m.lastTradedPrice == nil { 1427 return nil 1428 } 1429 lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice()) 1430 triggered, cancelled := m.stopOrders.PriceUpdated(lastTradedPrice) 1431 1432 if len(triggered) <= 0 && len(cancelled) <= 0 { 1433 return nil 1434 } 1435 1436 now := m.timeService.GetTimeNow() 1437 // remove from expiring orders + set updatedAt 1438 for _, v := range append(triggered, cancelled...) { 1439 v.UpdatedAt = now 1440 if v.Expiry.Expires() { 1441 m.expiringStopOrders.RemoveOrder(v.Expiry.ExpiresAt.UnixNano(), v.ID) 1442 } 1443 } 1444 evts := make([]events.Event, 0, len(cancelled)) 1445 for _, v := range cancelled { 1446 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 1447 } 1448 1449 m.broker.SendBatch(evts) 1450 1451 if len(triggered) <= 0 { 1452 return nil 1453 } 1454 1455 confirmations := m.submitStopOrders(ctx, triggered, types.StopOrderStatusTriggered, idgen) 1456 1457 return append(m.triggerStopOrders(ctx, idgen), confirmations...) 1458 } 1459 1460 // SubmitOrder submits the given order. 1461 func (m *Market) SubmitOrder(ctx context.Context, orderSubmission *types.OrderSubmission, party string, deterministicID string) (oc *types.OrderConfirmation, _ error) { 1462 idgen := idgeneration.New(deterministicID) 1463 return m.SubmitOrderWithIDGeneratorAndOrderID(ctx, orderSubmission, party, idgen, idgen.NextID(), true) 1464 } 1465 1466 // SubmitOrderWithIDGeneratorAndOrderID submits the given order. 1467 func (m *Market) SubmitOrderWithIDGeneratorAndOrderID(ctx context.Context, orderSubmission *types.OrderSubmission, party string, idgen common.IDGenerator, orderID string, checkForTriggers bool) (oc *types.OrderConfirmation, _ error) { 1468 m.idgen = idgen 1469 defer func() { m.idgen = nil }() 1470 1471 defer func() { 1472 if !checkForTriggers { 1473 return 1474 } 1475 1476 m.triggerStopOrders(ctx, idgen) 1477 }() 1478 1479 order := orderSubmission.IntoOrder(party) 1480 if order.Price != nil { 1481 order.OriginalPrice = order.Price.Clone() 1482 order.Price, _ = num.UintFromDecimal(order.Price.ToDecimal().Mul(m.priceFactor)) 1483 } 1484 order.CreatedAt = m.timeService.GetTimeNow().UnixNano() 1485 order.ID = orderID 1486 1487 if !m.canTrade() { 1488 order.Status = types.OrderStatusRejected 1489 order.Reason = types.OrderErrorMarketClosed 1490 m.broker.Send(events.NewOrderEvent(ctx, order)) 1491 return nil, common.ErrTradingNotAllowed 1492 } 1493 1494 if !m.canSubmitMaybeSell(order.Party, order.Side) { 1495 order.Status = types.OrderStatusRejected 1496 order.Reason = types.OrderErrorSellOrderNotAllowed 1497 m.broker.Send(events.NewOrderEvent(ctx, order)) 1498 return nil, common.ErrSellOrderNotAllowed 1499 } 1500 1501 conf, _, err := m.submitOrder(ctx, order) 1502 if err != nil { 1503 return nil, err 1504 } 1505 1506 if !m.as.InAuction() { 1507 m.checkForReferenceMoves(ctx, false) 1508 } 1509 return conf, nil 1510 } 1511 1512 // submitOrder validates and submits an order. 1513 func (m *Market) submitOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) { 1514 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "SubmitOrder") 1515 orderValidity := "invalid" 1516 defer func() { 1517 timer.EngineTimeCounterAdd() 1518 metrics.OrderCounterInc(m.mkt.ID, orderValidity) 1519 }() 1520 1521 // set those at the beginning as even rejected order get through the buffers 1522 order.Version = common.InitialOrderVersion 1523 order.Status = types.OrderStatusActive 1524 1525 if err := m.validateOrder(ctx, order); err != nil { 1526 return nil, nil, err 1527 } 1528 1529 if err := m.validateAccounts(ctx, order); err != nil { 1530 return nil, nil, err 1531 } 1532 1533 // Now that validation is handled, call the code to place the order 1534 orderConf, orderUpdates, err := m.submitValidatedOrder(ctx, order) 1535 if err == nil { 1536 orderValidity = "valid" 1537 } 1538 1539 if order.PeggedOrder != nil && order.IsFinished() { 1540 // remove the pegged order from anywhere 1541 m.removePeggedOrder(order) 1542 } 1543 1544 // insert an expiring order if it's either in the book 1545 // or in the parked list 1546 if order.IsExpireable() && !order.IsFinished() { 1547 m.expiringOrders.Insert(order.ID, order.ExpiresAt) 1548 } 1549 1550 return orderConf, orderUpdates, err 1551 } 1552 1553 func (m *Market) canCoverTradesAndFees(party string, partySide types.Side, trades []*types.Trade) error { 1554 // check that the party can afford the traded amount + fees 1555 if partySide == types.SideBuy { 1556 totalTraded := num.UintZero() 1557 for _, t := range trades { 1558 fees, err := m.calculateFeesForTrades([]*types.Trade{t}) 1559 if err != nil { 1560 m.log.Panic("failed to calculate fees for trade", logging.Trade(t)) 1561 } 1562 size := num.NewUint(t.Size) 1563 totalTraded.AddSum(size.Mul(size, t.Price), fees.TotalFeesAmountPerParty()[party]) 1564 } 1565 totalTraded, _ = num.UintFromDecimal(totalTraded.ToDecimal().Div(m.positionFactor)) 1566 if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, totalTraded, types.AccountTypeGeneral); err != nil { 1567 return err 1568 } 1569 } else { 1570 sizeTraded := uint64(0) 1571 for _, t := range trades { 1572 sizeTraded += t.Size 1573 } 1574 totalTraded := scaleBaseQuantityToAssetDP(sizeTraded, m.baseFactor) 1575 if err := m.collateral.PartyHasSufficientBalance(m.baseAsset, party, totalTraded, types.AccountTypeGeneral); err != nil { 1576 return err 1577 } 1578 } 1579 return nil 1580 } 1581 1582 // submitValidatedOrder submits a new order. 1583 func (m *Market) submitValidatedOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) { 1584 isPegged := order.PeggedOrder != nil 1585 if isPegged { 1586 order.Status = types.OrderStatusParked 1587 order.Reason = types.OrderErrorUnspecified 1588 1589 if m.as.InAuction() { 1590 order.SetIcebergPeaks() 1591 // as the order can't trade we don't transfer from the general account to the holding account in this case. 1592 m.peggedOrders.Park(order) 1593 // If we are in an auction, we don't insert this order into the book 1594 // Maybe should return an orderConfirmation with order state PARKED 1595 m.broker.Send(events.NewOrderEvent(ctx, order)) 1596 return &types.OrderConfirmation{Order: order}, nil, nil 1597 } 1598 err := m.repricePeggedOrder(order) 1599 if err != nil { 1600 order.SetIcebergPeaks() 1601 m.peggedOrders.Park(order) 1602 m.broker.Send(events.NewOrderEvent(ctx, order)) 1603 return &types.OrderConfirmation{Order: order}, nil, nil // nolint 1604 } 1605 } 1606 var trades []*types.Trade 1607 // we're not in auction (not opening, not any other auction 1608 if !m.as.InAuction() { 1609 // first we call the order book to evaluate auction triggers and get the list of trades 1610 var err error 1611 trades, err = m.checkPriceAndGetTrades(ctx, order) 1612 if err != nil { 1613 return nil, nil, m.unregisterAndReject(ctx, order, err) 1614 } 1615 // NB we don't apply fees here because if this is a sell the fees are taken from the quantity that the buyer pays (in quote asset) 1616 // so this is deferred to handling confirmations - by this point the aggressor must have sufficient funds to cover for fees so this should 1617 // not be an issue 1618 } 1619 1620 // check that the party can afford the trade and fees 1621 if trades != nil { 1622 if err := m.canCoverTradesAndFees(order.Party, order.Side, trades); err != nil { 1623 return nil, nil, m.unregisterAndReject(ctx, order, err) 1624 } 1625 } 1626 1627 // if an auction is ongoing and the order is pegged, park it and return 1628 if m.as.InAuction() && isPegged { 1629 if isPegged { 1630 m.peggedOrders.Park(order) 1631 } 1632 m.broker.Send(events.NewOrderEvent(ctx, order)) 1633 return &types.OrderConfirmation{Order: order}, nil, nil 1634 } 1635 1636 order.Status = types.OrderStatusActive 1637 1638 // Send the aggressive order into matching engine 1639 confirmation, err := m.matching.SubmitOrder(order) 1640 if err != nil { 1641 return nil, nil, m.unregisterAndReject(ctx, order, err) 1642 } 1643 1644 // if the order is not finished and remaining is non zero, we need to transfer the remaining base/quote from the general account 1645 // to the holding account for the market/asset. If an auction is on-going we also need to account for potential fees (applicable for buy orders only) 1646 if !order.IsFinished() && order.Remaining > 0 { 1647 err := m.transferToHoldingAccount(ctx, order) 1648 if err != nil { 1649 return nil, nil, m.unregisterAndReject(ctx, order, err) 1650 } 1651 } 1652 1653 // we replace the trades in the confirmation with the one we got initially 1654 // the contains the fees information 1655 // NB: I have to say this this is a weird way of doing it, why are we doing it twice? 1656 confirmation.Trades = trades 1657 1658 // Send out the order update here as handling the confirmation message 1659 // below might trigger an action that can change the order details. 1660 m.broker.Send(events.NewOrderEvent(ctx, order)) 1661 1662 orderUpdates := m.handleConfirmation(ctx, confirmation) 1663 m.handleConfirmationPassiveOrders(ctx, confirmation) 1664 return confirmation, orderUpdates, nil 1665 } 1666 1667 // checkPriceAndGetTrades calculates the trades that would be generated from the given order. 1668 func (m *Market) checkPriceAndGetTrades(ctx context.Context, order *types.Order) ([]*types.Trade, error) { 1669 trades, err := m.matching.GetTrades(order) 1670 if err != nil { 1671 return nil, err 1672 } 1673 1674 if order.PostOnly && len(trades) > 0 { 1675 return nil, types.OrderErrorPostOnlyOrderWouldTrade 1676 } 1677 1678 persistent := true 1679 switch order.TimeInForce { 1680 case types.OrderTimeInForceFOK, types.OrderTimeInForceGFN, types.OrderTimeInForceIOC: 1681 persistent = false 1682 } 1683 1684 for _, trade := range trades { 1685 if m.pMonitor.CheckPrice(ctx, m.as, trade.Price, persistent, false) { 1686 return nil, types.OrderErrorNonPersistentOrderOutOfPriceBounds 1687 } 1688 1689 if evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()); evt != nil { 1690 m.broker.Send(evt) 1691 } 1692 1693 // start the monitoring auction if required? 1694 if m.as.AuctionStart() { 1695 m.enterAuction(ctx) 1696 return nil, nil 1697 } 1698 } 1699 1700 return trades, nil 1701 } 1702 1703 // addParty adds the party to the market mapping. 1704 func (m *Market) addParty(party string) { 1705 if _, ok := m.parties[party]; !ok { 1706 m.parties[party] = struct{}{} 1707 } 1708 } 1709 1710 // applyFees handles transfer of fee payment from the *buyer* to the fees account. 1711 func (m *Market) applyFees(ctx context.Context, fees events.FeesTransfer, sourceAccountType types.AccountType) error { 1712 var ( 1713 transfers []*types.LedgerMovement 1714 err error 1715 ) 1716 1717 if !m.as.InAuction() { 1718 transfers, err = m.collateral.TransferSpotFeesContinuousTrading(ctx, m.GetID(), m.quoteAsset, fees) 1719 } else if m.as.IsMonitorAuction() || m.as.IsPAPAuction() { 1720 transfers, err = m.collateral.TransferSpotFees(ctx, m.GetID(), m.quoteAsset, fees, sourceAccountType) 1721 } else if m.as.IsFBA() { 1722 transfers, err = m.collateral.TransferSpotFees(ctx, m.GetID(), m.quoteAsset, fees, sourceAccountType) 1723 } 1724 1725 if len(transfers) > 0 { 1726 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 1727 } 1728 1729 m.marketActivityTracker.UpdateFeesFromTransfers(m.quoteAsset, m.GetID(), fees.Transfers()) 1730 return err 1731 } 1732 1733 func (m *Market) handleConfirmationPassiveOrders(ctx context.Context, conf *types.OrderConfirmation) { 1734 le := []*types.LedgerMovement{} 1735 1736 if conf.PassiveOrdersAffected != nil { 1737 evts := make([]events.Event, 0, len(conf.PassiveOrdersAffected)) 1738 1739 // Insert or update passive orders siting on the book 1740 for _, order := range conf.PassiveOrdersAffected { 1741 // set the `updatedAt` value as these orders have changed 1742 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 1743 evts = append(evts, events.NewOrderEvent(ctx, order)) 1744 1745 // If the order is a pegged order and is complete we must remove it from the pegged list 1746 if order.PeggedOrder != nil { 1747 if order.Remaining == 0 || order.Status != types.OrderStatusActive { 1748 m.removePeggedOrder(order) 1749 } 1750 } 1751 1752 if order.IsFinished() { 1753 m.releaseOrderFromHoldingAccount(ctx, order.ID, order.Party, order.Side) 1754 } 1755 1756 // remove the order from the expiring list 1757 // if it was a GTT order 1758 if order.IsExpireable() && order.IsFinished() { 1759 m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID) 1760 } 1761 } 1762 if len(le) > 0 { 1763 m.broker.Send(events.NewLedgerMovements(ctx, le)) 1764 } 1765 m.broker.SendBatch(evts) 1766 } 1767 } 1768 1769 func (m *Market) handleConfirmation(ctx context.Context, conf *types.OrderConfirmation) []*types.Order { 1770 // When re-submitting liquidity order, it happen that the pricing is putting 1771 // the order at a price which makes it uncross straight away. 1772 // then triggering this handleConfirmation flow, etc. 1773 // Although the order is considered aggressive, and we never expect in the flow 1774 // for an aggressive order to be pegged, so we never remove them from the pegged 1775 // list. All this impact the float of EnterAuction, which if triggered from there 1776 // will try to park all pegged orders, including this order which have never been 1777 // removed from the pegged list. We add this check to make sure that if the 1778 // aggressive order is pegged, we then do remove it from the list. 1779 if conf.Order.PeggedOrder != nil { 1780 if conf.Order.Remaining == 0 || conf.Order.Status != types.OrderStatusActive { 1781 m.removePeggedOrder(conf.Order) 1782 } 1783 } 1784 1785 end := m.as.CanLeave() 1786 orderUpdates := make([]*types.Order, 0, len(conf.PassiveOrdersAffected)+1) 1787 orderUpdates = append(orderUpdates, conf.Order) 1788 orderUpdates = append(orderUpdates, conf.PassiveOrdersAffected...) 1789 1790 if len(conf.Trades) == 0 { 1791 return orderUpdates 1792 } 1793 m.setLastTradedPrice(conf.Trades[len(conf.Trades)-1]) 1794 m.hasTraded = true 1795 1796 // Insert all trades resulted from the executed order 1797 tradeEvts := make([]events.Event, 0, len(conf.Trades)) 1798 tradedValue, _ := num.UintFromDecimal( 1799 conf.TradedValue().ToDecimal().Div(m.positionFactor)) 1800 1801 transfers := []*types.LedgerMovement{} 1802 for idx, trade := range conf.Trades { 1803 trade.SetIDs(m.idgen.NextID(), conf.Order, conf.PassiveOrdersAffected[idx]) 1804 notionalTraded, _ := num.UintFromDecimal(num.UintZero().Mul(num.NewUint(trade.Size), trade.Price).ToDecimal().Div(m.positionFactor)) 1805 m.marketActivityTracker.RecordNotionalTraded(m.quoteAsset, m.mkt.ID, notionalTraded) 1806 tradeTransfers := m.handleTrade(ctx, trade) 1807 transfers = append(transfers, tradeTransfers...) 1808 tradeEvts = append(tradeEvts, events.NewTradeEvent(ctx, *trade)) 1809 } 1810 if conf.Order.IsFinished() { 1811 m.releaseOrderFromHoldingAccount(ctx, conf.Order.ID, conf.Order.Party, conf.Order.Side) 1812 } 1813 1814 if len(transfers) > 0 { 1815 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 1816 } 1817 1818 if !m.as.InAuction() { 1819 aggressor := conf.Order.Party 1820 if quantum, err := m.collateral.GetAssetQuantum(m.quoteAsset); err == nil && !quantum.IsZero() { 1821 n, _ := num.UintFromDecimal(tradedValue.ToDecimal().Div(quantum)) 1822 m.marketActivityTracker.RecordNotionalTakerVolume(m.mkt.ID, aggressor, n) 1823 } 1824 } 1825 1826 m.feeSplitter.AddTradeValue(tradedValue) 1827 m.marketActivityTracker.AddValueTraded(m.quoteAsset, m.mkt.ID, tradedValue) 1828 m.broker.SendBatch(tradeEvts) 1829 // check reference moves if we have order updates, and we are not in an auction (or leaving an auction) 1830 // we handle reference moves in confirmMTM when leaving an auction already 1831 if len(orderUpdates) > 0 && !end && !m.as.InAuction() { 1832 m.checkForReferenceMoves( 1833 ctx, false) 1834 } 1835 1836 return orderUpdates 1837 } 1838 1839 // updateLiquidityFee computes the current LiquidityProvision fee and updates 1840 // the fee engine. 1841 func (m *Market) updateLiquidityFee(ctx context.Context) { 1842 var fee num.Decimal 1843 switch m.mkt.Fees.LiquidityFeeSettings.Method { 1844 case types.LiquidityFeeMethodConstant: 1845 fee = m.mkt.Fees.LiquidityFeeSettings.FeeConstant 1846 case types.LiquidityFeeMethodMarginalCost: 1847 fee = m.liquidityEngine.ProvisionsPerParty().FeeForTarget(m.getTargetStake()) 1848 case types.LiquidityFeeMethodWeightedAverage: 1849 fee = m.liquidityEngine.ProvisionsPerParty().FeeForWeightedAverage() 1850 default: 1851 m.log.Panic("unknown liquidity fee method") 1852 } 1853 1854 if !fee.Equals(m.getLiquidityFee()) { 1855 m.fee.SetLiquidityFee(fee) 1856 m.setLiquidityFee(fee) 1857 m.broker.Send( 1858 events.NewMarketUpdatedEvent(ctx, *m.mkt), 1859 ) 1860 } 1861 } 1862 1863 func (m *Market) setLiquidityFee(fee num.Decimal) { 1864 m.mkt.Fees.Factors.LiquidityFee = fee 1865 } 1866 1867 func (m *Market) getLiquidityFee() num.Decimal { 1868 return m.mkt.Fees.Factors.LiquidityFee 1869 } 1870 1871 func (m *Market) setLastTradedPrice(trade *types.Trade) { 1872 m.lastTradedPrice = trade.Price.Clone() 1873 } 1874 1875 func (m *Market) CancelAllStopOrders(ctx context.Context, partyID string) error { 1876 if !m.canTrade() { 1877 return common.ErrTradingNotAllowed 1878 } 1879 1880 stopOrders, err := m.stopOrders.Cancel(partyID, "") 1881 if err != nil { 1882 return err 1883 } 1884 1885 m.removeCancelledExpiringStopOrders(stopOrders) 1886 1887 evts := make([]events.Event, 0, len(stopOrders)) 1888 for _, v := range stopOrders { 1889 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 1890 } 1891 1892 m.broker.SendBatch(evts) 1893 1894 return nil 1895 } 1896 1897 // CancelAllOrders cancels all orders in the market. 1898 func (m *Market) CancelAllOrders(ctx context.Context, partyID string) ([]*types.OrderCancellationConfirmation, error) { 1899 if !m.canTrade() { 1900 return nil, common.ErrTradingNotAllowed 1901 } 1902 1903 // get all order for this party in the book 1904 orders := m.matching.GetOrdersPerParty(partyID) 1905 1906 // add all orders being eventually parked 1907 orders = append(orders, m.peggedOrders.GetAllParkedForParty(partyID)...) 1908 1909 // just an early exit, there's just no orders... 1910 if len(orders) <= 0 { 1911 return nil, nil 1912 } 1913 1914 // now we eventually dedup them 1915 uniq := map[string]*types.Order{} 1916 for _, v := range orders { 1917 uniq[v.ID] = v 1918 } 1919 1920 // put them back in the slice, and sort them 1921 orders = make([]*types.Order, 0, len(uniq)) 1922 for _, v := range uniq { 1923 orders = append(orders, v) 1924 } 1925 sort.Slice(orders, func(i, j int) bool { 1926 return orders[i].ID < orders[j].ID 1927 }) 1928 1929 cancellations := make([]*types.OrderCancellationConfirmation, 0, len(orders)) 1930 orderIDs := make([]string, 0, len(orders)) 1931 1932 // now iterate over all orders and cancel one by one. 1933 for _, order := range orders { 1934 cancellation, err := m.cancelOrderInBatch(ctx, partyID, order.ID) 1935 if err != nil { 1936 return nil, err 1937 } 1938 cancellations = append(cancellations, cancellation) 1939 orderIDs = append(orderIDs, order.ID) 1940 } 1941 1942 m.broker.Send(events.NewCancelledOrdersEvent(ctx, m.GetID(), partyID, orderIDs...)) 1943 1944 m.checkForReferenceMoves(ctx, false) 1945 1946 return cancellations, nil 1947 } 1948 1949 func (m *Market) CancelStopOrder( 1950 ctx context.Context, 1951 partyID, orderID string, 1952 ) error { 1953 if !m.canTrade() { 1954 return common.ErrTradingNotAllowed 1955 } 1956 1957 stopOrders, err := m.stopOrders.Cancel(partyID, orderID) 1958 if err != nil || len(stopOrders) <= 0 { // could return just an empty slice 1959 return err 1960 } 1961 1962 m.removeCancelledExpiringStopOrders(stopOrders) 1963 1964 evts := []events.Event{events.NewStopOrderEvent(ctx, stopOrders[0])} 1965 if len(stopOrders) > 1 { 1966 evts = append(evts, events.NewStopOrderEvent(ctx, stopOrders[1])) 1967 } 1968 1969 m.broker.SendBatch(evts) 1970 1971 return nil 1972 } 1973 1974 func (m *Market) removeCancelledExpiringStopOrders( 1975 stopOrders []*types.StopOrder, 1976 ) { 1977 for _, o := range stopOrders { 1978 if o.Expiry.Expires() { 1979 m.expiringStopOrders.RemoveOrder(o.Expiry.ExpiresAt.UnixNano(), o.ID) 1980 } 1981 } 1982 } 1983 1984 // CancelOrder canels a single order in the market. 1985 func (m *Market) CancelOrder(ctx context.Context, partyID, orderID string, deterministicID string) (oc *types.OrderCancellationConfirmation, _ error) { 1986 idgen := idgeneration.New(deterministicID) 1987 return m.CancelOrderWithIDGenerator(ctx, partyID, orderID, idgen) 1988 } 1989 1990 // CancelOrderWithIDGenerator cancels an order in the market. 1991 func (m *Market) CancelOrderWithIDGenerator(ctx context.Context, partyID, orderID string, idgen common.IDGenerator) (oc *types.OrderCancellationConfirmation, _ error) { 1992 m.idgen = idgen 1993 defer func() { m.idgen = nil }() 1994 1995 if !m.canTrade() { 1996 return nil, common.ErrTradingNotAllowed 1997 } 1998 1999 conf, err := m.cancelOrder(ctx, partyID, orderID) 2000 if err != nil { 2001 return conf, err 2002 } 2003 2004 if !m.as.InAuction() { 2005 m.checkForReferenceMoves(ctx, false) 2006 } 2007 2008 return conf, nil 2009 } 2010 2011 func (m *Market) cancelOrderInBatch(ctx context.Context, partyID, orderID string) (*types.OrderCancellationConfirmation, error) { 2012 return m.cancelSingleOrder(ctx, partyID, orderID, true) 2013 } 2014 2015 // cancelOrder cancels the given order. If the order is found on the book, we release locked funds from holding account to the general account of the party. 2016 func (m *Market) cancelOrder(ctx context.Context, partyID, orderID string) (*types.OrderCancellationConfirmation, error) { 2017 return m.cancelSingleOrder(ctx, partyID, orderID, false) 2018 } 2019 2020 // cancelSingleOrder cancels the given order. If the order is found on the book, we release locked funds from holding account to the general account of the party. 2021 func (m *Market) cancelSingleOrder(ctx context.Context, partyID, orderID string, inBatch bool) (*types.OrderCancellationConfirmation, error) { 2022 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "CancelOrder") 2023 defer timer.EngineTimeCounterAdd() 2024 2025 if m.closed { 2026 return nil, common.ErrMarketClosed 2027 } 2028 2029 order, foundOnBook, err := m.getOrderByID(orderID) 2030 if err != nil { 2031 return nil, err 2032 } 2033 2034 // Only allow the original order creator to cancel their order 2035 if order.Party != partyID { 2036 if m.log.GetLevel() == logging.DebugLevel { 2037 m.log.Debug("Party ID mismatch", 2038 logging.String("party-id", partyID), 2039 logging.String("order-id", orderID), 2040 logging.String("market", m.mkt.ID)) 2041 } 2042 return nil, types.ErrInvalidPartyID 2043 } 2044 2045 if foundOnBook { 2046 cancellation, err := m.matching.CancelOrder(order) 2047 if cancellation == nil || err != nil { 2048 if m.log.GetLevel() == logging.DebugLevel { 2049 m.log.Debug("Failure after cancel order from matching engine", 2050 logging.String("party-id", partyID), 2051 logging.String("order-id", orderID), 2052 logging.String("market", m.mkt.ID), 2053 logging.Error(err)) 2054 } 2055 return nil, err 2056 } 2057 m.releaseOrderFromHoldingAccount(ctx, orderID, order.Party, order.Side) 2058 } 2059 2060 if order.IsExpireable() { 2061 m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID) 2062 } 2063 2064 // If this is a pegged order, remove from pegged and parked lists 2065 if order.PeggedOrder != nil { 2066 m.removePeggedOrder(order) 2067 order.Status = types.OrderStatusCancelled 2068 } 2069 2070 // Publish the changed order details 2071 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 2072 if !inBatch { 2073 m.broker.Send(events.NewCancelledOrdersEvent(ctx, m.GetID(), partyID, orderID)) 2074 } 2075 2076 return &types.OrderCancellationConfirmation{Order: order}, nil 2077 } 2078 2079 // parkOrder removes the given order from the orderbook. parkOrder will panic if it encounters errors, which means that it reached an 2080 // invalid state. When the order is parked, the funds from the holding account are released to the general account. 2081 func (m *Market) parkOrder(ctx context.Context, orderID string) *types.Order { 2082 order, err := m.matching.RemoveOrder(orderID) 2083 if err != nil { 2084 m.log.Panic("Failure to remove order from matching engine", 2085 logging.OrderID(orderID), 2086 logging.Error(err)) 2087 } 2088 m.releaseOrderFromHoldingAccount(ctx, orderID, order.Party, order.Side) 2089 m.peggedOrders.Park(order) 2090 m.broker.Send(events.NewOrderEvent(ctx, order)) 2091 return order 2092 } 2093 2094 // AmendOrder amend an existing order from the order book. 2095 func (m *Market) AmendOrder(ctx context.Context, orderAmendment *types.OrderAmendment, party string, deterministicID string) (oc *types.OrderConfirmation, _ error) { 2096 idgen := idgeneration.New(deterministicID) 2097 return m.AmendOrderWithIDGenerator(ctx, orderAmendment, party, idgen) 2098 } 2099 2100 // AmendOrderWithIDGenerator amends an order. 2101 func (m *Market) AmendOrderWithIDGenerator(ctx context.Context, orderAmendment *types.OrderAmendment, party string, idgen common.IDGenerator) (oc *types.OrderConfirmation, _ error) { 2102 m.idgen = idgen 2103 defer func() { m.idgen = nil }() 2104 2105 defer func() { 2106 m.triggerStopOrders(ctx, idgen) 2107 }() 2108 2109 if !m.canTrade() { 2110 return nil, common.ErrTradingNotAllowed 2111 } 2112 2113 conf, _, err := m.amendOrder(ctx, orderAmendment, party) 2114 if err != nil { 2115 return nil, err 2116 } 2117 2118 if !m.as.InAuction() { 2119 m.checkForReferenceMoves(ctx, false) 2120 } 2121 return conf, nil 2122 } 2123 2124 // findOrderAndEnsureOwnership checks that the party is actually the owner of the order ID. 2125 func (m *Market) findOrderAndEnsureOwnership(orderID, partyID, marketID string) (exitingOrder *types.Order, foundOnBook bool, err error) { 2126 // Try and locate the existing order specified on the 2127 // order book in the matching engine for this market 2128 existingOrder, foundOnBook, err := m.getOrderByID(orderID) 2129 if err != nil { 2130 if m.log.GetLevel() == logging.DebugLevel { 2131 m.log.Debug("Invalid order ID", 2132 logging.OrderID(orderID), 2133 logging.PartyID(partyID), 2134 logging.MarketID(marketID), 2135 logging.Error(err)) 2136 } 2137 return nil, false, types.ErrInvalidOrderID 2138 } 2139 2140 // We can only amend this order if we created it 2141 if existingOrder.Party != partyID { 2142 if m.log.GetLevel() == logging.DebugLevel { 2143 m.log.Debug("Invalid party ID", 2144 logging.String("original party id:", existingOrder.Party), 2145 logging.PartyID(partyID), 2146 ) 2147 } 2148 return nil, false, types.ErrInvalidPartyID 2149 } 2150 2151 // Validate Market 2152 if existingOrder.MarketID != marketID { 2153 // we should never reach this point 2154 m.log.Panic("Market ID mismatch", 2155 logging.MarketID(m.mkt.ID), 2156 logging.Order(*existingOrder), 2157 logging.Error(types.ErrInvalidMarketID), 2158 ) 2159 } 2160 2161 return existingOrder, foundOnBook, err 2162 } 2163 2164 func (m *Market) amendOrder(ctx context.Context, orderAmendment *types.OrderAmendment, party string) (cnf *types.OrderConfirmation, orderUpdates []*types.Order, returnedErr error) { 2165 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "AmendOrder") 2166 defer timer.EngineTimeCounterAdd() 2167 2168 // Verify that the market is not closed 2169 if m.closed { 2170 return nil, nil, common.ErrMarketClosed 2171 } 2172 2173 existingOrder, foundOnBook, err := m.findOrderAndEnsureOwnership(orderAmendment.OrderID, party, m.GetID()) 2174 if err != nil { 2175 return nil, nil, err 2176 } 2177 2178 if err := m.validateOrderAmendment(existingOrder, orderAmendment); err != nil { 2179 return nil, nil, err 2180 } 2181 2182 amendedOrder, err := existingOrder.ApplyOrderAmendment(orderAmendment, m.timeService.GetTimeNow().UnixNano(), m.priceFactor) 2183 if err != nil { 2184 return nil, nil, err 2185 } 2186 2187 if err := m.checkOrderAmendForSpam(amendedOrder); err != nil { 2188 return nil, nil, err 2189 } 2190 2191 if orderAmendment.Price != nil && amendedOrder.OriginalPrice != nil { 2192 if err = m.validateTickSize(amendedOrder.OriginalPrice); err != nil { 2193 return nil, nil, err 2194 } 2195 } 2196 2197 // We do this first, just in case the party would also have 2198 // change the expiry, and that would have been caught by 2199 // the follow up checks, so we do not insert a non-existing 2200 // order in the expiring orders 2201 // if remaining is reduces <= 0, then order is cancelled 2202 if amendedOrder.Remaining <= 0 { 2203 confirm, err := m.cancelOrder( 2204 ctx, existingOrder.Party, existingOrder.ID) 2205 if err != nil { 2206 return nil, nil, err 2207 } 2208 return &types.OrderConfirmation{ 2209 Order: confirm.Order, 2210 }, nil, nil 2211 } 2212 2213 // If we have a pegged order that is no longer expiring, we need to remove it 2214 var ( 2215 needToRemoveExpiry, needToAddExpiry bool 2216 expiresAt int64 2217 ) 2218 2219 defer func() { 2220 // no errors, amend most likely happened properly 2221 if returnedErr == nil { 2222 if needToRemoveExpiry { 2223 m.expiringOrders.RemoveOrder(expiresAt, existingOrder.ID) 2224 } 2225 // need to make sure the order haven't been matched with the 2226 // amend, consuming the remain volume as well or we would 2227 // add an order while it's not needed to the expiring list 2228 if needToAddExpiry && cnf != nil && !cnf.Order.IsFinished() { 2229 m.expiringOrders.Insert(amendedOrder.ID, amendedOrder.ExpiresAt) 2230 } 2231 } 2232 }() 2233 2234 // if we are amending from GTT to GTC, flag ready to remove from expiry list 2235 if existingOrder.IsExpireable() && !amendedOrder.IsExpireable() { 2236 // We no longer need to handle the expiry 2237 needToRemoveExpiry = true 2238 expiresAt = existingOrder.ExpiresAt 2239 } 2240 2241 // if we are amending from GTC to GTT, flag ready to add to expiry list 2242 if !existingOrder.IsExpireable() && amendedOrder.IsExpireable() { 2243 // We need to handle the expiry 2244 needToAddExpiry = true 2245 } 2246 2247 // if both where expireable but we changed the duration 2248 // then we need to remove, then reinsert... 2249 if existingOrder.IsExpireable() && amendedOrder.IsExpireable() && 2250 existingOrder.ExpiresAt != amendedOrder.ExpiresAt { 2251 // Still expiring but needs to be updated in the expiring 2252 // orders pool 2253 needToRemoveExpiry = true 2254 needToAddExpiry = true 2255 expiresAt = existingOrder.ExpiresAt 2256 } 2257 2258 // if expiration has changed and is before the original creation time, reject this amend 2259 if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < existingOrder.CreatedAt { 2260 if m.log.GetLevel() == logging.DebugLevel { 2261 m.log.Debug("Amended expiry before original creation time", 2262 logging.Int64("existing-created-at", existingOrder.CreatedAt), 2263 logging.Int64("amended-expires-at", amendedOrder.ExpiresAt), 2264 logging.Order(*existingOrder)) 2265 } 2266 return nil, nil, types.ErrInvalidExpirationDatetime 2267 } 2268 2269 // if expiration has changed and is not 0, and is before currentTime 2270 // then we expire the order 2271 if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < amendedOrder.UpdatedAt { 2272 needToAddExpiry = false 2273 // remove the order from the expiring 2274 // at this point the order is still referenced at the time of expiry of the existingOrder 2275 if existingOrder.IsExpireable() { 2276 m.expiringOrders.RemoveOrder(existingOrder.ExpiresAt, amendedOrder.ID) 2277 } 2278 2279 // Update the existing message in place before we cancel it 2280 if foundOnBook { 2281 // Do not amend in place, the amend could be something 2282 // not supported for an amend in place, and not pass 2283 // the validation of the order book 2284 cancellation, err := m.matching.CancelOrder(existingOrder) 2285 if cancellation == nil || err != nil { 2286 m.log.Panic("Failure to cancel order from matching engine", 2287 logging.String("party-id", amendedOrder.Party), 2288 logging.String("order-id", amendedOrder.ID), 2289 logging.String("market", m.mkt.ID), 2290 logging.Error(err)) 2291 } 2292 m.releaseOrderFromHoldingAccount(ctx, existingOrder.ID, existingOrder.Party, existingOrder.Side) 2293 } 2294 2295 // Update the order in our stores (will be marked as cancelled) 2296 // set the proper status 2297 amendedOrder.Status = types.OrderStatusExpired 2298 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 2299 m.removePeggedOrder(amendedOrder) 2300 2301 return &types.OrderConfirmation{ 2302 Order: amendedOrder, 2303 }, nil, nil 2304 } 2305 2306 if existingOrder.PeggedOrder != nil { 2307 // Amend in place during an auction 2308 if m.as.InAuction() { 2309 ret := m.orderAmendWhenParked(amendedOrder) 2310 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 2311 return ret, nil, nil 2312 } 2313 err := m.repricePeggedOrder(amendedOrder) 2314 if err != nil { 2315 // Failed to reprice so we have to park the order 2316 if amendedOrder.Status != types.OrderStatusParked { 2317 // If we are live then park 2318 m.parkOrder(ctx, existingOrder.ID) 2319 } 2320 ret := m.orderAmendWhenParked(amendedOrder) 2321 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 2322 return ret, nil, nil 2323 } 2324 // We got a new valid price, if we are parked we need to unpark 2325 if amendedOrder.Status == types.OrderStatusParked { 2326 // we were parked, need to unpark 2327 m.peggedOrders.Unpark(amendedOrder.ID) 2328 return m.submitValidatedOrder(ctx, amendedOrder) 2329 } 2330 } 2331 2332 priceShift := amendedOrder.Price.NEQ(existingOrder.Price) 2333 sizeIncrease := amendedOrder.Size > existingOrder.Size 2334 sizeDecrease := amendedOrder.Size < existingOrder.Size 2335 expiryChange := amendedOrder.ExpiresAt != existingOrder.ExpiresAt 2336 timeInForceChange := amendedOrder.TimeInForce != existingOrder.TimeInForce 2337 2338 // If nothing changed, amend in place to update updatedAt and version number 2339 if !priceShift && !sizeIncrease && !sizeDecrease && !expiryChange && !timeInForceChange { 2340 ret := m.orderAmendInPlace(existingOrder, amendedOrder) 2341 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 2342 return ret, nil, nil 2343 } 2344 2345 icebergSizeIncrease := false 2346 if amendedOrder.IcebergOrder != nil && sizeIncrease { 2347 // iceberg orders size changes can always be done in-place because they either: 2348 // 1) decrease the size, which is already done in-place for all orders 2349 // 2) increase the size, which only increases the reserved remaining and not the "active" remaining of the iceberg 2350 // so we set an icebergSizeIncrease to skip the cancel-replace flow. 2351 sizeIncrease = false 2352 icebergSizeIncrease = true 2353 } 2354 2355 // if increase in size or change in price 2356 // ---> DO atomic cancel and submit 2357 if priceShift || sizeIncrease { 2358 return m.orderCancelReplace(ctx, existingOrder, amendedOrder) 2359 } 2360 2361 // if decrease in size or change in expiration date 2362 // ---> DO amend in place in matching engine 2363 if expiryChange || sizeDecrease || timeInForceChange || icebergSizeIncrease { 2364 m.releaseOrderFromHoldingAccount(ctx, amendedOrder.ID, amendedOrder.Party, amendedOrder.Side) 2365 ret := m.orderAmendInPlace(existingOrder, amendedOrder) 2366 m.broker.Send(events.NewOrderEvent(ctx, amendedOrder)) 2367 amt := m.calculateAmountBySide(ret.Order.Side, ret.Order.Price, ret.Order.TrueRemaining()) 2368 fees := num.UintZero() 2369 var err error 2370 if m.as.InAuction() { 2371 fees, err = m.calculateFees(ret.Order.Party, ret.Order.TrueRemaining(), ret.Order.Price, ret.Order.Side) 2372 if err != nil { 2373 return nil, nil, m.unregisterAndReject(ctx, ret.Order, err) 2374 } 2375 } 2376 asset := m.quoteAsset 2377 if ret.Order.Side == types.SideSell { 2378 asset = m.baseAsset 2379 } 2380 2381 // verify that the party has sufficient funds in their general account to cover for this amount 2382 if err := m.collateral.PartyHasSufficientBalance(asset, ret.Order.Party, num.Sum(amt, fees), types.AccountTypeGeneral); err != nil { 2383 return nil, nil, err 2384 } 2385 2386 transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, ret.Order.ID, ret.Order.Party, asset, amt, fees, types.AccountTypeGeneral) 2387 if err != nil { 2388 m.log.Panic("failed to transfer funds to holding account for order", logging.Order(ret.Order), logging.Error(err)) 2389 } 2390 m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer})) 2391 return ret, nil, nil 2392 } 2393 2394 // we should never reach this point as amendment was validated before 2395 // and every kind should have been handled down here. 2396 m.log.Panic( 2397 "invalid amend did not match any amendment combination", 2398 logging.String("amended-order", amendedOrder.String()), 2399 logging.String("existing-order", amendedOrder.String()), 2400 ) 2401 2402 return nil, nil, types.ErrEditNotAllowed 2403 } 2404 2405 func (m *Market) validateOrderAmendment(order *types.Order, amendment *types.OrderAmendment) error { 2406 if err := amendment.Validate(); err != nil { 2407 return err 2408 } 2409 // check TIME_IN_FORCE and expiry 2410 if amendment.TimeInForce == types.OrderTimeInForceGTT { 2411 // if expiresAt is before or equal to created at 2412 // we return an error, we know ExpiresAt is set because of amendment.Validate 2413 if *amendment.ExpiresAt <= order.CreatedAt { 2414 return types.OrderErrorExpiryAtBeforeCreatedAt 2415 } 2416 } 2417 2418 if (amendment.TimeInForce == types.OrderTimeInForceGFN || 2419 amendment.TimeInForce == types.OrderTimeInForceGFA) && 2420 amendment.TimeInForce != order.TimeInForce { 2421 // We cannot amend to a GFA/GFN orders 2422 return types.OrderErrorCannotAmendToGFAOrGFN 2423 } 2424 2425 if (order.TimeInForce == types.OrderTimeInForceGFN || 2426 order.TimeInForce == types.OrderTimeInForceGFA) && 2427 (amendment.TimeInForce != order.TimeInForce && 2428 amendment.TimeInForce != types.OrderTimeInForceUnspecified) { 2429 // We cannot amend from a GFA/GFN orders 2430 return types.OrderErrorCannotAmendFromGFAOrGFN 2431 } 2432 2433 if order.PeggedOrder == nil { 2434 // We cannot change a pegged orders details on a non pegged order 2435 if amendment.PeggedOffset != nil || 2436 amendment.PeggedReference != types.PeggedReferenceUnspecified { 2437 return types.OrderErrorCannotAmendPeggedOrderDetailsOnNonPeggedOrder 2438 } 2439 } else if amendment.Price != nil { 2440 // We cannot change the price on a pegged order 2441 return types.OrderErrorUnableToAmendPriceOnPeggedOrder 2442 } 2443 2444 // if side is buy we need to check that the party has sufficient funds in their general account to cover for the change in quote asset required 2445 if order.Side == types.SideBuy && (amendment.Price != nil || amendment.SizeDelta != 0) { 2446 remaining := order.Remaining 2447 // calculate the effective remaining after the change 2448 if amendment.SizeDelta < 0 { 2449 if remaining > uint64(-amendment.SizeDelta) { 2450 remaining -= uint64(-amendment.SizeDelta) 2451 } else { 2452 remaining = 0 2453 } 2454 } else { 2455 remaining += uint64(amendment.SizeDelta) 2456 } 2457 2458 // if nothing remains then no need to check anything 2459 if remaining == 0 { 2460 return nil 2461 } 2462 2463 // if the order is pegged and we're in an auction, we're done here 2464 if order.PeggedOrder != nil && m.as.InAuction() { 2465 return nil 2466 } 2467 2468 existingHoldingQty, existingHoldingFee := m.orderHoldingTracker.GetCurrentHolding(order.ID) 2469 oldHoldingRequirement := num.Sum(existingHoldingQty, existingHoldingFee) 2470 newFeesRequirement := num.UintZero() 2471 price := order.Price 2472 if amendment.Price != nil { 2473 price, _ = num.UintFromDecimal(amendment.Price.ToDecimal().Mul(m.priceFactor)) 2474 } 2475 if m.as.InAuction() { 2476 newFeesRequirement, _ = m.calculateFees(order.Party, remaining, price, order.Side) 2477 } 2478 if order.PeggedOrder != nil { 2479 p, err := m.getNewPeggedPrice(order) 2480 if err != nil { 2481 return err 2482 } 2483 price = p 2484 } 2485 newHoldingRequirement := num.Sum(m.calculateAmountBySide(order.Side, price, remaining), newFeesRequirement) 2486 if newHoldingRequirement.GT(oldHoldingRequirement) { 2487 if m.collateral.PartyHasSufficientBalance(m.quoteAsset, order.Party, num.UintZero().Sub(newHoldingRequirement, oldHoldingRequirement), types.AccountTypeGeneral) != nil { 2488 return fmt.Errorf("party does not have sufficient balance to cover the trade and fees") 2489 } 2490 } 2491 } 2492 2493 // if the side is sell and we want to sell more, need to check we're good for it 2494 if order.Side == types.SideSell && amendment.SizeDelta > 0 { 2495 if m.collateral.PartyHasSufficientBalance(m.baseAsset, order.Party, scaleBaseQuantityToAssetDP(uint64(amendment.SizeDelta), m.baseFactor), types.AccountTypeGeneral) != nil { 2496 return fmt.Errorf("party does not have sufficient balance to cover the new size") 2497 } 2498 } 2499 2500 return nil 2501 } 2502 2503 func (m *Market) GetQuoteAsset() string { 2504 return m.quoteAsset 2505 } 2506 2507 func (m *Market) Mkt() *types.Market { 2508 return m.mkt 2509 } 2510 2511 func (m *Market) StopSnapshots() { 2512 m.matching.StopSnapshots() 2513 m.tsCalc.StopSnapshots() 2514 m.liquidityEngine.StopSnapshots() 2515 m.orderHoldingTracker.StopSnapshots() 2516 } 2517 2518 func (m *Market) orderCancelReplace(ctx context.Context, existingOrder, newOrder *types.Order) (conf *types.OrderConfirmation, orders []*types.Order, err error) { 2519 wasInAuction := m.as.InAuction() 2520 defer func() { 2521 if err != nil { 2522 return 2523 } 2524 2525 orders = m.handleConfirmation(ctx, conf) 2526 m.handleConfirmationPassiveOrders(ctx, conf) 2527 // if we were in auction before we handled the transfer already, if we're finished, there's nothing to do here. 2528 if !conf.Order.IsFinished() && !wasInAuction { 2529 amt := m.calculateAmountBySide(newOrder.Side, newOrder.Price, newOrder.TrueRemaining()) 2530 asset := m.quoteAsset 2531 if newOrder.Side == types.SideSell { 2532 asset = m.baseAsset 2533 } 2534 transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, newOrder.ID, newOrder.Party, asset, amt, num.UintZero(), types.AccountTypeGeneral) 2535 if err != nil { 2536 m.log.Panic("failed to transfer funds to holding account for order", logging.Order(newOrder), logging.Error(err)) 2537 } 2538 m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer})) 2539 } 2540 m.broker.Send(events.NewOrderEvent(ctx, conf.Order)) 2541 }() 2542 2543 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderCancelReplace") 2544 defer timer.EngineTimeCounterAdd() 2545 // first at this point release the funds of the previous order from holding account 2546 // because we may be the aggressor 2547 m.releaseOrderFromHoldingAccount(ctx, newOrder.ID, newOrder.Party, newOrder.Side) 2548 2549 // make sure the order is on the book, this was done by canceling the order initially, but that could 2550 // trigger an auction in some cases. 2551 if o, err := m.matching.GetOrderByID(existingOrder.ID); err != nil || o == nil { 2552 m.log.Panic("Can't CancelReplace, the original order was not found", 2553 logging.OrderWithTag(*existingOrder, "existing-order"), 2554 logging.Error(err)) 2555 } 2556 // cancel-replace amend during auction is quite simple at this point 2557 if m.as.InAuction() { 2558 conf, err := m.matching.ReplaceOrder(existingOrder, newOrder) 2559 if err != nil { 2560 m.log.Panic("unable to submit order", logging.Error(err)) 2561 } 2562 if newOrder.PeggedOrder != nil { 2563 m.log.Panic("should never reach this point") 2564 } 2565 2566 amt := m.calculateAmountBySide(newOrder.Side, newOrder.Price, newOrder.TrueRemaining()) 2567 fees, err := m.calculateFees(newOrder.Party, newOrder.TrueRemaining(), newOrder.Price, newOrder.Side) 2568 if err != nil { 2569 return nil, nil, m.unregisterAndReject(ctx, newOrder, err) 2570 } 2571 asset := m.quoteAsset 2572 if newOrder.Side == types.SideSell { 2573 asset = m.baseAsset 2574 } 2575 transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, newOrder.ID, newOrder.Party, asset, amt, fees, types.AccountTypeGeneral) 2576 if err != nil { 2577 m.log.Panic("failed to transfer funds to holding account for order", logging.Order(newOrder), logging.Error(err)) 2578 } 2579 m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer})) 2580 2581 return conf, nil, nil 2582 } 2583 // if its an iceberg order with a price change and it is being submitted aggressively 2584 // set the visible remaining to the full size 2585 if newOrder.IcebergOrder != nil { 2586 newOrder.Remaining += newOrder.IcebergOrder.ReservedRemaining 2587 newOrder.IcebergOrder.ReservedRemaining = 0 2588 } 2589 2590 trades, err := m.checkPriceAndGetTrades(ctx, newOrder) 2591 if err != nil { 2592 return nil, nil, errors.New("couldn't insert order in book") 2593 } 2594 2595 // check that the party can afford the trade - if not return error and ignore the update 2596 if trades != nil { 2597 if err := m.canCoverTradesAndFees(newOrder.Party, newOrder.Side, trades); err != nil { 2598 return nil, nil, err 2599 } 2600 } 2601 2602 // "hot-swap" of the orders 2603 conf, err = m.matching.ReplaceOrder(existingOrder, newOrder) 2604 if err != nil { 2605 m.log.Panic("unable to submit order", logging.Error(err)) 2606 } 2607 2608 // replace the trades in the confirmation to have 2609 // the ones with the fees embedded 2610 conf.Trades = trades 2611 return conf, orders, nil 2612 } 2613 2614 // orderAmendInPlace amends the order in the order book. 2615 func (m *Market) orderAmendInPlace(originalOrder, amendOrder *types.Order) *types.OrderConfirmation { 2616 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderAmendInPlace") 2617 defer timer.EngineTimeCounterAdd() 2618 2619 err := m.matching.AmendOrder(originalOrder, amendOrder) 2620 if err != nil { 2621 // panic here, no good reason for a failure at this point 2622 m.log.Panic("Failure after amend order from matching engine (amend-in-place)", 2623 logging.OrderWithTag(*amendOrder, "new-order"), 2624 logging.OrderWithTag(*originalOrder, "old-order"), 2625 logging.Error(err)) 2626 } 2627 2628 return &types.OrderConfirmation{ 2629 Order: amendOrder, 2630 } 2631 } 2632 2633 // orderAmendWhenParked amends a parked pegged order. 2634 func (m *Market) orderAmendWhenParked(amendOrder *types.Order) *types.OrderConfirmation { 2635 amendOrder.Status = types.OrderStatusParked 2636 amendOrder.Price = num.UintZero() 2637 amendOrder.OriginalPrice = num.UintZero() 2638 m.peggedOrders.AmendParked(amendOrder) 2639 2640 return &types.OrderConfirmation{ 2641 Order: amendOrder, 2642 } 2643 } 2644 2645 // submitStopOrders gets a status as parameter. 2646 // this function is used on trigger but also on submission 2647 // at expiry, so just filters out with a parameter. 2648 func (m *Market) submitStopOrders(ctx context.Context, stopOrders []*types.StopOrder, status types.StopOrderStatus, idgen common.IDGenerator) []*types.OrderConfirmation { 2649 confirmations := []*types.OrderConfirmation{} 2650 evts := make([]events.Event, 0, len(stopOrders)) 2651 2652 // might contains both the triggered orders and the expired OCO 2653 for _, v := range stopOrders { 2654 if v.Status == status { 2655 conf, err := m.SubmitOrderWithIDGeneratorAndOrderID( 2656 ctx, v.OrderSubmission, v.Party, idgen, idgen.NextID(), false, 2657 ) 2658 if err != nil { 2659 // not much we can do at that point, let's log the error and move on? 2660 m.log.Error("could not submit stop order", 2661 logging.StopOrderSubmission(v), 2662 logging.Error(err)) 2663 } 2664 if err == nil && conf != nil { 2665 v.OrderID = conf.Order.ID 2666 confirmations = append(confirmations, conf) 2667 } 2668 } 2669 2670 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 2671 } 2672 2673 m.broker.SendBatch(evts) 2674 2675 return confirmations 2676 } 2677 2678 // removeExpiredOrders remove all expired orders from the order book 2679 // and also any pegged orders that are parked. 2680 func (m *Market) removeExpiredStopOrders(ctx context.Context, timestamp int64, idgen common.IDGenerator) []*types.OrderConfirmation { 2681 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredStopOrders") 2682 defer timer.EngineTimeCounterAdd() 2683 2684 toExpire := m.expiringStopOrders.Expire(timestamp) 2685 stopOrders := m.stopOrders.RemoveExpired(toExpire) 2686 2687 // ensure any OCO orders are also expire 2688 toExpireSet := map[string]struct{}{} 2689 for _, v := range toExpire { 2690 toExpireSet[v] = struct{}{} 2691 } 2692 2693 for _, so := range stopOrders { 2694 if _, ok := toExpireSet[so.ID]; !ok { 2695 if so.Expiry.Expires() { 2696 m.expiringStopOrders.RemoveOrder(so.Expiry.ExpiresAt.UnixNano(), so.ID) 2697 } 2698 } 2699 } 2700 2701 updatedAt := m.timeService.GetTimeNow() 2702 2703 if m.as.InAuction() { 2704 m.removeExpiredStopOrdersInAuction(ctx, updatedAt, stopOrders) 2705 return nil 2706 } 2707 2708 return m.removeExpiredStopOrdersInContinuous(ctx, updatedAt, stopOrders, idgen) 2709 } 2710 2711 func (m *Market) removeExpiredStopOrdersInAuction( 2712 ctx context.Context, 2713 updatedAt time.Time, 2714 stopOrders []*types.StopOrder, 2715 ) { 2716 evts := []events.Event{} 2717 for _, v := range stopOrders { 2718 v.UpdatedAt = updatedAt 2719 v.Status = types.StopOrderStatusExpired 2720 // nothing to do, can send the event now 2721 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 2722 } 2723 2724 m.broker.SendBatch(evts) 2725 } 2726 2727 func (m *Market) removeExpiredStopOrdersInContinuous( 2728 ctx context.Context, 2729 updatedAt time.Time, 2730 stopOrders []*types.StopOrder, 2731 idgen common.IDGenerator, 2732 ) []*types.OrderConfirmation { 2733 evts := []events.Event{} 2734 filteredOCO := []*types.StopOrder{} 2735 for _, v := range stopOrders { 2736 v.UpdatedAt = updatedAt 2737 if v.Status == types.StopOrderStatusExpired && v.Expiry.Expires() && *v.Expiry.ExpiryStrategy == types.StopOrderExpiryStrategySubmit { 2738 filteredOCO = append(filteredOCO, v) 2739 continue 2740 } 2741 // nothing to do, can send the event now 2742 evts = append(evts, events.NewStopOrderEvent(ctx, v)) 2743 } 2744 2745 m.broker.SendBatch(evts) 2746 2747 return m.submitStopOrders(ctx, filteredOCO, types.StopOrderStatusExpired, idgen) 2748 } 2749 2750 // RemoveExpiredOrders remove all expired orders from the order book 2751 // and also any pegged orders that are parked. 2752 func (m *Market) removeExpiredOrders(ctx context.Context, timestamp int64) []*types.Order { 2753 timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredOrders") 2754 defer timer.EngineTimeCounterAdd() 2755 2756 expired := []*types.Order{} 2757 toExp := m.expiringOrders.Expire(timestamp) 2758 if len(toExp) == 0 { 2759 return expired 2760 } 2761 ids := make([]string, 0, len(toExp)) 2762 for _, orderID := range toExp { 2763 var order *types.Order 2764 // The pegged expiry orders are copies and do not reflect the 2765 // current state of the order, therefore we look it up 2766 originalOrder, foundOnBook, err := m.getOrderByID(orderID) 2767 if err != nil { 2768 // nothing to do there. 2769 continue 2770 } 2771 // assign to the order the order from the book 2772 // so we get the most recent version from the book 2773 // to continue with 2774 order = originalOrder 2775 2776 // if the order was on the book basically 2777 // either a pegged + non parked 2778 // or a non-pegged order 2779 if foundOnBook { 2780 m.matching.DeleteOrder(order) 2781 // release any outstanding funds from the holding account to the general account 2782 m.releaseOrderFromHoldingAccount(ctx, order.ID, order.Party, order.Side) 2783 } 2784 2785 // if this was a pegged order 2786 // remove from the pegged / parked list 2787 if order.PeggedOrder != nil { 2788 m.removePeggedOrder(order) 2789 } 2790 2791 // now we add to the list of expired orders 2792 // and assign the appropriate status 2793 order.UpdatedAt = m.timeService.GetTimeNow().UnixNano() 2794 order.Status = types.OrderStatusExpired 2795 expired = append(expired, order) 2796 ids = append(ids, orderID) 2797 } 2798 if len(ids) > 0 { 2799 m.broker.Send(events.NewExpiredOrdersEvent(ctx, m.mkt.ID, ids)) 2800 } 2801 2802 // If we have removed an expired order, do we need to reprice any 2803 // or maybe notify the liquidity engine 2804 if len(expired) > 0 && !m.as.InAuction() { 2805 m.checkForReferenceMoves(ctx, false) 2806 } 2807 2808 return expired 2809 } 2810 2811 func (m *Market) getBestStaticAskPrice() (*num.Uint, error) { 2812 return m.matching.GetBestStaticAskPrice() 2813 } 2814 2815 func (m *Market) getBestStaticAskPriceAndVolume() (*num.Uint, uint64, error) { 2816 return m.matching.GetBestStaticAskPriceAndVolume() 2817 } 2818 2819 func (m *Market) getBestStaticBidPrice() (*num.Uint, error) { 2820 return m.matching.GetBestStaticBidPrice() 2821 } 2822 2823 func (m *Market) getBestStaticBidPriceAndVolume() (*num.Uint, uint64, error) { 2824 return m.matching.GetBestStaticBidPriceAndVolume() 2825 } 2826 2827 func (m *Market) getBestStaticPricesDecimal() (bid, ask num.Decimal, err error) { 2828 ask = num.DecimalZero() 2829 ubid, err := m.getBestStaticBidPrice() 2830 if err != nil { 2831 bid = num.DecimalZero() 2832 return 2833 } 2834 bid = ubid.ToDecimal() 2835 uask, err := m.getBestStaticAskPrice() 2836 if err != nil { 2837 ask = num.DecimalZero() 2838 return 2839 } 2840 ask = uask.ToDecimal() 2841 return 2842 } 2843 2844 func (m *Market) getStaticMidPrice(side types.Side) (*num.Uint, error) { 2845 bid, err := m.matching.GetBestStaticBidPrice() 2846 if err != nil { 2847 return num.UintZero(), err 2848 } 2849 ask, err := m.matching.GetBestStaticAskPrice() 2850 if err != nil { 2851 return num.UintZero(), err 2852 } 2853 mid := num.UintZero() 2854 one := num.NewUint(1) 2855 two := num.Sum(one, one) 2856 one, _ = num.UintFromDecimal(one.ToDecimal().Mul(m.priceFactor)) 2857 if side == types.SideBuy { 2858 mid = mid.Div(num.Sum(bid, ask, one), two) 2859 } else { 2860 mid = mid.Div(num.Sum(bid, ask), two) 2861 } 2862 2863 return mid, nil 2864 } 2865 2866 // removePeggedOrder looks through the pegged and parked list and removes the matching order if found. 2867 func (m *Market) removePeggedOrder(order *types.Order) { 2868 // remove if order was expiring 2869 m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID) 2870 // unpark will remove the order from the pegged orders data structure 2871 m.peggedOrders.Unpark(order.ID) 2872 } 2873 2874 // getOrderBy looks for the order in the order book and in the list 2875 // of pegged orders in the market. Returns the order if found, a bool 2876 // representing if the order was found on the order book and any error code. 2877 func (m *Market) getOrderByID(orderID string) (*types.Order, bool, error) { 2878 order, err := m.matching.GetOrderByID(orderID) 2879 if err == nil { 2880 return order, true, nil 2881 } 2882 2883 // The pegged order list contains all the pegged orders in the system 2884 // whether they are parked or live. Check this list of a matching order 2885 if o := m.peggedOrders.GetParkedByID(orderID); o != nil { 2886 return o, false, nil 2887 } 2888 2889 // We couldn't find it 2890 return nil, false, common.ErrOrderNotFound 2891 } 2892 2893 func (m *Market) getTargetStake() *num.Uint { 2894 return m.tsCalc.GetTargetStake(m.timeService.GetTimeNow()) 2895 } 2896 2897 func (m *Market) getSuppliedStake() *num.Uint { 2898 return m.liquidity.CalculateSuppliedStake() 2899 } 2900 2901 // canTrade returns true if the market state is active pending or suspended. 2902 func (m *Market) canTrade() bool { 2903 return m.mkt.State == types.MarketStateActive || 2904 m.mkt.State == types.MarketStatePending || 2905 m.mkt.State == types.MarketStateSuspended || 2906 m.mkt.State == types.MarketStateSuspendedViaGovernance 2907 } 2908 2909 func (m *Market) canSubmitMaybeSell(party string, side types.Side) bool { 2910 // buy side 2911 // or network party 2912 // or no empty allowedSellers list 2913 // are always fine 2914 if len(m.allowedSellers) <= 0 || side == types.SideBuy || party == types.NetworkParty { 2915 return true 2916 } 2917 2918 _, isAllowed := m.allowedSellers[party] 2919 return isAllowed 2920 } 2921 2922 // cleanupOnReject removes all resources created while the market was on PREPARED state. 2923 // at this point no fees would have been collected or anything like this. 2924 func (m *Market) cleanupOnReject(ctx context.Context) { 2925 m.stopAllLiquidityProvisionOnReject(ctx) 2926 2927 stopOrders := m.stopOrders.Settled() 2928 evts := make([]events.Event, 0, len(stopOrders)) 2929 for _, o := range stopOrders { 2930 evts = append(evts, events.NewStopOrderEvent(ctx, o)) 2931 } 2932 if len(evts) > 0 { 2933 m.broker.SendBatch(evts) 2934 } 2935 2936 tresps, err := m.collateral.ClearSpotMarket(ctx, m.GetID(), m.quoteAsset, m.getParties()) 2937 if err != nil { 2938 m.log.Panic("unable to cleanup a rejected market", 2939 logging.String("market-id", m.GetID()), 2940 logging.Error(err)) 2941 return 2942 } 2943 2944 m.stateVarEngine.UnregisterStateVariable(m.quoteAsset, m.mkt.ID) 2945 if len(tresps) > 0 { 2946 m.broker.Send(events.NewLedgerMovements(ctx, tresps)) 2947 } 2948 } 2949 2950 func (m *Market) stopAllLiquidityProvisionOnReject(ctx context.Context) { 2951 m.liquidity.StopAllLiquidityProvision(ctx) 2952 } 2953 2954 // GetTotalOrderBookLevelCount returns the total number of levels in the order book. 2955 func (m *Market) GetTotalOrderBookLevelCount() uint64 { 2956 return m.matching.GetOrderBookLevelCount() 2957 } 2958 2959 // GetTotalPeggedOrderCount returns the total number of pegged orders. 2960 func (m *Market) GetTotalPeggedOrderCount() uint64 { 2961 return m.matching.GetPeggedOrdersCount() 2962 } 2963 2964 // GetTotalStopOrderCount returns the total number of stop orders. 2965 func (m *Market) GetTotalStopOrderCount() uint64 { 2966 return m.stopOrders.GetStopOrderCount() 2967 } 2968 2969 // GetTotalOpenPositionCount returns the total number of open positions. 2970 func (m *Market) GetTotalOpenPositionCount() uint64 { 2971 return 0 2972 } 2973 2974 // GetTotalLPShapeCount returns the total number of LP shapes. 2975 func (m *Market) GetTotalLPShapeCount() uint64 { 2976 return 9 2977 } 2978 2979 // getCurrentMarkPrice returns the current mark price. 2980 func (m *Market) getCurrentMarkPrice() *num.Uint { 2981 m.markPriceLock.RLock() 2982 defer m.markPriceLock.RUnlock() 2983 if m.markPrice == nil { 2984 return num.UintZero() 2985 } 2986 return m.markPrice.Clone() 2987 } 2988 2989 // getLastTradedPrice returns the last traded price. 2990 func (m *Market) getLastTradedPrice() *num.Uint { 2991 if m.lastTradedPrice == nil { 2992 return num.UintZero() 2993 } 2994 return m.lastTradedPrice.Clone() 2995 } 2996 2997 // spot specific stuff 2998 2999 // processFeesTransfersOnEnterAuction handles the transfer from general account to holding account of fees to cover the trades that can take place 3000 // during auction. This is necessary as during auction the fees are split between the participating parties of a trade rather than paid by the aggressor. 3001 func (m *Market) processFeesTransfersOnEnterAuction(ctx context.Context) { 3002 parties := make([]string, 0, len(m.parties)) 3003 for v := range m.parties { 3004 parties = append(parties, v) 3005 } 3006 sort.Strings(parties) 3007 ordersToCancel := []*types.Order{} 3008 transfers := []*types.LedgerMovement{} 3009 for _, party := range parties { 3010 orders := m.matching.GetOrdersPerParty(party) 3011 for _, o := range orders { 3012 if o.Side == types.SideSell { 3013 continue 3014 } 3015 // if the side is buy then the fees are paid directly by the buyer which must have an account in quote asset 3016 // with sufficient funds 3017 fees, err := m.calculateFees(party, o.TrueRemaining(), o.Price, o.Side) 3018 if err != nil { 3019 m.log.Error("error calculating fees for order", logging.Order(o), logging.Error(err)) 3020 ordersToCancel = append(ordersToCancel, o) 3021 continue 3022 } 3023 if fees.IsZero() { 3024 continue 3025 } 3026 if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, fees, types.AccountTypeGeneral); err != nil { 3027 m.log.Error("party has insufficient funds to cover for fees for order", logging.Order(o), logging.Error(err)) 3028 ordersToCancel = append(ordersToCancel, o) 3029 continue 3030 } 3031 // party has sufficient funds to cover for fees - transfer fees from the party general account to the party holding account 3032 transfer, err := m.orderHoldingTracker.TransferFeeToHoldingAccount(ctx, o.ID, party, m.quoteAsset, fees, types.AccountTypeGeneral) 3033 if err != nil { 3034 m.log.Error("failed to transfer from general account to holding account", logging.Order(o), logging.Error(err)) 3035 ordersToCancel = append(ordersToCancel, o) 3036 continue 3037 } 3038 transfers = append(transfers, transfer) 3039 } 3040 } 3041 if len(transfers) > 0 { 3042 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 3043 } 3044 // cancel all orders with insufficient funds 3045 for _, o := range ordersToCancel { 3046 m.cancelOrder(ctx, o.Party, o.ID) 3047 } 3048 } 3049 3050 func (m *Market) checkFeeTransfersWhileInAuction(ctx context.Context) { 3051 parties := make([]string, 0, len(m.parties)) 3052 for v := range m.parties { 3053 parties = append(parties, v) 3054 } 3055 sort.Strings(parties) 3056 ordersToCancel := []*types.Order{} 3057 transfers := []*types.LedgerMovement{} 3058 for _, party := range parties { 3059 orders := m.matching.GetOrdersPerParty(party) 3060 for _, o := range orders { 3061 if o.Side == types.SideSell { 3062 continue 3063 } 3064 // if the side is buy then the fees are paid directly by the buyer which must have an account in quote asset 3065 // with sufficient funds 3066 fees, err := m.calculateFees(party, o.TrueRemaining(), o.Price, o.Side) 3067 if err != nil { 3068 m.log.Error("error calculating fees for order", logging.Order(o), logging.Error(err)) 3069 ordersToCancel = append(ordersToCancel, o) 3070 continue 3071 } 3072 if fees.IsZero() { 3073 continue 3074 } 3075 // check if we have already handled this fee and if so that it matches 3076 _, paidFees := m.orderHoldingTracker.GetCurrentHolding(o.ID) 3077 3078 if fees.GT(paidFees) { 3079 // We need to recalculate the fees amount 3080 var newFees num.Uint 3081 newFees.Sub(fees, paidFees) 3082 accType := types.AccountTypeGeneral 3083 if party == types.NetworkParty { 3084 var err error 3085 accType, _, err = m.getACcountTypesForPAP() 3086 if err != nil { 3087 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", o.ID)) 3088 } 3089 } 3090 if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, &newFees, accType); err != nil { 3091 m.log.Error("party has insufficient funds to cover for fees for order", logging.Order(o), logging.Error(err)) 3092 ordersToCancel = append(ordersToCancel, o) 3093 continue 3094 } 3095 // party has sufficient funds to cover for fees - transfer fees from the party general account to the party holding account 3096 transfer, err := m.orderHoldingTracker.TransferFeeToHoldingAccount(ctx, o.ID, party, m.quoteAsset, &newFees, accType) 3097 if err != nil { 3098 m.log.Error("failed to transfer from general account to holding account", logging.Order(o), logging.Error(err)) 3099 ordersToCancel = append(ordersToCancel, o) 3100 continue 3101 } 3102 transfers = append(transfers, transfer) 3103 } 3104 } 3105 } 3106 if len(transfers) > 0 { 3107 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 3108 } 3109 // cancel all orders with insufficient funds 3110 for _, o := range ordersToCancel { 3111 m.cancelOrder(ctx, o.Party, o.ID) 3112 } 3113 } 3114 3115 // processFeesReleaseOnLeaveAuction releases any fees locked for the duration of an auction. 3116 func (m *Market) processFeesReleaseOnLeaveAuction(ctx context.Context) { 3117 parties := make([]string, 0, len(m.parties)) 3118 for v := range m.parties { 3119 parties = append(parties, v) 3120 } 3121 sort.Strings(parties) 3122 transfers := []*types.LedgerMovement{} 3123 for _, party := range parties { 3124 orders := m.matching.GetOrdersPerParty(party) 3125 for _, o := range orders { 3126 if o.Side == types.SideBuy && o.PeggedOrder == nil { 3127 accType := types.AccountTypeGeneral 3128 if party == types.NetworkParty { 3129 var err error 3130 accType, _, err = m.getACcountTypesForPAP() 3131 if err != nil { 3132 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", o.ID)) 3133 } 3134 } 3135 transfer, err := m.orderHoldingTracker.ReleaseFeeFromHoldingAccount(ctx, o.ID, party, m.quoteAsset, accType) 3136 if err != nil { 3137 // it's valid, if fees for the order were zero, we don't update the holding account 3138 // cache so it can be legitimately not there. 3139 continue 3140 } 3141 transfers = append(transfers, transfer) 3142 } 3143 } 3144 } 3145 if len(transfers) > 0 { 3146 m.broker.Send(events.NewLedgerMovements(ctx, transfers)) 3147 } 3148 } 3149 3150 func (m *Market) handleTrade(ctx context.Context, trade *types.Trade) []*types.LedgerMovement { 3151 transfers := []*types.LedgerMovement{} 3152 // we need to transfer base from the seller to the buyer, 3153 // quote from buyer to the seller. 3154 // if we're in auction we first need to release the fee funds for the buyer 3155 // and release the funds for both sides from the holding accounts. 3156 fees, err := m.calculateFeesForTrades([]*types.Trade{trade}) 3157 if err != nil { 3158 m.log.Panic("failed to calculate fees for trade", logging.Trade(trade)) 3159 } 3160 if trade.Aggressor == types.SideUnspecified { 3161 fee := num.UintZero() 3162 if fees != nil { 3163 fee = fees.TotalFeesAmountPerParty()[trade.Buyer] 3164 } 3165 3166 // release buyer's trade + fees quote quantity from the holding account 3167 var transfer *types.LedgerMovement 3168 var err error 3169 // in auction the buyer and seller split the fees, but that just means that total they need to have in the holding account 3170 // is still quantity + fees/2 because the other half of the fees (the seller half) is paid out of what is supposed to go to the seller 3171 accType := types.AccountTypeGeneral 3172 if trade.Buyer == types.NetworkParty { 3173 accType, _, err = m.getACcountTypesForPAP() 3174 if err != nil { 3175 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.SellOrder)) 3176 } 3177 } 3178 transfer, err = m.orderHoldingTracker.ReleaseQuantityHoldingAccountAuctionEnd(ctx, trade.BuyOrder, trade.Buyer, m.quoteAsset, scaleQuoteQuantityToAssetDP(trade.Size, trade.Price, m.positionFactor), fee, accType) 3179 3180 if err != nil { 3181 m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade), logging.Error(err)) 3182 } 3183 transfers = append(transfers, transfer) 3184 3185 accType = types.AccountTypeGeneral 3186 if trade.Seller == types.NetworkParty { 3187 accType, _, err = m.getACcountTypesForPAP() 3188 if err != nil { 3189 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.SellOrder)) 3190 } 3191 } 3192 // release seller's base quantity from the holding account 3193 transfer, err = m.orderHoldingTracker.ReleaseQuantityHoldingAccount(ctx, trade.SellOrder, trade.Seller, m.baseAsset, scaleBaseQuantityToAssetDP(trade.Size, m.baseFactor), num.UintZero(), accType) 3194 if err != nil { 3195 m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade), logging.Error(err)) 3196 } 3197 transfers = append(transfers, transfer) 3198 } else { 3199 // if there is an aggressor, then we need to release the passive side from the holding account 3200 if trade.Aggressor == types.SideSell { // the aggressor is the seller so we need to release funds for the buyer from holding 3201 transfer, err := m.orderHoldingTracker.ReleaseQuantityHoldingAccount(ctx, trade.BuyOrder, trade.Buyer, m.quoteAsset, scaleQuoteQuantityToAssetDP(trade.Size, trade.Price, m.positionFactor), num.UintZero(), types.AccountTypeGeneral) 3202 if err != nil { 3203 m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade)) 3204 } 3205 transfers = append(transfers, transfer) 3206 } else { // the aggressor is the buyer, we release the funds for the seller from holding account 3207 transfer, err := m.orderHoldingTracker.ReleaseQuantityHoldingAccount(ctx, trade.SellOrder, trade.Seller, m.baseAsset, scaleBaseQuantityToAssetDP(trade.Size, m.baseFactor), num.UintZero(), types.AccountTypeGeneral) 3208 if err != nil { 3209 m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade)) 3210 } 3211 transfers = append(transfers, transfer) 3212 } 3213 } 3214 3215 // transfer base to buyer 3216 baseFromAccountType := types.AccountTypeGeneral 3217 baseToAccountType := types.AccountTypeGeneral 3218 quoteFromAccountType := types.AccountTypeGeneral 3219 quoteToAccountType := types.AccountTypeGeneral 3220 3221 if trade.Seller == types.NetworkParty { 3222 // if the network is the seller, then we want to transfer the base asset from the fromAccountType and the quote asset to the toAccountType 3223 baseFromAccountType, quoteToAccountType, err = m.getACcountTypesForPAP() 3224 if err != nil { 3225 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.SellOrder)) 3226 } 3227 } else if trade.Buyer == types.NetworkParty { 3228 // if the network is the buyer, then we want to transfer the quote base asset to the toAccountType and the quote asset from the fromAccountType 3229 quoteFromAccountType, baseToAccountType, err = m.getACcountTypesForPAP() 3230 if err != nil { 3231 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.BuyOrder)) 3232 } 3233 } 3234 transfer, err := m.collateral.TransferSpot(ctx, trade.Seller, trade.Buyer, m.baseAsset, scaleBaseQuantityToAssetDP(trade.Size, m.baseFactor), baseFromAccountType, baseToAccountType) 3235 if err != nil { 3236 m.log.Panic("failed to complete spot transfer", logging.Trade(trade)) 3237 } 3238 transfers = append(transfers, transfer) 3239 // transfer quote (potentially minus fees) to the seller 3240 transfer, err = m.collateral.TransferSpot(ctx, trade.Buyer, trade.Seller, m.quoteAsset, scaleQuoteQuantityToAssetDP(trade.Size, trade.Price, m.positionFactor), quoteFromAccountType, quoteToAccountType) 3241 if err != nil { 3242 m.log.Panic("failed to complete spot transfer", logging.Trade(trade)) 3243 } 3244 transfers = append(transfers, transfer) 3245 if fees != nil { 3246 m.applyFees(ctx, fees, quoteToAccountType) 3247 } 3248 return transfers 3249 } 3250 3251 // transferToHoldingAccount transfers the remaining funds + fees if needed from the general account to the holding account. 3252 func (m *Market) transferToHoldingAccount(ctx context.Context, order *types.Order) error { 3253 var err error 3254 amt := m.calculateAmountBySide(order.Side, order.Price, order.TrueRemaining()) 3255 fees := num.UintZero() 3256 if m.as.InAuction() && order.Side == types.SideBuy { 3257 fees, err = m.calculateFees(order.Party, order.TrueRemaining(), order.Price, order.Side) 3258 if err != nil { 3259 return err 3260 } 3261 } 3262 asset := m.quoteAsset 3263 if order.Side == types.SideSell { 3264 asset = m.baseAsset 3265 } 3266 3267 accountType := types.AccountTypeGeneral 3268 if order.Party == types.NetworkParty { 3269 accountType, _, err = m.getACcountTypesForPAP() 3270 if err != nil { 3271 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", order.ID)) 3272 } 3273 } 3274 3275 // verify that the party has sufficient funds in their general account to cover for this amount 3276 if err := m.collateral.PartyHasSufficientBalance(asset, order.Party, num.Sum(amt, fees), accountType); err != nil { 3277 return err 3278 } 3279 3280 transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, order.ID, order.Party, asset, amt, fees, accountType) 3281 if err != nil { 3282 m.log.Panic("failed to transfer funds to holding account for order", logging.Order(order), logging.Error(err)) 3283 } 3284 m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer})) 3285 return nil 3286 } 3287 3288 // releaseOrderFromHoldingAccount release all funds for a given order from holding account. If there are no funds to release it panics. 3289 func (m *Market) releaseOrderFromHoldingAccount(ctx context.Context, orderID, party string, side types.Side) { 3290 asset := m.quoteAsset 3291 if side == types.SideSell { 3292 asset = m.baseAsset 3293 } 3294 accType := types.AccountTypeGeneral 3295 if party == types.NetworkParty { 3296 var err error 3297 accType, _, err = m.getACcountTypesForPAP() 3298 if err != nil { 3299 m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", orderID)) 3300 } 3301 } 3302 transfer, err := m.orderHoldingTracker.ReleaseAllFromHoldingAccount(ctx, orderID, party, asset, accType) 3303 if err != nil { 3304 m.log.Panic("could not release funds from holding account", logging.String("order-id", orderID), logging.Error(err)) 3305 } 3306 if transfer != nil { 3307 m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer})) 3308 } 3309 } 3310 3311 // calculateFees calculate the amount of fees a party is due to pay given a side/price/size. 3312 // during opening auction there are no fees. 3313 func (m *Market) calculateFees(party string, size uint64, price *num.Uint, side types.Side) (*num.Uint, error) { 3314 if m.as.IsOpeningAuction() { 3315 return num.UintZero(), nil 3316 } 3317 3318 fakeTrade := &types.Trade{ 3319 Size: size, 3320 Price: price, 3321 Aggressor: side, 3322 } 3323 if side == types.SideBuy { 3324 fakeTrade.Buyer = party 3325 } else { 3326 fakeTrade.Seller = party 3327 } 3328 3329 fees, err := m.calculateFeesForTrades([]*types.Trade{fakeTrade}) 3330 if err != nil { 3331 return num.UintZero(), err 3332 } 3333 3334 // if we're in uncrossing governance auction fees will be nil 3335 if fees == nil { 3336 return num.UintZero(), nil 3337 } 3338 3339 return fees.TotalFeesAmountPerParty()[party], err 3340 } 3341 3342 func (m *Market) calculateFeesForTrades(trades []*types.Trade) (events.FeesTransfer, error) { 3343 var ( 3344 fees events.FeesTransfer 3345 err error 3346 ) 3347 if !m.as.InAuction() { 3348 fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 3349 } else if m.as.IsMonitorAuction() || m.as.IsPAPAuction() { 3350 // we are in auction mode 3351 fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 3352 } else if m.as.IsFBA() { 3353 fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 3354 } else { 3355 if !m.as.IsOpeningAuction() { 3356 fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) 3357 } 3358 } 3359 return fees, err 3360 } 3361 3362 // calculateAmountBySide calculates the amount *excluding* fees in the asset decimals. 3363 func (m *Market) calculateAmountBySide(side types.Side, price *num.Uint, size uint64) *num.Uint { 3364 if side == types.SideBuy { 3365 return num.Sum(scaleQuoteQuantityToAssetDP(size, price, m.positionFactor)) 3366 } 3367 return scaleBaseQuantityToAssetDP(size, m.baseFactor) 3368 } 3369 3370 // checkSufficientFunds checks if the aggressor party has in their general account sufficient funds to cover the trade + fees. 3371 func (m *Market) checkSufficientFunds(party string, side types.Side, price *num.Uint, size uint64, isPegged bool, accountType types.AccountType) error { 3372 required := m.calculateAmountBySide(side, price, size) 3373 if side == types.SideBuy { 3374 fees := num.UintZero() 3375 var err error 3376 if !isPegged { 3377 fees, err = m.calculateFees(party, size, price, side) 3378 if err != nil { 3379 return err 3380 } 3381 } 3382 3383 if m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, num.Sum(required, fees), accountType) != nil { 3384 return fmt.Errorf("party does not have sufficient balance to cover the trade and fees") 3385 } 3386 } else { 3387 if m.collateral.PartyHasSufficientBalance(m.baseAsset, party, required, accountType) != nil { 3388 return fmt.Errorf("party does not have sufficient balance to cover the trade and fees") 3389 } 3390 } 3391 return nil 3392 } 3393 3394 // convert the quantity to be transferred to the buyer to the base asset decimals. 3395 func scaleBaseQuantityToAssetDP(sizeUint uint64, baseFactor num.Decimal) *num.Uint { 3396 size := num.NewUint(sizeUint) 3397 total := size.ToDecimal().Mul(baseFactor) 3398 totalI, _ := num.UintFromDecimal(total) 3399 return totalI 3400 } 3401 3402 // convert the quantity to be transferred to the seller to the quote asset decimals. 3403 func scaleQuoteQuantityToAssetDP(sizeUint uint64, priceInAssetDP *num.Uint, positionFactor num.Decimal) *num.Uint { 3404 size := num.NewUint(sizeUint) 3405 total := size.Mul(priceInAssetDP, size).ToDecimal().Div(positionFactor) 3406 totalI, _ := num.UintFromDecimal(total) 3407 return totalI 3408 } 3409 3410 // closeSpotMarket terminates a market - this can be triggered only via governance. 3411 func (m *Market) closeSpotMarket(ctx context.Context) { 3412 if m.mkt.State != types.MarketStatePending { 3413 m.markPriceLock.Lock() 3414 m.markPrice = m.lastTradedPrice 3415 m.markPriceLock.Unlock() 3416 if m.pap != nil { 3417 m.stopPAP(ctx) 3418 } 3419 if err := m.closeMarket(ctx); err != nil { 3420 m.log.Error("could not close market", logging.Error(err)) 3421 } 3422 return 3423 } 3424 for party := range m.parties { 3425 _, err := m.CancelAllOrders(ctx, party) 3426 if err != nil { 3427 m.log.Debug("could not cancel orders for party", logging.PartyID(party), logging.Error(err)) 3428 } 3429 } 3430 err := m.closeCancelledMarket(ctx) 3431 if err != nil { 3432 m.log.Debug("could not close market", logging.MarketID(m.GetID())) 3433 return 3434 } 3435 } 3436 3437 func (m *Market) OnEpochEvent(ctx context.Context, epoch types.Epoch) { 3438 if m.closed { 3439 return 3440 } 3441 if epoch.Action == vega.EpochAction_EPOCH_ACTION_START { 3442 m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams) 3443 m.liquidity.OnEpochStart(ctx, m.timeService.GetTimeNow(), m.markPrice, m.midPrice(), m.getTargetStake(), m.positionFactor) 3444 m.epoch = epoch 3445 } else if epoch.Action == vega.EpochAction_EPOCH_ACTION_END { 3446 if !m.finalFeesDistributed { 3447 m.liquidity.OnEpochEnd(ctx, m.timeService.GetTimeNow(), epoch) 3448 } 3449 m.updateLiquidityFee(ctx) 3450 3451 m.banking.RegisterTradingFees(ctx, m.quoteAsset, m.fee.TotalTradingFeesPerParty()) 3452 3453 quoteAssetQuantum, _ := m.collateral.GetAssetQuantum(m.quoteAsset) 3454 feesStats := m.fee.GetFeesStatsOnEpochEnd(quoteAssetQuantum) 3455 feesStats.EpochSeq = epoch.Seq 3456 feesStats.Market = m.GetID() 3457 3458 m.broker.Send(events.NewFeesStatsEvent(ctx, feesStats)) 3459 } 3460 } 3461 3462 func (m *Market) OnEpochRestore(ctx context.Context, epoch types.Epoch) { 3463 m.epoch = epoch 3464 m.liquidityEngine.OnEpochRestore(epoch) 3465 } 3466 3467 func (m *Market) GetMarketCounters() *types.MarketCounters { 3468 return &types.MarketCounters{ 3469 StopOrderCounter: m.GetTotalStopOrderCount(), 3470 PeggedOrderCounter: m.GetTotalPeggedOrderCount(), 3471 OrderbookLevelCount: m.GetTotalOrderBookLevelCount(), 3472 PositionCount: 0, 3473 } 3474 } 3475 3476 func (m *Market) SubmitAMM(context.Context, *types.SubmitAMM, string) error { 3477 return errors.New("unimplemented") 3478 } 3479 3480 func (m *Market) AmendAMM(context.Context, *types.AmendAMM, string) error { 3481 return errors.New("unimplemented") 3482 } 3483 3484 func (m *Market) CancelAMM(context.Context, *types.CancelAMM, string) error { 3485 return errors.New("unimplemented") 3486 } 3487 3488 func (m *Market) ValidateSettlementData(_ *num.Uint) bool { 3489 return true 3490 } 3491 3492 // IDGen is an id generator for orders. 3493 type IDGen interface { 3494 NextID() string 3495 } 3496 3497 func (m *Market) checkOrderForSpam(side types.Side, orderPrice *num.Uint, orderSize uint64, peggedOrder *types.PeggedOrder, orderType vega.Order_Type, quantumMultiplier num.Decimal) error { 3498 assetQuantum, err := m.collateral.GetAssetQuantum(m.quoteAsset) 3499 if err != nil { 3500 return err 3501 } 3502 3503 var price *num.Uint 3504 if peggedOrder != nil || orderType == vega.Order_TYPE_MARKET { 3505 priceInMarket, _ := num.UintFromDecimal(m.getCurrentMarkPrice().ToDecimal().Div(m.priceFactor)) 3506 offset := num.UintZero() 3507 if peggedOrder != nil { 3508 offset = peggedOrder.Offset 3509 } 3510 if side == types.SideBuy { 3511 priceInMarket.AddSum(offset) 3512 } else { 3513 priceInMarket = priceInMarket.Sub(priceInMarket, offset) 3514 } 3515 price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) 3516 } else { 3517 price, _ = num.UintFromDecimal(orderPrice.ToDecimal().Mul(m.priceFactor)) 3518 } 3519 3520 value := num.UintZero().Mul(num.NewUint(orderSize), price).ToDecimal() 3521 value = value.Div(m.positionFactor) 3522 required := assetQuantum.Mul(quantumMultiplier) 3523 if value.LessThan(required) { 3524 return fmt.Errorf(fmt.Sprintf("order value (%s) is less than minimum holding requirement for spam (%s)", value.String(), required.String())) 3525 } 3526 return nil 3527 } 3528 3529 func (m *Market) checkOrderAmendForSpam(order *types.Order) error { 3530 return m.checkOrderForSpam( 3531 order.Side, 3532 order.Price, 3533 order.Size, 3534 order.PeggedOrder, 3535 order.Type, 3536 m.minHoldingQuantumMultiplier) 3537 } 3538 3539 func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmission, party string, quantumMultiplier num.Decimal) error { 3540 return m.checkOrderForSpam( 3541 orderSubmission.Side, 3542 orderSubmission.Price, 3543 orderSubmission.Size, 3544 orderSubmission.PeggedOrder, 3545 orderSubmission.Type, 3546 quantumMultiplier) 3547 } 3548 3549 func (m *Market) enterAutomatedPurchaseAuction(ctx context.Context, orderID string, orderSide types.Side, orderPrice *num.Uint, orderSize uint64, reference string, duration time.Duration) (string, error) { 3550 if !m.canTrade() { 3551 return "", fmt.Errorf(fmt.Sprintf("cannot trade in market %s", m.mkt.ID)) 3552 } 3553 3554 // if we're in governance auction we're not placing automated purchase orders because we don't have 3555 // control over when the market will be resumed 3556 if m.mkt.TradingMode == types.MarketTradingModeSuspendedViaGovernance { 3557 return "", fmt.Errorf(fmt.Sprintf("cannot enter auction in market %s - market is in governance suspension", m.mkt.ID)) 3558 } 3559 3560 if m.as.InAuction() { 3561 now := m.timeService.GetTimeNow() 3562 aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second) 3563 if aRemaining >= int64(duration.Seconds()) { 3564 return "", nil 3565 } 3566 m.as.ExtendAuctionAutomatedPurchase(types.AuctionDuration{ 3567 Duration: int64(duration.Seconds()) - aRemaining, 3568 }) 3569 if evt := m.as.AuctionExtended(ctx, now); evt != nil { 3570 m.broker.Send(evt) 3571 } 3572 } else { 3573 m.as.StartAutomatedPurchaseAuction(m.timeService.GetTimeNow(), int64(duration.Seconds())) 3574 m.mkt.TradingMode = types.MarketTradingModeAutomatedPuchaseAuction 3575 m.mkt.State = types.MarketStateSuspended 3576 m.enterAuction(ctx) 3577 m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) 3578 } 3579 // if we were able to enter an auction, now place the order for the network account 3580 if m.as.InAuction() { 3581 os := &types.OrderSubmission{ 3582 MarketID: m.mkt.ID, 3583 Price: orderPrice.Clone(), 3584 Size: orderSize, 3585 Side: orderSide, 3586 TimeInForce: types.OrderTimeInForceGFA, 3587 Type: types.OrderTypeLimit, 3588 Reference: reference, 3589 } 3590 conf, err := m.SubmitOrder(ctx, os, types.NetworkParty, orderID) 3591 if err != nil { 3592 return "", err 3593 } 3594 return conf.Order.ID, nil 3595 } 3596 return "", nil 3597 } 3598 3599 func (m *Market) getACcountTypesForPAP() (types.AccountType, types.AccountType, error) { 3600 if m.pap == nil { 3601 return types.AccountTypeUnspecified, types.AccountTypeUnspecified, fmt.Errorf("protocol automated purchase not defined for market") 3602 } 3603 return m.pap.getACcountTypesForPAP() 3604 } 3605 3606 func (m *Market) BeginBlock(ctx context.Context) {}