code.vegaprotocol.io/vega@v0.79.0/core/risk/isolated_margin_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
    17  
    18  import (
    19  	"testing"
    20  
    21  	"code.vegaprotocol.io/vega/core/types"
    22  	"code.vegaprotocol.io/vega/libs/num"
    23  
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestCalcMarginForOrdersBySideBuyContinous(t *testing.T) {
    28  	orders := []*types.Order{
    29  		{Side: types.SideBuy, Remaining: 10, Price: num.NewUint(50), Status: types.OrderStatusActive},
    30  		{Side: types.SideBuy, Remaining: 20, Price: num.NewUint(40), Status: types.OrderStatusActive},
    31  		{Side: types.SideBuy, Remaining: 30, Price: num.NewUint(20), Status: types.OrderStatusActive},
    32  	}
    33  	currentPos := int64(0)
    34  	marginFactor := num.DecimalFromFloat(0.5)
    35  	positionFactor := num.DecimalFromInt64(10)
    36  
    37  	buyOrderInfo, sellOrderInfo := extractOrderInfo(orders)
    38  
    39  	// no position
    40  	// orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95
    41  	orderSideMargin := calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
    42  	require.Equal(t, num.NewUint(95), orderSideMargin)
    43  
    44  	staticResult := CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
    45  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
    46  
    47  	// long position - similar to no position, nothing is covered
    48  	// orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95
    49  	currentPos = 20
    50  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
    51  	require.Equal(t, num.NewUint(95), orderSideMargin)
    52  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
    53  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
    54  
    55  	// short position
    56  	// part of the top order is covered, i.e. only 6 count:
    57  	// orderMargin = 0.5*(6 * 50 + 20 * 40 + 30 * 20)/10 = 85
    58  	currentPos = -4
    59  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
    60  	require.Equal(t, num.NewUint(85), orderSideMargin)
    61  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
    62  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
    63  
    64  	// short position
    65  	// all of the top order is covered, a some of the second one too
    66  	// orderMargin = 0.5*(0 * 50 + 10 * 40 + 30 * 20)/10 = 50
    67  	currentPos = -20
    68  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
    69  	require.Equal(t, num.NewUint(50), orderSideMargin)
    70  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
    71  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
    72  
    73  	// short position
    74  	// all of the orders are covered by position on the other side
    75  	currentPos = -60
    76  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
    77  	require.Equal(t, num.UintZero(), orderSideMargin)
    78  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
    79  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
    80  }
    81  
    82  func TestCalcMarginForOrdersBySideSellContinous(t *testing.T) {
    83  	orders := []*types.Order{
    84  		{Side: types.SideSell, Remaining: 10, Price: num.NewUint(20), Status: types.OrderStatusActive},
    85  		{Side: types.SideSell, Remaining: 20, Price: num.NewUint(40), Status: types.OrderStatusActive},
    86  		{Side: types.SideSell, Remaining: 30, Price: num.NewUint(50), Status: types.OrderStatusActive},
    87  	}
    88  	currentPos := int64(0)
    89  	marginFactor := num.DecimalFromFloat(0.5)
    90  	positionFactor := num.DecimalFromInt64(10)
    91  
    92  	buyOrderInfo, sellOrderInfo := extractOrderInfo(orders)
    93  
    94  	// no position
    95  	// orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125
    96  	orderSideMargin := calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
    97  	require.Equal(t, num.NewUint(125), orderSideMargin)
    98  	staticResult := CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
    99  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   100  
   101  	// short position - similar to no position, nothing is covered
   102  	// orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125
   103  	currentPos = -20
   104  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
   105  	require.Equal(t, num.NewUint(125), orderSideMargin)
   106  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   107  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   108  
   109  	// long position
   110  	// part of the top order is covered, i.e. only 6 count:
   111  	// orderMargin = 0.5*(6 * 20 + 20 * 40 + 30 * 50)/10 = 121
   112  	currentPos = 4
   113  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
   114  	require.Equal(t, num.NewUint(121), orderSideMargin)
   115  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   116  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   117  
   118  	// long position
   119  	// all of the top order is covered, a some of the second one too
   120  	// orderMargin = 0.5*(0 * 20 + 10 * 40 + 30 * 50)/10 = 95
   121  	currentPos = 20
   122  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
   123  	require.Equal(t, num.NewUint(95), orderSideMargin)
   124  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   125  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   126  
   127  	// long position
   128  	// all of the orders are covered by position on the other side
   129  	currentPos = 60
   130  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil)
   131  	require.Equal(t, num.UintZero(), calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, nil))
   132  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   133  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   134  }
   135  
   136  func TestCalcMarginForOrdersBySideBuyAuction(t *testing.T) {
   137  	orders := []*types.Order{
   138  		{Side: types.SideBuy, Remaining: 10, Price: num.NewUint(50), Status: types.OrderStatusActive},
   139  		{Side: types.SideBuy, Remaining: 20, Price: num.NewUint(40), Status: types.OrderStatusActive},
   140  		{Side: types.SideBuy, Remaining: 30, Price: num.NewUint(20), Status: types.OrderStatusActive},
   141  	}
   142  	currentPos := int64(0)
   143  	marginFactor := num.DecimalFromFloat(0.5)
   144  	positionFactor := num.DecimalFromInt64(10)
   145  	auctionPrice := num.NewUint(42)
   146  
   147  	buyOrderInfo, sellOrderInfo := extractOrderInfo(orders)
   148  
   149  	// no position
   150  	// orderMargin = 0.5*(10 * 50 + 20 * 42 + 30 * 42)/10 = 130 (using the max between the order and auction price)
   151  	orderSideMargin := calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   152  	require.Equal(t, num.NewUint(130), orderSideMargin)
   153  	staticResult := CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   154  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   155  
   156  	// long position - similar to no position, nothing is covered (using the max between the order and auction price)
   157  	// orderMargin = 0.5*(10 * 50 + 20 * 42 + 30 * 42)/10 = 130
   158  	currentPos = 20
   159  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   160  	require.Equal(t, num.NewUint(130), orderSideMargin)
   161  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   162  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   163  
   164  	// short position
   165  	// part of the top order is covered, i.e. only 6 count:
   166  	// orderMargin = 0.5*(6 * 50 + 20 * 42 + 30 * 42)/10 = 120
   167  	currentPos = -4
   168  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   169  	require.Equal(t, num.NewUint(120), orderSideMargin)
   170  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   171  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   172  
   173  	// short position
   174  	// all of the top order is covered, a some of the second one too
   175  	// orderMargin = 0.5*(0 * 50 + 10 * 42 + 30 * 42)/10 = 84
   176  	currentPos = -20
   177  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   178  	require.Equal(t, num.NewUint(84), orderSideMargin)
   179  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   180  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   181  
   182  	// short position
   183  	// all of the orders are covered by position on the other side
   184  	currentPos = -60
   185  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   186  	require.Equal(t, num.UintZero(), orderSideMargin)
   187  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   188  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   189  }
   190  
   191  func TestCalcMarginForOrdersBySideSellAuction(t *testing.T) {
   192  	orders := []*types.Order{
   193  		{Side: types.SideSell, Remaining: 10, Price: num.NewUint(20), Status: types.OrderStatusActive},
   194  		{Side: types.SideSell, Remaining: 20, Price: num.NewUint(40), Status: types.OrderStatusActive},
   195  		{Side: types.SideSell, Remaining: 30, Price: num.NewUint(50), Status: types.OrderStatusActive},
   196  	}
   197  	currentPos := int64(0)
   198  	marginFactor := num.DecimalFromFloat(0.5)
   199  	positionFactor := num.DecimalFromInt64(10)
   200  	auctionPrice := num.NewUint(42)
   201  
   202  	buyOrderInfo, sellOrderInfo := extractOrderInfo(orders)
   203  
   204  	// no position
   205  	// orderMargin = 0.5*(10 * 42 + 20 * 42 + 30 * 50)/10 = 138
   206  	orderSideMargin := calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   207  	require.Equal(t, num.NewUint(138), orderSideMargin)
   208  	staticResult := CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   209  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   210  
   211  	// short position - similar to no position, nothing is covered
   212  	// orderMargin = 0.5*(10 * 42 + 20 * 42 + 30 * 50)/10 = 138
   213  	currentPos = -20
   214  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   215  	require.Equal(t, num.NewUint(138), orderSideMargin)
   216  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   217  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   218  
   219  	// long position
   220  	// part of the top order is covered, i.e. only 6 count:
   221  	// orderMargin = 0.5*(6 * 42 + 20 * 42 + 30 * 50)/10 = 129
   222  	currentPos = 4
   223  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   224  	require.Equal(t, num.NewUint(129), orderSideMargin)
   225  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   226  	require.Equal(t, staticResult.RoundDown(0).String(), orderSideMargin.String()) // equal within rounding
   227  
   228  	// long position
   229  	// all of the top order is covered, a some of the second one too
   230  	// orderMargin = 0.5*(0 * 42 + 10 * 42 + 30 * 50)/10 = 96
   231  	currentPos = 20
   232  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   233  	require.Equal(t, num.NewUint(96), orderSideMargin)
   234  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   235  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   236  
   237  	// long position
   238  	// all of the orders are covered by position on the other side
   239  	currentPos = 60
   240  	orderSideMargin = calcOrderSideMargin(currentPos, orders, positionFactor, marginFactor, auctionPrice)
   241  	require.Equal(t, num.UintZero(), orderSideMargin)
   242  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalFromUint(auctionPrice))
   243  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   244  }
   245  
   246  func TestCalcOrderMarginContinous(t *testing.T) {
   247  	orders := []*types.Order{
   248  		{Side: types.SideSell, Remaining: 10, Price: num.NewUint(20), Status: types.OrderStatusActive},
   249  		{Side: types.SideBuy, Remaining: 10, Price: num.NewUint(50), Status: types.OrderStatusActive},
   250  		{Side: types.SideSell, Remaining: 20, Price: num.NewUint(40), Status: types.OrderStatusActive},
   251  		{Side: types.SideBuy, Remaining: 20, Price: num.NewUint(40), Status: types.OrderStatusActive},
   252  		{Side: types.SideSell, Remaining: 30, Price: num.NewUint(50), Status: types.OrderStatusActive},
   253  		{Side: types.SideBuy, Remaining: 30, Price: num.NewUint(20), Status: types.OrderStatusActive},
   254  	}
   255  	currentPos := int64(0)
   256  	marginFactor := num.DecimalFromFloat(0.5)
   257  	positionFactor := num.DecimalFromInt64(10)
   258  
   259  	buyOrderInfo, sellOrderInfo := extractOrderInfo(orders)
   260  
   261  	// no position
   262  	// buy orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95
   263  	// sell orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125
   264  	// order margin = max(95,125) = 125
   265  	orderSideMargin := CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil)
   266  	require.Equal(t, num.NewUint(125), orderSideMargin)
   267  	staticResult := CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   268  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   269  
   270  	// long position
   271  	// buy orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95
   272  	// sell orderMargin = 0.5*(6 * 20 + 20 * 40 + 30 * 50)/10 = 121
   273  	currentPos = 4
   274  	orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil)
   275  	require.Equal(t, num.NewUint(121), orderSideMargin)
   276  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   277  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   278  
   279  	// longer position
   280  	// buy orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95
   281  	// sell orderMargin =  0.5*(0 * 20 + 5 * 40 + 30 * 50)/10 = 85
   282  	currentPos = 25
   283  	orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil)
   284  	require.Equal(t, num.NewUint(95), orderSideMargin)
   285  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   286  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   287  
   288  	// short position
   289  	// buy orderMargin = 0.5*(6 * 50 + 20 * 40 + 30 * 20)/10 = 85
   290  	// sell orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125
   291  	currentPos = -4
   292  	orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil)
   293  	require.Equal(t, num.NewUint(125), orderSideMargin)
   294  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   295  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   296  
   297  	// shorter position
   298  	// buy orderMargin = 0.5*(0 * 50 + 10 * 40 + 30 * 20)/10 = 50
   299  	// sell orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125
   300  	currentPos = -20
   301  	orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil)
   302  	require.Equal(t, num.NewUint(125), orderSideMargin)
   303  	staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero())
   304  	require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String())
   305  }
   306  
   307  func TestGetIsolatedMarginTransfersOnPositionChangeIncrease(t *testing.T) {
   308  	party := "Zohar"
   309  	asset := "BTC"
   310  
   311  	marginFactor := num.NewDecimalFromFloat(0.5)
   312  	curMarginBalance := num.NewUint(1000)
   313  	positionFactor := num.DecimalFromInt64(10)
   314  
   315  	// go long trades
   316  	trades := []*types.Trade{
   317  		{Size: 5, Price: num.NewUint(12)},
   318  		{Size: 10, Price: num.NewUint(10)},
   319  	}
   320  
   321  	// position going up from 0 to 15 (increasing)
   322  	// required margin topup is equal to: 0.5 * (5*12+10*10)/10 = 8
   323  	transfer := getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideBuy, 15, positionFactor, marginFactor, curMarginBalance, num.UintZero(), nil, false, false)
   324  	// i.e. take from order margin account to the margin account
   325  	require.Equal(t, types.TransferTypeIsolatedMarginLow, transfer[0].Type)
   326  	require.Equal(t, num.NewUint(8), transfer[0].Amount.Amount)
   327  	require.Equal(t, num.NewUint(8), transfer[0].MinAmount)
   328  
   329  	// position going up from 0 to -15 (increasing)
   330  	// go short trades
   331  	trades = []*types.Trade{
   332  		{Size: 10, Price: num.NewUint(10)},
   333  		{Size: 5, Price: num.NewUint(12)},
   334  	}
   335  	// required margin topup is equal to: 0.5 * (5*12+10*10)/10 = 8
   336  	transfer = getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideSell, -15, positionFactor, marginFactor, curMarginBalance, num.UintZero(), nil, false, false)
   337  	// i.e. take from order margin account to the margin account
   338  	require.Equal(t, types.TransferTypeIsolatedMarginLow, transfer[0].Type)
   339  	require.Equal(t, num.NewUint(8), transfer[0].Amount.Amount)
   340  	require.Equal(t, num.NewUint(8), transfer[0].MinAmount)
   341  }
   342  
   343  func TestGetIsolatedMarginTransfersOnPositionChangeDecrease(t *testing.T) {
   344  	party := "Zohar"
   345  	asset := "BTC"
   346  
   347  	marginFactor := num.NewDecimalFromFloat(0.5)
   348  	curMarginBalance := num.NewUint(40)
   349  	positionFactor := num.DecimalFromInt64(10)
   350  
   351  	trades := []*types.Trade{
   352  		{Size: 5, Price: num.NewUint(12)},
   353  		{Size: 10, Price: num.NewUint(10)},
   354  	}
   355  	markPrice := num.NewUint(12)
   356  	// position going down from 20 to 5 (decreasing)
   357  	// required margin topup is equal to: (40+20/10*-2)  * 15/20) = 27
   358  	transfer := getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideSell, 5, positionFactor, marginFactor, curMarginBalance, num.UintZero(), markPrice, false, false)
   359  	// i.e. release from the margin account to the general account
   360  	require.Equal(t, types.TransferTypeMarginHigh, transfer[0].Type)
   361  	require.Equal(t, num.NewUint(27), transfer[0].Amount.Amount)
   362  	require.Equal(t, num.NewUint(27), transfer[0].MinAmount)
   363  
   364  	// position going down from 20 to 5 (decreasing)
   365  	trades = []*types.Trade{
   366  		{Size: 5, Price: num.NewUint(10)},
   367  		{Size: 10, Price: num.NewUint(12)},
   368  	}
   369  	// required margin release is equal to: (40+20/10*-1)  * 15/20) = 28
   370  	transfer = getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideBuy, -5, positionFactor, marginFactor, curMarginBalance, num.UintZero(), markPrice, false, false)
   371  	// i.e. release from margin account general account
   372  	require.Equal(t, types.TransferTypeMarginHigh, transfer[0].Type)
   373  	require.Equal(t, num.NewUint(28), transfer[0].Amount.Amount)
   374  	require.Equal(t, num.NewUint(28), transfer[0].MinAmount)
   375  }
   376  
   377  func TestGetIsolatedMarginTransfersOnPositionChangeSwitchSides(t *testing.T) {
   378  	party := "Zohar"
   379  	asset := "BTC"
   380  
   381  	marginFactor := num.NewDecimalFromFloat(0.5)
   382  	curMarginBalance := num.NewUint(1000)
   383  	positionFactor := num.DecimalFromInt64(10)
   384  
   385  	trades := []*types.Trade{
   386  		{Size: 15, Price: num.NewUint(11)},
   387  		{Size: 10, Price: num.NewUint(12)},
   388  	}
   389  	// position going from 20 to -5 (switching sides)
   390  	// required margin release is equal to: we release all 1000 margin, then require 0.5 * 5 * 12 / 10
   391  	transfer := getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideSell, -5, positionFactor, marginFactor, curMarginBalance, num.UintZero(), nil, false, false)
   392  	// i.e. release from the margin account to the general account
   393  	require.Equal(t, types.TransferTypeMarginHigh, transfer[0].Type)
   394  	require.Equal(t, num.NewUint(997), transfer[0].Amount.Amount)
   395  	require.Equal(t, num.NewUint(997), transfer[0].MinAmount)
   396  
   397  	curMarginBalance = num.NewUint(1)
   398  	transfer = getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideSell, -5, positionFactor, marginFactor, curMarginBalance, num.UintZero(), nil, false, false)
   399  
   400  	// now we expect to need 2 more to be added from the order margin account
   401  	require.Equal(t, types.TransferTypeMarginLow, transfer[0].Type)
   402  	require.Equal(t, num.NewUint(2), transfer[0].Amount.Amount)
   403  	require.Equal(t, num.NewUint(2), transfer[0].MinAmount)
   404  
   405  	curMarginBalance = num.NewUint(1000)
   406  	trades = []*types.Trade{
   407  		{Size: 10, Price: num.NewUint(12)},
   408  		{Size: 15, Price: num.NewUint(11)},
   409  	}
   410  	// position going from -20 to 5 (switching sides)
   411  	// required margin release is equal to: we release all 1000 margin, then require 0.5 * 5 * 11 / 10
   412  	transfer = getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideBuy, 5, positionFactor, marginFactor, curMarginBalance, num.UintZero(), nil, false, false)
   413  	// i.e. release from the margin account to the general account
   414  	require.Equal(t, types.TransferTypeMarginHigh, transfer[0].Type)
   415  	require.Equal(t, num.NewUint(998), transfer[0].Amount.Amount)
   416  	require.Equal(t, num.NewUint(998), transfer[0].MinAmount)
   417  
   418  	// try the same as above for switching sides to short
   419  	curMarginBalance = num.NewUint(1)
   420  	transfer = getIsolatedMarginTransfersOnPositionChange(party, asset, trades, types.SideSell, -5, positionFactor, marginFactor, curMarginBalance, num.UintZero(), nil, false, false)
   421  
   422  	// now we expect to need 1 more to be added from the order margin account
   423  	require.Equal(t, types.TransferTypeMarginLow, transfer[0].Type)
   424  	require.Equal(t, num.NewUint(1), transfer[0].Amount.Amount)
   425  	require.Equal(t, num.NewUint(1), transfer[0].MinAmount)
   426  }
   427  
   428  func extractOrderInfo(orders []*types.Order) (buyOrders, sellOrders []*OrderInfo) {
   429  	buyOrders, sellOrders = []*OrderInfo{}, []*OrderInfo{}
   430  	for _, o := range orders {
   431  		if o.Status == types.OrderStatusActive {
   432  			remaining := o.TrueRemaining()
   433  			price := o.Price.ToDecimal()
   434  			isMarketOrder := o.Type == types.OrderTypeMarket
   435  			if o.Side == types.SideBuy {
   436  				buyOrders = append(buyOrders, &OrderInfo{TrueRemaining: remaining, Price: price, IsMarketOrder: isMarketOrder})
   437  			}
   438  			if o.Side == types.SideSell {
   439  				sellOrders = append(sellOrders, &OrderInfo{TrueRemaining: remaining, Price: price, IsMarketOrder: isMarketOrder})
   440  			}
   441  		}
   442  	}
   443  	return buyOrders, sellOrders
   444  }