code.vegaprotocol.io/vega@v0.79.0/core/plugins/positions_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  // 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/plugins"
    27  	"code.vegaprotocol.io/vega/core/types"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  )
    33  
    34  type tradeStub struct {
    35  	size  int64
    36  	price *num.Uint
    37  }
    38  
    39  type posPluginTst struct {
    40  	*plugins.Positions
    41  	ctrl  *gomock.Controller
    42  	ctx   context.Context
    43  	cfunc context.CancelFunc
    44  }
    45  
    46  func TestMultipleTradesOfSameSize(t *testing.T) {
    47  	position := getPosPlugin(t)
    48  	defer position.Finish()
    49  	market := "market-id"
    50  	ps := events.NewSettlePositionEvent(position.ctx, "party1", market, num.NewUint(1000), []events.TradeSettlement{
    51  		tradeStub{
    52  			size:  -1,
    53  			price: num.NewUint(1000),
    54  		},
    55  		tradeStub{
    56  			size:  -1,
    57  			price: num.NewUint(1000),
    58  		},
    59  	}, 1, num.DecimalFromFloat(1))
    60  	position.Push(ps)
    61  	pp, err := position.GetPositionsByMarket(market)
    62  	assert.NoError(t, err)
    63  	assert.NotZero(t, len(pp))
    64  	// average entry price should be 1k
    65  	assert.Equal(t, ps.Price(), pp[0].AverageEntryPrice)
    66  }
    67  
    68  func TestMultipleTradesAndLossSocializationPartyNoOpenVolume(t *testing.T) {
    69  	position := getPosPlugin(t)
    70  	defer position.Finish()
    71  	market := "market-id"
    72  	ps := events.NewSettlePositionEvent(position.ctx, "party1", market, num.NewUint(1000), []events.TradeSettlement{
    73  		tradeStub{
    74  			size:  2,
    75  			price: num.NewUint(1000),
    76  		},
    77  		tradeStub{
    78  			size:  -2,
    79  			price: num.NewUint(1500),
    80  		},
    81  	}, 1, num.DecimalFromFloat(1))
    82  	position.Push(ps)
    83  	pp, err := position.GetPositionsByMarket(market)
    84  	assert.NoError(t, err)
    85  	assert.NotZero(t, len(pp))
    86  	// average entry price should be 1k
    87  	// initially calculation say the RealisedPNL should be 1000
    88  	assert.Equal(t, "1000", pp[0].RealisedPnl.String())
    89  
    90  	// then we process the event for LossSocialization
    91  	lsevt := events.NewLossSocializationEvent(position.ctx, "party1", market, num.NewUint(300), true, 1, types.LossTypeUnspecified)
    92  	position.Push(lsevt)
    93  	pp, err = position.GetPositionsByMarket(market)
    94  	assert.NoError(t, err)
    95  	assert.NotZero(t, len(pp))
    96  	// with the changes, the RealisedPNL should be 700
    97  	assert.Equal(t, "700", pp[0].RealisedPnl.String())
    98  	assert.Equal(t, "0", pp[0].UnrealisedPnl.String())
    99  }
   100  
   101  func TestDistressedPartyUpdate(t *testing.T) {
   102  	position := getPosPlugin(t)
   103  	defer position.Finish()
   104  	market := "market-id"
   105  	ps := events.NewSettlePositionEvent(position.ctx, "party1", market, num.NewUint(1000), []events.TradeSettlement{
   106  		tradeStub{
   107  			size:  2,
   108  			price: num.NewUint(1000),
   109  		},
   110  		tradeStub{
   111  			size:  3,
   112  			price: num.NewUint(1200),
   113  		},
   114  	}, 1, num.DecimalFromFloat(1))
   115  	position.Push(ps)
   116  	pp, err := position.GetPositionsByMarket(market)
   117  	assert.NoError(t, err)
   118  	assert.NotZero(t, len(pp))
   119  	// average entry price should be 1k
   120  	// initially calculation say the RealisedPNL should be 1000
   121  	assert.Equal(t, "0", pp[0].RealisedPnl.String())
   122  	assert.Equal(t, "-600", pp[0].UnrealisedPnl.String())
   123  
   124  	// then we process the event for LossSocialization
   125  	lsevt := events.NewLossSocializationEvent(position.ctx, "party1", market, num.NewUint(300), true, 1, types.LossTypeUnspecified)
   126  	position.Push(lsevt)
   127  	pp, err = position.GetPositionsByMarket(market)
   128  	assert.NoError(t, err)
   129  	assert.NotZero(t, len(pp))
   130  	// with the changes, the RealisedPNL should be 700
   131  	assert.Equal(t, "-300", pp[0].RealisedPnl.String())
   132  	assert.Equal(t, "-600", pp[0].UnrealisedPnl.String())
   133  	// now assume this party is distressed, and we've taken all their funds
   134  	sde := events.NewSettleDistressed(position.ctx, "party1", market, num.UintZero(), num.NewUint(100), 1)
   135  	position.Push(sde)
   136  	pp, err = position.GetPositionsByMarket(market)
   137  	assert.NoError(t, err)
   138  	assert.NotZero(t, len(pp))
   139  	assert.Equal(t, "0", pp[0].UnrealisedPnl.String())
   140  	assert.Equal(t, "-1000", pp[0].RealisedPnl.String())
   141  }
   142  
   143  func TestMultipleTradesAndLossSocializationPartyWithOpenVolume(t *testing.T) {
   144  	position := getPosPlugin(t)
   145  	defer position.Finish()
   146  	market := "market-id"
   147  	ps := events.NewSettlePositionEvent(position.ctx, "party1", market, num.NewUint(1000), []events.TradeSettlement{
   148  		tradeStub{
   149  			size:  2,
   150  			price: num.NewUint(1000),
   151  		},
   152  		tradeStub{
   153  			size:  3,
   154  			price: num.NewUint(1200),
   155  		},
   156  	}, 1, num.DecimalFromFloat(1))
   157  	position.Push(ps)
   158  	pp, err := position.GetPositionsByMarket(market)
   159  	assert.NoError(t, err)
   160  	assert.NotZero(t, len(pp))
   161  	// average entry price should be 1k
   162  	// initially calculation say the RealisedPNL should be 1000
   163  	assert.Equal(t, "0", pp[0].RealisedPnl.String())
   164  	assert.Equal(t, "-600", pp[0].UnrealisedPnl.String())
   165  
   166  	// then we process the event for LossSocialization
   167  	lsevt := events.NewLossSocializationEvent(position.ctx, "party1", market, num.NewUint(300), true, 1, types.LossTypeUnspecified)
   168  	position.Push(lsevt)
   169  	pp, err = position.GetPositionsByMarket(market)
   170  	assert.NoError(t, err)
   171  	assert.NotZero(t, len(pp))
   172  	// with the changes, the RealisedPNL should be 700
   173  	assert.Equal(t, "-300", pp[0].RealisedPnl.String())
   174  	assert.Equal(t, "-600", pp[0].UnrealisedPnl.String())
   175  }
   176  
   177  func getPosPlugin(t *testing.T) *posPluginTst {
   178  	t.Helper()
   179  	ctrl := gomock.NewController(t)
   180  	ctx, cfunc := context.WithCancel(context.Background())
   181  	p := plugins.NewPositions(ctx)
   182  	tst := posPluginTst{
   183  		Positions: p,
   184  		ctrl:      ctrl,
   185  		ctx:       ctx,
   186  		cfunc:     cfunc,
   187  	}
   188  	return &tst
   189  }
   190  
   191  func (p *posPluginTst) Finish() {
   192  	p.cfunc() // cancel context
   193  	defer p.ctrl.Finish()
   194  }
   195  
   196  func (t tradeStub) Size() int64 {
   197  	return t.size
   198  }
   199  
   200  func (t tradeStub) Price() *num.Uint {
   201  	return t.price.Clone()
   202  }
   203  
   204  func (t tradeStub) MarketPrice() *num.Uint {
   205  	return t.price.Clone()
   206  }