code.vegaprotocol.io/vega@v0.79.0/core/risk/engine_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 "fmt" 21 "math" 22 "sort" 23 "testing" 24 "time" 25 26 bmocks "code.vegaprotocol.io/vega/core/broker/mocks" 27 "code.vegaprotocol.io/vega/core/config" 28 "code.vegaprotocol.io/vega/core/events" 29 "code.vegaprotocol.io/vega/core/matching" 30 "code.vegaprotocol.io/vega/core/risk" 31 "code.vegaprotocol.io/vega/core/risk/mocks" 32 "code.vegaprotocol.io/vega/core/types" 33 "code.vegaprotocol.io/vega/core/types/statevar" 34 "code.vegaprotocol.io/vega/libs/num" 35 "code.vegaprotocol.io/vega/logging" 36 proto "code.vegaprotocol.io/vega/protos/vega" 37 38 "github.com/golang/mock/gomock" 39 "github.com/stretchr/testify/assert" 40 "github.com/stretchr/testify/require" 41 ) 42 43 var DefaultSlippageFactor = num.DecimalFromFloat(0.1) 44 45 func peggedOrderCounterForTest(int64) {} 46 47 type MLEvent interface { 48 events.Event 49 MarginLevels() proto.MarginLevels 50 } 51 52 type testEngine struct { 53 *risk.Engine 54 ctrl *gomock.Controller 55 model *mocks.MockModel 56 orderbook *mocks.MockOrderbook 57 tsvc *mocks.MockTimeService 58 broker *bmocks.MockBroker 59 as *mocks.MockAuctionState 60 } 61 62 // implements the events.Margin interface. 63 type testMargin struct { 64 party string 65 size int64 66 buy int64 67 sell int64 68 price uint64 69 transfer *types.Transfer 70 asset string 71 margin uint64 72 orderMargin uint64 73 general uint64 74 market string 75 buySumProduct uint64 76 sellSumProduct uint64 77 marginShortFall uint64 78 } 79 80 var ( 81 riskFactors = types.RiskFactor{ 82 Short: num.DecimalFromFloat(.20), 83 Long: num.DecimalFromFloat(.25), 84 } 85 86 markPrice = num.NewUint(100) 87 ) 88 89 func TestUpdateMargins(t *testing.T) { 90 t.Run("test time update", testMarginLevelsTS) 91 t.Run("Top up margin test", testMarginTopup) 92 t.Run("Noop margin test", testMarginNoop) 93 t.Run("Margin too high (overflow)", testMarginOverflow) 94 t.Run("Margin too high (overflow) - auction ending", testMarginOverflowAuctionEnd) 95 t.Run("Update Margin with orders in book", testMarginWithOrderInBook) 96 t.Run("Update Margin with orders in book 2", testMarginWithOrderInBook2) 97 t.Run("Update Margin with orders in book after parameters update", testMarginWithOrderInBookAfterParamsUpdate) 98 t.Run("Top up fail on new order", testMarginTopupOnOrderFailInsufficientFunds) 99 t.Run("Margin not released in auction", testMarginNotReleasedInAuction) 100 t.Run("Initial margin requirement must be met", testInitialMarginRequirement) 101 } 102 103 func testMarginLevelsTS(t *testing.T) { 104 eng := getTestEngine(t, num.DecimalOne()) 105 106 ctx, cfunc := context.WithCancel(context.Background()) 107 defer cfunc() 108 evt := testMargin{ 109 party: "party1", 110 size: 1, 111 price: 1000, 112 asset: "ETH", 113 margin: 10, // required margin will be > 30 so ensure we don't have enough 114 general: 100000, // plenty of balance for the transfer anyway 115 market: "ETH/DEC19", 116 } 117 118 now := time.Now() 119 eng.tsvc.EXPECT().GetTimeNow().DoAndReturn( 120 func() time.Time { 121 return now 122 }).Times(1) 123 124 eng.as.EXPECT().InAuction().AnyTimes().Return(false) 125 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(e []events.Event) { 126 mle, ok := e[0].(MLEvent) 127 assert.True(t, ok) 128 ml := mle.MarginLevels() 129 assert.Equal(t, now.UnixNano(), ml.Timestamp) 130 }) 131 132 evts := []events.Margin{evt} 133 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil) 134 assert.Equal(t, 1, len(resp)) 135 // ensure we get the correct transfer request back, correct amount etc... 136 trans := resp[0].Transfer() 137 assert.EqualValues(t, 44, trans.Amount.Amount.Uint64()) 138 // min = 35 so we go back to maintenance level 139 assert.EqualValues(t, 35, trans.MinAmount.Uint64()) 140 assert.Equal(t, types.TransferTypeMarginLow, trans.Type) 141 } 142 143 func TestNegativeMargin(t *testing.T) { 144 eng := getTestEngine(t, num.DecimalFromInt64(6)) 145 mtmPrice := num.NewUint(20) 146 147 ctx, cfunc := context.WithCancel(context.Background()) 148 defer cfunc() 149 evt := testMargin{ 150 party: "party1", 151 size: -1, 152 price: 10, // holding at 10 153 asset: "ETH", 154 margin: 1, // required margin will be > 30 so ensure we don't have enough 155 general: 10000000000000000000, // plenty of balance for the transfer anyway 156 market: "ETH/DEC19", 157 sell: 2, // potential short -1 158 buy: 2, 159 } 160 161 now := time.Now() 162 eng.tsvc.EXPECT().GetTimeNow().DoAndReturn( 163 func() time.Time { 164 return now 165 }).AnyTimes() 166 167 eng.as.EXPECT().InAuction().AnyTimes().Return(false) 168 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 169 170 // increment is negative 171 inc := num.DecimalFromFloat(-10) 172 riskEvts := eng.UpdateMarginsOnSettlement(ctx, []events.Margin{evt}, mtmPrice, inc, nil) 173 require.NotEmpty(t, riskEvts) 174 initial := riskEvts[0].Transfer().Amount.Amount 175 require.Equal(t, "5", initial.String()) 176 } 177 178 func testMarginTopup(t *testing.T) { 179 eng := getTestEngine(t, num.DecimalOne()) 180 181 ctx, cfunc := context.WithCancel(context.Background()) 182 defer cfunc() 183 evt := testMargin{ 184 party: "party1", 185 size: 1, 186 price: 1000, 187 asset: "ETH", 188 margin: 10, // required margin will be > 30 so ensure we don't have enough 189 general: 100000, // plenty of balance for the transfer anyway 190 market: "ETH/DEC19", 191 } 192 eng.tsvc.EXPECT().GetTimeNow().Times(1) 193 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 194 eng.as.EXPECT().InAuction().AnyTimes().Return(false) 195 evts := []events.Margin{evt} 196 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil) 197 assert.Equal(t, 1, len(resp)) 198 // ensure we get the correct transfer request back, correct amount etc... 199 trans := resp[0].Transfer() 200 assert.EqualValues(t, 44, trans.Amount.Amount.Uint64()) 201 // min = 35 so we go back to maintenance level 202 assert.EqualValues(t, 35, trans.MinAmount.Uint64()) 203 assert.Equal(t, types.TransferTypeMarginLow, trans.Type) 204 } 205 206 func TestMarginTopupPerpetual(t *testing.T) { 207 eng := getTestEngine(t, num.DecimalOne()) 208 209 ctx, cfunc := context.WithCancel(context.Background()) 210 defer cfunc() 211 evt := testMargin{ 212 party: "party1", 213 size: 1, 214 price: 1000, 215 asset: "ETH", 216 margin: 10, // required margin will be > 30 so ensure we don't have enough 217 general: 100000, // plenty of balance for the transfer anyway 218 market: "ETH/DEC19", 219 } 220 221 // lets pretend the perpetual margin factor is 0.5 and the funding payement was 10, 5 222 inc := num.DecimalFromFloat(0.5).Mul(num.DecimalFromInt64(10)) 223 224 eng.tsvc.EXPECT().GetTimeNow().Times(2) 225 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 226 eng.as.EXPECT().InAuction().AnyTimes().Return(false) 227 evts := []events.Margin{evt} 228 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, inc, nil) 229 assert.Equal(t, 1, len(resp)) 230 231 mm := resp[0].MarginLevels().MaintenanceMargin 232 assert.Equal(t, "50", mm.String()) 233 234 // now do it again with the funding payment negated, the margin should be as if we were not a perp 235 // and 5 less 236 resp = eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, inc.Neg(), nil) 237 assert.Equal(t, 1, len(resp)) 238 239 mm = resp[0].MarginLevels().MaintenanceMargin 240 assert.Equal(t, "45", mm.String()) 241 } 242 243 func testMarginNotReleasedInAuction(t *testing.T) { 244 eng := getTestEngine(t, num.DecimalOne()) 245 246 ctx, cfunc := context.WithCancel(context.Background()) 247 defer cfunc() 248 evt := testMargin{ 249 party: "party1", 250 size: 1, 251 price: 1000, 252 asset: "ETH", 253 margin: 70, // relese level is 35 so we need more than that 254 general: 100000, 255 market: "ETH/DEC19", 256 } 257 eng.tsvc.EXPECT().GetTimeNow().Times(1) 258 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 259 eng.as.EXPECT().InAuction().AnyTimes().Return(true) 260 eng.as.EXPECT().CanLeave().AnyTimes().Return(false) 261 evts := []events.Margin{evt} 262 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil) 263 assert.Equal(t, 0, len(resp)) 264 } 265 266 func testMarginTopupOnOrderFailInsufficientFunds(t *testing.T) { 267 eng := getTestEngine(t, num.DecimalOne()) 268 269 _, cfunc := context.WithCancel(context.Background()) 270 defer cfunc() 271 evt := testMargin{ 272 party: "party1", 273 size: 1, 274 price: 1000, 275 asset: "ETH", 276 margin: 10, // maring and general combined are not enough to get a sufficient margin 277 general: 10, 278 market: "ETH/DEC19", 279 } 280 eng.tsvc.EXPECT().GetTimeNow().Times(1) 281 eng.as.EXPECT().InAuction().AnyTimes().Return(false) 282 riskevt, _, err := eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 283 assert.Nil(t, riskevt) 284 assert.NotNil(t, err) 285 assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error()) 286 } 287 288 func testMarginNoop(t *testing.T) { 289 eng := getTestEngine(t, num.DecimalOne()) 290 291 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 292 ctx, cfunc := context.WithCancel(context.Background()) 293 defer cfunc() 294 evt := testMargin{ 295 party: "party1", 296 size: 1, 297 price: 1000, 298 asset: "ETH", 299 margin: 30, // more than enough margin to cover the position, not enough to trigger transfer to general 300 general: 100000, // plenty of balance for the transfer anyway 301 market: "ETH/DEC19", 302 } 303 eng.tsvc.EXPECT().GetTimeNow().Times(1) 304 eng.as.EXPECT().InAuction().AnyTimes().Return(false) 305 306 evts := []events.Margin{evt} 307 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil) 308 assert.Equal(t, 1, len(resp)) 309 } 310 311 func testMarginOverflow(t *testing.T) { 312 eng := getTestEngine(t, num.DecimalOne()) 313 314 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 315 ctx, cfunc := context.WithCancel(context.Background()) 316 defer cfunc() 317 evt := testMargin{ 318 party: "party1", 319 size: 1, 320 price: 1000, 321 asset: "ETH", 322 margin: 500, // required margin will be > 35 (release), so ensure we don't have enough 323 general: 100000, // plenty of balance for the transfer anyway 324 market: "ETH/DEC19", 325 } 326 eng.tsvc.EXPECT().GetTimeNow().Times(1) 327 eng.as.EXPECT().InAuction().Times(2).Return(false) 328 evts := []events.Margin{evt} 329 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil) 330 assert.Equal(t, 1, len(resp)) 331 332 // ensure we get the correct transfer request back, correct amount etc... 333 trans := resp[0].Transfer() 334 assert.EqualValues(t, 446, trans.Amount.Amount.Uint64()) 335 assert.Equal(t, types.TransferTypeMarginHigh, trans.Type) 336 } 337 338 func testMarginOverflowAuctionEnd(t *testing.T) { 339 eng := getTestEngine(t, num.DecimalOne()) 340 341 eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() 342 ctx, cfunc := context.WithCancel(context.Background()) 343 defer cfunc() 344 evt := testMargin{ 345 party: "party1", 346 size: 1, 347 price: 1000, 348 asset: "ETH", 349 margin: 500, // required margin will be > 35 (release), so ensure we don't have enough 350 general: 100000, // plenty of balance for the transfer anyway 351 market: "ETH/DEC19", 352 } 353 // we're still in auction... 354 eng.tsvc.EXPECT().GetTimeNow().Times(1) 355 eng.as.EXPECT().InAuction().Times(2).Return(true) 356 // but the auction is ending 357 eng.as.EXPECT().CanLeave().Times(2).Return(true) 358 // eng.as.EXPECT().InAuction().AnyTimes().Return(false) 359 evts := []events.Margin{evt} 360 resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), markPrice) 361 assert.Equal(t, 1, len(resp)) 362 363 // ensure we get the correct transfer request back, correct amount etc... 364 trans := resp[0].Transfer() 365 assert.EqualValues(t, 446, trans.Amount.Amount.Uint64()) 366 assert.Equal(t, types.TransferTypeMarginHigh, trans.Type) 367 } 368 369 func TestMarginWithNoOrdersOnBook(t *testing.T) { 370 // assure state-aware and static methods provide results consistent with each other 371 r := &types.RiskFactor{ 372 Short: num.DecimalFromFloat(.11), 373 Long: num.DecimalFromFloat(.10), 374 } 375 mc := &types.MarginCalculator{ 376 ScalingFactors: &types.ScalingFactors{ 377 SearchLevel: num.DecimalFromFloat(1.1), 378 InitialMargin: num.DecimalFromFloat(1.2), 379 CollateralRelease: num.DecimalFromFloat(1.3), 380 }, 381 } 382 markPrice := int64(144) 383 384 marketID := "testingmarket" 385 386 conf := config.NewDefaultConfig() 387 log := logging.NewTestLogger() 388 ctrl := gomock.NewController(t) 389 model := mocks.NewMockModel(ctrl) 390 ts := mocks.NewMockTimeService(ctrl) 391 broker := bmocks.NewMockBroker(ctrl) 392 393 ts.EXPECT().GetTimeNow().AnyTimes() 394 broker.EXPECT().Send(gomock.Any()).AnyTimes() 395 396 model.EXPECT().DefaultRiskFactors().Return(r).AnyTimes() 397 398 statevar := mocks.NewMockStateVarEngine(ctrl) 399 statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 400 statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 401 book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest) 402 403 testCases := []struct { 404 expectedMargin string 405 positionSize int64 406 buyOrders []*risk.OrderInfo 407 sellOrders []*risk.OrderInfo 408 linearSlippageFactor num.Decimal 409 quadraticSlippageFactor num.Decimal 410 margin_funding_factor float64 411 funding_payment_to_date float64 412 auction bool 413 auctionPrice num.Decimal 414 }{ 415 { 416 expectedMargin: "87", 417 positionSize: 6, 418 buyOrders: nil, 419 sellOrders: nil, 420 linearSlippageFactor: num.DecimalZero(), 421 quadraticSlippageFactor: num.DecimalZero(), 422 margin_funding_factor: 0, 423 funding_payment_to_date: 0, 424 auction: false, 425 }, 426 { 427 expectedMargin: "96", 428 positionSize: -6, 429 buyOrders: nil, 430 sellOrders: nil, 431 linearSlippageFactor: num.DecimalZero(), 432 quadraticSlippageFactor: num.DecimalZero(), 433 margin_funding_factor: 0, 434 funding_payment_to_date: 0, 435 auction: true, 436 }, 437 { 438 expectedMargin: "335", 439 positionSize: 9, 440 buyOrders: []*risk.OrderInfo{ 441 { 442 TrueRemaining: 3, 443 Price: num.DecimalFromInt64(markPrice - 3), 444 IsMarketOrder: false, 445 }, 446 { 447 TrueRemaining: 5, 448 Price: num.DecimalFromInt64(markPrice - 12), 449 IsMarketOrder: false, 450 }, 451 }, 452 sellOrders: []*risk.OrderInfo{ 453 { 454 TrueRemaining: 5, 455 Price: num.DecimalFromInt64(markPrice + 2), 456 IsMarketOrder: false, 457 }, 458 { 459 TrueRemaining: 2, 460 Price: num.DecimalFromInt64(markPrice + 7), 461 IsMarketOrder: false, 462 }, 463 }, 464 linearSlippageFactor: num.DecimalZero(), 465 quadraticSlippageFactor: num.DecimalZero(), 466 margin_funding_factor: 1, 467 funding_payment_to_date: 10, 468 auction: false, 469 }, 470 { 471 expectedMargin: "328", 472 positionSize: 9, 473 buyOrders: []*risk.OrderInfo{ 474 { 475 TrueRemaining: 3, 476 Price: num.DecimalFromInt64(markPrice - 3), 477 IsMarketOrder: false, 478 }, 479 { 480 TrueRemaining: 5, 481 Price: num.DecimalFromInt64(markPrice - 12), 482 IsMarketOrder: false, 483 }, 484 }, 485 sellOrders: []*risk.OrderInfo{ 486 { 487 TrueRemaining: 5, 488 Price: num.DecimalFromInt64(markPrice + 2), 489 IsMarketOrder: false, 490 }, 491 { 492 TrueRemaining: 2, 493 Price: num.DecimalFromInt64(markPrice + 7), 494 IsMarketOrder: false, 495 }, 496 }, 497 linearSlippageFactor: num.DecimalZero(), 498 quadraticSlippageFactor: num.DecimalZero(), 499 margin_funding_factor: 1, 500 funding_payment_to_date: 10, 501 auction: true, 502 }, 503 { 504 expectedMargin: "232", 505 positionSize: -7, 506 buyOrders: []*risk.OrderInfo{ 507 { 508 TrueRemaining: 3, 509 Price: num.DecimalFromInt64(markPrice - 3), 510 IsMarketOrder: false, 511 }, 512 { 513 TrueRemaining: 5, 514 Price: num.DecimalFromInt64(markPrice - 12), 515 IsMarketOrder: false, 516 }, 517 }, 518 sellOrders: []*risk.OrderInfo{ 519 { 520 TrueRemaining: 5, 521 Price: num.DecimalFromInt64(markPrice + 2), 522 IsMarketOrder: false, 523 }, 524 { 525 TrueRemaining: 2, 526 Price: num.DecimalFromInt64(markPrice + 7), 527 IsMarketOrder: false, 528 }, 529 }, 530 linearSlippageFactor: num.DecimalFromFloat(0.01), 531 quadraticSlippageFactor: num.DecimalZero(), 532 margin_funding_factor: 1, 533 funding_payment_to_date: 10, 534 auction: false, 535 }, 536 { 537 expectedMargin: "236", 538 positionSize: -7, 539 buyOrders: []*risk.OrderInfo{ 540 { 541 TrueRemaining: 3, 542 Price: num.DecimalFromInt64(markPrice - 3), 543 IsMarketOrder: false, 544 }, 545 { 546 TrueRemaining: 5, 547 Price: num.DecimalFromInt64(markPrice - 12), 548 IsMarketOrder: false, 549 }, 550 }, 551 sellOrders: []*risk.OrderInfo{ 552 { 553 TrueRemaining: 5, 554 Price: num.DecimalFromInt64(markPrice + 2), 555 IsMarketOrder: false, 556 }, 557 { 558 TrueRemaining: 2, 559 Price: num.DecimalFromInt64(markPrice + 7), 560 IsMarketOrder: false, 561 }, 562 }, 563 linearSlippageFactor: num.DecimalFromFloat(0.01), 564 quadraticSlippageFactor: num.DecimalFromFloat(0.0001), 565 margin_funding_factor: 1, 566 funding_payment_to_date: 10, 567 auction: true, 568 }, 569 { 570 expectedMargin: "121", 571 positionSize: 1, 572 buyOrders: []*risk.OrderInfo{}, 573 sellOrders: []*risk.OrderInfo{ 574 { 575 TrueRemaining: 5, 576 Price: num.DecimalFromInt64(markPrice + 2), 577 IsMarketOrder: false, 578 }, 579 { 580 TrueRemaining: 2, 581 Price: num.DecimalFromInt64(markPrice + 7), 582 IsMarketOrder: false, 583 }, 584 }, 585 linearSlippageFactor: num.DecimalFromFloat(0.01), 586 quadraticSlippageFactor: num.DecimalZero(), 587 margin_funding_factor: 1, 588 funding_payment_to_date: 10, 589 auction: false, 590 }, 591 { 592 expectedMargin: "1754", 593 positionSize: 1, 594 buyOrders: []*risk.OrderInfo{ 595 { 596 TrueRemaining: 100, 597 Price: num.DecimalFromInt64(1), 598 IsMarketOrder: false, 599 }, 600 { 601 TrueRemaining: 20, 602 Price: num.DecimalFromInt64(2), 603 IsMarketOrder: false, 604 }, 605 }, 606 sellOrders: []*risk.OrderInfo{}, 607 linearSlippageFactor: num.DecimalFromFloat(0.01), 608 quadraticSlippageFactor: num.DecimalZero(), 609 margin_funding_factor: 1, 610 funding_payment_to_date: 10, 611 auction: false, 612 auctionPrice: num.DecimalFromInt64(123), 613 }, 614 } 615 616 for _, tc := range testCases { 617 buy := int64(0) 618 buySumProduct := uint64(0) 619 for _, o := range tc.buyOrders { 620 buy += int64(o.TrueRemaining) 621 buySumProduct += o.TrueRemaining * o.Price.BigInt().Uint64() 622 } 623 sell := int64(0) 624 sellSumProduct := uint64(0) 625 for _, o := range tc.sellOrders { 626 sell += int64(o.TrueRemaining) 627 sellSumProduct += o.TrueRemaining * o.Price.BigInt().Uint64() 628 } 629 630 evt := testMargin{ 631 party: "tx", 632 size: tc.positionSize, 633 buy: buy, 634 sell: sell, 635 buySumProduct: buySumProduct, 636 sellSumProduct: sellSumProduct, 637 price: uint64(markPrice), 638 asset: "ETH", 639 margin: 0, 640 general: 100000, 641 market: marketID, 642 } 643 644 constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date) 645 as := mocks.NewMockAuctionState(ctrl) 646 as.EXPECT().InAuction().AnyTimes().Return(tc.auction).AnyTimes() 647 as.EXPECT().CanLeave().AnyTimes().Return(!tc.auction).AnyTimes() 648 testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, marketID, "ETH", statevar, num.DecimalFromInt64(1), false, nil, tc.linearSlippageFactor, tc.quadraticSlippageFactor) 649 650 apUint, overflow := num.UintFromDecimal(tc.auctionPrice) 651 require.False(t, overflow) 652 653 riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, num.UintFromUint64(uint64(markPrice)), constantPerUnitPositionSize, apUint) 654 require.NoError(t, err) 655 require.NotNil(t, riskevt) 656 margins := riskevt.MarginLevels() 657 require.Equal(t, tc.expectedMargin, margins.MaintenanceMargin.String()) 658 659 marginRecalcualted := risk.CalculateMaintenanceMarginWithSlippageFactors(evt.size, tc.buyOrders, tc.sellOrders, num.DecimalFromInt64(markPrice), num.DecimalOne(), tc.linearSlippageFactor, tc.quadraticSlippageFactor, r.Long, r.Short, constantPerUnitPositionSize, tc.auction, tc.auctionPrice) 660 require.Equal(t, margins.MaintenanceMargin.Float64(), marginRecalcualted.RoundUp(0).InexactFloat64()) 661 } 662 } 663 664 func testMarginWithOrderInBook(t *testing.T) { 665 // custom risk factors 666 r := &types.RiskFactor{ 667 Short: num.DecimalFromFloat(.11), 668 Long: num.DecimalFromFloat(.10), 669 } 670 // custom scaling factor 671 mc := &types.MarginCalculator{ 672 ScalingFactors: &types.ScalingFactors{ 673 SearchLevel: num.DecimalFromFloat(1.1), 674 InitialMargin: num.DecimalFromFloat(1.2), 675 CollateralRelease: num.DecimalFromFloat(1.3), 676 }, 677 } 678 679 markPrice := num.NewUint(144) 680 681 // list of order in the book before the test happen 682 ordersInBook := []struct { 683 volume int64 684 price *num.Uint 685 tid string 686 side types.Side 687 }{ 688 // asks 689 {volume: 3, price: num.NewUint(258), tid: "t1", side: types.SideSell}, 690 {volume: 5, price: num.NewUint(240), tid: "t2", side: types.SideSell}, 691 {volume: 3, price: num.NewUint(188), tid: "t3", side: types.SideSell}, 692 693 // bids 694 {volume: 1, price: num.NewUint(120), tid: "t4", side: types.SideBuy}, 695 {volume: 4, price: num.NewUint(110), tid: "t5", side: types.SideBuy}, 696 {volume: 5, price: num.NewUint(108), tid: "t6", side: types.SideBuy}, 697 } 698 699 marketID := "testingmarket" 700 701 conf := config.NewDefaultConfig() 702 log := logging.NewTestLogger() 703 ctrl := gomock.NewController(t) 704 model := mocks.NewMockModel(ctrl) 705 ts := mocks.NewMockTimeService(ctrl) 706 broker := bmocks.NewMockBroker(ctrl) 707 as := mocks.NewMockAuctionState(ctrl) 708 ts.EXPECT().GetTimeNow().Times(1) 709 broker.EXPECT().Send(gomock.Any()).AnyTimes() 710 711 // instantiate the book then fill it with the orders 712 713 book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest) 714 715 for _, v := range ordersInBook { 716 o := &types.Order{ 717 ID: fmt.Sprintf("o-%v-%v", v.tid, marketID), 718 MarketID: marketID, 719 Party: "A", 720 Side: v.side, 721 Price: v.price.Clone(), 722 Size: uint64(v.volume), 723 Remaining: uint64(v.volume), 724 TimeInForce: types.OrderTimeInForceGTT, 725 Type: types.OrderTypeLimit, 726 Status: types.OrderStatusActive, 727 ExpiresAt: 10000, 728 } 729 _, err := book.SubmitOrder(o) 730 assert.Nil(t, err) 731 } 732 733 model.EXPECT().DefaultRiskFactors().Return(r).Times(1) 734 as.EXPECT().InAuction().AnyTimes().Return(false) 735 statevar := mocks.NewMockStateVarEngine(ctrl) 736 statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) 737 statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()) 738 testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, "mktid", "ETH", statevar, num.DecimalFromInt64(1), false, nil, DefaultSlippageFactor, DefaultSlippageFactor) 739 evt := testMargin{ 740 party: "tx", 741 size: 10, 742 buy: 4, 743 sell: 8, 744 price: 144, 745 asset: "ETH", 746 margin: 500, 747 general: 100000, 748 market: "ETH/DEC19", 749 } 750 // insufficient orders on the book 751 riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, markPrice.Clone(), num.DecimalZero(), nil) 752 assert.NotNil(t, riskevt) 753 if riskevt == nil { 754 t.Fatal("expecting non nil risk update") 755 } 756 757 // maintenance margin = (10*0.1+100*0.1)*144+144*0.1*14=1785.6=1786 758 assert.Nil(t, err) 759 margins := riskevt.MarginLevels() 760 searchLevel, _ := mc.ScalingFactors.SearchLevel.Float64() 761 initialMargin, _ := mc.ScalingFactors.InitialMargin.Float64() 762 colRelease, _ := mc.ScalingFactors.CollateralRelease.Float64() 763 assert.EqualValues(t, 1786, margins.MaintenanceMargin.Uint64()) 764 assert.Equal(t, uint64(1786*searchLevel), margins.SearchLevel.Uint64()) 765 assert.Equal(t, uint64(1786*initialMargin), margins.InitialMargin.Uint64()) 766 assert.Equal(t, uint64(1786*colRelease), margins.CollateralReleaseLevel.Uint64()) 767 } 768 769 // testcase 1 from: https://drive.google.com/file/d/1B8-rLK2NB6rWvjzZX9sLtqOQzLz8s2ky/view 770 func testMarginWithOrderInBook2(t *testing.T) { 771 // custom risk factors 772 r := &types.RiskFactor{ 773 Short: num.DecimalFromFloat(.2), 774 Long: num.DecimalFromFloat(.1), 775 } 776 _ = r 777 // custom scaling factor 778 mc := &types.MarginCalculator{ 779 ScalingFactors: &types.ScalingFactors{ 780 SearchLevel: num.DecimalFromFloat(3.2), 781 InitialMargin: num.DecimalFromFloat(4), 782 CollateralRelease: num.DecimalFromFloat(5), 783 }, 784 } 785 786 // list of order in the book before the test happen 787 ordersInBook := []struct { 788 volume int64 789 price *num.Uint 790 tid string 791 side types.Side 792 }{ 793 // asks 794 {volume: 100, price: num.NewUint(250), tid: "t1", side: types.SideSell}, 795 {volume: 11, price: num.NewUint(140), tid: "t2", side: types.SideSell}, 796 {volume: 2, price: num.NewUint(112), tid: "t3", side: types.SideSell}, 797 // bids 798 {volume: 1, price: num.NewUint(100), tid: "t4", side: types.SideBuy}, 799 {volume: 3, price: num.NewUint(96), tid: "t5", side: types.SideBuy}, 800 {volume: 15, price: num.NewUint(90), tid: "t6", side: types.SideBuy}, 801 {volume: 50, price: num.NewUint(87), tid: "t7", side: types.SideBuy}, 802 } 803 804 marketID := "testingmarket" 805 806 conf := config.NewDefaultConfig() 807 log := logging.NewTestLogger() 808 ctrl := gomock.NewController(t) 809 model := mocks.NewMockModel(ctrl) 810 ts := mocks.NewMockTimeService(ctrl) 811 broker := bmocks.NewMockBroker(ctrl) 812 as := mocks.NewMockAuctionState(ctrl) 813 ts.EXPECT().GetTimeNow().Times(1) 814 broker.EXPECT().Send(gomock.Any()).AnyTimes() 815 816 model.EXPECT().DefaultRiskFactors().Return(r).Times(1) 817 818 as.EXPECT().InAuction().AnyTimes().Return(false) 819 // instantiate the book then fill it with the orders 820 821 book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest) 822 823 for _, v := range ordersInBook { 824 o := &types.Order{ 825 ID: fmt.Sprintf("o-%v-%v", v.tid, marketID), 826 MarketID: marketID, 827 Party: "A", 828 Side: v.side, 829 Price: v.price.Clone(), 830 Size: uint64(v.volume), 831 Remaining: uint64(v.volume), 832 TimeInForce: types.OrderTimeInForceGTT, 833 Type: types.OrderTypeLimit, 834 Status: types.OrderStatusActive, 835 ExpiresAt: 10000, 836 } 837 _, err := book.SubmitOrder(o) 838 assert.Nil(t, err) 839 } 840 841 statevar := mocks.NewMockStateVarEngine(ctrl) 842 statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) 843 statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()) 844 testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, "mktid", "ETH", statevar, num.DecimalFromInt64(1), false, nil, DefaultSlippageFactor, DefaultSlippageFactor) 845 evt := testMargin{ 846 party: "tx", 847 size: 13, 848 buy: 0, 849 sell: 0, 850 price: 150, 851 asset: "ETH", 852 margin: 0, 853 general: 100000, 854 market: "ETH/DEC19", 855 } 856 857 previousMarkPrice := num.NewUint(103) 858 859 riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, previousMarkPrice, num.DecimalZero(), nil) 860 assert.NotNil(t, riskevt) 861 if riskevt == nil { 862 t.Fatal("expecting non nil risk update") 863 } 864 assert.Nil(t, err) 865 margins := riskevt.MarginLevels() 866 searchLevel, _ := mc.ScalingFactors.SearchLevel.Float64() 867 initialMargin, _ := mc.ScalingFactors.InitialMargin.Float64() 868 colRelease, _ := mc.ScalingFactors.CollateralRelease.Float64() 869 870 assert.Equal(t, uint64(2009), margins.MaintenanceMargin.Uint64()) 871 assert.Equal(t, uint64(2009*searchLevel), margins.SearchLevel.Uint64()) 872 assert.Equal(t, uint64(2009*initialMargin), margins.InitialMargin.Uint64()) 873 assert.Equal(t, uint64(2009*colRelease), margins.CollateralReleaseLevel.Uint64()) 874 } 875 876 func testMarginWithOrderInBookAfterParamsUpdate(t *testing.T) { 877 // custom risk factors 878 r := &types.RiskFactor{ 879 Short: num.DecimalFromFloat(.11), 880 Long: num.DecimalFromFloat(.10), 881 } 882 // custom scaling factor 883 mc := &types.MarginCalculator{ 884 ScalingFactors: &types.ScalingFactors{ 885 SearchLevel: num.DecimalFromFloat(1.1), 886 InitialMargin: num.DecimalFromFloat(1.2), 887 CollateralRelease: num.DecimalFromFloat(1.3), 888 }, 889 } 890 891 markPrice := num.NewUint(144) 892 893 // list of order in the book before the test happen 894 ordersInBook := []struct { 895 volume int64 896 price *num.Uint 897 tid string 898 side types.Side 899 }{ 900 // asks 901 {volume: 3, price: num.NewUint(258), tid: "t1", side: types.SideSell}, 902 {volume: 5, price: num.NewUint(240), tid: "t2", side: types.SideSell}, 903 {volume: 3, price: num.NewUint(188), tid: "t3", side: types.SideSell}, 904 905 // bids 906 {volume: 1, price: num.NewUint(120), tid: "t4", side: types.SideBuy}, 907 {volume: 4, price: num.NewUint(110), tid: "t5", side: types.SideBuy}, 908 {volume: 5, price: num.NewUint(108), tid: "t6", side: types.SideBuy}, 909 } 910 911 marketID := "testingmarket" 912 913 conf := config.NewDefaultConfig() 914 log := logging.NewTestLogger() 915 ctrl := gomock.NewController(t) 916 model := mocks.NewMockModel(ctrl) 917 ts := mocks.NewMockTimeService(ctrl) 918 broker := bmocks.NewMockBroker(ctrl) 919 as := mocks.NewMockAuctionState(ctrl) 920 ts.EXPECT().GetTimeNow().Times(2) 921 broker.EXPECT().Send(gomock.Any()).AnyTimes() 922 923 // instantiate the book then fill it with the orders 924 925 book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest) 926 927 for _, v := range ordersInBook { 928 o := &types.Order{ 929 ID: fmt.Sprintf("o-%v-%v", v.tid, marketID), 930 MarketID: marketID, 931 Party: "A", 932 Side: v.side, 933 Price: v.price.Clone(), 934 Size: uint64(v.volume), 935 Remaining: uint64(v.volume), 936 TimeInForce: types.OrderTimeInForceGTT, 937 Type: types.OrderTypeLimit, 938 Status: types.OrderStatusActive, 939 ExpiresAt: 10000, 940 } 941 _, err := book.SubmitOrder(o) 942 assert.Nil(t, err) 943 } 944 945 model.EXPECT().DefaultRiskFactors().Return(r).Times(1) 946 as.EXPECT().InAuction().AnyTimes().Return(false) 947 statevarEngine := mocks.NewMockStateVarEngine(ctrl) 948 statevarEngine.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) 949 statevarEngine.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()) 950 asset := "ETH" 951 testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, marketID, asset, statevarEngine, num.DecimalFromInt64(1), false, nil, DefaultSlippageFactor, DefaultSlippageFactor) 952 953 evt := testMargin{ 954 party: "tx", 955 size: 10, 956 buy: 4, 957 sell: 8, 958 price: 144, 959 asset: asset, 960 margin: 500, 961 general: 100000, 962 market: marketID, 963 } 964 riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, markPrice.Clone(), num.DecimalZero(), nil) 965 require.NotNil(t, riskevt) 966 require.Nil(t, err) 967 968 margins := riskevt.MarginLevels() 969 searchLevel, _ := mc.ScalingFactors.SearchLevel.Float64() 970 initialMargin, _ := mc.ScalingFactors.InitialMargin.Float64() 971 colRelease, _ := mc.ScalingFactors.CollateralRelease.Float64() 972 assert.EqualValues(t, 1786, margins.MaintenanceMargin.Uint64()) 973 assert.Equal(t, uint64(1786*searchLevel), margins.SearchLevel.Uint64()) 974 assert.Equal(t, uint64(1786*initialMargin), margins.InitialMargin.Uint64()) 975 assert.Equal(t, uint64(1786*colRelease), margins.CollateralReleaseLevel.Uint64()) 976 977 updatedRF := &types.RiskFactor{ 978 Short: num.DecimalFromFloat(.12), 979 Long: num.DecimalFromFloat(.11), 980 } 981 updatedMC := &types.MarginCalculator{ 982 ScalingFactors: &types.ScalingFactors{ 983 SearchLevel: num.DecimalFromFloat(1.2), 984 InitialMargin: num.DecimalFromFloat(1.4), 985 CollateralRelease: num.DecimalFromFloat(1.4), 986 }, 987 } 988 989 // updating the slippage should change the margin too 990 updatedSlippage := num.DecimalFromFloat(0.1).Mul(DefaultSlippageFactor) 991 model.EXPECT().DefaultRiskFactors().Return(updatedRF).Times(1) 992 statevarEngine.EXPECT().NewEvent(asset, marketID, statevar.EventTypeMarketUpdated) 993 testE.UpdateModel(statevarEngine, updatedMC, model, updatedSlippage, updatedSlippage) 994 995 evt = testMargin{ 996 party: "tx", 997 size: 10, 998 buy: 4, 999 sell: 8, 1000 price: 144, 1001 asset: asset, 1002 margin: 500, 1003 general: 100000, 1004 market: marketID, 1005 } 1006 riskevt, _, err = testE.UpdateMarginOnNewOrder(context.Background(), evt, markPrice.Clone(), num.DecimalZero(), nil) 1007 require.NotNil(t, riskevt) 1008 require.Nil(t, err) 1009 1010 margins = riskevt.MarginLevels() 1011 searchLevel, _ = updatedMC.ScalingFactors.SearchLevel.Float64() 1012 initialMargin, _ = updatedMC.ScalingFactors.InitialMargin.Float64() 1013 colRelease, _ = updatedMC.ScalingFactors.CollateralRelease.Float64() 1014 assert.EqualValues(t, 381, margins.MaintenanceMargin.Uint64()) 1015 assert.Equal(t, uint64(381*searchLevel), margins.SearchLevel.Uint64()) 1016 assert.Equal(t, uint64(381*initialMargin), margins.InitialMargin.Uint64()) 1017 assert.Equal(t, uint64(381*colRelease), margins.CollateralReleaseLevel.Uint64()) 1018 } 1019 1020 func testInitialMarginRequirement(t *testing.T) { 1021 eng := getTestEngine(t, num.DecimalOne()) 1022 1023 _, cfunc := context.WithCancel(context.Background()) 1024 defer cfunc() 1025 1026 initialMargin := uint64(336) 1027 1028 evt := testMargin{ 1029 party: "party1", 1030 size: -4, 1031 price: 1000, 1032 asset: "ETH", 1033 margin: 0, 1034 general: initialMargin - 1, 1035 market: "ETH/DEC19", 1036 } 1037 eng.tsvc.EXPECT().GetTimeNow().Times(6) 1038 eng.as.EXPECT().InAuction().Times(2).Return(false) 1039 eng.broker.EXPECT().SendBatch(gomock.Any()).Times(3) 1040 riskevt, _, err := eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 1041 assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error()) 1042 assert.Nil(t, riskevt) 1043 1044 evt.general = initialMargin 1045 riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 1046 assert.NoError(t, err) 1047 assert.NotNil(t, riskevt) 1048 assert.True(t, riskevt.MarginLevels().InitialMargin.EQ(num.NewUint(initialMargin))) 1049 1050 eng.as.EXPECT().InAuction().Times(4).Return(true) 1051 eng.as.EXPECT().CanLeave().Times(4).Return(false) 1052 1053 slippageFactor := DefaultSlippageFactor.InexactFloat64() 1054 size := math.Abs(float64(evt.size)) 1055 rf := eng.GetRiskFactors() 1056 initialMarginScalingFactor := 1.2 1057 initialMarginAuction := math.Ceil(initialMarginScalingFactor * (size*slippageFactor + size*size*slippageFactor + size*rf.Short.InexactFloat64()) * markPrice.ToDecimal().InexactFloat64()) 1058 1059 evt.general = uint64(initialMarginAuction) - 1 1060 riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 1061 assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error()) 1062 assert.Nil(t, riskevt) 1063 1064 evt.general = uint64(initialMarginAuction) 1065 riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 1066 assert.NoError(t, err) 1067 assert.NotNil(t, riskevt) 1068 assert.True(t, riskevt.MarginLevels().InitialMargin.EQ(num.NewUint(uint64(initialMarginAuction)))) 1069 1070 evt.sell = 7 1071 evt.sellSumProduct = 123 1072 1073 ordersBit := evt.SellSumProduct().Float64() * rf.Short.InexactFloat64() 1074 initialMarginAuction = math.Ceil(initialMarginAuction + initialMarginScalingFactor*ordersBit) 1075 1076 evt.general = uint64(initialMarginAuction) - 1 1077 riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 1078 assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error()) 1079 assert.Nil(t, riskevt) 1080 1081 evt.general = uint64(math.Ceil(initialMarginAuction)) 1082 riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil) 1083 assert.NoError(t, err) 1084 assert.NotNil(t, riskevt) 1085 assert.True(t, riskevt.MarginLevels().InitialMargin.EQ(num.NewUint(uint64(initialMarginAuction)))) 1086 } 1087 1088 func TestMaintenanceMarign(t *testing.T) { 1089 relativeTolerance := num.DecimalFromFloat(0.000001) 1090 1091 testCases := []struct { 1092 markPrice float64 1093 positionFactor float64 1094 positionSize int64 1095 buyOrders []*risk.OrderInfo 1096 sellOrders []*risk.OrderInfo 1097 linearSlippageFactor float64 1098 quadraticSlippageFactor float64 1099 riskFactorLong float64 1100 riskFactorShort float64 1101 margin_funding_factor float64 1102 funding_payment_to_date float64 1103 auction bool 1104 }{ 1105 { 1106 markPrice: 123.4, 1107 positionFactor: 1, 1108 positionSize: 40000, 1109 buyOrders: []*risk.OrderInfo{ 1110 {1, num.NewDecimalFromFloat(0), false}, 1111 }, 1112 sellOrders: []*risk.OrderInfo{ 1113 {1, num.NewDecimalFromFloat(0), false}, 1114 }, 1115 linearSlippageFactor: 0, 1116 quadraticSlippageFactor: 0, 1117 riskFactorLong: 0.1, 1118 riskFactorShort: 0.1, 1119 auction: false, 1120 }, 1121 { 1122 markPrice: 123.4, 1123 positionFactor: 10, 1124 positionSize: 40000, 1125 buyOrders: []*risk.OrderInfo{ 1126 {1, num.NewDecimalFromFloat(111.1), false}, 1127 }, 1128 sellOrders: []*risk.OrderInfo{ 1129 {1, num.NewDecimalFromFloat(133.3), false}, 1130 }, 1131 linearSlippageFactor: 0, 1132 quadraticSlippageFactor: 0, 1133 riskFactorLong: 0.1, 1134 riskFactorShort: 0.1, 1135 auction: true, 1136 }, 1137 { 1138 markPrice: 123.4, 1139 positionFactor: 10, 1140 positionSize: 0, 1141 buyOrders: []*risk.OrderInfo{ 1142 {1, num.NewDecimalFromFloat(111.1), false}, 1143 }, 1144 sellOrders: []*risk.OrderInfo{ 1145 {1, num.NewDecimalFromFloat(133.3), false}, 1146 }, 1147 linearSlippageFactor: 0, 1148 quadraticSlippageFactor: 0, 1149 riskFactorLong: 0.1, 1150 riskFactorShort: 0.1, 1151 auction: true, 1152 }, 1153 { 1154 markPrice: 123.4, 1155 positionFactor: 10, 1156 positionSize: 0, 1157 buyOrders: []*risk.OrderInfo{ 1158 {10000, num.NewDecimalFromFloat(111.1), false}, 1159 }, 1160 sellOrders: []*risk.OrderInfo{ 1161 {30000, num.NewDecimalFromFloat(133.3), false}, 1162 }, 1163 linearSlippageFactor: 0, 1164 quadraticSlippageFactor: 0, 1165 riskFactorLong: 0.1, 1166 riskFactorShort: 0.2, 1167 auction: false, 1168 }, 1169 { 1170 markPrice: 123.4, 1171 positionFactor: 100, 1172 positionSize: 0, 1173 buyOrders: []*risk.OrderInfo{ 1174 {40000, num.NewDecimalFromFloat(111.1), false}, 1175 }, 1176 sellOrders: []*risk.OrderInfo{ 1177 {30000, num.NewDecimalFromFloat(133.3), false}, 1178 }, 1179 linearSlippageFactor: 0.5, 1180 quadraticSlippageFactor: 0.1, 1181 riskFactorLong: 0.1, 1182 riskFactorShort: 0.2, 1183 auction: false, 1184 }, 1185 { 1186 markPrice: 123.4, 1187 positionFactor: 100, 1188 positionSize: 0, 1189 buyOrders: []*risk.OrderInfo{ 1190 {10000, num.NewDecimalFromFloat(111.4), false}, 1191 {30000, num.NewDecimalFromFloat(111), false}, 1192 }, 1193 sellOrders: []*risk.OrderInfo{ 1194 {10000, num.NewDecimalFromFloat(133.9), false}, 1195 {20000, num.NewDecimalFromFloat(133.0), false}, 1196 }, 1197 linearSlippageFactor: 0.5, 1198 quadraticSlippageFactor: 0.1, 1199 riskFactorLong: 0.1, 1200 riskFactorShort: 0.2, 1201 auction: false, 1202 }, 1203 { 1204 markPrice: 123.4, 1205 positionFactor: 100, 1206 positionSize: 0, 1207 buyOrders: []*risk.OrderInfo{ 1208 {10000, num.NewDecimalFromFloat(111.4), false}, 1209 {30000, num.NewDecimalFromFloat(111), false}, 1210 {20000, num.NewDecimalFromFloat(0), true}, 1211 }, 1212 sellOrders: []*risk.OrderInfo{ 1213 {10000, num.NewDecimalFromFloat(133.9), false}, 1214 {20000, num.NewDecimalFromFloat(133.0), false}, 1215 {30000, num.NewDecimalFromFloat(0), true}, 1216 }, 1217 linearSlippageFactor: 0.5, 1218 quadraticSlippageFactor: 0.1, 1219 riskFactorLong: 0.1, 1220 riskFactorShort: 0.2, 1221 auction: false, 1222 }, 1223 { 1224 markPrice: 123.4, 1225 positionFactor: 100, 1226 positionSize: 0, 1227 buyOrders: []*risk.OrderInfo{ 1228 {10000, num.NewDecimalFromFloat(111.4), false}, 1229 {30000, num.NewDecimalFromFloat(111), false}, 1230 {20000, num.NewDecimalFromFloat(0), true}, 1231 }, 1232 sellOrders: []*risk.OrderInfo{ 1233 {10000, num.NewDecimalFromFloat(133.9), false}, 1234 {20000, num.NewDecimalFromFloat(133.0), false}, 1235 {30000, num.NewDecimalFromFloat(0), true}, 1236 }, 1237 linearSlippageFactor: 0.5, 1238 quadraticSlippageFactor: 0.1, 1239 riskFactorLong: 0.1, 1240 riskFactorShort: 0.2, 1241 auction: false, 1242 margin_funding_factor: 0.5, 1243 funding_payment_to_date: 75, 1244 }, 1245 { 1246 markPrice: 123.4, 1247 positionFactor: 100, 1248 positionSize: 0, 1249 buyOrders: []*risk.OrderInfo{ 1250 {10000, num.NewDecimalFromFloat(111.4), false}, 1251 {30000, num.NewDecimalFromFloat(111), false}, 1252 {20000, num.NewDecimalFromFloat(0), true}, 1253 }, 1254 sellOrders: []*risk.OrderInfo{ 1255 {10000, num.NewDecimalFromFloat(133.9), false}, 1256 {20000, num.NewDecimalFromFloat(133.0), false}, 1257 {30000, num.NewDecimalFromFloat(0), true}, 1258 }, 1259 linearSlippageFactor: 0.5, 1260 quadraticSlippageFactor: 0.1, 1261 riskFactorLong: 0.1, 1262 riskFactorShort: 0.2, 1263 auction: false, 1264 margin_funding_factor: 1, 1265 funding_payment_to_date: 300, 1266 }, 1267 } 1268 1269 for i, tc := range testCases { 1270 markPrice := num.DecimalFromFloat(tc.markPrice) 1271 positionFactor := num.DecimalFromFloat(tc.positionFactor) 1272 buySumProduct := num.DecimalZero() 1273 sellSumProduct := num.DecimalZero() 1274 buySize := int64(0) 1275 sellSize := int64(0) 1276 1277 linearSlippageFactor := num.DecimalFromFloat(tc.linearSlippageFactor) 1278 quadraticSlippageFactor := num.DecimalFromFloat(tc.quadraticSlippageFactor) 1279 riskFactorLong := num.DecimalFromFloat(tc.riskFactorLong) 1280 riskFactorShort := num.DecimalFromFloat(tc.riskFactorShort) 1281 1282 constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date) 1283 1284 positionSize := tc.positionSize 1285 for _, o := range tc.buyOrders { 1286 s := int64(o.TrueRemaining) 1287 if o.IsMarketOrder { 1288 positionSize += s 1289 } else { 1290 buySize += s 1291 buySumProduct = buySumProduct.Add(num.DecimalFromInt64(s).Mul(o.Price)) 1292 } 1293 } 1294 1295 for _, o := range tc.sellOrders { 1296 s := int64(o.TrueRemaining) 1297 if o.IsMarketOrder { 1298 positionSize -= s 1299 } else { 1300 sellSize += s 1301 sellSumProduct = sellSumProduct.Add(num.DecimalFromInt64(s).Mul(o.Price)) 1302 } 1303 } 1304 1305 openVolume := num.DecimalFromInt64(positionSize).Div(positionFactor) 1306 openVolumeAbs := openVolume.Abs() 1307 expectedMarginShort, expectedMarginLong := num.DecimalZero(), num.DecimalZero() 1308 slippage := markPrice.Mul(openVolumeAbs.Mul(linearSlippageFactor).Add(openVolumeAbs.Mul(openVolumeAbs).Mul(quadraticSlippageFactor))) 1309 1310 if positionSize-sellSize < 0 { 1311 expectedMarginShort = slippage.Add(openVolumeAbs.Mul(markPrice).Mul(riskFactorShort)) 1312 orders := num.DecimalFromInt64(sellSize).Div(positionFactor).Abs().Mul(riskFactorShort) 1313 if tc.auction { 1314 expectedMarginShort = expectedMarginShort.Add(orders.Mul(sellSumProduct)) 1315 } else { 1316 expectedMarginShort = expectedMarginShort.Add(orders.Mul(markPrice)) 1317 } 1318 } 1319 if positionSize+buySize > 0 { 1320 expectedMarginLong = slippage.Add(openVolumeAbs.Mul(markPrice).Mul(riskFactorLong)) 1321 orders := num.DecimalFromInt64(buySize).Div(positionFactor).Abs().Mul(riskFactorLong) 1322 if tc.auction { 1323 expectedMarginLong = expectedMarginLong.Add(orders.Mul(buySumProduct)) 1324 } else { 1325 expectedMarginLong = expectedMarginLong.Add(orders.Mul(markPrice)) 1326 } 1327 } 1328 expectedMargin := num.MaxD(expectedMarginShort, expectedMarginLong).Add(num.MaxD(num.DecimalZero(), openVolume.Mul(constantPerUnitPositionSize))) 1329 1330 actualMargin := risk.CalculateMaintenanceMarginWithSlippageFactors(tc.positionSize, tc.buyOrders, tc.sellOrders, markPrice, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, tc.auction, num.DecimalZero()) 1331 1332 require.True(t, expectedMargin.Div(actualMargin).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: expectedMargin=%s, actualMargin:=%s", i+1, expectedMargin, actualMargin)) 1333 } 1334 } 1335 1336 func TestLiquidationPriceWithNoOrders(t *testing.T) { 1337 relativeTolerance := num.DecimalFromFloat(0.000001) 1338 1339 testCases := []struct { 1340 markPrice float64 1341 positionFactor float64 1342 positionSize int64 1343 linearSlippageFactor float64 1344 quadraticSlippageFactor float64 1345 riskFactorLong float64 1346 riskFactorShort float64 1347 collateralFactor float64 1348 margin_funding_factor float64 1349 funding_payment_to_date float64 1350 expectError bool 1351 }{ 1352 { 1353 markPrice: 123.4, 1354 positionFactor: 1, 1355 positionSize: 40000, 1356 linearSlippageFactor: 0, 1357 quadraticSlippageFactor: 0, 1358 riskFactorLong: 0.1, 1359 riskFactorShort: 0.11, 1360 collateralFactor: 1.7, 1361 }, 1362 { 1363 markPrice: 1234.5, 1364 positionFactor: 10, 1365 positionSize: 40000, 1366 linearSlippageFactor: 0.5, 1367 quadraticSlippageFactor: 0, 1368 riskFactorLong: 0.1, 1369 riskFactorShort: 0.11, 1370 collateralFactor: 1.1, 1371 }, 1372 { 1373 markPrice: 1234.5, 1374 positionFactor: 100, 1375 positionSize: -40000, 1376 linearSlippageFactor: 0.5, 1377 quadraticSlippageFactor: 0.01, 1378 riskFactorLong: 0.1, 1379 riskFactorShort: 0.11, 1380 collateralFactor: 3, 1381 }, 1382 { 1383 markPrice: 1234.5, 1384 positionFactor: 1000, 1385 positionSize: -40000, 1386 linearSlippageFactor: 0.5, 1387 quadraticSlippageFactor: 0.1, 1388 riskFactorLong: 0.1, 1389 riskFactorShort: 0.11, 1390 collateralFactor: 0.2, 1391 }, 1392 { 1393 markPrice: 1, 1394 positionFactor: 1, 1395 positionSize: 1, 1396 linearSlippageFactor: 0, 1397 quadraticSlippageFactor: 0, 1398 riskFactorLong: 1, 1399 riskFactorShort: 1, 1400 collateralFactor: 2.5, 1401 expectError: true, 1402 }, 1403 { 1404 markPrice: 110, 1405 positionFactor: 1, 1406 positionSize: 41, 1407 linearSlippageFactor: 0.5, 1408 quadraticSlippageFactor: 0.01, 1409 riskFactorLong: 0.1, 1410 riskFactorShort: 0.11, 1411 collateralFactor: 1000, 1412 }, 1413 { 1414 markPrice: 110, 1415 positionFactor: 1, 1416 positionSize: 41, 1417 linearSlippageFactor: 0.05, 1418 quadraticSlippageFactor: 0, 1419 riskFactorLong: 0.1, 1420 riskFactorShort: 0.11, 1421 collateralFactor: 3, 1422 margin_funding_factor: 0.5, 1423 funding_payment_to_date: 50, 1424 }, 1425 { 1426 markPrice: 110, 1427 positionFactor: 1, 1428 positionSize: -41, 1429 linearSlippageFactor: 0.05, 1430 quadraticSlippageFactor: 0, 1431 riskFactorLong: 0.1, 1432 riskFactorShort: 0.11, 1433 collateralFactor: 3, 1434 margin_funding_factor: 1, 1435 funding_payment_to_date: -300, 1436 }, 1437 { 1438 markPrice: 110, 1439 positionFactor: 1, 1440 positionSize: -41, 1441 linearSlippageFactor: 0.05, 1442 quadraticSlippageFactor: 0, 1443 riskFactorLong: 0.1, 1444 riskFactorShort: 0.11, 1445 collateralFactor: 3, 1446 margin_funding_factor: 1, 1447 funding_payment_to_date: 300, 1448 }, 1449 } 1450 1451 for i, tc := range testCases { 1452 markPrice := num.DecimalFromFloat(tc.markPrice) 1453 positionFactor := num.DecimalFromFloat(tc.positionFactor) 1454 1455 linearSlippageFactor := num.DecimalFromFloat(tc.linearSlippageFactor) 1456 quadraticSlippageFactor := num.DecimalFromFloat(tc.quadraticSlippageFactor) 1457 riskFactorLong := num.DecimalFromFloat(tc.riskFactorLong) 1458 riskFactorShort := num.DecimalFromFloat(tc.riskFactorShort) 1459 constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date) 1460 maintenanceMargin := risk.CalculateMaintenanceMarginWithSlippageFactors(tc.positionSize, nil, nil, markPrice, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalZero()) 1461 1462 maintenanceMarginFp := maintenanceMargin.InexactFloat64() 1463 require.Greater(t, maintenanceMarginFp, 0.0) 1464 1465 liquidationPrice, _, _, err := risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, nil, nil, markPrice, maintenanceMargin, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalZero()) 1466 if tc.expectError { 1467 require.Error(t, err) 1468 continue 1469 } 1470 require.NoError(t, err) 1471 1472 liquidationPriceFp := liquidationPrice.InexactFloat64() 1473 require.GreaterOrEqual(t, liquidationPriceFp, 0.0) 1474 1475 require.True(t, markPrice.Div(liquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v:", i+1)) 1476 1477 collateral := maintenanceMargin.Mul(num.DecimalFromFloat(tc.collateralFactor)) 1478 1479 liquidationPrice, _, _, err = risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, nil, nil, markPrice, collateral, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne()) 1480 require.NoError(t, err) 1481 require.False(t, liquidationPrice.IsNegative()) 1482 1483 liquidationPriceFp = liquidationPrice.InexactFloat64() 1484 require.GreaterOrEqual(t, liquidationPriceFp, 0.0) 1485 1486 marginAtLiquidationPrice := risk.CalculateMaintenanceMarginWithSlippageFactors(tc.positionSize, nil, nil, liquidationPrice, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalZero()) 1487 openVolume := num.DecimalFromInt64(tc.positionSize).Div(positionFactor) 1488 mtmLoss := liquidationPrice.Sub(markPrice).Mul(openVolume) 1489 collateralAfterLoss := collateral.Add(mtmLoss) 1490 1491 if !marginAtLiquidationPrice.IsZero() { 1492 require.True(t, collateralAfterLoss.Div(marginAtLiquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: collateralAfterLoss=%s, marginAtLiquidationPrice:=%s", i+1, collateralAfterLoss, marginAtLiquidationPrice)) 1493 } else { 1494 require.True(t, liquidationPrice.IsZero(), fmt.Sprintf("Test case %v:", i+1)) 1495 require.True(t, collateralAfterLoss.IsNegative(), fmt.Sprintf("Test case %v:", i+1)) 1496 } 1497 1498 liquidationPriceIsolatedMode, _, _, err := risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, nil, nil, markPrice, collateral, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, true, num.DecimalOne()) 1499 require.NoError(t, err) 1500 require.Equal(t, liquidationPrice, liquidationPrice, liquidationPriceIsolatedMode) 1501 } 1502 } 1503 1504 func TestLiquidationPriceWithOrders(t *testing.T) { 1505 relativeTolerance := num.DecimalFromFloat(0.01) 1506 testCases := []struct { 1507 markPrice float64 1508 positionFactor float64 1509 positionSize int64 1510 buyOrders []*risk.OrderInfo 1511 sellOrders []*risk.OrderInfo 1512 linearSlippageFactor float64 1513 quadraticSlippageFactor float64 1514 riskFactorLong float64 1515 riskFactorShort float64 1516 collateralAvailable float64 1517 margin_funding_factor float64 1518 funding_payment_to_date float64 1519 }{ 1520 { 1521 markPrice: 123.4, 1522 positionFactor: 1, 1523 positionSize: 0, 1524 buyOrders: []*risk.OrderInfo{ 1525 {1, num.NewDecimalFromFloat(110), false}, 1526 }, 1527 sellOrders: []*risk.OrderInfo{ 1528 {1, num.NewDecimalFromFloat(130), false}, 1529 }, 1530 linearSlippageFactor: 0, 1531 quadraticSlippageFactor: 0, 1532 riskFactorLong: 0.1, 1533 riskFactorShort: 0.11, 1534 collateralAvailable: 100, 1535 }, 1536 { 1537 markPrice: 123.4, 1538 positionFactor: 1, 1539 positionSize: 0, 1540 buyOrders: []*risk.OrderInfo{ 1541 {39, num.NewDecimalFromFloat(110), false}, 1542 }, 1543 sellOrders: []*risk.OrderInfo{ 1544 {40, num.NewDecimalFromFloat(130), false}, 1545 }, 1546 linearSlippageFactor: 0.5, 1547 quadraticSlippageFactor: 0.01, 1548 riskFactorLong: 0.1, 1549 riskFactorShort: 0.11, 1550 collateralAvailable: 50800, 1551 }, 1552 { 1553 markPrice: 123.4, 1554 positionFactor: 1, 1555 positionSize: 20, 1556 buyOrders: []*risk.OrderInfo{ 1557 {39, num.NewDecimalFromFloat(110), false}, 1558 }, 1559 sellOrders: []*risk.OrderInfo{ 1560 {40, num.NewDecimalFromFloat(130), false}, 1561 }, 1562 linearSlippageFactor: 0.01, 1563 quadraticSlippageFactor: 0.000001, 1564 riskFactorLong: 0.1, 1565 riskFactorShort: 0.11, 1566 collateralAvailable: 50800, 1567 }, 1568 { 1569 markPrice: 123.4, 1570 positionFactor: 100, 1571 positionSize: -2000, 1572 buyOrders: []*risk.OrderInfo{ 1573 {3900, num.NewDecimalFromFloat(110), false}, 1574 }, 1575 sellOrders: []*risk.OrderInfo{ 1576 {4000, num.NewDecimalFromFloat(130), false}, 1577 }, 1578 linearSlippageFactor: 0.01, 1579 quadraticSlippageFactor: 0.000001, 1580 riskFactorLong: 0.1, 1581 riskFactorShort: 0.11, 1582 collateralAvailable: 50800, 1583 }, 1584 { 1585 markPrice: 123.4, 1586 positionFactor: 0.1, 1587 positionSize: -2, 1588 buyOrders: []*risk.OrderInfo{ 1589 {3, num.NewDecimalFromFloat(110), false}, 1590 }, 1591 sellOrders: []*risk.OrderInfo{ 1592 {4, num.NewDecimalFromFloat(130), false}, 1593 }, 1594 linearSlippageFactor: 0.01, 1595 quadraticSlippageFactor: 0.000001, 1596 riskFactorLong: 0.1, 1597 riskFactorShort: 0.11, 1598 collateralAvailable: 50800, 1599 }, 1600 { 1601 markPrice: 101.2, 1602 positionFactor: 100, 1603 positionSize: -2000, 1604 buyOrders: []*risk.OrderInfo{ 1605 {3900, num.NewDecimalFromFloat(110), false}, 1606 }, 1607 sellOrders: []*risk.OrderInfo{ 1608 {4000, num.NewDecimalFromFloat(130), false}, 1609 }, 1610 linearSlippageFactor: 0.01, 1611 quadraticSlippageFactor: 0.000001, 1612 riskFactorLong: 0.1, 1613 riskFactorShort: 0.11, 1614 collateralAvailable: 50800, 1615 }, 1616 { 1617 markPrice: 144.5, 1618 positionFactor: 100, 1619 positionSize: -2000, 1620 buyOrders: []*risk.OrderInfo{ 1621 {3900, num.NewDecimalFromFloat(110), false}, 1622 }, 1623 sellOrders: []*risk.OrderInfo{ 1624 {4000, num.NewDecimalFromFloat(130), false}, 1625 }, 1626 linearSlippageFactor: 0.01, 1627 quadraticSlippageFactor: 0.000001, 1628 riskFactorLong: 0.1, 1629 riskFactorShort: 0.11, 1630 collateralAvailable: 50800, 1631 }, 1632 { 1633 markPrice: 123.4, 1634 positionFactor: 100, 1635 positionSize: -2000, 1636 buyOrders: []*risk.OrderInfo{ 1637 {1800, num.NewDecimalFromFloat(100), false}, 1638 {1700, num.NewDecimalFromFloat(110), false}, 1639 }, 1640 sellOrders: []*risk.OrderInfo{ 1641 {3000, num.NewDecimalFromFloat(120), false}, 1642 {2000, num.NewDecimalFromFloat(130), false}, 1643 {1000, num.NewDecimalFromFloat(140), false}, 1644 }, 1645 linearSlippageFactor: 0.01, 1646 quadraticSlippageFactor: 0.000001, 1647 riskFactorLong: 0.1, 1648 riskFactorShort: 0.11, 1649 collateralAvailable: 50800, 1650 }, 1651 { 1652 markPrice: 123.4, 1653 positionFactor: 100, 1654 positionSize: -2000, 1655 buyOrders: []*risk.OrderInfo{ 1656 {1800, num.NewDecimalFromFloat(100), false}, 1657 {1700, num.NewDecimalFromFloat(0), true}, 1658 }, 1659 sellOrders: []*risk.OrderInfo{ 1660 {3000, num.NewDecimalFromFloat(120), false}, 1661 {2000, num.NewDecimalFromFloat(130), false}, 1662 {1000, num.NewDecimalFromFloat(0), true}, 1663 }, 1664 linearSlippageFactor: 0.01, 1665 quadraticSlippageFactor: 0.000001, 1666 riskFactorLong: 0.1, 1667 riskFactorShort: 0.11, 1668 collateralAvailable: 50800, 1669 }, 1670 { 1671 markPrice: 123.4, 1672 positionFactor: 1, 1673 positionSize: 1, 1674 buyOrders: []*risk.OrderInfo{}, 1675 sellOrders: []*risk.OrderInfo{ 1676 {2, num.NewDecimalFromFloat(0), true}, 1677 {99, num.NewDecimalFromFloat(0), true}, 1678 }, 1679 linearSlippageFactor: 0.01, 1680 quadraticSlippageFactor: 0.000001, 1681 riskFactorLong: 0.1, 1682 riskFactorShort: 0.11, 1683 collateralAvailable: 2345, 1684 }, 1685 { 1686 markPrice: 123.4, 1687 positionFactor: 1, 1688 positionSize: 1, 1689 buyOrders: []*risk.OrderInfo{}, 1690 sellOrders: []*risk.OrderInfo{ 1691 {1, num.NewDecimalFromFloat(0), true}, 1692 {100, num.NewDecimalFromFloat(0), true}, 1693 }, 1694 linearSlippageFactor: 0.01, 1695 quadraticSlippageFactor: 0.000001, 1696 riskFactorLong: 0.1, 1697 riskFactorShort: 0.11, 1698 collateralAvailable: 2345, 1699 }, 1700 { 1701 markPrice: 123.4, 1702 positionFactor: 100, 1703 positionSize: -2000, 1704 buyOrders: []*risk.OrderInfo{ 1705 {1800, num.NewDecimalFromFloat(100), false}, 1706 {1700, num.NewDecimalFromFloat(0), true}, 1707 }, 1708 sellOrders: []*risk.OrderInfo{ 1709 {3000, num.NewDecimalFromFloat(120), false}, 1710 {2000, num.NewDecimalFromFloat(130), false}, 1711 {1000, num.NewDecimalFromFloat(0), true}, 1712 }, 1713 linearSlippageFactor: 0.01, 1714 quadraticSlippageFactor: 0.000001, 1715 riskFactorLong: 0.1, 1716 riskFactorShort: 0.11, 1717 collateralAvailable: 50800, 1718 margin_funding_factor: 0.5, 1719 funding_payment_to_date: 50, 1720 }, 1721 { 1722 markPrice: 123.4, 1723 positionFactor: 100, 1724 positionSize: -2000, 1725 buyOrders: []*risk.OrderInfo{ 1726 {1800, num.NewDecimalFromFloat(100), false}, 1727 {1700, num.NewDecimalFromFloat(0), true}, 1728 }, 1729 sellOrders: []*risk.OrderInfo{ 1730 {3000, num.NewDecimalFromFloat(120), false}, 1731 {2000, num.NewDecimalFromFloat(130), false}, 1732 {1000, num.NewDecimalFromFloat(0), true}, 1733 }, 1734 linearSlippageFactor: 0.01, 1735 quadraticSlippageFactor: 0.000001, 1736 riskFactorLong: 0.1, 1737 riskFactorShort: 0.11, 1738 collateralAvailable: 50800, 1739 margin_funding_factor: 0.5, 1740 funding_payment_to_date: -50, 1741 }, 1742 { 1743 markPrice: 123.4, 1744 positionFactor: 1, 1745 positionSize: 20, 1746 buyOrders: []*risk.OrderInfo{ 1747 {39, num.NewDecimalFromFloat(110), false}, 1748 }, 1749 sellOrders: []*risk.OrderInfo{ 1750 {40, num.NewDecimalFromFloat(130), false}, 1751 }, 1752 linearSlippageFactor: 0.01, 1753 quadraticSlippageFactor: 0.000001, 1754 riskFactorLong: 0.1, 1755 riskFactorShort: 0.11, 1756 collateralAvailable: 50800, 1757 margin_funding_factor: 1, 1758 funding_payment_to_date: 300, 1759 }, 1760 } 1761 1762 for i, tc := range testCases { 1763 markPrice := num.DecimalFromFloat(tc.markPrice) 1764 positionFactor := num.DecimalFromFloat(tc.positionFactor) 1765 collateral := num.DecimalFromFloat(tc.collateralAvailable) 1766 1767 linearSlippageFactor := num.DecimalFromFloat(tc.linearSlippageFactor) 1768 quadraticSlippageFactor := num.DecimalFromFloat(tc.quadraticSlippageFactor) 1769 riskFactorLong := num.DecimalFromFloat(tc.riskFactorLong) 1770 riskFactorShort := num.DecimalFromFloat(tc.riskFactorShort) 1771 1772 constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date) 1773 positionOnly, withBuy, withSell, err := risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, tc.buyOrders, tc.sellOrders, markPrice, collateral, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne()) 1774 require.NoError(t, err, fmt.Sprintf("Test case %v:", i+1)) 1775 1776 sPositionOnly := positionOnly.String() 1777 sWithBuy := withBuy.String() 1778 sWithSell := withSell.String() 1779 1780 t.Logf("positionOnly=%s, withBuy=%s, withSell=%s", sPositionOnly, sWithBuy, sWithSell) 1781 1782 if tc.positionSize == 0 { 1783 require.True(t, positionOnly.IsZero(), fmt.Sprintf("Test case %v:", i+1)) 1784 } 1785 if tc.positionSize > 0 { 1786 require.True(t, withBuy.GreaterThanOrEqual(positionOnly), fmt.Sprintf("Test case %v:", i+1)) 1787 } 1788 if tc.positionSize < 0 { 1789 require.True(t, withSell.LessThanOrEqual(positionOnly), fmt.Sprintf("Test case %v:", i+1)) 1790 } 1791 1792 for _, o := range tc.buyOrders { 1793 if o.IsMarketOrder { 1794 o.Price = markPrice 1795 } 1796 } 1797 1798 for _, o := range tc.sellOrders { 1799 if o.IsMarketOrder { 1800 o.Price = markPrice 1801 } 1802 } 1803 1804 sort.Slice(tc.buyOrders, func(i, j int) bool { 1805 return tc.buyOrders[i].Price.GreaterThan(tc.buyOrders[j].Price) 1806 }) 1807 sort.Slice(tc.sellOrders, func(i, j int) bool { 1808 return tc.sellOrders[i].Price.LessThan(tc.sellOrders[j].Price) 1809 }) 1810 1811 newPositionSize := tc.positionSize 1812 mtmDelta := num.DecimalZero() 1813 lastMarkPrice := markPrice 1814 for _, o := range tc.buyOrders { 1815 if o.Price.LessThan(withBuy) { 1816 break 1817 } 1818 mtmDelta = mtmDelta.Add(num.DecimalFromInt64(newPositionSize).Mul(o.Price.Sub(lastMarkPrice))) 1819 newPositionSize += int64(o.TrueRemaining) 1820 lastMarkPrice = o.Price 1821 } 1822 collateralAfterMtm := collateral.Add(mtmDelta) 1823 liquidationPriceForNewPosition, _, _, err := risk.CalculateLiquidationPriceWithSlippageFactors(newPositionSize, nil, nil, lastMarkPrice, collateralAfterMtm, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne()) 1824 require.NoError(t, err, fmt.Sprintf("Test case %v:", i+1)) 1825 require.True(t, withBuy.Equal(liquidationPriceForNewPosition), fmt.Sprintf("Test case %v: withBuy=%s, newPositionOnly=%s", i+1, withBuy.String(), liquidationPriceForNewPosition.String())) 1826 1827 if tc.positionSize < 0 && newPositionSize > 0 { 1828 require.True(t, withBuy.LessThan(positionOnly), fmt.Sprintf("Test case %v:", i+1)) 1829 } 1830 1831 marginAtLiquidationPrice := risk.CalculateMaintenanceMarginWithSlippageFactors(newPositionSize, nil, nil, liquidationPriceForNewPosition, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, num.DecimalZero(), false, num.DecimalZero()) 1832 openVolume := num.DecimalFromInt64(newPositionSize).Div(positionFactor) 1833 mtmLoss := liquidationPriceForNewPosition.Sub(lastMarkPrice).Mul(openVolume) 1834 fundingLoss := num.MaxD(num.DecimalZero(), openVolume.Mul(constantPerUnitPositionSize)).Mul(num.DecimalFromFloat(-1)) 1835 collateralAfterLoss := collateralAfterMtm.Add(mtmLoss).Add(fundingLoss) 1836 1837 if !marginAtLiquidationPrice.IsZero() { 1838 require.True(t, collateralAfterLoss.Div(marginAtLiquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: collateralAfterLoss=%s, marginAtLiquidationPrice:=%s", i+1, collateralAfterLoss, marginAtLiquidationPrice)) 1839 } else { 1840 require.True(t, liquidationPriceForNewPosition.IsZero(), fmt.Sprintf("Test case %v:", i+1)) 1841 } 1842 1843 newPositionSize = tc.positionSize 1844 mtmDelta = num.DecimalZero() 1845 lastMarkPrice = markPrice 1846 for _, o := range tc.sellOrders { 1847 if o.Price.GreaterThan(withSell) { 1848 break 1849 } 1850 mtmDelta = mtmDelta.Add(num.DecimalFromInt64(newPositionSize).Div(positionFactor).Mul(o.Price.Sub(lastMarkPrice))) 1851 newPositionSize -= int64(o.TrueRemaining) 1852 lastMarkPrice = o.Price 1853 } 1854 collateralAfterMtm = collateral.Add(mtmDelta) 1855 liquidationPriceForNewPosition, _, _, err = risk.CalculateLiquidationPriceWithSlippageFactors(newPositionSize, nil, nil, lastMarkPrice, collateralAfterMtm, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne()) 1856 require.NoError(t, err, fmt.Sprintf("Test case %v:", i+1)) 1857 require.True(t, withSell.Equal(liquidationPriceForNewPosition), fmt.Sprintf("Test case %v: withSell=%s, newPositionOnly=%s", i+1, withSell.String(), liquidationPriceForNewPosition.String())) 1858 1859 if tc.positionSize > 0 && newPositionSize < 0 { 1860 require.True(t, withSell.GreaterThan(positionOnly), fmt.Sprintf("Test case %v:", i+1)) 1861 } 1862 1863 // recalculate without funding loss and compensate for it when getting the expectation 1864 marginAtLiquidationPrice = risk.CalculateMaintenanceMarginWithSlippageFactors(newPositionSize, nil, nil, liquidationPriceForNewPosition, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, num.DecimalZero(), false, num.DecimalZero()) 1865 openVolume = num.DecimalFromInt64(newPositionSize).Div(positionFactor) 1866 mtmLoss = liquidationPriceForNewPosition.Sub(lastMarkPrice).Mul(openVolume) 1867 fundingLoss = num.MaxD(num.DecimalZero(), openVolume.Mul(constantPerUnitPositionSize)).Mul(num.DecimalFromFloat(-1)) 1868 collateralAfterLoss = collateralAfterMtm.Add(mtmLoss).Add(fundingLoss) 1869 1870 if !marginAtLiquidationPrice.IsZero() { 1871 require.True(t, collateralAfterLoss.Div(marginAtLiquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: collateralAfterLoss=%s, marginAtLiquidationPrice:=%s", i+1, collateralAfterLoss, marginAtLiquidationPrice)) 1872 } else { 1873 require.True(t, liquidationPriceForNewPosition.IsZero(), fmt.Sprintf("Test case %v:", i+1)) 1874 } 1875 } 1876 } 1877 1878 func getTestEngine(t *testing.T, dp num.Decimal) *testEngine { 1879 t.Helper() 1880 cpy := riskFactors 1881 cpyPtr := &cpy 1882 ctrl := gomock.NewController(t) 1883 model := mocks.NewMockModel(ctrl) 1884 conf := risk.NewDefaultConfig() 1885 conf.StreamMarginLevelsVerbose = true 1886 ob := mocks.NewMockOrderbook(ctrl) 1887 ts := mocks.NewMockTimeService(ctrl) 1888 broker := bmocks.NewMockBroker(ctrl) 1889 as := mocks.NewMockAuctionState(ctrl) 1890 model.EXPECT().DefaultRiskFactors().Return(cpyPtr).Times(1) 1891 statevar := mocks.NewMockStateVarEngine(ctrl) 1892 statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) 1893 statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()) 1894 engine := risk.NewEngine(logging.NewTestLogger(), 1895 conf, 1896 getMarginCalculator(), 1897 model, 1898 ob, 1899 as, 1900 ts, 1901 broker, 1902 "mktid", 1903 "ETH", 1904 statevar, 1905 dp, 1906 false, 1907 nil, 1908 DefaultSlippageFactor, 1909 DefaultSlippageFactor, 1910 ) 1911 1912 return &testEngine{ 1913 Engine: engine, 1914 ctrl: ctrl, 1915 model: model, 1916 orderbook: ob, 1917 tsvc: ts, 1918 broker: broker, 1919 as: as, 1920 } 1921 } 1922 1923 func getMarginCalculator() *types.MarginCalculator { 1924 return &types.MarginCalculator{ 1925 ScalingFactors: &types.ScalingFactors{ 1926 SearchLevel: num.DecimalFromFloat(1.1), 1927 InitialMargin: num.DecimalFromFloat(1.2), 1928 CollateralRelease: num.DecimalFromFloat(1.4), 1929 }, 1930 } 1931 } 1932 1933 func (m testMargin) AverageEntryPrice() *num.Uint { 1934 absSize := m.size 1935 if absSize < 0 { 1936 absSize = -absSize 1937 } 1938 return num.UintZero().Mul(m.Price(), num.NewUint(uint64(absSize))) 1939 } 1940 1941 func (m testMargin) Party() string { 1942 return m.party 1943 } 1944 1945 func (m testMargin) MarketID() string { 1946 return m.market 1947 } 1948 1949 func (m testMargin) Asset() string { 1950 return m.asset 1951 } 1952 1953 func (m testMargin) MarginBalance() *num.Uint { 1954 return num.NewUint(m.margin) 1955 } 1956 1957 func (m testMargin) OrderMarginBalance() *num.Uint { 1958 return num.NewUint(m.orderMargin) 1959 } 1960 1961 func (m testMargin) GeneralBalance() *num.Uint { 1962 return num.NewUint(m.general) 1963 } 1964 1965 func (m testMargin) GeneralAccountBalance() *num.Uint { 1966 return num.NewUint(m.general) 1967 } 1968 1969 func (m testMargin) BondBalance() *num.Uint { 1970 return num.UintZero() 1971 } 1972 1973 func (m testMargin) Price() *num.Uint { 1974 return num.NewUint(m.price) 1975 } 1976 1977 func (m testMargin) Buy() int64 { 1978 return m.buy 1979 } 1980 1981 func (m testMargin) Sell() int64 { 1982 return m.sell 1983 } 1984 1985 func (m testMargin) Size() int64 { 1986 return m.size 1987 } 1988 1989 func (m testMargin) BuySumProduct() *num.Uint { 1990 return num.NewUint(m.buySumProduct) 1991 } 1992 1993 func (m testMargin) SellSumProduct() *num.Uint { 1994 return num.NewUint(m.sellSumProduct) 1995 } 1996 1997 func (m testMargin) VWBuy() *num.Uint { 1998 if m.buy == 0 { 1999 num.UintZero() 2000 } 2001 return num.UintZero().Div(m.BuySumProduct(), num.NewUint(uint64(m.buy))) 2002 } 2003 2004 func (m testMargin) VWSell() *num.Uint { 2005 if m.sell == 0 { 2006 num.UintZero() 2007 } 2008 return num.UintZero().Div(m.SellSumProduct(), num.NewUint(uint64(m.sell))) 2009 } 2010 2011 func (m testMargin) ClearPotentials() {} 2012 2013 func (m testMargin) Transfer() *types.Transfer { 2014 return m.transfer 2015 } 2016 2017 func (m testMargin) MarginShortFall() *num.Uint { 2018 return num.NewUint(m.marginShortFall) 2019 }