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 }