code.vegaprotocol.io/vega@v0.79.0/core/execution/future/special_orders.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  //lint:file-ignore U1000 Ignore unused functions
    17  
    18  package future
    19  
    20  import (
    21  	"context"
    22  	"sort"
    23  
    24  	"code.vegaprotocol.io/vega/core/events"
    25  	"code.vegaprotocol.io/vega/core/execution/common"
    26  	"code.vegaprotocol.io/vega/core/metrics"
    27  	"code.vegaprotocol.io/vega/core/types"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/logging"
    30  )
    31  
    32  func (m *Market) repricePeggedOrders(
    33  	ctx context.Context,
    34  	changes uint8,
    35  ) (parked []*types.Order, toSubmit []*types.Order) {
    36  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "repricePeggedOrders")
    37  
    38  	// Go through *all* of the pegged orders and remove from the order book
    39  	// NB: this is getting all of the pegged orders that are unparked in the order book AND all
    40  	// the parked pegged orders.
    41  	allPeggedIDs := m.matching.GetActivePeggedOrderIDs()
    42  	allPeggedIDs = append(allPeggedIDs, m.peggedOrders.GetParkedIDs()...)
    43  	for _, oid := range allPeggedIDs {
    44  		var (
    45  			order *types.Order
    46  			err   error
    47  		)
    48  		if m.peggedOrders.IsParked(oid) {
    49  			order = m.peggedOrders.GetParkedByID(oid)
    50  		} else {
    51  			order, err = m.matching.GetOrderByID(oid)
    52  			if err != nil {
    53  				m.log.Panic("if order is not parked, it should be on the book", logging.OrderID(oid))
    54  			}
    55  		}
    56  		if common.OrderReferenceCheck(*order).HasMoved(changes) {
    57  			// First if the order isn't parked, then
    58  			// we will just remove if from the orderbook
    59  			if order.Status != types.OrderStatusParked {
    60  				// Remove order if any volume remains,
    61  				// otherwise it's already been popped by the matching engine.
    62  				cancellation, err := m.matching.CancelOrder(order)
    63  				if cancellation == nil || err != nil {
    64  					m.log.Panic("Failure after cancel order from matching engine",
    65  						logging.Order(*order),
    66  						logging.Error(err))
    67  				}
    68  
    69  				// Remove it from the party position
    70  				_ = m.position.UnregisterOrder(ctx, order)
    71  			} else {
    72  				// unpark before it's reparked next eventually
    73  				m.peggedOrders.Unpark(order.ID)
    74  			}
    75  
    76  			if price, err := m.getNewPeggedPrice(order); err != nil {
    77  				// Failed to reprice, we need to park again
    78  				order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
    79  				order.Status = types.OrderStatusParked
    80  				order.Price = num.UintZero()
    81  				order.OriginalPrice = nil
    82  				m.broker.Send(events.NewOrderEvent(ctx, order))
    83  				parked = append(parked, order)
    84  			} else {
    85  				// Repriced so all good make sure status is correct
    86  				order.Price = price.Clone()
    87  				order.OriginalPrice, _ = num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
    88  				order.Status = types.OrderStatusActive
    89  				order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
    90  				toSubmit = append(toSubmit, order)
    91  			}
    92  		}
    93  	}
    94  
    95  	timer.EngineTimeCounterAdd()
    96  
    97  	return parked, toSubmit
    98  }
    99  
   100  func (m *Market) reSubmitPeggedOrders(
   101  	ctx context.Context,
   102  	toSubmitOrders []*types.Order,
   103  ) ([]*types.Order, map[string]events.MarketPosition) {
   104  	var (
   105  		partiesPos    = map[string]events.MarketPosition{}
   106  		updatedOrders = []*types.Order{}
   107  		evts          = []events.Event{}
   108  	)
   109  
   110  	// Reinsert all the orders
   111  	for _, order := range toSubmitOrders {
   112  		m.matching.ReSubmitSpecialOrders(order)
   113  		partiesPos[order.Party] = m.position.RegisterOrder(ctx, order)
   114  		updatedOrders = append(updatedOrders, order)
   115  		evts = append(evts, events.NewOrderEvent(ctx, order))
   116  	}
   117  
   118  	// send new order events
   119  	m.broker.SendBatch(evts)
   120  
   121  	return updatedOrders, partiesPos
   122  }
   123  
   124  func (m *Market) repriceAllSpecialOrders(
   125  	ctx context.Context,
   126  	changes uint8,
   127  	orderUpdates []*types.Order,
   128  ) []*types.Order {
   129  	if changes == 0 && len(orderUpdates) <= 0 {
   130  		// nothing to do, prices didn't move,
   131  		// no orders have been updated, there's no
   132  		// reason pegged order should get repriced
   133  		return nil
   134  	}
   135  
   136  	// first we get all the pegged orders to be resubmitted with a new price
   137  	var parked, toSubmit []*types.Order
   138  	if changes != 0 {
   139  		parked, toSubmit = m.repricePeggedOrders(ctx, changes)
   140  		for _, topark := range parked {
   141  			m.peggedOrders.Park(topark)
   142  		}
   143  	}
   144  
   145  	needsPeggedUpdates := len(parked) > 0 || len(toSubmit) > 0
   146  
   147  	if !needsPeggedUpdates && len(toSubmit) < 1 {
   148  		return nil
   149  	}
   150  
   151  	updatedOrders, partiesPos := m.reSubmitPeggedOrders(ctx, toSubmit)
   152  	risks, _, _ := m.updateMargins(ctx, partiesPos)
   153  	if len(risks) > 0 {
   154  		transfers, distressed, _, err := m.collateral.MarginUpdate(
   155  			ctx, m.GetID(), risks)
   156  		if err == nil && len(transfers) > 0 {
   157  			evt := events.NewLedgerMovements(ctx, transfers)
   158  			m.broker.Send(evt)
   159  		}
   160  		for _, p := range distressed {
   161  			distressedParty := p.Party()
   162  			for _, o := range updatedOrders {
   163  				if o.Party == distressedParty && o.Status == types.OrderStatusActive {
   164  					// cancel only the pegged orders, the reset will get picked up during regular closeout flow if need be
   165  					_, err := m.cancelOrder(ctx, distressedParty, o.ID)
   166  					if err != nil {
   167  						m.log.Panic("Failed to cancel order",
   168  							logging.Error(err),
   169  							logging.String("OrderID", o.ID))
   170  					}
   171  				}
   172  			}
   173  		}
   174  	}
   175  
   176  	return updatedOrders
   177  }
   178  
   179  func (m *Market) updateMargins(ctx context.Context, partiesPos map[string]events.MarketPosition) ([]events.Risk, []events.MarketPosition, map[string]*num.Uint) {
   180  	// an ordered list of positions
   181  	var (
   182  		positions     = make([]events.MarketPosition, 0, len(partiesPos))
   183  		marginsBefore = map[string]*num.Uint{}
   184  		id            = m.GetID()
   185  	)
   186  	// now we can check parties positions
   187  	for party, pos := range partiesPos {
   188  		if m.getMarginMode(pos.Party()) == types.MarginModeIsolatedMargin {
   189  			continue
   190  		}
   191  		positions = append(positions, pos)
   192  		mar, err := m.collateral.GetPartyMarginAccount(id, party, m.settlementAsset)
   193  		if err != nil {
   194  			m.log.Panic("party have position without a margin",
   195  				logging.MarketID(id),
   196  				logging.PartyID(party),
   197  			)
   198  		}
   199  		marginsBefore[party] = mar.Balance
   200  	}
   201  
   202  	sort.Slice(positions, func(i, j int) bool {
   203  		return positions[i].Party() < positions[j].Party()
   204  	})
   205  
   206  	// now we calculate all the new margins
   207  	return m.updateMargin(ctx, positions), positions, marginsBefore
   208  }
   209  
   210  func (m *Market) enterAuctionSpecialOrders(ctx context.Context) {
   211  	// First remove all GFN orders from the peg list.
   212  	ordersEvts := m.peggedOrders.EnterAuction(ctx)
   213  	m.broker.SendBatch(ordersEvts)
   214  
   215  	// Park all pegged orders
   216  	m.parkAllPeggedOrders(ctx)
   217  }