code.vegaprotocol.io/vega@v0.79.0/core/matching/side.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 matching 17 18 import ( 19 "encoding/binary" 20 "fmt" 21 "sort" 22 23 "code.vegaprotocol.io/vega/core/types" 24 "code.vegaprotocol.io/vega/libs/crypto" 25 "code.vegaprotocol.io/vega/libs/num" 26 "code.vegaprotocol.io/vega/logging" 27 28 "github.com/pkg/errors" 29 ) 30 31 // ErrPriceNotFound signals that a price was not found on the book side. 32 var ErrPriceNotFound = errors.New("price-volume pair not found") 33 34 // OrderBookSide represent a side of the book, either Sell or Buy. 35 type OrderBookSide struct { 36 side types.Side 37 log *logging.Logger 38 levels []*PriceLevel 39 offbook OffbookSource 40 } 41 42 func (s *OrderBookSide) Hash() []byte { 43 // 32 num.Uint.Bytes() for price + 8 for volume 44 output := make([]byte, len(s.levels)*40) 45 var i int 46 for _, l := range s.levels { 47 // Data is already coming as big endian out of 48 // Uint.Bytes() 49 price := l.price.Bytes() 50 copy(output[i:], price[:]) 51 i += 32 52 binary.BigEndian.PutUint64(output[i:], l.volume) 53 i += 8 54 } 55 return crypto.Hash(output) 56 } 57 58 func (s *OrderBookSide) cleanup() { 59 s.levels = nil 60 } 61 62 // When we leave an auction we need to remove any orders marked as GFA. 63 func (s *OrderBookSide) getOrdersToCancel(auction bool) []*types.Order { 64 ordersToCancel := make([]*types.Order, 0) 65 for _, pricelevel := range s.levels { 66 for _, order := range pricelevel.orders { 67 // Find orders to cancel 68 if (order.TimeInForce == types.OrderTimeInForceGFA && !auction) || 69 (order.TimeInForce == types.OrderTimeInForceGFN && auction) { 70 // Save order to send back to client 71 ordersToCancel = append(ordersToCancel, order) 72 } 73 } 74 } 75 return ordersToCancel 76 } 77 78 func (s *OrderBookSide) addOrder(o *types.Order) { 79 // update the price-volume map 80 s.getPriceLevel(o.Price).addOrder(o) 81 } 82 83 // BestPriceAndVolume returns the top of book price and volume 84 // returns an error if the book is empty. 85 func (s *OrderBookSide) BestPriceAndVolume() (*num.Uint, uint64, error) { 86 if len(s.levels) <= 0 { 87 return num.UintZero(), 0, errors.New("no orders on the book") 88 } 89 last := len(s.levels) - 1 90 return s.levels[last].price.Clone(), s.levels[last].volume, nil 91 } 92 93 // BestStaticPrice returns the top of book price for non pegged orders 94 // We do not keep count of the volume which makes this slightly quicker 95 // returns an error if the book is empty. 96 func (s *OrderBookSide) BestStaticPrice() (*num.Uint, error) { 97 if len(s.levels) <= 0 { 98 return num.UintZero(), errors.New("no orders on the book") 99 } 100 101 for i := len(s.levels) - 1; i >= 0; i-- { 102 pricelevel := s.levels[i] 103 for _, order := range pricelevel.orders { 104 if order.PeggedOrder == nil { 105 return pricelevel.price.Clone(), nil 106 } 107 } 108 } 109 return num.UintZero(), errors.New("no non pegged orders found on the book") 110 } 111 112 // BestStaticPriceAndVolume returns the top of book price for non pegged orders 113 // returns an error if the book is empty. 114 func (s *OrderBookSide) BestStaticPriceAndVolume() (*num.Uint, uint64, error) { 115 if len(s.levels) <= 0 { 116 return num.UintZero(), 0, errors.New("no orders on the book") 117 } 118 119 var ( 120 bestPrice = num.UintZero() 121 bestVolume uint64 122 ) 123 for i := len(s.levels) - 1; i >= 0; i-- { 124 pricelevel := s.levels[i] 125 for _, order := range pricelevel.orders { 126 if order.PeggedOrder == nil { 127 bestPrice = pricelevel.price 128 bestVolume += order.Remaining 129 } 130 } 131 // If we found a price, return it 132 if bestPrice.GT(num.UintZero()) { 133 return bestPrice.Clone(), bestVolume, nil 134 } 135 } 136 return num.UintZero(), 0, errors.New("no non pegged orders found on the book") 137 } 138 139 func (s *OrderBookSide) amendIcebergOrder(amendOrder *types.Order, oldOrder *types.Order, priceLevelIndex int, orderIndex int) (int64, error) { 140 if amendOrder.Remaining > oldOrder.Remaining { 141 // iceberg amend should never increase the visible remaining 142 return 0, types.ErrOrderAmendFailure 143 } 144 145 // set the new order in the level 146 s.levels[priceLevelIndex].orders[orderIndex] = amendOrder 147 148 // iceberg orders are a little different because they can be increased or decreased in size but 149 // amended in place. This is because on increase only the reserve amount it changed. 150 oldReserved := oldOrder.IcebergOrder.ReservedRemaining 151 amendReserved := amendOrder.IcebergOrder.ReservedRemaining 152 if amendReserved > oldReserved { 153 // only increased volume diff is easy 154 inc := amendReserved - oldReserved 155 s.levels[priceLevelIndex].volume += inc 156 return int64(inc), nil 157 } 158 159 if amendReserved < oldReserved { 160 dec := oldOrder.Remaining - amendOrder.Remaining 161 dec += oldReserved - amendReserved 162 s.levels[priceLevelIndex].reduceVolume(dec) 163 return -int64(dec), nil 164 } 165 166 // this is the case where we have an iceberg with no reserve, and reducing its visible peak 167 if oldOrder.Remaining < amendOrder.Remaining { 168 panic("we should not be increasing iceberg visble size in-place") 169 } 170 return -int64(oldOrder.Remaining - amendOrder.Remaining), nil 171 } 172 173 func (s *OrderBookSide) amendOrder(orderAmend *types.Order) (int64, error) { 174 priceLevelIndex := -1 175 orderIndex := -1 176 var oldOrder *types.Order 177 178 for idx, priceLevel := range s.levels { 179 if priceLevel.price.EQ(orderAmend.Price) { 180 priceLevelIndex = idx 181 for j, order := range priceLevel.orders { 182 if order.ID == orderAmend.ID { 183 orderIndex = j 184 oldOrder = order 185 break 186 } 187 } 188 break 189 } 190 } 191 192 if oldOrder == nil || priceLevelIndex == -1 || orderIndex == -1 { 193 return 0, types.ErrOrderNotFound 194 } 195 196 if oldOrder.Party != orderAmend.Party { 197 return 0, types.ErrOrderAmendFailure 198 } 199 200 if oldOrder.Reference != orderAmend.Reference { 201 return 0, types.ErrOrderAmendFailure 202 } 203 204 if oldOrder.IcebergOrder != nil { 205 return s.amendIcebergOrder(orderAmend, oldOrder, priceLevelIndex, orderIndex) 206 } 207 208 if oldOrder.Size < orderAmend.Size && 209 oldOrder.Remaining < orderAmend.Size { 210 return 0, types.ErrOrderAmendFailure 211 } 212 213 reduceBy := oldOrder.Remaining - orderAmend.Remaining 214 s.levels[priceLevelIndex].orders[orderIndex] = orderAmend 215 s.levels[priceLevelIndex].reduceVolume(reduceBy) 216 return -int64(reduceBy), nil 217 } 218 219 // ExtractOrders extracts the orders from the top of the book until the volume amount is hit, 220 // if removeOrders is set to True then the relevant orders also get removed. 221 func (s *OrderBookSide) ExtractOrders(price *num.Uint, volume uint64, removeOrders bool) []*types.Order { 222 extractedOrders := []*types.Order{} 223 if volume == 0 { 224 return extractedOrders 225 } 226 227 var ( 228 totalVolume uint64 229 checkPrice func(*num.Uint) bool 230 ) 231 if s.side == types.SideBuy { 232 checkPrice = func(orderPrice *num.Uint) bool { return orderPrice.GTE(price) } 233 } else { 234 checkPrice = func(orderPrice *num.Uint) bool { return orderPrice.LTE(price) } 235 } 236 237 for i := len(s.levels) - 1; i >= 0; i-- { 238 pricelevel := s.levels[i] 239 var toRemove int 240 for _, order := range pricelevel.orders { 241 // Check the price is good and the total volume will not be exceeded 242 if checkPrice(order.Price) && totalVolume+order.TrueRemaining() <= volume { 243 // Remove this order 244 extractedOrders = append(extractedOrders, order.Clone()) 245 totalVolume += order.TrueRemaining() 246 // Remove the order from the price level 247 toRemove++ 248 } else { 249 // We should never get to here unless the passed in price 250 // and volume are not correct 251 s.log.Panic("Failed to extract orders as not enough volume within price limits", 252 logging.BigUint("price", price), 253 logging.Uint64("required-volume", volume), 254 logging.Uint64("found-volume", totalVolume), 255 logging.Bool("remove-orders", removeOrders)) 256 } 257 258 // If we have the right amount, stop processing 259 if totalVolume == volume { 260 break 261 } 262 } 263 264 if removeOrders { 265 for ; toRemove > 0; toRemove-- { 266 pricelevel.removeOrder(0) 267 } 268 // Erase this price level which will be at the end of the slice 269 if len(pricelevel.orders) == 0 { 270 s.levels[i] = nil 271 s.levels = s.levels[:len(s.levels)-1] 272 } 273 } 274 275 // Check if we have done enough 276 if totalVolume == volume { 277 break 278 } 279 } 280 // If we get here and don't have the full amount of volume 281 // something has gone wrong 282 if totalVolume != volume { 283 s.log.Panic("Failed to extract orders as not enough volume on the book", 284 logging.BigUint("price", price), 285 logging.Uint64("volume", volume), 286 logging.Uint64("total-volume", totalVolume), 287 ) 288 } 289 290 return extractedOrders 291 } 292 293 // RemoveOrder will remove an order from the book. 294 func (s *OrderBookSide) RemoveOrder(o *types.Order) (*types.Order, error) { 295 // first we try to find the pricelevel of the order 296 var i int 297 if o.Side == types.SideBuy { 298 i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.GTE(o.Price) }) 299 } else { 300 // sell side levels should be ordered in ascending 301 i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.LTE(o.Price) }) 302 } 303 // we did not found the level 304 // then the order do not exists in the price level 305 if i >= len(s.levels) { 306 return nil, types.ErrOrderNotFound 307 } 308 309 // now we may have a few orders with the same timestamp 310 // lets iterate over them in order to find the right one 311 finaloidx := -1 312 for index, order := range s.levels[i].orders { 313 if order.ID == o.ID { 314 finaloidx = index 315 break 316 } 317 } 318 319 var order *types.Order 320 // remove the order from the 321 if finaloidx != -1 { 322 order = s.levels[i].orders[finaloidx] 323 s.levels[i].removeOrder(finaloidx) 324 } else { 325 // We could not find the matching order, return an error 326 return nil, types.ErrOrderNotFound 327 } 328 329 if len(s.levels[i].orders) <= 0 { 330 s.levels = s.levels[:i+copy(s.levels[i:], s.levels[i+1:])] 331 } 332 333 return order, nil 334 } 335 336 func (s *OrderBookSide) getPriceLevelIfExists(price *num.Uint) *PriceLevel { 337 var i int 338 if s.side == types.SideBuy { 339 // buy side levels should be ordered in ascending 340 i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.GTE(price) }) 341 } else { 342 // sell side levels should be ordered in descending 343 i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.LTE(price) }) 344 } 345 346 // we found the level just return it. 347 if i < len(s.levels) && s.levels[i].price.EQ(price) { 348 return s.levels[i] 349 } 350 return nil 351 } 352 353 func (s *OrderBookSide) getPriceLevel(price *num.Uint) *PriceLevel { 354 var i int 355 if s.side == types.SideBuy { 356 // buy side levels should be ordered in ascending 357 i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.GTE(price) }) 358 } else { 359 // sell side levels should be ordered in descending 360 i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.LTE(price) }) 361 } 362 363 // we found the level just return it. 364 if i < len(s.levels) && s.levels[i].price.EQ(price) { 365 return s.levels[i] 366 } 367 368 // append new elem first to make sure we have enough place 369 // this would reallocate sufficiently then 370 // no risk of this being a empty order, as it's overwritten just next with 371 // the slice insert 372 level := NewPriceLevel(price.Clone()) 373 s.levels = append(s.levels, nil) 374 copy(s.levels[i+1:], s.levels[i:]) 375 s.levels[i] = level 376 return level 377 } 378 379 func (s *OrderBookSide) getLevelsForPrice(price *num.Uint) []*PriceLevel { 380 ret := make([]*PriceLevel, 0, len(s.levels)) 381 // buy is ASCENDING, start at the highest buy price until we find a buy order that will not trade 382 // at the given price (ie given price is > buy order price). 383 cmpF := price.GT 384 if s.side == types.SideSell { 385 // sell is DESCENDING, start at the lowest sell order price, until we find a sell order that won't trade 386 // at the given price (ie given price < sell order price). 387 cmpF = price.LT 388 } 389 for i := len(s.levels) - 1; i >= 0; i-- { 390 if cmpF(s.levels[i].price) { 391 return ret 392 } 393 ret = append(ret, s.levels[i]) 394 } 395 return ret 396 } 397 398 // GetVolume returns the volume at the given pricelevel. 399 func (s *OrderBookSide) GetVolume(price *num.Uint) (uint64, error) { 400 priceLevel := s.getPriceLevelIfExists(price) 401 402 if priceLevel == nil { 403 return 0, ErrPriceNotFound 404 } 405 406 return priceLevel.volume, nil 407 } 408 409 // fakeUncross returns hypothetical trades if the order book side were to be uncrossed with the agg order supplied, 410 // checkWashTrades checks non-FOK orders for wash trades if set to true (FOK orders are always checked for wash trades). 411 func (s *OrderBookSide) fakeUncross(agg *types.Order, checkWashTrades bool) ([]*types.Trade, error) { 412 defer s.uncrossFinished() 413 414 // get a copy of the order passed in, so we can rely on fakeUncross to do its job 415 fake := agg.Clone() 416 417 var ( 418 trades []*types.Trade 419 offbookOrders []*types.Order 420 totalVolumeToFill uint64 421 ) 422 if agg.TimeInForce == types.OrderTimeInForceFOK { 423 var checkPrice func(*num.Uint) bool 424 if agg.Side == types.SideBuy { 425 checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.LTE(agg.Price) } 426 } else { 427 checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.GTE(agg.Price) } 428 } 429 430 // first check for volume between the theoretical best price and the first price level 431 _, oo := s.uncrossOffbook(len(s.levels), fake, true) 432 for _, order := range oo { 433 totalVolumeToFill += order.Remaining 434 } 435 436 for i := len(s.levels) - 1; i >= 0; i-- { 437 level := s.levels[i] 438 // we don't have to account for network orders, they don't apply in price monitoring 439 // nor do fees apply 440 if checkPrice(level.price) || agg.Type == types.OrderTypeMarket { 441 for _, order := range level.orders { 442 if agg.Party == order.Party { 443 return nil, ErrWashTrade 444 } 445 totalVolumeToFill += order.Remaining 446 if totalVolumeToFill >= agg.Remaining { 447 break 448 } 449 } 450 451 _, oo := s.uncrossOffbook(i, fake, true) 452 for _, order := range oo { 453 totalVolumeToFill += order.Remaining 454 } 455 } 456 457 if totalVolumeToFill >= agg.Remaining { 458 break 459 } 460 } 461 462 // FOK order could not be filled 463 if totalVolumeToFill < agg.Remaining { 464 return nil, nil 465 } 466 467 // reset the offbook source so we can then do it all again.... 468 s.uncrossFinished() 469 } 470 471 // get a copy of the order passed in, so we can rely on fakeUncross to do its job 472 fake = agg.Clone() 473 var ( 474 idx = len(s.levels) - 1 475 ntrades []*types.Trade 476 err error 477 checkPrice func(*num.Uint) bool 478 ) 479 480 if fake.Side == types.SideBuy { 481 checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.GT(agg.Price) } 482 } else { 483 checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.LT(agg.Price) } 484 } 485 486 trades, offbookOrders = s.uncrossOffbook(idx+1, fake, true) 487 488 // in here we iterate from the end, as it's easier to remove the 489 // price levels from the back of the slice instead of from the front 490 // also it will allow us to reduce allocations 491 for idx >= 0 && fake.Remaining > 0 { 492 // not a market order && buy side price is too low => break 493 if agg.Type != types.OrderTypeMarket && checkPrice(s.levels[idx].price) { 494 break 495 } 496 fake, ntrades, err = s.levels[idx].fakeUncross(fake, checkWashTrades) 497 trades = append(trades, ntrades...) 498 // break if a wash trade is detected 499 if err != nil && err == ErrWashTrade { 500 break 501 } 502 503 if fake.Remaining != 0 { 504 obTrades, obOrders := s.uncrossOffbook(idx, fake, true) 505 trades = append(trades, obTrades...) 506 offbookOrders = append(offbookOrders, obOrders...) 507 } 508 509 // the orders are still part of the levels, so we just have to move on anyway 510 idx-- 511 } 512 513 return trades, err 514 } 515 516 // fakeUncrossAuction returns hypothetical trades if the order book side were to be uncrossed with the agg orders supplied, wash trades are allowed. 517 func (s *OrderBookSide) fakeUncrossAuction(orders []*types.Order) ([]*types.Trade, error) { 518 defer s.uncrossFinished() 519 // in here we iterate from the end, as it's easier to remove the 520 // price levels from the back of the slice instead of from the front 521 // also it will allow us to reduce allocations 522 nOrders := len(orders) 523 if nOrders == 0 { 524 return []*types.Trade{}, nil 525 } 526 527 checkPrice := func(levelPrice *num.Uint, order *types.Order) bool { 528 if order.Side == types.SideBuy { 529 return levelPrice.GT(order.Price) 530 } 531 return levelPrice.LT(order.Price) 532 } 533 534 var ( 535 ntrades []*types.Trade 536 iOrder = 0 537 trades []*types.Trade 538 lvl *PriceLevel 539 err error 540 fake *types.Order 541 ) 542 543 for ; iOrder < len(orders); iOrder++ { 544 fake = orders[iOrder].Clone() 545 ntrades, _ = s.uncrossOffbook(len(s.levels), fake, false) 546 trades = append(trades, ntrades...) 547 548 // no more to trade in this pre-orderbook region for AMM's, we now need to move to orderbook 549 if fake.Remaining != 0 { 550 break 551 } 552 } 553 554 if iOrder >= nOrders { 555 return trades, nil 556 } 557 558 for idx := len(s.levels) - 1; idx >= 0; idx-- { 559 // since all of uncrossOrders will be traded away and at the same uncrossing price 560 // iceberg orders are sent in as their full value instead of refreshing at each step 561 if fake.IcebergOrder != nil { 562 fake.Remaining += fake.IcebergOrder.ReservedRemaining 563 fake.IcebergOrder.ReservedRemaining = 0 564 } 565 566 haveOffbookVolume := true 567 568 // clone price level 569 lvl = clonePriceLevel(s.levels[idx]) 570 for lvl.volume > 0 || haveOffbookVolume { 571 // not a market order && buy side price is too low => continue 572 if fake.Type != types.OrderTypeMarket && checkPrice(lvl.price, fake) { 573 break 574 } 575 576 _, ntrades, _, err = lvl.uncross(fake, false) 577 if err != nil { 578 return nil, err 579 } 580 trades = append(trades, ntrades...) 581 582 if fake.Remaining != 0 { 583 ntrades, _ := s.uncrossOffbook(idx, fake, true) 584 trades = append(trades, ntrades...) 585 586 // if we couldn't consume the whole order with this AMM volume in this region 587 // we need to move onto the next orderbook price level 588 if fake.Remaining != 0 { 589 haveOffbookVolume = false 590 } 591 } 592 593 if fake.Remaining == 0 { 594 iOrder++ 595 if iOrder >= nOrders { 596 return trades, nil 597 } 598 fake = orders[iOrder].Clone() 599 } 600 } 601 } 602 return trades, nil 603 } 604 605 func clonePriceLevel(lvl *PriceLevel) *PriceLevel { 606 orders := make([]*types.Order, 0, len(lvl.orders)) 607 for _, o := range lvl.orders { 608 orders = append(orders, o.Clone()) 609 } 610 return &PriceLevel{ 611 price: lvl.price.Clone(), 612 orders: orders, 613 volume: lvl.volume, 614 } 615 } 616 617 // betweenLevels returns the inner, outer bounds for the given idx in the price levels. 618 // Usually this means (inner, outer) = (lvl[i].price, lvl[i-1].price) but we also handle 619 // the past the first and last price levels. 620 func (s *OrderBookSide) betweenLevels(idx int, last *num.Uint) (*num.Uint, *num.Uint) { 621 // there are no price levels, so between is from low to high 622 if len(s.levels) == 0 { 623 return nil, last 624 } 625 626 // we're at the first price level, we pass back nil for the first level since we do not know the bound for this 627 if idx == len(s.levels) { 628 return nil, s.levels[idx-1].price 629 } 630 631 // we're at the last price level 632 if idx == 0 { 633 return s.levels[0].price, last 634 } 635 return s.levels[idx].price, s.levels[idx-1].price 636 } 637 638 func (s *OrderBookSide) uncrossFinished() { 639 if s.offbook != nil { 640 s.offbook.NotifyFinished() 641 } 642 } 643 644 func (s *OrderBookSide) uncrossOffbook(idx int, agg *types.Order, fake bool) ([]*types.Trade, []*types.Order) { 645 if s.offbook == nil { 646 return nil, nil 647 } 648 649 // get the bounds between price levels for the given price level index 650 inner, outer := s.betweenLevels(idx, agg.Price) 651 652 // submit the order to the offbook source for volume between those bounds 653 orders := s.offbook.SubmitOrder(agg, inner, outer) 654 655 trades := make([]*types.Trade, 0, len(orders)) 656 for _, o := range orders { 657 size := min(agg.Remaining, o.Remaining) 658 trade := newTrade(agg, o, size) 659 agg.Remaining -= size 660 if !fake { 661 o.Remaining -= size 662 } 663 trades = append(trades, trade) 664 } 665 666 return trades, orders 667 } 668 669 // uncross returns trades after order book side gets uncrossed with the agg order supplied, 670 // checkWashTrades checks non-FOK orders for wash trades if set to true (FOK orders are always checked for wash trades). 671 func (s *OrderBookSide) uncross(agg *types.Order, checkWashTrades bool) ([]*types.Trade, []*types.Order, *num.Uint, error) { 672 var ( 673 trades []*types.Trade 674 impactedOrders []*types.Order 675 lastTradedPrice = num.UintZero() 676 totalVolumeToFill uint64 677 checkPrice func(*num.Uint) bool 678 ) 679 680 fake := agg.Clone() 681 682 if agg.Side == types.SideSell { 683 checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.GTE(agg.Price) } 684 } else { 685 checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.LTE(agg.Price) } 686 } 687 688 if agg.TimeInForce == types.OrderTimeInForceFOK { 689 _, oo := s.uncrossOffbook(len(s.levels), fake, true) 690 for _, order := range oo { 691 totalVolumeToFill += order.Remaining 692 } 693 694 // Process these backwards 695 for i := len(s.levels) - 1; i >= 0; i-- { 696 level := s.levels[i] 697 if checkPrice(level.price) || agg.Type == types.OrderTypeMarket || agg.Type == types.OrderTypeNetwork { 698 // We have to process every order to check for wash trades 699 for _, order := range level.orders { 700 // Check for wash trading 701 if agg.Party == order.Party { 702 // Stop the order and return 703 agg.Status = types.OrderStatusStopped 704 return nil, nil, lastTradedPrice, ErrWashTrade 705 } 706 // in case of network trades, we want to calculate an accurate average price to return 707 totalVolumeToFill += order.Remaining 708 709 _, oo := s.uncrossOffbook(i, fake, true) 710 for _, order := range oo { 711 totalVolumeToFill += order.Remaining 712 } 713 714 if totalVolumeToFill >= agg.Remaining { 715 break 716 } 717 } 718 } 719 if totalVolumeToFill >= agg.Remaining { 720 break 721 } 722 } 723 724 if s.log.GetLevel() == logging.DebugLevel { 725 s.log.Debug(fmt.Sprintf("totalVolumeToFill %d until price %d, remaining %d\n", totalVolumeToFill, agg.Price, agg.Remaining)) 726 } 727 728 if totalVolumeToFill < agg.Remaining { 729 return trades, impactedOrders, lastTradedPrice, nil 730 } 731 732 // reset the offsource book so we can then do it all again.... 733 s.uncrossFinished() 734 } 735 736 var ( 737 idx = len(s.levels) - 1 738 filled bool 739 ntrades []*types.Trade 740 nimpact []*types.Order 741 err error 742 ) 743 744 // first check for off source volume between the best theoretical price and the first price level 745 trades, impactedOrders = s.uncrossOffbook(idx+1, agg, false) 746 filled = agg.Remaining == 0 747 748 // in here we iterate from the end, as it's easier to remove the 749 // price levels from the back of the slice instead of from the front 750 // also it will allow us to reduce allocations 751 for !filled && idx >= 0 { 752 if checkPrice(s.levels[idx].price) || agg.Type == types.OrderTypeMarket || agg.Type == types.OrderTypeNetwork { 753 filled, ntrades, nimpact, err = s.levels[idx].uncross(agg, checkWashTrades) 754 trades = append(trades, ntrades...) 755 impactedOrders = append(impactedOrders, nimpact...) 756 // break if a wash trade is detected 757 if err != nil && err == ErrWashTrade { 758 break 759 } 760 761 if !filled { 762 // now check for off source volume between the price levels 763 ot, oo := s.uncrossOffbook(idx, agg, false) 764 trades = append(trades, ot...) 765 impactedOrders = append(impactedOrders, oo...) 766 filled = agg.Remaining == 0 767 } 768 769 if len(s.levels[idx].orders) <= 0 { 770 idx-- 771 } 772 } else { 773 break 774 } 775 } 776 777 // now we nil the price levels that have been completely emptied out 778 // then we resize the slice 779 if idx < 0 || len(s.levels[idx].orders) > 0 { 780 // do not remove this one as it's not emptied already 781 idx++ 782 } 783 if idx < len(s.levels) { 784 // nil out the pricelevels so they get collected at some point 785 for i := idx; i < len(s.levels); i++ { 786 s.levels[i] = nil 787 } 788 s.levels = s.levels[:idx] 789 } 790 791 if agg.Type == types.OrderTypeNetwork { 792 totalPrice := num.UintZero() 793 for _, t := range trades { 794 // totalPrice += t.Price * t.Size 795 totalPrice.Add( 796 totalPrice, 797 num.UintZero().Mul(t.Price, num.NewUint(t.Size)), 798 ) 799 } 800 // now we are done with uncrossing, 801 // we can set back the price of the netorder to the average 802 // price over the whole volume 803 // agg.Price = totalPrice / agg.Size 804 agg.Price.Div(totalPrice, num.NewUint(agg.Size)) 805 } 806 807 if len(trades) > 0 { 808 lastTradedPrice = trades[len(trades)-1].Price.Clone() 809 } 810 return trades, impactedOrders, lastTradedPrice, err 811 } 812 813 func (s *OrderBookSide) getLevels() []*PriceLevel { 814 return s.levels 815 } 816 817 func (s *OrderBookSide) getOrderCount() int64 { 818 var orderCount int64 819 for _, level := range s.levels { 820 orderCount = orderCount + int64(len(level.orders)) 821 } 822 return orderCount 823 } 824 825 func (s *OrderBookSide) getTotalVolume() int64 { 826 var volume int64 827 for _, level := range s.levels { 828 volume = volume + int64(level.volume) 829 } 830 return volume 831 }