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 }