code.vegaprotocol.io/vega@v0.79.0/core/plugins/positions.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 plugins 17 18 import ( 19 "context" 20 "sync" 21 22 "code.vegaprotocol.io/vega/core/events" 23 "code.vegaprotocol.io/vega/core/subscribers" 24 "code.vegaprotocol.io/vega/core/types" 25 "code.vegaprotocol.io/vega/libs/num" 26 "code.vegaprotocol.io/vega/protos/vega" 27 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 28 29 "github.com/pkg/errors" 30 ) 31 32 var ErrMarketNotFound = errors.New("could not find market") 33 34 // FP FundingPaymentsEvent. 35 type FP interface { 36 events.Event 37 MarketID() string 38 IsParty(id string) bool 39 FundingPayments() *eventspb.FundingPayments 40 } 41 42 // SE SettleEvent - common denominator between SPE & SDE. 43 type SE interface { 44 events.Event 45 PartyID() string 46 MarketID() string 47 Price() *num.Uint 48 Timestamp() int64 49 } 50 51 // SPE SettlePositionEvent. 52 type SPE interface { 53 SE 54 PositionFactor() num.Decimal 55 Trades() []events.TradeSettlement 56 Timestamp() int64 57 } 58 59 // SDE SettleDistressedEvent. 60 type SDE interface { 61 SE 62 Margin() *num.Uint 63 Timestamp() int64 64 } 65 66 // LSE LossSocializationEvent. 67 type LSE interface { 68 events.Event 69 PartyID() string 70 MarketID() string 71 Amount() *num.Int 72 Timestamp() int64 73 IsFunding() bool 74 } 75 76 // DOC DistressedOrdersClosedEvent. 77 type DOC interface { 78 events.Event 79 MarketID() string 80 Parties() []string 81 } 82 83 // DPE DistressedPositionsEvent. 84 type DPE interface { 85 events.Event 86 MarketID() string 87 DistressedParties() []string 88 SafeParties() []string 89 } 90 91 // SME SettleMarketEvent. 92 type SME interface { 93 MarketID() string 94 SettledPrice() *num.Uint 95 PositionFactor() num.Decimal 96 TxHash() string 97 } 98 99 // TE TradeEvent. 100 type TE interface { 101 MarketID() string 102 IsParty(id string) bool // we don't use this one, but it's to make sure we identify the event correctly 103 Trade() vega.Trade 104 } 105 106 // Positions plugin taking settlement data to build positions API data. 107 type Positions struct { 108 *subscribers.Base 109 mu *sync.RWMutex 110 data map[string]map[string]Position 111 factors map[string]num.Decimal 112 } 113 114 func NewPositions(ctx context.Context) *Positions { 115 return &Positions{ 116 Base: subscribers.NewBase(ctx, 10, true), 117 mu: &sync.RWMutex{}, 118 data: map[string]map[string]Position{}, 119 factors: map[string]num.Decimal{}, 120 } 121 } 122 123 func (p *Positions) Push(evts ...events.Event) { 124 if len(evts) == 0 { 125 return 126 } 127 // lock here, because some of these events are sent in batch (if not all of them) 128 p.mu.Lock() 129 for _, e := range evts { 130 switch te := e.(type) { 131 case SPE: 132 p.updatePosition(te) 133 case SDE: 134 p.updateSettleDestressed(te) 135 case LSE: 136 p.applyLossSocialization(te) 137 case DOC: 138 p.applyDistressedOrders(te) 139 case DPE: 140 p.applyDistressedPositions(te) 141 case SME: 142 p.handleSettleMarket(te) 143 case FP: 144 p.handleFundingPayments(te) 145 case TE: 146 p.handleTradeEvent(te) 147 } 148 } 149 p.mu.Unlock() 150 } 151 152 func (p *Positions) handleRegularTrade(e TE) { 153 trade := e.Trade() 154 if trade.Type == types.TradeTypeNetworkCloseOutBad { 155 return 156 } 157 marketID := e.MarketID() 158 partyPos, ok := p.data[marketID] 159 if !ok { 160 // @TODO should this be done? 161 return 162 } 163 buyerFee, sellerFee := getFeeAmounts(&trade) 164 buyer, ok := partyPos[trade.Buyer] 165 if !ok { 166 buyer = Position{ 167 Position: types.NewPosition(marketID, trade.Buyer), 168 AverageEntryPriceFP: num.DecimalZero(), 169 RealisedPnlFP: num.DecimalZero(), 170 UnrealisedPnlFP: num.DecimalZero(), 171 } 172 } 173 buyer.setFees(buyerFee) 174 seller, ok := partyPos[trade.Seller] 175 if !ok { 176 seller = Position{ 177 Position: types.NewPosition(marketID, trade.Seller), 178 AverageEntryPriceFP: num.DecimalZero(), 179 RealisedPnlFP: num.DecimalZero(), 180 UnrealisedPnlFP: num.DecimalZero(), 181 } 182 } 183 seller.setFees(sellerFee) 184 partyPos[trade.Buyer] = buyer 185 partyPos[trade.Seller] = seller 186 p.data[marketID] = partyPos 187 } 188 189 // handle trade event closing distressed parties. 190 func (p *Positions) handleTradeEvent(e TE) { 191 trade := e.Trade() 192 if trade.Type != types.TradeTypeNetworkCloseOutBad { 193 p.handleRegularTrade(e) 194 return 195 } 196 marketID := e.MarketID() 197 partyPos, ok := p.data[marketID] 198 if !ok { 199 return 200 } 201 posFactor := num.DecimalOne() 202 // keep track of position factors 203 if pf, ok := p.factors[marketID]; ok { 204 posFactor = pf 205 } 206 mPrice, _ := num.UintFromString(trade.Price, 10) 207 markPriceDec := num.DecimalFromUint(mPrice) 208 size := int64(trade.Size) 209 pos, ok := partyPos[types.NetworkParty] 210 if !ok { 211 pos = Position{ 212 Position: types.NewPosition(marketID, types.NetworkParty), 213 AverageEntryPriceFP: num.DecimalZero(), 214 RealisedPnlFP: num.DecimalZero(), 215 UnrealisedPnlFP: num.DecimalZero(), 216 } 217 } 218 dParty := trade.Seller 219 networkFee, otherFee := getFeeAmounts(&trade) 220 if trade.Seller == types.NetworkParty { 221 size *= -1 222 dParty = trade.Buyer 223 networkFee, otherFee = otherFee, networkFee 224 } 225 other, ok := partyPos[dParty] 226 if !ok { 227 other = Position{ 228 Position: types.NewPosition(marketID, dParty), 229 AverageEntryPriceFP: num.DecimalZero(), 230 RealisedPnlFP: num.DecimalZero(), 231 UnrealisedPnlFP: num.DecimalZero(), 232 } 233 } 234 other.setFees(otherFee) 235 other.ResetSince() 236 pos.setFees(networkFee) 237 opened, closed := calculateOpenClosedVolume(pos.OpenVolume, size) 238 realisedPnlDelta := markPriceDec.Sub(pos.AverageEntryPriceFP).Mul(num.DecimalFromInt64(closed)).Div(posFactor) 239 pos.RealisedPnl = pos.RealisedPnl.Add(realisedPnlDelta) 240 pos.RealisedPnlFP = pos.RealisedPnlFP.Add(realisedPnlDelta) 241 // what was realised is no longer unrealised 242 pos.UnrealisedPnl = pos.UnrealisedPnl.Sub(realisedPnlDelta) 243 pos.UnrealisedPnlFP = pos.UnrealisedPnlFP.Sub(realisedPnlDelta) 244 pos.OpenVolume -= closed 245 246 pos.AverageEntryPriceFP = updateVWAP(pos.AverageEntryPriceFP, pos.OpenVolume, opened, mPrice) 247 pos.AverageEntryPrice, _ = num.UintFromDecimal(pos.AverageEntryPriceFP.Round(0)) 248 pos.OpenVolume += opened 249 mtm(&pos, mPrice, posFactor) 250 partyPos[types.NetworkParty] = pos 251 partyPos[dParty] = other 252 p.data[marketID] = partyPos 253 } 254 255 func (p *Positions) handleFundingPayments(e FP) { 256 marketID := e.MarketID() 257 partyPos, ok := p.data[marketID] 258 if !ok { 259 return 260 } 261 payments := e.FundingPayments().Payments 262 for _, pay := range payments { 263 pos, ok := partyPos[pay.PartyId] 264 if !ok { 265 continue 266 } 267 amt, _ := num.DecimalFromString(pay.Amount) 268 iAmt, _ := num.IntFromDecimal(amt) 269 pos.RealisedPnl = pos.RealisedPnl.Add(amt) 270 pos.RealisedPnlFP = pos.RealisedPnlFP.Add(amt) 271 // add funding totals 272 pos.FundingPaymentAmount.Add(iAmt) 273 pos.FundingPaymentAmountSince.Add(iAmt) 274 partyPos[pay.PartyId] = pos 275 } 276 p.data[marketID] = partyPos 277 } 278 279 func (p *Positions) applyDistressedPositions(e DPE) { 280 marketID := e.MarketID() 281 partyPos, ok := p.data[marketID] 282 if !ok { 283 return 284 } 285 for _, party := range e.DistressedParties() { 286 if pos, ok := partyPos[party]; ok { 287 pos.state = vega.PositionStatus_POSITION_STATUS_DISTRESSED 288 partyPos[party] = pos 289 } 290 } 291 for _, party := range e.SafeParties() { 292 if pos, ok := partyPos[party]; ok { 293 pos.state = vega.PositionStatus_POSITION_STATUS_UNSPECIFIED 294 partyPos[party] = pos 295 } 296 } 297 p.data[marketID] = partyPos 298 } 299 300 func (p *Positions) applyDistressedOrders(e DOC) { 301 marketID, parties := e.MarketID(), e.Parties() 302 partyPos, ok := p.data[marketID] 303 if !ok { 304 return 305 } 306 for _, party := range parties { 307 if pos, ok := partyPos[party]; ok { 308 pos.state = vega.PositionStatus_POSITION_STATUS_ORDERS_CLOSED 309 partyPos[party] = pos 310 } 311 } 312 p.data[marketID] = partyPos 313 } 314 315 func (p *Positions) applyLossSocialization(e LSE) { 316 iAmt := e.Amount() 317 marketID, partyID, amountLoss := e.MarketID(), e.PartyID(), num.DecimalFromInt(iAmt) 318 pos, ok := p.data[marketID][partyID] 319 if !ok { 320 return 321 } 322 if amountLoss.IsNegative() { 323 pos.loss = pos.loss.Add(amountLoss) 324 } else { 325 pos.adjustment = pos.adjustment.Add(amountLoss) 326 } 327 if e.IsFunding() { 328 // adjust funding amounts if needed. 329 pos.FundingPaymentAmount.Add(iAmt) 330 pos.FundingPaymentAmountSince.Add(iAmt) 331 } 332 pos.RealisedPnlFP = pos.RealisedPnlFP.Add(amountLoss) 333 pos.RealisedPnl = pos.RealisedPnl.Add(amountLoss) 334 335 pos.Position.UpdatedAt = e.Timestamp() 336 p.data[marketID][partyID] = pos 337 } 338 339 func (p *Positions) updatePosition(e SPE) { 340 mID, tID := e.MarketID(), e.PartyID() 341 if _, ok := p.data[mID]; !ok { 342 p.data[mID] = map[string]Position{} 343 } 344 calc, ok := p.data[mID][tID] 345 if !ok { 346 calc = seToProto(e) 347 } 348 updateSettlePosition(&calc, e) 349 calc.Position.UpdatedAt = e.Timestamp() 350 p.data[mID][tID] = calc 351 } 352 353 func (p *Positions) updateSettleDestressed(e SDE) { 354 mID, tID := e.MarketID(), e.PartyID() 355 if _, ok := p.data[mID]; !ok { 356 p.data[mID] = map[string]Position{} 357 } 358 calc, ok := p.data[mID][tID] 359 if !ok { 360 calc = seToProto(e) 361 } 362 margin := e.Margin() 363 calc.RealisedPnl = calc.RealisedPnl.Add(calc.UnrealisedPnl) 364 calc.RealisedPnlFP = calc.RealisedPnlFP.Add(calc.UnrealisedPnlFP) 365 calc.OpenVolume = 0 366 calc.UnrealisedPnl = num.DecimalZero() 367 calc.AverageEntryPrice = num.UintZero() 368 // realised P&L includes whatever we had in margin account at this point 369 dMargin := num.DecimalFromUint(margin) 370 calc.RealisedPnl = calc.RealisedPnl.Sub(dMargin) 371 calc.RealisedPnlFP = calc.RealisedPnlFP.Sub(dMargin) 372 // @TODO average entry price shouldn't be affected(?) 373 // the volume now is zero, though, so we'll end up moving this position to storage 374 calc.UnrealisedPnlFP = num.DecimalZero() 375 calc.AverageEntryPriceFP = num.DecimalZero() 376 calc.Position.UpdatedAt = e.Timestamp() 377 calc.state = vega.PositionStatus_POSITION_STATUS_CLOSED_OUT 378 p.data[mID][tID] = calc 379 } 380 381 func (p *Positions) handleSettleMarket(e SME) { 382 market := e.MarketID() 383 posFactor := e.PositionFactor() 384 // keep track of position factors 385 if _, ok := p.factors[market]; !ok { 386 p.factors[market] = posFactor 387 } 388 markPriceDec := num.DecimalFromUint(e.SettledPrice()) 389 mp, ok := p.data[market] 390 if !ok { 391 panic(ErrMarketNotFound) 392 } 393 for pid, pos := range mp { 394 openVolumeDec := num.DecimalFromInt64(pos.OpenVolume) 395 396 unrealisedPnl := openVolumeDec.Mul(markPriceDec.Sub(pos.AverageEntryPriceFP)).Div(posFactor).Round(0) 397 pos.RealisedPnl = pos.RealisedPnl.Add(unrealisedPnl) 398 pos.UnrealisedPnl = num.DecimalZero() 399 p.data[market][pid] = pos 400 } 401 } 402 403 // GetPositionsByMarketAndParty get the position of a single party in a given market. 404 func (p *Positions) GetPositionsByMarketAndParty(market, party string) (*types.Position, error) { 405 p.mu.RLock() 406 defer p.mu.RUnlock() 407 mp, ok := p.data[market] 408 if !ok { 409 return nil, nil 410 } 411 pos, ok := mp[party] 412 if !ok { 413 return nil, nil 414 } 415 return &pos.Position, nil 416 } 417 418 func (p *Positions) GetStateByMarketAndParty(market, party string) (vega.PositionStatus, error) { 419 p.mu.RLock() 420 defer p.mu.RUnlock() 421 mp, ok := p.data[market] 422 if !ok { 423 return vega.PositionStatus_POSITION_STATUS_UNSPECIFIED, nil 424 } 425 if pos, ok := mp[party]; ok { 426 return pos.state, nil 427 } 428 return vega.PositionStatus_POSITION_STATUS_UNSPECIFIED, nil 429 } 430 431 // GetPositionsByParty get all positions for a given party. 432 func (p *Positions) GetPositionsByParty(party string) ([]*types.Position, error) { 433 p.mu.RLock() 434 defer p.mu.RUnlock() 435 // at most, party is active in all markets 436 positions := make([]*types.Position, 0, len(p.data)) 437 for _, parties := range p.data { 438 if pos, ok := parties[party]; ok { 439 positions = append(positions, &pos.Position) 440 } 441 } 442 if len(positions) == 0 { 443 return nil, nil 444 // return nil, ErrPartyNotFound 445 } 446 return positions, nil 447 } 448 449 func (p *Positions) GetPositionStatesByParty(party string) ([]vega.PositionStatus, error) { 450 p.mu.RLock() 451 defer p.mu.RUnlock() 452 // max 1 state per market 453 states := make([]vega.PositionStatus, 0, len(p.data)) 454 for _, parties := range p.data { 455 if pos, ok := parties[party]; ok { 456 states = append(states, pos.state) 457 } 458 } 459 return states, nil 460 } 461 462 // GetAllPositions returns all positions, across markets. 463 func (p *Positions) GetAllPositions() ([]*types.Position, error) { 464 p.mu.RLock() 465 defer p.mu.RUnlock() 466 var pos []*types.Position 467 for k := range p.data { 468 // guesstimate what the slice cap ought to be: number of markets * number of parties in 1 market 469 pos = make([]*types.Position, 0, len(p.data)*len(p.data[k])) 470 break 471 } 472 for _, parties := range p.data { 473 for _, tp := range parties { 474 tp := tp 475 pos = append(pos, &tp.Position) 476 } 477 } 478 return pos, nil 479 } 480 481 // GetPositionsByMarket get all party positions in a given market. 482 func (p *Positions) GetPositionsByMarket(market string) ([]*types.Position, error) { 483 p.mu.RLock() 484 defer p.mu.RUnlock() 485 mp, ok := p.data[market] 486 if !ok { 487 return nil, ErrMarketNotFound 488 } 489 s := make([]*types.Position, 0, len(mp)) 490 for _, tp := range mp { 491 tp := tp 492 s = append(s, &tp.Position) 493 } 494 return s, nil 495 } 496 497 func calculateOpenClosedVolume(currentOpenVolume, tradedVolume int64) (int64, int64) { 498 if currentOpenVolume != 0 && ((currentOpenVolume > 0) != (tradedVolume > 0)) { 499 var closedVolume int64 500 if absUint64(tradedVolume) > absUint64(currentOpenVolume) { 501 closedVolume = currentOpenVolume 502 } else { 503 closedVolume = -tradedVolume 504 } 505 return tradedVolume + closedVolume, closedVolume 506 } 507 return tradedVolume, 0 508 } 509 510 func closeV(p *Position, closedVolume int64, tradedPrice *num.Uint, positionFactor num.Decimal) num.Decimal { 511 if closedVolume == 0 { 512 return num.DecimalZero() 513 } 514 realisedPnlDelta := num.DecimalFromUint(tradedPrice).Sub(p.AverageEntryPriceFP).Mul(num.DecimalFromInt64(closedVolume)).Div(positionFactor) 515 p.RealisedPnlFP = p.RealisedPnlFP.Add(realisedPnlDelta) 516 p.OpenVolume -= closedVolume 517 return realisedPnlDelta 518 } 519 520 func updateVWAP(vwap num.Decimal, volume int64, addVolume int64, addPrice *num.Uint) num.Decimal { 521 if volume+addVolume == 0 { 522 return num.DecimalZero() 523 } 524 525 volumeDec := num.DecimalFromInt64(volume) 526 addVolumeDec := num.DecimalFromInt64(addVolume) 527 addPriceDec := num.DecimalFromUint(addPrice) 528 529 // return ((vwap * float64(volume)) + (float64(addPrice) * float64(addVolume))) / (float64(volume) + float64(addVolume)) 530 return vwap.Mul(volumeDec).Add(addPriceDec.Mul(addVolumeDec)).Div(volumeDec.Add(addVolumeDec)) 531 } 532 533 func openV(p *Position, openedVolume int64, tradedPrice *num.Uint) { 534 // calculate both average entry price here. 535 p.AverageEntryPriceFP = updateVWAP(p.AverageEntryPriceFP, p.OpenVolume, openedVolume, tradedPrice) 536 p.OpenVolume += openedVolume 537 } 538 539 func mtm(p *Position, markPrice *num.Uint, positionFactor num.Decimal) { 540 if p.OpenVolume == 0 { 541 p.UnrealisedPnlFP = num.DecimalZero() 542 p.UnrealisedPnl = num.DecimalZero() 543 return 544 } 545 markPriceDec := num.DecimalFromUint(markPrice) 546 openVolumeDec := num.DecimalFromInt64(p.OpenVolume) 547 548 // p.UnrealisedPnlFP = float64(p.OpenVolume) * (float64(markPrice) - p.AverageEntryPriceFP) 549 p.UnrealisedPnlFP = openVolumeDec.Mul(markPriceDec.Sub(p.AverageEntryPriceFP)).Div(positionFactor) 550 } 551 552 func updateSettlePosition(p *Position, e SPE) { 553 for _, t := range e.Trades() { 554 reset := p.OpenVolume == 0 555 pr := t.Price() 556 openedVolume, closedVolume := calculateOpenClosedVolume(p.OpenVolume, t.Size()) 557 _ = closeV(p, closedVolume, pr, e.PositionFactor()) 558 before := p.OpenVolume 559 openV(p, openedVolume, pr) 560 // was the position zero, or did the position flip sides? 561 if reset || (before < 0 && p.OpenVolume > 0) || (before > 0 && p.OpenVolume < 0) { 562 p.ResetSince() 563 } 564 p.AverageEntryPrice, _ = num.UintFromDecimal(p.AverageEntryPriceFP.Round(0)) 565 566 p.RealisedPnl = p.RealisedPnlFP.Round(0) 567 } 568 mtm(p, e.Price(), e.PositionFactor()) 569 p.UnrealisedPnl = p.UnrealisedPnlFP.Round(0) 570 } 571 572 type Position struct { 573 types.Position 574 AverageEntryPriceFP num.Decimal 575 RealisedPnlFP num.Decimal 576 UnrealisedPnlFP num.Decimal 577 578 // what the party lost because of loss socialization 579 loss num.Decimal 580 // what a party was missing which triggered loss socialization 581 adjustment num.Decimal 582 state vega.PositionStatus 583 } 584 585 func seToProto(e SE) Position { 586 return Position{ 587 Position: types.NewPosition(e.MarketID(), e.PartyID()), 588 AverageEntryPriceFP: num.DecimalZero(), 589 RealisedPnlFP: num.DecimalZero(), 590 UnrealisedPnlFP: num.DecimalZero(), 591 } 592 } 593 594 func absUint64(v int64) uint64 { 595 if v < 0 { 596 v *= -1 597 } 598 return uint64(v) 599 } 600 601 func (p *Positions) Types() []events.Type { 602 return []events.Type{ 603 events.SettlePositionEvent, 604 events.SettleDistressedEvent, 605 events.LossSocializationEvent, 606 events.DistressedOrdersClosedEvent, 607 events.DistressedPositionsEvent, 608 events.SettleMarketEvent, 609 events.FundingPaymentsEvent, 610 events.TradeEvent, 611 } 612 } 613 614 func (p *Position) setFees(fee *feeAmounts) { 615 p.TakerFeesPaid.AddSum(fee.taker) 616 p.TakerFeesPaidSince.AddSum(fee.taker) 617 p.MakerFeesReceived.AddSum(fee.maker) 618 p.MakerFeesReceivedSince.AddSum(fee.maker) 619 p.FeesPaid.AddSum(fee.other) 620 p.FeesPaidSince.AddSum(fee.other) 621 }