code.vegaprotocol.io/vega@v0.79.0/core/positions/market_position.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 positions
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"math"
    22  
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  	"code.vegaprotocol.io/vega/logging"
    26  )
    27  
    28  // MarketPosition represents the position of a party inside a market.
    29  type MarketPosition struct {
    30  	// Actual volume
    31  	size int64
    32  	// Potential volume (orders not yet accepted/rejected)
    33  	buy, sell int64
    34  
    35  	partyID string
    36  	price   *num.Uint
    37  
    38  	// sum of size*price for party's buy/sell orders
    39  	buySumProduct, sellSumProduct *num.Uint
    40  
    41  	// this doesn't have to be included in checkpoints or snapshots
    42  	// yes, it's technically state, but the main reason for this field is to cut down on the number
    43  	// of events we send out.
    44  	distressed bool
    45  
    46  	averageEntryPrice *num.Uint
    47  }
    48  
    49  func NewMarketPosition(party string) *MarketPosition {
    50  	return &MarketPosition{
    51  		partyID:           party,
    52  		price:             num.UintZero(),
    53  		buySumProduct:     num.UintZero(),
    54  		sellSumProduct:    num.UintZero(),
    55  		averageEntryPrice: num.UintZero(),
    56  	}
    57  }
    58  
    59  func (p MarketPosition) Clone() *MarketPosition {
    60  	cpy := p
    61  	cpy.price = p.price.Clone()
    62  	cpy.buySumProduct = p.buySumProduct.Clone()
    63  	cpy.sellSumProduct = p.sellSumProduct.Clone()
    64  	cpy.averageEntryPrice = p.averageEntryPrice.Clone()
    65  	return &cpy
    66  }
    67  
    68  func (p *MarketPosition) Closed() bool {
    69  	// p.size can be negative
    70  	// p.buy and p.sell can be only positive
    71  	return p.size == 0 && p.buy+p.sell == 0
    72  }
    73  
    74  // UpdateInPlaceOnTrades takes a clone of the receiver position, and updates it with the position from the given trades.
    75  func (p *MarketPosition) UpdateInPlaceOnTrades(log *logging.Logger, traderSide types.Side, trades []*types.Trade, order *types.Order) *MarketPosition {
    76  	isMO := order.Type == types.OrderTypeMarket
    77  	pos := p.Clone()
    78  	for _, t := range trades {
    79  		if isMO {
    80  			pos.price = CalcVWAP(pos.price, pos.size, int64(t.Size), t.Price)
    81  		}
    82  		pos.averageEntryPrice = CalcVWAP(pos.averageEntryPrice, pos.size, int64(t.Size), t.Price)
    83  		if traderSide == types.SideBuy {
    84  			pos.size += int64(t.Size)
    85  		} else {
    86  			pos.size -= int64(t.Size)
    87  		}
    88  		// if we bought then we want to decrease the order size for this side so add=false
    89  		// and vice versa for sell
    90  		pos.UpdateOnOrderChange(log, traderSide, order.Price, t.Size, false)
    91  	}
    92  	return pos
    93  }
    94  
    95  func (p *MarketPosition) SetParty(party string) { p.partyID = party }
    96  
    97  func (p *MarketPosition) RegisterOrder(log *logging.Logger, order *types.Order) {
    98  	p.UpdateOnOrderChange(log, order.Side, order.Price, order.TrueRemaining(), true)
    99  }
   100  
   101  func (p *MarketPosition) UnregisterOrder(log *logging.Logger, order *types.Order) {
   102  	p.UpdateOnOrderChange(log, order.Side, order.Price, order.TrueRemaining(), false)
   103  }
   104  
   105  func (p *MarketPosition) UpdateOnOrderChange(log *logging.Logger, side types.Side, price *num.Uint, sizeChange uint64, add bool) {
   106  	if sizeChange == 0 {
   107  		return
   108  	}
   109  	iSizeChange := int64(sizeChange)
   110  	if side == types.SideBuy {
   111  		if !add && p.buy < iSizeChange {
   112  			log.Panic("cannot unregister order with potential buy + size change < 0",
   113  				logging.Int64("potential-buy", p.buy),
   114  				logging.Uint64("size-change", sizeChange))
   115  		}
   116  
   117  		if add && p.buy > math.MaxInt64-iSizeChange {
   118  			log.Panic("order too large to register, will overflow",
   119  				logging.Int64("potential-buy", p.buy),
   120  				logging.Uint64("size-change", sizeChange))
   121  		}
   122  
   123  		// recalculate sumproduct
   124  		if add {
   125  			p.buySumProduct.Add(p.buySumProduct, num.UintZero().Mul(price, num.NewUint(sizeChange)))
   126  			p.buy += iSizeChange
   127  		} else {
   128  			p.buySumProduct.Sub(p.buySumProduct, num.UintZero().Mul(price, num.NewUint(sizeChange)))
   129  			p.buy -= iSizeChange
   130  		}
   131  		if p.buy == 0 && !p.buySumProduct.IsZero() {
   132  			log.Panic("Non-zero buy sum-product with no buy orders",
   133  				logging.PartyID(p.partyID),
   134  				logging.BigUint("buy-sum-product", p.buySumProduct))
   135  		}
   136  		return
   137  	}
   138  
   139  	if !add && p.sell < iSizeChange {
   140  		log.Panic("cannot unregister order with potential sell + size change < 0",
   141  			logging.Int64("potential-sell", p.sell),
   142  			logging.Uint64("size-change", sizeChange))
   143  	}
   144  
   145  	if add && p.sell > math.MaxInt64-iSizeChange {
   146  		log.Panic("order too large to register, will overflow",
   147  			logging.Int64("potential-sell", p.sell),
   148  			logging.Uint64("size-change", sizeChange))
   149  	}
   150  
   151  	// recalculate sumproduct
   152  	if add {
   153  		p.sellSumProduct.Add(p.sellSumProduct, num.UintZero().Mul(price, num.NewUint(sizeChange)))
   154  		p.sell += iSizeChange
   155  	} else {
   156  		p.sellSumProduct.Sub(p.sellSumProduct, num.UintZero().Mul(price, num.NewUint(sizeChange)))
   157  		p.sell -= iSizeChange
   158  	}
   159  	if p.sell == 0 && !p.sellSumProduct.IsZero() {
   160  		log.Panic("Non-zero sell sum-product with no sell orders",
   161  			logging.PartyID(p.partyID),
   162  			logging.BigUint("sell-sum-product", p.sellSumProduct))
   163  	}
   164  }
   165  
   166  // AmendOrder unregisters the original order and then registers the newly amended order
   167  // this method is a quicker way of handling separate unregister+register pairs.
   168  func (p *MarketPosition) AmendOrder(log *logging.Logger, originalOrder, newOrder *types.Order) {
   169  	switch originalOrder.Side {
   170  	case types.SideBuy:
   171  		if uint64(p.buy) < originalOrder.TrueRemaining() {
   172  			log.Panic("cannot amend order with remaining > potential buy",
   173  				logging.Order(*originalOrder),
   174  				logging.Int64("potential-buy", p.buy))
   175  		}
   176  	case types.SideSell:
   177  		if uint64(p.sell) < originalOrder.TrueRemaining() {
   178  			log.Panic("cannot amend order with remaining > potential sell",
   179  				logging.Order(*originalOrder),
   180  				logging.Int64("potential-sell", p.sell))
   181  		}
   182  	}
   183  
   184  	p.UnregisterOrder(log, originalOrder)
   185  	p.RegisterOrder(log, newOrder)
   186  }
   187  
   188  // String returns a string representation of a market.
   189  func (p MarketPosition) String() string {
   190  	return fmt.Sprintf("size:%v, buy:%v, sell:%v, price:%v, partyID:%v",
   191  		p.size, p.buy, p.sell, p.price, p.partyID)
   192  }
   193  
   194  // AverageEntryPrice returns the volume weighted average price.
   195  func (p MarketPosition) AverageEntryPrice() *num.Uint {
   196  	return p.averageEntryPrice
   197  }
   198  
   199  // Buy will returns the potential buys for a given position.
   200  func (p MarketPosition) Buy() int64 {
   201  	return p.buy
   202  }
   203  
   204  // Sell returns the potential sells for the position.
   205  func (p MarketPosition) Sell() int64 {
   206  	return p.sell
   207  }
   208  
   209  // Size returns the current size of the position.
   210  func (p MarketPosition) Size() int64 {
   211  	return p.size
   212  }
   213  
   214  // Party returns the party to which this positions is associated.
   215  func (p MarketPosition) Party() string {
   216  	return p.partyID
   217  }
   218  
   219  // Price returns the current price for this position.
   220  func (p MarketPosition) Price() *num.Uint {
   221  	if p.price != nil {
   222  		return p.price.Clone()
   223  	}
   224  	return num.UintZero()
   225  }
   226  
   227  // BuySumProduct - get sum of size * price of party's buy orders.
   228  func (p MarketPosition) BuySumProduct() *num.Uint {
   229  	if p.buySumProduct != nil {
   230  		return p.buySumProduct.Clone()
   231  	}
   232  	return num.UintZero()
   233  }
   234  
   235  // SellSumProduct - get sum of size * price of party's sell orders.
   236  func (p MarketPosition) SellSumProduct() *num.Uint {
   237  	if p.sellSumProduct != nil {
   238  		return p.sellSumProduct.Clone()
   239  	}
   240  	return num.UintZero()
   241  }
   242  
   243  // VWBuy - get volume weighted buy price for unmatched buy orders.
   244  func (p MarketPosition) VWBuy() *num.Uint {
   245  	if p.buySumProduct != nil && p.buy != 0 {
   246  		vol := num.NewUint(uint64(p.buy))
   247  		return vol.Div(p.buySumProduct, vol)
   248  	}
   249  	return num.UintZero()
   250  }
   251  
   252  // VWSell - get volume weighted sell price for unmatched sell orders.
   253  func (p MarketPosition) VWSell() *num.Uint {
   254  	if p.sellSumProduct != nil && p.sell != 0 {
   255  		vol := num.NewUint(uint64(p.sell))
   256  		return vol.Div(p.sellSumProduct, vol)
   257  	}
   258  	return num.UintZero()
   259  }
   260  
   261  // ValidateOrder returns an error is the order is so large that the position engine does not have the precision
   262  // to register it.
   263  func (p MarketPosition) ValidateOrderRegistration(s uint64, side types.Side) error {
   264  	size := int64(s)
   265  	if size == 0 {
   266  		return nil
   267  	}
   268  
   269  	// check that the cast to int64 hasn't pushed it backwards
   270  	if size < 0 {
   271  		return errors.New("cannot register position without causing overflow")
   272  	}
   273  
   274  	amt := p.buy
   275  	if side == types.SideSell {
   276  		amt = p.sell
   277  	}
   278  
   279  	if size > math.MaxInt64-amt {
   280  		return errors.New("cannot register position without causing overflow")
   281  	}
   282  
   283  	return nil
   284  }
   285  
   286  func (p MarketPosition) OrderReducesExposure(ord *types.Order) bool {
   287  	if ord == nil || p.Size() == 0 || ord.PeggedOrder != nil {
   288  		return false
   289  	}
   290  	// long position and short order
   291  	if p.Size() > 0 && ord.Side == types.SideSell {
   292  		// market order reduces exposure and doesn't flip position to the other side
   293  		if p.Size()-int64(ord.Remaining) >= 0 && ord.Type == types.OrderTypeMarket {
   294  			return true
   295  		}
   296  		// sum of all short limit orders wouldn't flip the position if filled (ord already included in pos)
   297  		if p.Size()-p.Sell() >= 0 && ord.Type == types.OrderTypeLimit {
   298  			return true
   299  		}
   300  	}
   301  	// short position and long order
   302  	if p.Size() < 0 && ord.Side == types.SideBuy {
   303  		// market order reduces exposure and doesn't flip position to the other side
   304  		if p.Size()+int64(ord.Remaining) <= 0 && ord.Type == types.OrderTypeMarket {
   305  			return true
   306  		}
   307  		// sum of all long limit orders wouldn't flip the position if filled (ord already included in pos)
   308  		if p.Size()+p.Buy() <= 0 && ord.Type == types.OrderTypeLimit {
   309  			return true
   310  		}
   311  	}
   312  	return false
   313  }
   314  
   315  // OrderReducesOnlyExposure returns true if the order reduce the position and the extra size if it was to flip the position side.
   316  func (p MarketPosition) OrderReducesOnlyExposure(ord *types.Order) (reduce bool, extraSize uint64) {
   317  	// if already closed, or increasing position, we shortcut
   318  	if p.Size() == 0 || (p.Size() < 0 && ord.Side == types.SideSell) || (p.Size() > 0 && ord.Side == types.SideBuy) {
   319  		return false, 0
   320  	}
   321  
   322  	size := p.Size()
   323  	if size < 0 {
   324  		size = -size
   325  	}
   326  	if extraSizeI := size - int64(ord.Remaining); extraSizeI < 0 {
   327  		return true, uint64(-extraSizeI)
   328  	}
   329  	return true, 0
   330  }