code.vegaprotocol.io/vega@v0.79.0/datanode/entities/position_test.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_test 17 18 // No race condition checks on these tests, the channels are buffered to avoid actual issues 19 // we are aware that the tests themselves can be written in an unsafe way, but that's the tests 20 // not the code itsel. The behaviour of the tests is 100% reliable. 21 import ( 22 "context" 23 "testing" 24 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/types" 27 "code.vegaprotocol.io/vega/datanode/entities" 28 "code.vegaprotocol.io/vega/libs/num" 29 "code.vegaprotocol.io/vega/protos/vega" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 func TestMultipleTradesOfSameSize(t *testing.T) { 36 ctx := context.Background() 37 market := "market-id" 38 party := "party1" 39 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 40 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 41 tradeStub{ 42 size: -1, 43 price: num.NewUint(1000), 44 }, 45 tradeStub{ 46 size: -1, 47 price: num.NewUint(1000), 48 }, 49 }, 1, num.DecimalFromFloat(1)) 50 position.UpdateWithPositionSettlement(ps) 51 pp := position.ToProto() 52 // average entry price should be 1k 53 assert.Equal(t, ps.Price().String(), pp.AverageEntryPrice) 54 } 55 56 func TestMultipleTradesAndLossSocializationPartyNoOpenVolume(t *testing.T) { 57 ctx := context.Background() 58 market := "market-id" 59 party := "party1" 60 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 61 62 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 63 tradeStub{ 64 size: 2, 65 price: num.NewUint(1000), 66 }, 67 tradeStub{ 68 size: -2, 69 price: num.NewUint(1500), 70 }, 71 }, 1, num.DecimalFromFloat(1)) 72 position.UpdateWithPositionSettlement(ps) 73 pp := position.ToProto() 74 assert.Equal(t, "1000", pp.RealisedPnl) 75 76 // then we process the event for LossSocialization 77 lsevt := events.NewLossSocializationEvent(ctx, party, market, num.NewUint(300), true, 1, types.LossTypeUnspecified) 78 position.UpdateWithLossSocialization(lsevt) 79 pp = position.ToProto() 80 assert.Equal(t, "700", pp.RealisedPnl) 81 assert.Equal(t, "0", pp.UnrealisedPnl) 82 } 83 84 func TestDistressedPartyUpdate(t *testing.T) { 85 ctx := context.Background() 86 market := "market-id" 87 party := "party1" 88 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 89 pf := num.DecimalFromFloat(1) 90 91 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 92 tradeStub{ 93 size: 2, 94 price: num.NewUint(1000), 95 }, 96 tradeStub{ 97 size: 3, 98 price: num.NewUint(1200), 99 }, 100 }, 1, pf) 101 position.UpdateWithPositionSettlement(ps) 102 pp := position.ToProto() 103 assert.Equal(t, "0", pp.RealisedPnl) 104 assert.Equal(t, "-600", pp.UnrealisedPnl) 105 106 // then we process the event for LossSocialization 107 lsevt := events.NewLossSocializationEvent(ctx, party, market, num.NewUint(300), true, 1, types.LossTypeUnspecified) 108 position.UpdateWithLossSocialization(lsevt) 109 pp = position.ToProto() 110 assert.Equal(t, "-300", pp.RealisedPnl) 111 assert.Equal(t, "-600", pp.UnrealisedPnl) 112 113 // now assume this party is distressed, and we've taken all their funds 114 sde := events.NewSettleDistressed(ctx, party, market, num.UintZero(), num.NewUint(100), 1) 115 position.UpdateWithSettleDistressed(sde) 116 // ensure the position is flagged as distressed. 117 assert.Equal(t, entities.PositionStatusClosedOut, position.DistressedStatus) 118 pp = position.ToProto() 119 assert.Equal(t, "0", pp.UnrealisedPnl) 120 assert.Equal(t, "-1000", pp.RealisedPnl) 121 122 // now submit process a closeout trade event 123 position.UpdateWithTrade(vega.Trade{ 124 Size: 5, 125 Price: "1200", 126 AssetPrice: "1200", 127 Type: vega.Trade_TYPE_NETWORK_CLOSE_OUT_BAD, 128 }, true, pf) 129 // now ensure the position status still is what we expect it to be 130 assert.Equal(t, entities.PositionStatusClosedOut, position.DistressedStatus) 131 132 // next, assume the party has topped up, and traded again. 133 position.UpdateWithTrade(vega.Trade{ 134 Size: 1, 135 Price: "1200", 136 AssetPrice: "1200", 137 Type: vega.Trade_TYPE_DEFAULT, 138 }, true, pf) 139 // now the distressed status ought to be cleared. 140 assert.Equal(t, entities.PositionStatusUnspecified, position.DistressedStatus) 141 } 142 143 func TestMultipleTradesAndLossSocializationPartyWithOpenVolume(t *testing.T) { 144 ctx := context.Background() 145 market := "market-id" 146 party := "party1" 147 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 148 149 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 150 tradeStub{ 151 size: 2, 152 price: num.NewUint(1000), 153 }, 154 tradeStub{ 155 size: 3, 156 price: num.NewUint(1200), 157 }, 158 }, 1, num.DecimalFromFloat(1)) 159 position.UpdateWithPositionSettlement(ps) 160 pp := position.ToProto() 161 assert.Equal(t, "0", pp.RealisedPnl) 162 assert.Equal(t, "-600", pp.UnrealisedPnl) 163 164 // then we process the event for LossSocialization 165 lsevt := events.NewLossSocializationEvent(ctx, party, market, num.NewUint(300), true, 1, types.LossTypeUnspecified) 166 position.UpdateWithLossSocialization(lsevt) 167 pp = position.ToProto() 168 assert.Equal(t, "-300", pp.RealisedPnl) 169 assert.Equal(t, "-600", pp.UnrealisedPnl) 170 } 171 172 func TestPnLWithPositionDecimals(t *testing.T) { 173 ctx := context.Background() 174 market := "market-id" 175 party := "party1" 176 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 177 dp := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(3)) 178 179 // first update with trades 180 trade := vega.Trade{ 181 Id: "t1", 182 MarketId: market, 183 Price: "1000", 184 Size: 2, 185 Buyer: party, 186 Seller: "seller", 187 AssetPrice: "1000", 188 } 189 position.UpdateWithTrade(trade, false, dp) 190 trade.Id = "t2" 191 trade.Size = 3 192 trade.Price = "1200" 193 trade.AssetPrice = "1200" 194 position.UpdateWithTrade(trade, false, dp) 195 pp := position.ToProto() 196 assert.Equal(t, "0", pp.RealisedPnl) 197 assert.Equal(t, "0", pp.UnrealisedPnl) 198 // now MTM settlement event, contains the same trades, mark price is 1k 199 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 200 tradeStub{ 201 size: 2, 202 price: num.NewUint(1000), 203 }, 204 tradeStub{ 205 size: 3, 206 price: num.NewUint(1200), 207 }, 208 }, 1, dp) 209 position.UpdateWithPositionSettlement(ps) 210 pp = position.ToProto() 211 assert.Equal(t, "0", pp.RealisedPnl) 212 assert.Equal(t, "-1", pp.UnrealisedPnl) 213 assert.EqualValues(t, 5, pp.OpenVolume) 214 215 // let's make it look like this party is trading, buyer in this case 216 trade = vega.Trade{ 217 Id: "t3", 218 MarketId: market, 219 Price: "1150", 220 Size: 1, 221 Buyer: party, 222 Seller: "seller", 223 AssetPrice: "1150", 224 } 225 // position.UpdateWithTrade(trade, false, num.DecimalFromFloat(1)) 226 position.UpdateWithTrade(trade, false, dp) 227 pp = position.ToProto() 228 assert.Equal(t, "0", pp.RealisedPnl) 229 assert.Equal(t, "0", pp.UnrealisedPnl) 230 assert.EqualValues(t, 6, pp.OpenVolume) 231 // now assume this last trade was the only trade that occurred before MTM 232 ps = events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1150), []events.TradeSettlement{ 233 tradeStub{ 234 size: 1, 235 price: num.NewUint(1150), 236 }, 237 }, 1, dp) 238 position.UpdateWithPositionSettlement(ps) 239 pp = position.ToProto() 240 assert.Equal(t, "0", pp.RealisedPnl) 241 assert.Equal(t, "0", pp.UnrealisedPnl) 242 assert.EqualValues(t, 6, pp.OpenVolume) 243 // now close a position to see some realised PnL 244 trade = vega.Trade{ 245 Id: "t4", 246 MarketId: market, 247 Price: "1250", 248 Size: 1, 249 Buyer: "buyer", 250 Seller: party, 251 AssetPrice: "1250", 252 } 253 position.UpdateWithTrade(trade, true, dp) 254 pp = position.ToProto() 255 assert.Equal(t, "0", pp.RealisedPnl) 256 assert.Equal(t, "1", pp.UnrealisedPnl) 257 assert.EqualValues(t, 5, pp.OpenVolume) 258 ps = events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1250), []events.TradeSettlement{ 259 tradeStub{ 260 size: -1, 261 price: num.NewUint(1250), 262 }, 263 }, 1, dp) 264 position.UpdateWithPositionSettlement(ps) 265 pp = position.ToProto() 266 assert.Equal(t, "0", pp.RealisedPnl) 267 assert.Equal(t, "1", pp.UnrealisedPnl) 268 assert.EqualValues(t, 5, pp.OpenVolume) 269 // now close the position 270 trade = vega.Trade{ 271 Id: "t5", 272 MarketId: market, 273 Price: "1300", 274 Size: 5, 275 Buyer: "buyer", 276 Seller: party, 277 AssetPrice: "1300", 278 } 279 position.UpdateWithTrade(trade, true, dp) 280 pp = position.ToProto() 281 assert.Equal(t, "1", pp.RealisedPnl) 282 assert.Equal(t, "0", pp.UnrealisedPnl) 283 assert.EqualValues(t, 0, pp.OpenVolume) 284 ps = events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1250), []events.TradeSettlement{ 285 tradeStub{ 286 size: -5, 287 price: num.NewUint(1300), 288 }, 289 }, 1, dp) 290 position.UpdateWithPositionSettlement(ps) 291 pp = position.ToProto() 292 assert.Equal(t, "1", pp.RealisedPnl) 293 assert.Equal(t, "0", pp.UnrealisedPnl) 294 assert.EqualValues(t, 0, pp.OpenVolume) 295 } 296 297 func TestTradeFees(t *testing.T) { 298 // ctx := context.Background() 299 market := "market-id" 300 party := "party1" 301 sParty := "seller" 302 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 303 sPos := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(sParty)) 304 dp := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(3)) 305 306 // first update with trades 307 trade := vega.Trade{ 308 Id: "t1", 309 MarketId: market, 310 Price: "1000", 311 Size: 2, 312 Buyer: party, 313 Seller: sParty, 314 AssetPrice: "1000", 315 Aggressor: types.SideSell, 316 BuyerFee: &vega.Fee{ 317 MakerFee: "10", 318 InfrastructureFee: "1", 319 LiquidityFee: "2", 320 TreasuryFee: "1", 321 BuyBackFee: "0", 322 HighVolumeMakerFee: "0", 323 }, 324 SellerFee: &vega.Fee{ 325 MakerFee: "20", 326 InfrastructureFee: "2", 327 LiquidityFee: "3", 328 TreasuryFee: "2", 329 BuyBackFee: "1", 330 HighVolumeMakerFee: "1", 331 }, 332 } 333 bFeesPaid := num.NewDecimalFromFloat(4) 334 sFeesPaid := num.NewDecimalFromFloat(9) 335 bMaker := num.NewDecimalFromFloat(10) 336 sMaker := num.NewDecimalFromFloat(20) 337 position.UpdateWithTrade(trade, false, dp) 338 sPos.UpdateWithTrade(trade, true, dp) 339 require.True(t, position.FeesPaid.Equal(bFeesPaid)) 340 require.True(t, sPos.FeesPaid.Equal(sFeesPaid)) 341 // maker fees swap 342 require.True(t, sPos.TakerFeesPaid.Equal(sMaker)) 343 require.True(t, position.MakerFeesReceived.Equal(sMaker)) 344 // we have an aggressor, so only one side received maker fees 345 require.True(t, sPos.MakerFeesReceived.Equal(num.DecimalZero())) 346 require.True(t, position.TakerFeesPaid.Equal(bMaker)) 347 // now the same trade but with no aggressor 348 trade.Aggressor = types.SideUnspecified 349 position = entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 350 sPos = entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(sParty)) 351 position.UpdateWithTrade(trade, false, dp) 352 sPos.UpdateWithTrade(trade, true, dp) 353 require.True(t, position.FeesPaid.Equal(bFeesPaid)) 354 require.True(t, sPos.FeesPaid.Equal(sFeesPaid)) 355 // maker fees swap 356 require.True(t, sPos.TakerFeesPaid.Equal(sMaker)) 357 require.True(t, position.MakerFeesReceived.Equal(sMaker)) 358 // we have an aggressor, so only one side received maker fees 359 require.True(t, sPos.MakerFeesReceived.Equal(bMaker)) 360 require.True(t, position.TakerFeesPaid.Equal(bMaker)) 361 } 362 363 func TestPnLWithTradeDecimals(t *testing.T) { 364 ctx := context.Background() 365 market := "market-id" 366 party := "party1" 367 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 368 dp := num.DecimalFromFloat(3) 369 370 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 371 tradeStub{ 372 size: 2, 373 price: num.NewUint(1000), 374 }, 375 tradeStub{ 376 size: 3, 377 price: num.NewUint(1200), 378 }, 379 }, 1, dp) 380 position.UpdateWithPositionSettlement(ps) 381 pp := position.ToProto() 382 assert.Equal(t, "0", pp.RealisedPnl) 383 assert.Equal(t, "-200", pp.UnrealisedPnl) 384 385 // then we process the event for LossSocialization 386 lsevt := events.NewLossSocializationEvent(ctx, party, market, num.NewUint(300), true, 1, types.LossTypeUnspecified) 387 position.UpdateWithLossSocialization(lsevt) 388 pp = position.ToProto() 389 assert.Equal(t, "-300", pp.RealisedPnl) 390 assert.Equal(t, "-200", pp.UnrealisedPnl) 391 // let's make it look like this party is trading, buyer in this case 392 trade := vega.Trade{ 393 Id: "t1", 394 MarketId: market, 395 Price: "1150", 396 Size: 1, 397 Buyer: party, 398 Seller: "seller", 399 AssetPrice: "1150", 400 } 401 // position.UpdateWithTrade(trade, false, num.DecimalFromFloat(1)) 402 position.UpdateWithTrade(trade, false, dp) 403 pp = position.ToProto() 404 assert.Equal(t, "-300", pp.RealisedPnl) 405 assert.Equal(t, "50", pp.UnrealisedPnl) 406 } 407 408 func TestUpdateWithTradesAndFundingPayment(t *testing.T) { 409 ctx := context.Background() 410 market := "market-id" 411 party := "party1" 412 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 413 dp := num.DecimalFromFloat(3) 414 trades := []tradeStub{ 415 { 416 size: 2, 417 price: num.NewUint(1200), 418 }, 419 { 420 size: 3, 421 price: num.NewUint(1000), 422 }, 423 } 424 // this is the order in which the events will be sent/received 425 position.UpdateWithTrade(trades[0].ToVega(dp), false, dp) 426 pp := position.ToProto() 427 assert.Equal(t, "0", pp.RealisedPnl) 428 assert.Equal(t, "0", pp.UnrealisedPnl) 429 position.ApplyFundingPayment(num.NewInt(100)) 430 pp = position.ToProto() 431 assert.Equal(t, "100", position.FundingPaymentAmount.String()) 432 assert.Equal(t, "0", pp.UnrealisedPnl, pp.AverageEntryPrice) 433 position.UpdateWithTrade(trades[1].ToVega(dp), false, dp) 434 pp = position.ToProto() 435 assert.Equal(t, "100", position.FundingPaymentAmount.String()) 436 assert.Equal(t, "-133", pp.UnrealisedPnl, pp.AverageEntryPrice) 437 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{trades[0], trades[1]}, 1, dp) 438 position.UpdateWithPositionSettlement(ps) 439 psp := position.ToProto() 440 assert.Equal(t, "100", position.FundingPaymentAmount.String()) 441 assert.Equal(t, "-133", psp.UnrealisedPnl) 442 position.ApplyFundingPayment(num.NewInt(-50)) 443 pp = position.ToProto() 444 assert.Equal(t, "50", position.FundingPaymentAmount.String()) 445 assert.Equal(t, "-133", pp.UnrealisedPnl, pp.AverageEntryPrice) 446 } 447 448 type tradeStub struct { 449 size int64 450 price *num.Uint 451 marketPrice *num.Uint 452 } 453 454 func (t tradeStub) Size() int64 { 455 return t.size 456 } 457 458 func (t tradeStub) Price() *num.Uint { 459 return t.price.Clone() 460 } 461 462 func (t tradeStub) MarketPrice() *num.Uint { 463 if t.marketPrice != nil { 464 return t.marketPrice.Clone() 465 } 466 return t.price.Clone() 467 } 468 469 func (t tradeStub) ToVega(dp num.Decimal) vega.Trade { 470 // dp = num.DecimalFromFloat(10).Pow(dp) 471 // size, _ := num.DecimalFromInt64(t.size).Abs().Mul(dp).Float64() 472 size := uint64(t.size) 473 if t.size < 0 { 474 size = uint64(-t.size) 475 } 476 return vega.Trade{ 477 Size: size, 478 Price: t.price.String(), 479 AssetPrice: t.price.String(), 480 } 481 } 482 483 func TestCalculateOpenClosedVolume(t *testing.T) { 484 open := int64(0) 485 closed := int64(0) 486 // no pending volume, new buy trade of 100, expect to open 100 close 0 487 open, closed = entities.CalculateOpenClosedVolume(0, 100) 488 require.Equal(t, int64(100), open) 489 require.Equal(t, int64(0), closed) 490 491 // no pending volume, new sell trade of 100, expect to open -100 close 0 492 open, closed = entities.CalculateOpenClosedVolume(0, -100) 493 require.Equal(t, int64(-100), open) 494 require.Equal(t, int64(0), closed) 495 496 // we have a pending open volume of 100 and we get a new buy trade of 50, expect to return opened 50, close 0 497 open, closed = entities.CalculateOpenClosedVolume(100, 50) 498 require.Equal(t, int64(50), open) 499 require.Equal(t, int64(0), closed) 500 501 // we have a pending open volume of -100 and we get a new sell trade of 50, expect to return opened -50, close 0 502 open, closed = entities.CalculateOpenClosedVolume(-100, -50) 503 require.Equal(t, int64(-50), open) 504 require.Equal(t, int64(0), closed) 505 506 // we have a pending open volume of 100 and we get a new sell trade of 50, expect to return opened 0, close 50 507 open, closed = entities.CalculateOpenClosedVolume(100, -50) 508 require.Equal(t, int64(0), open) 509 require.Equal(t, int64(50), closed) 510 511 // we have a pending open volume of -100 and we get a new buy trade of 50, expect to return opened 0, close -50 512 open, closed = entities.CalculateOpenClosedVolume(-100, 50) 513 require.Equal(t, int64(0), open) 514 require.Equal(t, int64(-50), closed) 515 516 // we have a pending open volume of 100 and we get a new sell trade of 150, expect to return opened -50, close 100 517 open, closed = entities.CalculateOpenClosedVolume(100, -150) 518 require.Equal(t, int64(-50), open) 519 require.Equal(t, int64(100), closed) 520 521 // we have a pending open volume of -100 and we get a new buy trade of 150, expect to return opened 50, close -100 522 open, closed = entities.CalculateOpenClosedVolume(-100, 150) 523 require.Equal(t, int64(50), open) 524 require.Equal(t, int64(-100), closed) 525 } 526 527 func TestWithDifferingMarketAssetPrecision(t *testing.T) { 528 ctx := context.Background() 529 market := "market-id" 530 party := "party1" 531 position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party)) 532 ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{ 533 tradeStub{ 534 size: -5, 535 price: num.NewUint(10000000), 536 marketPrice: num.NewUint(1000), 537 }, 538 tradeStub{ 539 size: -5, 540 price: num.NewUint(10000000), 541 marketPrice: num.NewUint(1000), 542 }, 543 }, 1, num.DecimalFromFloat(1)) 544 position.UpdateWithPositionSettlement(ps) 545 pp := position.ToProto() 546 547 // average entry price should be 1k in market precision 548 assert.Equal(t, ps.Price().String(), pp.AverageEntryPrice) 549 assert.Equal(t, "1000", position.AverageEntryMarketPrice.String()) 550 assert.Equal(t, "10000000", position.AverageEntryPrice.String()) 551 552 // now update with a trade 553 trade := vega.Trade{ 554 Price: "2000", 555 AssetPrice: "20000000", 556 Size: 10, 557 } 558 position.UpdateWithTrade(trade, true, num.DecimalOne()) 559 assert.Equal(t, "1500", position.PendingAverageEntryMarketPrice.String()) 560 assert.Equal(t, "15000000", position.PendingAverageEntryPrice.String()) 561 assert.Equal(t, int64(-20), position.PendingOpenVolume) 562 563 trade = vega.Trade{ 564 Price: "1000", 565 AssetPrice: "10000000", 566 Size: 5, 567 } 568 position.UpdateWithTrade(trade, false, num.DecimalOne()) 569 assert.Equal(t, "1500", position.PendingAverageEntryMarketPrice.String()) 570 assert.Equal(t, "15000000", position.PendingAverageEntryPrice.String()) 571 assert.Equal(t, int64(-15), position.PendingOpenVolume) 572 assert.Equal(t, "25000000", position.PendingRealisedPnl.String()) 573 assert.Equal(t, "75000000", position.PendingUnrealisedPnl.String()) 574 }