code.vegaprotocol.io/vega@v0.79.0/datanode/entities/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 entities 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "time" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/types" 25 "code.vegaprotocol.io/vega/libs/num" 26 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 27 "code.vegaprotocol.io/vega/protos/vega" 28 29 "github.com/shopspring/decimal" 30 ) 31 32 type positionSettlement interface { 33 Price() *num.Uint 34 PositionFactor() num.Decimal 35 Trades() []events.TradeSettlement 36 TxHash() string 37 } 38 39 type lossSocialization interface { 40 Amount() *num.Int 41 TxHash() string 42 IsFunding() bool 43 } 44 45 type settleDistressed interface { 46 Margin() *num.Uint 47 TxHash() string 48 } 49 50 type settleMarket interface { 51 SettledPrice() *num.Uint 52 PositionFactor() num.Decimal 53 TxHash() string 54 } 55 56 type Position struct { 57 MarketID MarketID 58 PartyID PartyID 59 OpenVolume int64 60 RealisedPnl decimal.Decimal 61 UnrealisedPnl decimal.Decimal 62 AverageEntryPrice decimal.Decimal 63 AverageEntryMarketPrice decimal.Decimal 64 Loss decimal.Decimal // what the party lost because of loss socialization 65 Adjustment decimal.Decimal // what a party was missing which triggered loss socialization 66 TxHash TxHash 67 VegaTime time.Time 68 // keep track of trades that haven't been settled as separate fields 69 // these will be zeroed out once we process settlement events 70 PendingOpenVolume int64 71 PendingRealisedPnl decimal.Decimal 72 PendingUnrealisedPnl decimal.Decimal 73 PendingAverageEntryPrice decimal.Decimal 74 PendingAverageEntryMarketPrice decimal.Decimal 75 LossSocialisationAmount decimal.Decimal 76 DistressedStatus PositionStatus 77 TakerFeesPaid num.Decimal 78 MakerFeesReceived num.Decimal 79 FeesPaid num.Decimal 80 TakerFeesPaidSince num.Decimal 81 MakerFeesReceivedSince num.Decimal 82 FeesPaidSince num.Decimal 83 FundingPaymentAmount num.Decimal 84 FundingPaymentAmountSince num.Decimal 85 } 86 87 func NewEmptyPosition(marketID MarketID, partyID PartyID) Position { 88 return Position{ 89 MarketID: marketID, 90 PartyID: partyID, 91 OpenVolume: 0, 92 RealisedPnl: num.DecimalZero(), 93 UnrealisedPnl: num.DecimalZero(), 94 AverageEntryPrice: num.DecimalZero(), 95 AverageEntryMarketPrice: num.DecimalZero(), 96 Loss: num.DecimalZero(), 97 Adjustment: num.DecimalZero(), 98 PendingOpenVolume: 0, 99 PendingRealisedPnl: num.DecimalZero(), 100 PendingUnrealisedPnl: num.DecimalZero(), 101 PendingAverageEntryPrice: num.DecimalZero(), 102 PendingAverageEntryMarketPrice: num.DecimalZero(), 103 LossSocialisationAmount: num.DecimalZero(), 104 DistressedStatus: PositionStatusUnspecified, 105 TakerFeesPaid: num.DecimalZero(), 106 MakerFeesReceived: num.DecimalZero(), 107 FeesPaid: num.DecimalZero(), 108 TakerFeesPaidSince: num.DecimalZero(), 109 MakerFeesReceivedSince: num.DecimalZero(), 110 FeesPaidSince: num.DecimalZero(), 111 FundingPaymentAmount: num.DecimalZero(), 112 FundingPaymentAmountSince: num.DecimalZero(), 113 } 114 } 115 116 func (p *Position) updateWithBadTrade(trade vega.Trade, seller bool, pf num.Decimal) { 117 size := int64(trade.Size) 118 if seller { 119 size *= -1 120 } 121 // update the open volume (not pending) directly, otherwise the settle position event resets the network position. 122 price, _ := num.UintFromString(trade.AssetPrice, 10) 123 mPrice, _ := num.UintFromString(trade.Price, 10) 124 125 openedVolume, closedVolume := CalculateOpenClosedVolume(p.OpenVolume, size) 126 realisedPnlDelta := num.DecimalFromUint(price).Sub(p.AverageEntryPrice).Mul(num.DecimalFromInt64(closedVolume)).Div(pf) 127 p.RealisedPnl = p.RealisedPnl.Add(realisedPnlDelta) 128 p.OpenVolume -= closedVolume 129 130 p.AverageEntryPrice = updateVWAP(p.AverageEntryPrice, p.OpenVolume, openedVolume, price) 131 p.AverageEntryMarketPrice = updateVWAP(p.AverageEntryMarketPrice, p.OpenVolume, openedVolume, mPrice) 132 p.OpenVolume += openedVolume 133 // no MTM - this isn't a settlement event, we're just adding the trade adding distressed volume to network 134 // for the same reason, no syncPending call. 135 } 136 137 func (p *Position) UpdateWithTrade(trade vega.Trade, seller bool, pf num.Decimal) { 138 // we have to ensure that we know the price/position factor 139 size := int64(trade.Size) 140 if seller { 141 size *= -1 142 } 143 // add fees paid/received 144 fees := getFeeAmountsForSide(&trade, seller) 145 maker, taker, other := num.DecimalFromUint(fees.maker), num.DecimalFromUint(fees.taker), num.DecimalFromUint(fees.other) 146 p.MakerFeesReceived = p.MakerFeesReceived.Add(maker) 147 p.TakerFeesPaid = p.TakerFeesPaid.Add(taker) 148 p.FeesPaid = p.FeesPaid.Add(other) 149 // check if we should reset the "since" fields for fees 150 since := p.PendingOpenVolume == 0 151 // close out trade doesn't require the MTM calculation to be performed 152 // the distressed trader will be handled through a settle distressed event, the network 153 // open volume should just be updated, the average entry price is unchanged. 154 assetPrice, _ := num.DecimalFromString(trade.AssetPrice) 155 marketPrice, _ := num.DecimalFromString(trade.Price) 156 157 // Scale the trade to the correct size 158 opened, closed := CalculateOpenClosedVolume(p.PendingOpenVolume, size) 159 realisedPnlDelta := assetPrice.Sub(p.PendingAverageEntryPrice).Mul(num.DecimalFromInt64(closed)).Div(pf) 160 p.PendingRealisedPnl = p.PendingRealisedPnl.Add(realisedPnlDelta) 161 // did we start with a positive/negative position? 162 pos := p.PendingOpenVolume > 0 163 p.PendingOpenVolume -= closed 164 165 marketPriceUint, _ := num.UintFromDecimal(marketPrice) 166 assetPriceUint, _ := num.UintFromDecimal(assetPrice) 167 168 p.PendingAverageEntryPrice = updateVWAP(p.PendingAverageEntryPrice, p.PendingOpenVolume, opened, assetPriceUint) 169 p.PendingAverageEntryMarketPrice = updateVWAP(p.PendingAverageEntryMarketPrice, p.PendingOpenVolume, opened, marketPriceUint) 170 p.PendingOpenVolume += opened 171 // either the position is no longer 0, or the position has flipped sides (and is non-zero) 172 if since || (pos != (p.PendingOpenVolume > 0) && p.PendingOpenVolume != 0) { 173 p.MakerFeesReceivedSince = num.DecimalZero() 174 p.TakerFeesPaidSince = num.DecimalZero() 175 p.FeesPaidSince = num.DecimalZero() 176 } 177 if p.PendingOpenVolume != 0 { 178 // running total of fees paid since get incremented 179 p.MakerFeesReceivedSince = p.MakerFeesReceivedSince.Add(maker) 180 p.TakerFeesPaidSince = p.TakerFeesPaidSince.Add(taker) 181 p.FeesPaidSince = p.FeesPaidSince.Add(other) 182 } 183 p.pendingMTM(assetPrice, pf) 184 if trade.Type == types.TradeTypeNetworkCloseOutBad { 185 p.updateWithBadTrade(trade, seller, pf) 186 } else if p.DistressedStatus == PositionStatusClosedOut { 187 // Not a closeout trade, but the position is currently still marked as distressed. 188 // This indicates the party was closed out previously, but has topped up and opened a new position. 189 p.DistressedStatus = PositionStatusUnspecified 190 } 191 } 192 193 func (p *Position) ApplyFundingPayment(amount *num.Int) { 194 amt := num.DecimalFromInt(amount) 195 p.FundingPaymentAmount = p.FundingPaymentAmount.Add(amt) 196 p.FundingPaymentAmountSince = p.FundingPaymentAmountSince.Add(amt) 197 // da := num.DecimalFromInt(amount) 198 // p.PendingRealisedPnl = p.PendingRealisedPnl.Add(da) 199 // p.RealisedPnl = p.RealisedPnl.Add(da) 200 } 201 202 func (p *Position) UpdateOrdersClosed() { 203 p.DistressedStatus = PositionStatusOrdersClosed 204 } 205 206 func (p *Position) ToggleDistressedStatus() { 207 // if currently marked as distressed -> mark as safe 208 if p.DistressedStatus == PositionStatusDistressed { 209 p.DistressedStatus = PositionStatusUnspecified 210 return 211 } 212 // was safe, is now distressed 213 p.DistressedStatus = PositionStatusDistressed 214 } 215 216 func (p *Position) UpdateWithPositionSettlement(e positionSettlement) { 217 pf := e.PositionFactor() 218 resetFP := false 219 for _, t := range e.Trades() { 220 if p.OpenVolume == 0 { 221 resetFP = true 222 } 223 openedVolume, closedVolume := CalculateOpenClosedVolume(p.OpenVolume, t.Size()) 224 // Deal with any volume we have closed 225 realisedPnlDelta := num.DecimalFromUint(t.Price()).Sub(p.AverageEntryPrice).Mul(num.DecimalFromInt64(closedVolume)).Div(pf) 226 p.RealisedPnl = p.RealisedPnl.Add(realisedPnlDelta) 227 pos := p.OpenVolume > 0 228 p.OpenVolume -= closedVolume 229 230 // Then with any we have opened 231 p.AverageEntryPrice = updateVWAP(p.AverageEntryPrice, p.OpenVolume, openedVolume, t.Price()) 232 p.AverageEntryMarketPrice = updateVWAP(p.AverageEntryMarketPrice, p.OpenVolume, openedVolume, t.MarketPrice()) 233 p.OpenVolume += openedVolume 234 // check if position flipped 235 if !resetFP && (pos != (p.OpenVolume > 0) && p.OpenVolume != 0) { 236 resetFP = true 237 } 238 } 239 if resetFP { 240 p.FundingPaymentAmountSince = num.DecimalZero() 241 } 242 p.mtm(e.Price(), pf) 243 p.TxHash = TxHash(e.TxHash()) 244 p.syncPending() 245 } 246 247 func (p *Position) syncPending() { 248 // update pending fields to match current ones 249 p.PendingOpenVolume = p.OpenVolume 250 p.PendingRealisedPnl = p.RealisedPnl 251 p.PendingUnrealisedPnl = p.UnrealisedPnl 252 p.PendingAverageEntryPrice = p.AverageEntryPrice 253 p.PendingAverageEntryMarketPrice = p.AverageEntryMarketPrice 254 } 255 256 func (p *Position) UpdateWithLossSocialization(e lossSocialization) { 257 amountLoss := num.DecimalFromInt(e.Amount()) 258 259 if amountLoss.IsNegative() { 260 p.Loss = p.Loss.Add(amountLoss) 261 p.LossSocialisationAmount = p.LossSocialisationAmount.Sub(amountLoss) 262 } else { 263 p.Adjustment = p.Adjustment.Add(amountLoss) 264 p.LossSocialisationAmount = p.LossSocialisationAmount.Add(amountLoss) 265 } 266 if e.IsFunding() { 267 // adjust if this is a loss socialisation resulting from a funding payment settlement. 268 p.FundingPaymentAmount = p.FundingPaymentAmount.Add(amountLoss) 269 p.FundingPaymentAmountSince = p.FundingPaymentAmountSince.Add(amountLoss) 270 } 271 272 p.RealisedPnl = p.RealisedPnl.Add(amountLoss) 273 p.TxHash = TxHash(e.TxHash()) 274 p.syncPending() 275 } 276 277 func (p *Position) UpdateWithSettleDistressed(e settleDistressed) { 278 margin := num.DecimalFromUint(e.Margin()) 279 p.RealisedPnl = p.RealisedPnl.Add(p.UnrealisedPnl) 280 p.RealisedPnl = p.RealisedPnl.Sub(margin) // realised P&L includes whatever we had in margin account at this point 281 p.UnrealisedPnl = num.DecimalZero() 282 p.AverageEntryPrice = num.DecimalZero() // @TODO average entry price shouldn't be affected(?) 283 p.AverageEntryPrice = num.DecimalZero() 284 p.OpenVolume = 0 285 p.TxHash = TxHash(e.TxHash()) 286 p.DistressedStatus = PositionStatusClosedOut 287 p.FundingPaymentAmountSince = num.DecimalZero() 288 p.FeesPaidSince = num.DecimalZero() 289 p.MakerFeesReceivedSince = num.DecimalZero() 290 p.TakerFeesPaidSince = num.DecimalZero() 291 p.syncPending() 292 } 293 294 func (p *Position) UpdateWithSettleMarket(e settleMarket) { 295 markPriceDec := num.DecimalFromUint(e.SettledPrice()) 296 openVolumeDec := num.DecimalFromInt64(p.OpenVolume) 297 298 unrealisedPnl := openVolumeDec.Mul(markPriceDec.Sub(p.AverageEntryPrice)).Div(e.PositionFactor()) 299 p.RealisedPnl = p.RealisedPnl.Add(unrealisedPnl) 300 p.UnrealisedPnl = num.DecimalZero() 301 p.OpenVolume = 0 302 p.TxHash = TxHash(e.TxHash()) 303 p.syncPending() 304 } 305 306 func (p Position) ToProto() *vega.Position { 307 var timestamp int64 308 if !p.VegaTime.IsZero() { 309 timestamp = p.VegaTime.UnixNano() 310 } 311 // we use the pending values when converting to protos 312 // so trades are reflected as accurately as possible 313 return &vega.Position{ 314 MarketId: p.MarketID.String(), 315 PartyId: p.PartyID.String(), 316 OpenVolume: p.PendingOpenVolume, 317 RealisedPnl: p.PendingRealisedPnl.Round(0).String(), 318 UnrealisedPnl: p.PendingUnrealisedPnl.Round(0).String(), 319 AverageEntryPrice: p.PendingAverageEntryMarketPrice.Round(0).String(), 320 UpdatedAt: timestamp, 321 LossSocialisationAmount: p.LossSocialisationAmount.Round(0).String(), 322 PositionStatus: vega.PositionStatus(p.DistressedStatus), 323 TakerFeesPaid: p.TakerFeesPaid.String(), 324 MakerFeesReceived: p.MakerFeesReceived.String(), 325 FeesPaid: p.FeesPaid.String(), 326 TakerFeesPaidSince: p.TakerFeesPaidSince.String(), 327 MakerFeesReceivedSince: p.MakerFeesReceivedSince.String(), 328 FeesPaidSince: p.FeesPaidSince.String(), 329 FundingPaymentAmount: p.FundingPaymentAmount.String(), 330 FundingPaymentAmountSince: p.FundingPaymentAmountSince.String(), 331 } 332 } 333 334 func (p Position) ToProtoEdge(_ ...any) (*v2.PositionEdge, error) { 335 return &v2.PositionEdge{ 336 Node: p.ToProto(), 337 Cursor: p.Cursor().Encode(), 338 }, nil 339 } 340 341 func (p *Position) AverageEntryPriceUint() *num.Uint { 342 uint, overflow := num.UintFromDecimal(p.AverageEntryPrice) 343 if overflow { 344 panic("couldn't convert average entry price from decimal to uint") 345 } 346 return uint 347 } 348 349 func (p *Position) mtm(markPrice *num.Uint, positionFactor num.Decimal) { 350 if p.OpenVolume == 0 { 351 p.UnrealisedPnl = num.DecimalZero() 352 return 353 } 354 markPriceDec := num.DecimalFromUint(markPrice) 355 openVolumeDec := num.DecimalFromInt64(p.OpenVolume) 356 357 p.UnrealisedPnl = openVolumeDec.Mul(markPriceDec.Sub(p.AverageEntryPrice)).Div(positionFactor) 358 } 359 360 func (p *Position) pendingMTM(price, sf num.Decimal) { 361 if p.PendingOpenVolume == 0 { 362 p.PendingUnrealisedPnl = num.DecimalZero() 363 return 364 } 365 366 vol := num.DecimalFromInt64(p.PendingOpenVolume) 367 p.PendingUnrealisedPnl = vol.Mul(price.Sub(p.PendingAverageEntryPrice)).Div(sf) 368 } 369 370 func CalculateOpenClosedVolume(currentOpenVolume, tradedVolume int64) (int64, int64) { 371 if currentOpenVolume != 0 && ((currentOpenVolume > 0) != (tradedVolume > 0)) { 372 var closedVolume int64 373 if absUint64(tradedVolume) > absUint64(currentOpenVolume) { 374 closedVolume = currentOpenVolume 375 } else { 376 closedVolume = -tradedVolume 377 } 378 return tradedVolume + closedVolume, closedVolume 379 } 380 return tradedVolume, 0 381 } 382 383 func absUint64(v int64) uint64 { 384 if v < 0 { 385 v *= -1 386 } 387 return uint64(v) 388 } 389 390 func updateVWAP(vwap num.Decimal, volume int64, addVolume int64, addPrice *num.Uint) num.Decimal { 391 if volume+addVolume == 0 { 392 return num.DecimalZero() 393 } 394 395 volumeDec := num.DecimalFromInt64(volume) 396 addVolumeDec := num.DecimalFromInt64(addVolume) 397 addPriceDec := num.DecimalFromUint(addPrice) 398 399 return vwap.Mul(volumeDec).Add(addPriceDec.Mul(addVolumeDec)).Div(volumeDec.Add(addVolumeDec)) 400 } 401 402 type PositionKey struct { 403 MarketID MarketID 404 PartyID PartyID 405 VegaTime time.Time 406 } 407 408 func (p Position) Cursor() *Cursor { 409 pc := PositionCursor{ 410 MarketID: p.MarketID, 411 PartyID: p.PartyID, 412 VegaTime: p.VegaTime, 413 } 414 415 return NewCursor(pc.String()) 416 } 417 418 func (p Position) Key() PositionKey { 419 return PositionKey{p.MarketID, p.PartyID, p.VegaTime} 420 } 421 422 var PositionColumns = []string{ 423 "market_id", "party_id", "open_volume", "realised_pnl", "unrealised_pnl", 424 "average_entry_price", "average_entry_market_price", "loss", "adjustment", "tx_hash", "vega_time", "pending_open_volume", 425 "pending_realised_pnl", "pending_unrealised_pnl", "pending_average_entry_price", "pending_average_entry_market_price", 426 "loss_socialisation_amount", "distressed_status", "taker_fees_paid", "maker_fees_received", "fees_paid", 427 "taker_fees_paid_since", "maker_fees_received_since", "fees_paid_since", "funding_payment_amount", "funding_payment_amount_since", 428 } 429 430 func (p Position) ToRow() []interface{} { 431 return []interface{}{ 432 p.MarketID, p.PartyID, p.OpenVolume, p.RealisedPnl, p.UnrealisedPnl, 433 p.AverageEntryPrice, p.AverageEntryMarketPrice, p.Loss, p.Adjustment, p.TxHash, p.VegaTime, p.PendingOpenVolume, 434 p.PendingRealisedPnl, p.PendingUnrealisedPnl, p.PendingAverageEntryPrice, p.PendingAverageEntryMarketPrice, 435 p.LossSocialisationAmount, p.DistressedStatus, p.TakerFeesPaid, p.MakerFeesReceived, p.FeesPaid, 436 p.TakerFeesPaidSince, p.MakerFeesReceivedSince, p.FeesPaidSince, p.FundingPaymentAmount, p.FundingPaymentAmountSince, 437 } 438 } 439 440 func (p Position) Equal(q Position) bool { 441 return p.MarketID == q.MarketID && 442 p.PartyID == q.PartyID && 443 p.OpenVolume == q.OpenVolume && 444 p.RealisedPnl.Equal(q.RealisedPnl) && 445 p.UnrealisedPnl.Equal(q.UnrealisedPnl) && 446 p.AverageEntryPrice.Equal(q.AverageEntryPrice) && 447 p.AverageEntryMarketPrice.Equal(q.AverageEntryMarketPrice) && 448 p.Loss.Equal(q.Loss) && 449 p.Adjustment.Equal(q.Adjustment) && 450 p.TxHash == q.TxHash && 451 p.VegaTime.Equal(q.VegaTime) && 452 p.PendingOpenVolume == q.PendingOpenVolume && 453 p.PendingAverageEntryPrice.Equal(q.PendingAverageEntryPrice) && 454 p.PendingAverageEntryMarketPrice.Equal(q.PendingAverageEntryMarketPrice) && 455 p.PendingRealisedPnl.Equal(q.PendingRealisedPnl) && 456 p.PendingUnrealisedPnl.Equal(q.PendingUnrealisedPnl) 457 // p.PendingUnrealisedPnl.Equal(q.PendingUnrealisedPnl) && 458 // loss socialisation amount doesn't seem to work currently 459 // p.LossSocialisationAmount.Equal(q.LossSocialisationAmount) && 460 // p.DistressedStatus == q.DistressedStatus 461 } 462 463 type PositionCursor struct { 464 VegaTime time.Time `json:"vega_time"` 465 PartyID PartyID `json:"party_id"` 466 MarketID MarketID `json:"market_id"` 467 } 468 469 func (rc PositionCursor) String() string { 470 bs, err := json.Marshal(rc) 471 if err != nil { 472 // This should never happen. 473 panic(fmt.Errorf("could not marshal order cursor: %w", err)) 474 } 475 return string(bs) 476 } 477 478 func (rc *PositionCursor) Parse(cursorString string) error { 479 if cursorString == "" { 480 return nil 481 } 482 return json.Unmarshal([]byte(cursorString), rc) 483 }