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 }