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  }