code.vegaprotocol.io/vega@v0.79.0/core/risk/isolated_margin_ex_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 risk_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  
    22  	"code.vegaprotocol.io/vega/core/risk"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  
    26  	"github.com/golang/mock/gomock"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func TestSwitchFromIsolatedMargin(t *testing.T) {
    31  	e := getTestEngine(t, num.DecimalOne())
    32  	evt := testMargin{
    33  		party:       "party1",
    34  		size:        1,
    35  		price:       1000,
    36  		asset:       "ETH",
    37  		margin:      10,
    38  		orderMargin: 20,
    39  		general:     100000,
    40  		market:      "ETH/DEC19",
    41  	}
    42  	// we're switching from margin to isolated, we expect to release all of the order margin
    43  	e.as.EXPECT().InAuction().Return(false).AnyTimes()
    44  	e.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
    45  	e.tsvc.EXPECT().GetTimeNow().AnyTimes()
    46  	risk := e.SwitchFromIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), nil)
    47  	require.Equal(t, num.NewUint(20), risk.Transfer().Amount.Amount)
    48  	require.Equal(t, num.NewUint(20), risk.Transfer().MinAmount)
    49  	require.Equal(t, types.TransferTypeIsolatedMarginLow, risk.Transfer().Type)
    50  	require.Equal(t, "party1", risk.Transfer().Owner)
    51  	require.Equal(t, num.UintZero(), risk.MarginLevels().OrderMargin)
    52  }
    53  
    54  func TestSwithToIsolatedMarginContinuous(t *testing.T) {
    55  	positionFactor := num.DecimalOne()
    56  	e := getTestEngine(t, positionFactor)
    57  	evt := testMargin{
    58  		party:       "party1",
    59  		size:        1,
    60  		price:       1000,
    61  		asset:       "ETH",
    62  		margin:      10,
    63  		orderMargin: 0,
    64  		general:     500,
    65  		market:      "ETH/DEC19",
    66  	}
    67  	// we're switching from margin to isolated, we expect to release all of the order margin
    68  	e.as.EXPECT().InAuction().Return(false).AnyTimes()
    69  	e.tsvc.EXPECT().GetTimeNow().AnyTimes()
    70  	e.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
    71  
    72  	// margin factor too low - 0.01 * 1000 * 1 = 10 < 31 initial margin
    73  	_, err := e.SwitchToIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), []*types.Order{}, num.DecimalFromFloat(0.01), nil)
    74  	require.Equal(t, "required position margin must be greater than initial margin", err.Error())
    75  
    76  	// not enough in general account to cover
    77  	// required position margin (600) + require order margin (0) - margin balance (10) > general account balance (500)
    78  	_, err = e.SwitchToIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), []*types.Order{}, num.DecimalFromFloat(0.6), nil)
    79  	require.Equal(t, "insufficient balance in general account to cover for required order margin", err.Error())
    80  
    81  	// case1 - need to topup margin account only
    82  	marginFactor := num.DecimalFromFloat(0.5)
    83  	orders := []*types.Order{}
    84  	riskEvent, err := e.SwitchToIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), orders, marginFactor, num.UintZero())
    85  	require.NoError(t, err)
    86  	require.Equal(t, 1, len(riskEvent))
    87  	require.Equal(t, num.NewUint(490), riskEvent[0].Transfer().Amount.Amount)
    88  	require.Equal(t, num.NewUint(490), riskEvent[0].Transfer().MinAmount)
    89  	require.Equal(t, types.TransferTypeMarginLow, riskEvent[0].Transfer().Type)
    90  	require.Equal(t, "party1", riskEvent[0].Transfer().Owner)
    91  	require.Equal(t, num.NewUint(0), riskEvent[0].MarginLevels().OrderMargin)
    92  
    93  	buyOrderInfo, sellOrderInfo := extractOrderInfo(orders)
    94  	requiredPositionMarginStatic, requiredOrderMarginStatic := risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, nil)
    95  	require.True(t, !requiredPositionMarginStatic.IsZero())
    96  	require.True(t, requiredOrderMarginStatic.IsZero())
    97  	transferRecalc := requiredPositionMarginStatic.Sub(evt.MarginBalance().ToDecimal())
    98  	require.True(t, riskEvent[0].Transfer().Amount.Amount.ToDecimal().Sub(transferRecalc).IsZero())
    99  
   100  	// case2 we have also some orders
   101  	orders = []*types.Order{
   102  		{Side: types.SideBuy, Remaining: 1, Price: num.NewUint(1000), Status: types.OrderStatusActive},
   103  	}
   104  
   105  	// not enough in general account to cover
   106  	// required position margin (300) + require order margin (300) - margin balance (10) > general account balance (500)
   107  	_, err = e.SwitchToIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), orders, num.DecimalFromFloat(0.3), nil)
   108  	require.Equal(t, "insufficient balance in general account to cover for required order margin", err.Error())
   109  
   110  	evt.general = 10000
   111  	marginFactor = num.DecimalFromFloat(0.3)
   112  	riskEvent, err = e.SwitchToIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), orders, marginFactor, nil)
   113  	require.NoError(t, err)
   114  	require.Equal(t, 2, len(riskEvent))
   115  	require.Equal(t, num.NewUint(290), riskEvent[0].Transfer().Amount.Amount)
   116  	require.Equal(t, num.NewUint(290), riskEvent[0].Transfer().MinAmount)
   117  	require.Equal(t, types.TransferTypeMarginLow, riskEvent[0].Transfer().Type)
   118  	require.Equal(t, "party1", riskEvent[0].Transfer().Owner)
   119  	require.Equal(t, num.NewUint(300), riskEvent[0].MarginLevels().OrderMargin)
   120  
   121  	buyOrderInfo, sellOrderInfo = extractOrderInfo(orders)
   122  	requiredPositionMarginStatic, requiredOrderMarginStatic = risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, nil)
   123  	require.True(t, !requiredPositionMarginStatic.IsZero())
   124  	require.True(t, !requiredOrderMarginStatic.IsZero())
   125  	transferRecalc = requiredPositionMarginStatic.Sub(evt.MarginBalance().ToDecimal())
   126  	require.True(t, riskEvent[0].Transfer().Amount.Amount.ToDecimal().Sub(transferRecalc).IsZero())
   127  
   128  	require.Equal(t, num.NewUint(300), riskEvent[1].Transfer().Amount.Amount)
   129  	require.Equal(t, num.NewUint(300), riskEvent[1].Transfer().MinAmount)
   130  	require.Equal(t, types.TransferTypeOrderMarginLow, riskEvent[1].Transfer().Type)
   131  	require.Equal(t, "party1", riskEvent[1].Transfer().Owner)
   132  	require.Equal(t, num.NewUint(300), riskEvent[0].MarginLevels().OrderMargin)
   133  	transferRecalc = requiredOrderMarginStatic.Sub(evt.OrderMarginBalance().ToDecimal())
   134  	require.True(t, riskEvent[1].Transfer().Amount.Amount.ToDecimal().Sub(transferRecalc).IsZero())
   135  
   136  	// case3 - need to release from margin account and order margin account back into general account
   137  	evt.margin += 600
   138  	evt.orderMargin += 400
   139  	marginFactor = num.DecimalFromFloat(0.3)
   140  	riskEvent, err = e.SwitchToIsolatedMargin(context.Background(), evt, num.NewUint(100), num.DecimalOne(), orders, marginFactor, nil)
   141  	require.NoError(t, err)
   142  	require.Equal(t, 2, len(riskEvent))
   143  	require.Equal(t, num.NewUint(310), riskEvent[0].Transfer().Amount.Amount)
   144  	require.Equal(t, num.NewUint(310), riskEvent[0].Transfer().MinAmount)
   145  	require.Equal(t, types.TransferTypeMarginHigh, riskEvent[0].Transfer().Type)
   146  	require.Equal(t, "party1", riskEvent[0].Transfer().Owner)
   147  	require.Equal(t, num.NewUint(300), riskEvent[0].MarginLevels().OrderMargin)
   148  
   149  	buyOrderInfo, sellOrderInfo = extractOrderInfo(orders)
   150  	requiredPositionMarginStatic, requiredOrderMarginStatic = risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, nil)
   151  	require.True(t, !requiredPositionMarginStatic.IsZero())
   152  	require.True(t, !requiredOrderMarginStatic.IsZero())
   153  	transferRecalc = evt.MarginBalance().ToDecimal().Sub(requiredPositionMarginStatic)
   154  	require.True(t, riskEvent[0].Transfer().Amount.Amount.ToDecimal().Sub(transferRecalc).IsZero())
   155  
   156  	require.Equal(t, num.NewUint(100), riskEvent[1].Transfer().Amount.Amount)
   157  	require.Equal(t, num.NewUint(100), riskEvent[1].Transfer().MinAmount)
   158  	require.Equal(t, types.TransferTypeOrderMarginHigh, riskEvent[1].Transfer().Type)
   159  	require.Equal(t, "party1", riskEvent[1].Transfer().Owner)
   160  	require.Equal(t, num.NewUint(300), riskEvent[0].MarginLevels().OrderMargin)
   161  
   162  	transferRecalc = evt.OrderMarginBalance().ToDecimal().Sub(requiredOrderMarginStatic)
   163  	require.True(t, riskEvent[1].Transfer().Amount.Amount.ToDecimal().Sub(transferRecalc).IsZero())
   164  }
   165  
   166  func extractOrderInfo(orders []*types.Order) (buyOrders, sellOrders []*risk.OrderInfo) {
   167  	buyOrders, sellOrders = []*risk.OrderInfo{}, []*risk.OrderInfo{}
   168  	for _, o := range orders {
   169  		if o.Status == types.OrderStatusActive {
   170  			remaining := o.TrueRemaining()
   171  			price := o.Price.ToDecimal()
   172  			isMarketOrder := o.Type == types.OrderTypeMarket
   173  			if o.Side == types.SideBuy {
   174  				buyOrders = append(buyOrders, &risk.OrderInfo{TrueRemaining: remaining, Price: price, IsMarketOrder: isMarketOrder})
   175  			}
   176  			if o.Side == types.SideSell {
   177  				sellOrders = append(sellOrders, &risk.OrderInfo{TrueRemaining: remaining, Price: price, IsMarketOrder: isMarketOrder})
   178  			}
   179  		}
   180  	}
   181  	return buyOrders, sellOrders
   182  }