code.vegaprotocol.io/vega@v0.79.0/core/plugins/position_spec_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 plugins_test 17 18 import ( 19 "context" 20 "testing" 21 22 "code.vegaprotocol.io/vega/core/events" 23 "code.vegaprotocol.io/vega/core/plugins" 24 "code.vegaprotocol.io/vega/libs/num" 25 26 "github.com/stretchr/testify/assert" 27 ) 28 29 type expect struct { 30 AverageEntryPrice *num.Uint 31 OpenVolume int64 32 RealisedPNL num.Decimal 33 UnrealisedPNL num.Decimal 34 } 35 36 func TestPositionSpecSuite(t *testing.T) { 37 market := "market-id" 38 ctx := context.Background() 39 testcases := []struct { 40 run string 41 pos plugins.SPE 42 expect expect 43 }{ 44 { 45 run: "Long gets more long", 46 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 47 tradeStub{ 48 size: 100, 49 price: num.NewUint(50), 50 }, 51 tradeStub{ 52 size: 25, 53 price: num.NewUint(100), 54 }, 55 }, 1, num.DecimalFromFloat(1)), 56 expect: expect{ 57 AverageEntryPrice: num.NewUint(60), 58 OpenVolume: 125, 59 UnrealisedPNL: num.NewDecimalFromFloat(5000.0), 60 RealisedPNL: num.NewDecimalFromFloat(0.0), 61 }, 62 }, 63 { 64 run: "Long gets less long", 65 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 66 tradeStub{ 67 size: 100, 68 price: num.NewUint(50), 69 }, 70 tradeStub{ 71 size: -25, 72 price: num.NewUint(100), 73 }, 74 }, 1, num.DecimalFromFloat(1)), 75 expect: expect{ 76 AverageEntryPrice: num.NewUint(50), 77 OpenVolume: 75, 78 UnrealisedPNL: num.NewDecimalFromFloat(3750), 79 RealisedPNL: num.NewDecimalFromFloat(1250), 80 }, 81 }, 82 { 83 run: "Long gets closed", 84 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 85 tradeStub{ 86 size: 100, 87 price: num.NewUint(50), 88 }, 89 tradeStub{ 90 size: -100, 91 price: num.NewUint(100), 92 }, 93 }, 1, num.DecimalFromFloat(1)), 94 expect: expect{ 95 OpenVolume: 0, 96 AverageEntryPrice: num.UintZero(), 97 UnrealisedPNL: num.NewDecimalFromFloat(0), 98 RealisedPNL: num.NewDecimalFromFloat(5000), 99 }, 100 }, 101 { 102 run: "Long gets turned short", 103 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 104 tradeStub{ 105 size: 100, 106 price: num.NewUint(50), 107 }, 108 tradeStub{ 109 size: -125, 110 price: num.NewUint(100), 111 }, 112 }, 1, num.DecimalFromFloat(1)), 113 expect: expect{ 114 OpenVolume: -25, 115 AverageEntryPrice: num.NewUint(100), 116 UnrealisedPNL: num.NewDecimalFromFloat(0), 117 RealisedPNL: num.NewDecimalFromFloat(5000), 118 }, 119 }, 120 { 121 run: "Short gets more short", 122 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 123 tradeStub{ 124 size: -100, 125 price: num.NewUint(50), 126 }, 127 tradeStub{ 128 size: -25, 129 price: num.NewUint(100), 130 }, 131 }, 1, num.DecimalFromFloat(1)), 132 expect: expect{ 133 OpenVolume: -125, 134 AverageEntryPrice: num.NewUint(60), 135 UnrealisedPNL: num.NewDecimalFromFloat(-5000), 136 RealisedPNL: num.NewDecimalFromFloat(0), 137 }, 138 }, 139 { 140 run: "short gets less short", 141 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 142 tradeStub{ 143 size: -100, 144 price: num.NewUint(50), 145 }, 146 tradeStub{ 147 size: 25, 148 price: num.NewUint(100), 149 }, 150 }, 1, num.DecimalFromFloat(1)), 151 expect: expect{ 152 OpenVolume: -75, 153 AverageEntryPrice: num.NewUint(50), 154 UnrealisedPNL: num.NewDecimalFromFloat(-3750), 155 RealisedPNL: num.NewDecimalFromFloat(-1250), 156 }, 157 }, 158 { 159 run: "Short gets closed", 160 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 161 tradeStub{ 162 size: -100, 163 price: num.NewUint(50), 164 }, 165 tradeStub{ 166 size: 100, 167 price: num.NewUint(100), 168 }, 169 }, 1, num.DecimalFromFloat(1)), 170 expect: expect{ 171 OpenVolume: 0, 172 AverageEntryPrice: num.UintZero(), 173 UnrealisedPNL: num.NewDecimalFromFloat(0), 174 RealisedPNL: num.NewDecimalFromFloat(-5000), 175 }, 176 }, 177 { 178 run: "Short gets turned long", 179 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 180 tradeStub{ 181 size: -100, 182 price: num.NewUint(50), 183 }, 184 tradeStub{ 185 size: 125, 186 price: num.NewUint(100), 187 }, 188 }, 1, num.DecimalFromFloat(1)), 189 expect: expect{ 190 OpenVolume: 25, 191 AverageEntryPrice: num.NewUint(100), 192 UnrealisedPNL: num.NewDecimalFromFloat(0), 193 RealisedPNL: num.NewDecimalFromFloat(-5000), 194 }, 195 }, 196 { 197 run: "Long trade up and down", 198 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(75), []events.TradeSettlement{ 199 tradeStub{ 200 size: 100, 201 price: num.NewUint(100), 202 }, 203 tradeStub{ 204 size: -25, 205 price: num.NewUint(25), 206 }, 207 tradeStub{ 208 size: 50, 209 price: num.NewUint(50), 210 }, 211 tradeStub{ 212 size: -100, 213 price: num.NewUint(75), 214 }, 215 }, 1, num.DecimalFromFloat(1)), 216 expect: expect{ 217 OpenVolume: 25, 218 AverageEntryPrice: num.NewUint(80), 219 UnrealisedPNL: num.NewDecimalFromFloat(-125), 220 RealisedPNL: num.NewDecimalFromFloat(-2375), 221 }, 222 }, 223 { 224 run: "Profit before and after turning (start long)", 225 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 226 tradeStub{ 227 size: 100, 228 price: num.NewUint(50), 229 }, 230 tradeStub{ 231 size: -150, 232 price: num.NewUint(100), 233 }, 234 tradeStub{ 235 size: 50, 236 price: num.NewUint(25), 237 }, 238 }, 1, num.DecimalFromFloat(1)), 239 expect: expect{ 240 OpenVolume: 0, 241 AverageEntryPrice: num.UintZero(), 242 UnrealisedPNL: num.NewDecimalFromFloat(0), 243 RealisedPNL: num.NewDecimalFromFloat(8750), 244 }, 245 }, 246 { 247 run: "Profit before and after turning (start short)", 248 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 249 tradeStub{ 250 size: -100, 251 price: num.NewUint(100), 252 }, 253 tradeStub{ 254 size: 150, 255 price: num.NewUint(25), 256 }, 257 tradeStub{ 258 size: -50, 259 price: num.NewUint(50), 260 }, 261 }, 1, num.DecimalFromFloat(1)), 262 expect: expect{ 263 OpenVolume: 0, 264 AverageEntryPrice: num.UintZero(), 265 UnrealisedPNL: num.NewDecimalFromFloat(0), 266 RealisedPNL: num.NewDecimalFromFloat(8750), 267 }, 268 }, 269 { 270 run: "Profit before and loss after turning (start long)", 271 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 272 tradeStub{ 273 size: 100, 274 price: num.NewUint(50), 275 }, 276 tradeStub{ 277 size: -150, 278 price: num.NewUint(100), 279 }, 280 tradeStub{ 281 size: 50, 282 price: num.NewUint(250), 283 }, 284 }, 1, num.DecimalFromFloat(1)), 285 expect: expect{ 286 OpenVolume: 0, 287 AverageEntryPrice: num.UintZero(), 288 UnrealisedPNL: num.NewDecimalFromFloat(0), 289 RealisedPNL: num.NewDecimalFromFloat(-2500), 290 }, 291 }, 292 { 293 run: "Profit before and loss after turning (start short)", 294 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 295 tradeStub{ 296 size: -100, 297 price: num.NewUint(100), 298 }, 299 tradeStub{ 300 size: 150, 301 price: num.NewUint(50), 302 }, 303 tradeStub{ 304 size: -50, 305 price: num.NewUint(25), 306 }, 307 }, 1, num.DecimalFromFloat(1)), 308 expect: expect{ 309 OpenVolume: 0, 310 AverageEntryPrice: num.UintZero(), 311 UnrealisedPNL: num.NewDecimalFromFloat(0), 312 RealisedPNL: num.NewDecimalFromFloat(3750), 313 }, 314 }, 315 { 316 run: "Scenario from Tamlyn's spreadsheet on Google Drive at https://drive.google.com/open?id=1XJESwh5cypALqlYludWobAOEH1Pz-1xS", 317 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(1010), []events.TradeSettlement{ 318 tradeStub{ 319 size: 5, 320 price: num.NewUint(1000), 321 }, 322 tradeStub{ 323 size: 2, 324 price: num.NewUint(1050), 325 }, 326 tradeStub{ 327 size: -4, 328 price: num.NewUint(900), 329 }, 330 tradeStub{ 331 size: -3, 332 price: num.NewUint(1070), 333 }, 334 tradeStub{ 335 size: 3, 336 price: num.NewUint(1060), 337 }, 338 tradeStub{ 339 size: -5, 340 price: num.NewUint(1010), 341 }, 342 tradeStub{ 343 size: -3, 344 price: num.NewUint(980), 345 }, 346 tradeStub{ 347 size: 2, 348 price: num.NewUint(1030), 349 }, 350 tradeStub{ 351 size: 3, 352 price: num.NewUint(982), 353 }, 354 tradeStub{ 355 size: -4, 356 price: num.NewUint(1020), 357 }, 358 tradeStub{ 359 size: 6, 360 price: num.NewUint(1010), 361 }, 362 }, 1, num.DecimalFromFloat(1)), 363 expect: expect{ 364 OpenVolume: 2, 365 AverageEntryPrice: num.NewUint(1010), 366 UnrealisedPNL: num.NewDecimalFromFloat(0), 367 RealisedPNL: num.NewDecimalFromFloat(-446), 368 }, 369 }, 370 { 371 run: "Scenario from jeremy", 372 pos: events.NewSettlePositionEvent(ctx, "party1", market, num.NewUint(100), []events.TradeSettlement{ 373 tradeStub{ 374 size: 1, 375 price: num.NewUint(1931), 376 }, 377 tradeStub{ 378 size: 4, 379 price: num.NewUint(1931), 380 }, 381 tradeStub{ 382 size: -1, 383 price: num.NewUint(1923), 384 }, 385 tradeStub{ 386 size: -4, 387 price: num.NewUint(1923), 388 }, 389 tradeStub{ 390 size: 7, 391 price: num.NewUint(1927), 392 }, 393 tradeStub{ 394 size: -2, 395 price: num.NewUint(1926), 396 }, 397 tradeStub{ 398 size: -1, 399 price: num.NewUint(1926), 400 }, 401 tradeStub{ 402 size: -4, 403 price: num.NewUint(1926), 404 }, 405 tradeStub{ 406 size: 1, 407 price: num.NewUint(1934), 408 }, 409 tradeStub{ 410 size: 7, 411 price: num.NewUint(1933), 412 }, 413 tradeStub{ 414 size: 1, 415 price: num.NewUint(1932), 416 }, 417 tradeStub{ 418 size: 1, 419 price: num.NewUint(1932), 420 }, 421 tradeStub{ 422 size: -8, 423 price: num.NewUint(1926), 424 }, 425 tradeStub{ 426 size: -2, 427 price: num.NewUint(1926), 428 }, 429 }, 1, num.DecimalFromFloat(1)), 430 expect: expect{ 431 OpenVolume: 0, 432 AverageEntryPrice: num.UintZero(), 433 UnrealisedPNL: num.NewDecimalFromFloat(0), 434 RealisedPNL: num.NewDecimalFromFloat(-116), 435 }, 436 }, 437 } 438 439 for _, tc := range testcases { 440 t.Run(tc.run, func(t *testing.T) { 441 ps := tc.pos 442 position := getPosPlugin(t) 443 defer position.Finish() 444 position.Push(ps) 445 pp, err := position.GetPositionsByMarket(market) 446 assert.NoError(t, err) 447 assert.NotZero(t, len(pp)) 448 // average entry price should be 1k 449 assert.Equal(t, tc.expect.AverageEntryPrice, pp[0].AverageEntryPrice, "invalid average entry price") 450 assert.Equal(t, tc.expect.OpenVolume, pp[0].OpenVolume, "invalid open volume") 451 assert.Equal(t, tc.expect.UnrealisedPNL.String(), pp[0].UnrealisedPnl.String(), "invalid unrealised pnl") 452 assert.Equal(t, tc.expect.RealisedPNL.String(), pp[0].RealisedPnl.String(), "invalid realised pnl") 453 }) 454 } 455 }