code.vegaprotocol.io/vega@v0.79.0/core/risk/isolated_margin.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 "context" 20 "fmt" 21 "sort" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/types" 25 "code.vegaprotocol.io/vega/libs/num" 26 ) 27 28 // calculateIsolatedMargins calculates the required margins for a party in isolated margin mode. 29 // It is calculating margin the same way as the cross margin mode does and then enriches the result with the order margin requirement. 30 // For isolated margin search levels and release levels are set to 0. 31 // auctionPrice is nil if not in an auction, otherwise the max(markPrice, indicativePrice). 32 // NB: pure calculation, no events emitted, no state changed. 33 func (e *Engine) calculateIsolatedMargins(m events.Margin, marketObservable *num.Uint, inc num.Decimal, marginFactor num.Decimal, auctionPrice *num.Uint, orders []*types.Order) *types.MarginLevels { 34 auction := e.as.InAuction() && !e.as.CanLeave() 35 // NB:we don't include orders when calculating margin for isolated margin as they are margined separately! 36 margins := e.calculateMargins(m, marketObservable, *e.factors, false, auction, inc, auctionPrice) 37 margins.OrderMargin = CalcOrderMargins(m.Size(), orders, e.positionFactor, marginFactor, auctionPrice) 38 margins.CollateralReleaseLevel = num.UintZero() 39 margins.SearchLevel = num.UintZero() 40 margins.MarginMode = types.MarginModeIsolatedMargin 41 margins.MarginFactor = marginFactor 42 margins.Party = m.Party() 43 margins.Asset = m.Asset() 44 margins.MarketID = m.MarketID() 45 margins.Timestamp = e.timeSvc.GetTimeNow().UnixNano() 46 return margins 47 } 48 49 // ReleaseExcessMarginAfterAuctionUncrossing is called after auction uncrossing to release excess order margin due to orders placed during an auction 50 // when the price used for order margin is the auction price rather than the order price. 51 func (e *Engine) ReleaseExcessMarginAfterAuctionUncrossing(ctx context.Context, m events.Margin, marketObservable *num.Uint, increment num.Decimal, marginFactor num.Decimal, orders []*types.Order) events.Risk { 52 margins := e.calculateIsolatedMargins(m, marketObservable, increment, marginFactor, nil, orders) 53 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 54 if margins.OrderMargin.LT(m.OrderMarginBalance()) { 55 amt := num.UintZero().Sub(m.OrderMarginBalance(), margins.OrderMargin) 56 return &marginChange{ 57 Margin: m, 58 transfer: &types.Transfer{ 59 Owner: m.Party(), 60 Type: types.TransferTypeOrderMarginHigh, 61 Amount: &types.FinancialAmount{ 62 Asset: m.Asset(), 63 Amount: amt, 64 }, 65 MinAmount: amt.Clone(), 66 }, 67 margins: margins, 68 } 69 } 70 return nil 71 } 72 73 // UpdateIsolatedMarginOnAggressor is called when a new order comes in and is matched immediately. 74 // NB: evt has the position after the trades + orders need to include the new order with the updated remaining. 75 // returns an error if the new margin is invalid or if the margin account cannot be topped up from general account. 76 // if successful it updates the margin level and returns the transfer that is needed for the topup of the margin account or release from the margin account excess. 77 func (e *Engine) UpdateIsolatedMarginOnAggressor(ctx context.Context, evt events.Margin, marketObservable *num.Uint, increment num.Decimal, orders []*types.Order, trades []*types.Trade, marginFactor num.Decimal, traderSide types.Side, isAmend bool, fees *num.Uint) ([]events.Risk, error) { 78 if evt == nil { 79 return nil, nil 80 } 81 margins := e.calculateIsolatedMargins(evt, marketObservable, increment, marginFactor, nil, orders) 82 tradedSize := int64(0) 83 side := trades[0].Aggressor 84 requiredMargin := num.UintZero() 85 for _, t := range trades { 86 tradedSize += int64(t.Size) 87 requiredMargin.AddSum(num.UintZero().Mul(t.Price, num.NewUint(t.Size))) 88 } 89 if side == types.SideSell { 90 tradedSize = -tradedSize 91 } 92 oldPosition := evt.Size() - tradedSize 93 if evt.Size()*oldPosition >= 0 { // position didn't switch sides 94 if int64Abs(oldPosition) < int64Abs(evt.Size()) { // position increased 95 requiredMargin, _ = num.UintFromDecimal(requiredMargin.ToDecimal().Div(e.positionFactor).Mul(marginFactor)) 96 if num.Sum(requiredMargin, evt.MarginBalance()).LT(margins.MaintenanceMargin) { 97 return nil, ErrInsufficientFundsForMaintenanceMargin 98 } 99 if !isAmend && requiredMargin.GT(evt.GeneralAccountBalance()) { 100 return nil, ErrInsufficientFundsForMarginInGeneralAccount 101 } 102 if isAmend && requiredMargin.GT(num.Sum(evt.GeneralAccountBalance(), evt.OrderMarginBalance())) { 103 return nil, ErrInsufficientFundsForMarginInGeneralAccount 104 } 105 // new order, given that they can cover for the trade, do they have enough left to cover the fees? 106 if !isAmend && num.Sum(requiredMargin, fees).GT(num.Sum(evt.GeneralAccountBalance(), evt.MarginBalance())) { 107 return nil, ErrInsufficientFundsToCoverTradeFees 108 } 109 // amended order, given that they can cover for the trade, do they have enough left to cover the fees for the amended order's trade? 110 if isAmend && num.Sum(requiredMargin, fees).GT(num.Sum(evt.GeneralAccountBalance(), evt.MarginBalance(), evt.OrderMarginBalance())) { 111 return nil, ErrInsufficientFundsToCoverTradeFees 112 } 113 } 114 } else { 115 // position did switch sides 116 requiredMargin = num.UintZero() 117 pos := int64Abs(oldPosition) 118 totalSize := uint64(0) 119 for _, t := range trades { 120 if pos >= t.Size { 121 pos -= t.Size 122 totalSize += t.Size 123 } else if pos == 0 { 124 requiredMargin.AddSum(num.UintZero().Mul(t.Price, num.NewUint(t.Size))) 125 } else { 126 size := t.Size - pos 127 requiredMargin.AddSum(num.UintZero().Mul(t.Price, num.NewUint(size))) 128 pos = 0 129 } 130 } 131 // The new margin required balance is requiredMargin, so we need to check that: 132 // 1) it's greater than maintenance margin to keep the invariant 133 // 2) there are sufficient funds in what's currently in the margin account + general account to cover for the new required margin 134 requiredMargin, _ = num.UintFromDecimal(requiredMargin.ToDecimal().Div(e.positionFactor).Mul(marginFactor)) 135 if num.Sum(requiredMargin, evt.MarginBalance()).LT(margins.MaintenanceMargin) { 136 return nil, ErrInsufficientFundsForMaintenanceMargin 137 } 138 if requiredMargin.GT(num.Sum(evt.GeneralAccountBalance(), evt.MarginBalance())) { 139 return nil, ErrInsufficientFundsForMarginInGeneralAccount 140 } 141 if num.Sum(requiredMargin, fees).GT(num.Sum(evt.GeneralAccountBalance(), evt.MarginBalance())) { 142 return nil, ErrInsufficientFundsToCoverTradeFees 143 } 144 } 145 146 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 147 transfers := getIsolatedMarginTransfersOnPositionChange(evt.Party(), evt.Asset(), trades, traderSide, evt.Size(), e.positionFactor, marginFactor, evt.MarginBalance(), evt.OrderMarginBalance(), marketObservable, true, isAmend) 148 if transfers == nil { 149 return nil, nil 150 } 151 ret := []events.Risk{} 152 for _, t := range transfers { 153 ret = append(ret, &marginChange{ 154 Margin: evt, 155 transfer: t, 156 margins: margins, 157 }) 158 } 159 return ret, nil 160 } 161 162 // UpdateIsolatedMarginOnOrder checks that the party has sufficient cover for the given orders including the new one. It returns an error if the party doesn't have sufficient cover and the necessary transfers otherwise. 163 // NB: auctionPrice should be nil in continuous mode. 164 func (e *Engine) UpdateIsolatedMarginOnOrder(ctx context.Context, evt events.Margin, orders []*types.Order, marketObservable *num.Uint, auctionPrice *num.Uint, increment num.Decimal, marginFactor num.Decimal) (events.Risk, error) { 165 auction := e.as.InAuction() && !e.as.CanLeave() 166 var ap *num.Uint 167 if auction { 168 ap = auctionPrice 169 } 170 margins := e.calculateIsolatedMargins(evt, marketObservable, increment, marginFactor, ap, orders) 171 172 // if the margin account balance + the required order margin is less than the maintenance margin, return error 173 if margins.OrderMargin.GT(evt.OrderMarginBalance()) && num.UintZero().Sub(margins.OrderMargin, evt.OrderMarginBalance()).GT(evt.GeneralAccountBalance()) { 174 return nil, ErrInsufficientFundsForMarginInGeneralAccount 175 } 176 177 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 178 var amt *num.Uint 179 tp := types.TransferTypeOrderMarginLow 180 if margins.OrderMargin.GT(evt.OrderMarginBalance()) { 181 amt = num.UintZero().Sub(margins.OrderMargin, evt.OrderMarginBalance()) 182 } else { 183 amt = num.UintZero().Sub(evt.OrderMarginBalance(), margins.OrderMargin) 184 tp = types.TransferTypeOrderMarginHigh 185 } 186 187 var trnsfr *types.Transfer 188 if amt.IsZero() { 189 return nil, nil 190 } 191 192 trnsfr = &types.Transfer{ 193 Owner: evt.Party(), 194 Type: tp, 195 Amount: &types.FinancialAmount{ 196 Asset: evt.Asset(), 197 Amount: amt, 198 }, 199 MinAmount: amt.Clone(), 200 } 201 202 change := &marginChange{ 203 Margin: evt, 204 transfer: trnsfr, 205 margins: margins, 206 } 207 return change, nil 208 } 209 210 func (e *Engine) UpdateIsolatedMarginOnOrderCancel(ctx context.Context, evt events.Margin, orders []*types.Order, marketObservable *num.Uint, auctionPrice *num.Uint, increment num.Decimal, marginFactor num.Decimal) (events.Risk, error) { 211 auction := e.as.InAuction() && !e.as.CanLeave() 212 var ap *num.Uint 213 if auction { 214 ap = auctionPrice 215 } 216 margins := e.calculateIsolatedMargins(evt, marketObservable, increment, marginFactor, ap, orders) 217 if margins.OrderMargin.GT(evt.OrderMarginBalance()) { 218 return nil, ErrInsufficientFundsForOrderMargin 219 } 220 221 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 222 var amt *num.Uint 223 tp := types.TransferTypeOrderMarginHigh 224 amt = num.UintZero().Sub(evt.OrderMarginBalance(), margins.OrderMargin) 225 226 var trnsfr *types.Transfer 227 if amt.IsZero() { 228 return nil, nil 229 } 230 231 trnsfr = &types.Transfer{ 232 Owner: evt.Party(), 233 Type: tp, 234 Amount: &types.FinancialAmount{ 235 Asset: evt.Asset(), 236 Amount: amt, 237 }, 238 MinAmount: amt.Clone(), 239 } 240 241 change := &marginChange{ 242 Margin: evt, 243 transfer: trnsfr, 244 margins: margins, 245 } 246 return change, nil 247 } 248 249 // UpdateIsolatedMarginOnPositionChanged is called upon changes to the position of a party in isolated margin mode. 250 // Depending on the nature of the change it checks if it needs to move funds into our out of the margin account from the 251 // order margin account or to the general account. 252 // At this point we don't enforce any invariants just calculate transfers. 253 func (e *Engine) UpdateIsolatedMarginsOnPositionChange(ctx context.Context, evt events.Margin, marketObservable *num.Uint, increment num.Decimal, orders []*types.Order, trades []*types.Trade, traderSide types.Side, marginFactor num.Decimal) ([]events.Risk, error) { 254 if evt == nil { 255 return nil, nil 256 } 257 margins := e.calculateIsolatedMargins(evt, marketObservable, increment, marginFactor, nil, orders) 258 ret := []events.Risk{} 259 transfer := getIsolatedMarginTransfersOnPositionChange(evt.Party(), evt.Asset(), trades, traderSide, evt.Size(), e.positionFactor, marginFactor, evt.MarginBalance(), evt.OrderMarginBalance(), marketObservable, false, false) 260 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 261 if transfer != nil { 262 ret = append(ret, &marginChange{ 263 Margin: evt, 264 transfer: transfer[0], 265 margins: margins, 266 }) 267 } 268 var amtForRelease *num.Uint 269 if !evt.OrderMarginBalance().IsZero() && margins.OrderMargin.IsZero() && transfer != nil && evt.OrderMarginBalance().GT(transfer[0].Amount.Amount) { 270 amtForRelease = num.UintZero().Sub(evt.OrderMarginBalance(), transfer[0].Amount.Amount) 271 } 272 273 // if there's no more order margin requirement, release remaining order margin 274 if amtForRelease != nil && !amtForRelease.IsZero() { 275 ret = append(ret, 276 &marginChange{ 277 Margin: evt, 278 transfer: &types.Transfer{ 279 Owner: evt.Party(), 280 Type: types.TransferTypeOrderMarginHigh, 281 Amount: &types.FinancialAmount{ 282 Asset: evt.Asset(), 283 Amount: amtForRelease, 284 }, 285 MinAmount: amtForRelease.Clone(), 286 }, 287 margins: margins, 288 }, 289 ) 290 } 291 return ret, nil 292 } 293 294 func (e *Engine) CheckMarginInvariants(ctx context.Context, evt events.Margin, marketObservable *num.Uint, increment num.Decimal, orders []*types.Order, marginFactor num.Decimal) (events.Risk, error) { 295 margins := e.calculateIsolatedMargins(evt, marketObservable, increment, marginFactor, nil, orders) 296 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 297 return e.checkMarginInvariants(evt, margins) 298 } 299 300 // CheckMarginInvariants returns an error if the margin invariants are invalidated, i.e. if margin balance < margin level or order margin balance < order margin level. 301 func (e *Engine) checkMarginInvariants(evt events.Margin, margins *types.MarginLevels) (events.Risk, error) { 302 ret := &marginChange{ 303 Margin: evt, 304 transfer: nil, 305 margins: margins, 306 } 307 if evt.MarginBalance().LT(margins.MaintenanceMargin) { 308 return ret, ErrInsufficientFundsForMaintenanceMargin 309 } 310 if evt.OrderMarginBalance().LT(margins.OrderMargin) { 311 return ret, ErrInsufficientFundsForOrderMargin 312 } 313 return ret, nil 314 } 315 316 // SwitchToIsolatedMargin attempts to switch the party from cross margin mode to isolated mode. 317 // Error can be returned if it is not possible for the party to switch at this moment. 318 // If successful the new margin levels are buffered and the required margin level, margin balances, and transfers (aka events.risk) is returned. 319 func (e *Engine) SwitchToIsolatedMargin(ctx context.Context, evt events.Margin, marketObservable *num.Uint, inc num.Decimal, orders []*types.Order, marginFactor num.Decimal, auctionPrice *num.Uint) ([]events.Risk, error) { 320 margins := e.calculateIsolatedMargins(evt, marketObservable, inc, marginFactor, auctionPrice, orders) 321 risk, err := switchToIsolatedMargin(evt, margins, orders, marginFactor, e.positionFactor) 322 if err != nil { 323 return nil, err 324 } 325 326 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 327 return risk, nil 328 } 329 330 // SwitchFromIsolatedMargin switches the party from isolated margin mode to cross margin mode. 331 // This includes: 332 // 1. recalcualtion of the required margin in cross margin mode + margin levels are buffered 333 // 2. return a transfer of all the balance from order margin account to margin account 334 // NB: cannot fail. 335 func (e *Engine) SwitchFromIsolatedMargin(ctx context.Context, evt events.Margin, marketObservable *num.Uint, inc num.Decimal, auctionPrice *num.Uint) events.Risk { 336 amt := evt.OrderMarginBalance().Clone() 337 auction := e.as.InAuction() && !e.as.CanLeave() 338 margins := e.calculateMargins(evt, marketObservable, *e.factors, true, auction, inc, auctionPrice) 339 margins.Party = evt.Party() 340 margins.Asset = evt.Asset() 341 margins.MarketID = evt.MarketID() 342 margins.Timestamp = e.timeSvc.GetTimeNow().UnixNano() 343 e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins)) 344 345 return &marginChange{ 346 Margin: evt, 347 transfer: &types.Transfer{ 348 Owner: evt.Party(), 349 Type: types.TransferTypeIsolatedMarginLow, 350 MinAmount: amt, 351 Amount: &types.FinancialAmount{ 352 Asset: evt.Asset(), 353 Amount: amt.Clone(), 354 }, 355 }, 356 margins: margins, 357 } 358 } 359 360 // getIsolatedMarginTransfersOnPositionChange returns the transfers that need to be made to/from the margin account in isolated margin mode 361 // when the position changes. This handles the 3 different cases of position change (increase, decrease, switch sides). 362 // NB: positionSize is *after* the trades. 363 func getIsolatedMarginTransfersOnPositionChange(party, asset string, trades []*types.Trade, traderSide types.Side, positionSize int64, positionFactor, marginFactor num.Decimal, curMarginBalance, orderMarginBalance, markPrice *num.Uint, aggressiveSide bool, isAmend bool) []*types.Transfer { 364 positionDelta := int64(0) 365 marginToAdd := num.UintZero() 366 vwap := num.UintZero() 367 for _, t := range trades { 368 positionDelta += int64(t.Size) 369 marginToAdd.AddSum(num.UintZero().Mul(t.Price, num.NewUint(t.Size))) 370 vwap.AddSum(num.UintZero().Mul(t.Price, num.NewUint(t.Size))) 371 } 372 vwap = num.UintZero().Div(vwap, num.NewUint(uint64(positionDelta))) 373 if traderSide == types.SideSell { 374 positionDelta = -positionDelta 375 } 376 oldPosition := positionSize - positionDelta 377 378 if positionSize*oldPosition >= 0 { // position didn't switch sides 379 if int64Abs(oldPosition) < int64Abs(positionSize) { // position increased 380 marginToAdd, _ = num.UintFromDecimal(marginToAdd.ToDecimal().Div(positionFactor).Mul(marginFactor)) 381 if !isAmend { 382 // need to top up the margin account from the order margin account 383 var tp types.TransferType 384 if aggressiveSide { 385 tp = types.TransferTypeMarginLow 386 } else { 387 tp = types.TransferTypeIsolatedMarginLow 388 } 389 return []*types.Transfer{{ 390 Owner: party, 391 Type: tp, 392 Amount: &types.FinancialAmount{ 393 Asset: asset, 394 Amount: marginToAdd, 395 }, 396 MinAmount: marginToAdd, 397 }} 398 } 399 if marginToAdd.LTE(orderMarginBalance) { 400 return []*types.Transfer{{ 401 Owner: party, 402 Type: types.TransferTypeIsolatedMarginLow, 403 Amount: &types.FinancialAmount{ 404 Asset: asset, 405 Amount: marginToAdd, 406 }, 407 MinAmount: marginToAdd, 408 }} 409 } 410 generalTopUp := num.UintZero().Sub(marginToAdd, orderMarginBalance) 411 return []*types.Transfer{ 412 { 413 Owner: party, 414 Type: types.TransferTypeIsolatedMarginLow, 415 Amount: &types.FinancialAmount{ 416 Asset: asset, 417 Amount: orderMarginBalance, 418 }, 419 MinAmount: orderMarginBalance, 420 }, { 421 Owner: party, 422 Type: types.TransferTypeMarginLow, 423 Amount: &types.FinancialAmount{ 424 Asset: asset, 425 Amount: generalTopUp, 426 }, 427 MinAmount: generalTopUp, 428 }, 429 } 430 } 431 // position decreased 432 // marginToRelease = balanceBefore + positionBefore x (newTradeVWAP - markPrice) x |totalTradeSize|/|positionBefore| 433 theoreticalAccountBalance, _ := num.UintFromDecimal(vwap.ToDecimal().Sub(markPrice.ToDecimal()).Mul(num.DecimalFromInt64(int64(int64Abs(oldPosition)))).Div(positionFactor).Add(curMarginBalance.ToDecimal())) 434 marginToRelease := num.UintZero().Div(num.UintZero().Mul(theoreticalAccountBalance, num.NewUint(int64Abs(positionDelta))), num.NewUint(int64Abs(oldPosition))) 435 // need to top up the margin account 436 return []*types.Transfer{{ 437 Owner: party, 438 Type: types.TransferTypeMarginHigh, 439 Amount: &types.FinancialAmount{ 440 Asset: asset, 441 Amount: marginToRelease, 442 }, 443 MinAmount: marginToRelease, 444 }} 445 } 446 447 // position switched sides, we need to handles the two sides separately 448 // first calculate the amount that would be released 449 marginToRelease := curMarginBalance.Clone() 450 marginToAdd = num.UintZero() 451 pos := int64Abs(oldPosition) 452 totalSize := uint64(0) 453 for _, t := range trades { 454 if pos >= t.Size { 455 pos -= t.Size 456 totalSize += t.Size 457 } else if pos == 0 { 458 marginToAdd.AddSum(num.UintZero().Mul(t.Price, num.NewUint(t.Size))) 459 } else { 460 size := t.Size - pos 461 marginToAdd.AddSum(num.UintZero().Mul(t.Price, num.NewUint(size))) 462 pos = 0 463 } 464 } 465 marginToAdd, _ = num.UintFromDecimal(marginToAdd.ToDecimal().Div(positionFactor).Mul(marginFactor)) 466 topup := num.UintZero() 467 release := num.UintZero() 468 if marginToAdd.GT(marginToRelease) { 469 topup = num.UintZero().Sub(marginToAdd, marginToRelease) 470 } else { 471 release = num.UintZero().Sub(marginToRelease, marginToAdd) 472 } 473 474 amt := topup 475 tp := types.TransferTypeMarginLow 476 if aggressiveSide { 477 tp = types.TransferTypeMarginLow 478 } 479 if !release.IsZero() { 480 amt = release 481 tp = types.TransferTypeMarginHigh 482 } 483 484 if amt.IsZero() { 485 return nil 486 } 487 488 return []*types.Transfer{{ 489 Owner: party, 490 Type: tp, 491 Amount: &types.FinancialAmount{ 492 Asset: asset, 493 Amount: amt, 494 }, 495 MinAmount: amt, 496 }} 497 } 498 499 func (e *Engine) CalcOrderMarginsForClosedOutParty(orders []*types.Order, marginFactor num.Decimal) *num.Uint { 500 return CalcOrderMargins(0, orders, e.positionFactor, marginFactor, nil) 501 } 502 503 // CalcOrderMargins calculates the the order margin required for the party given their current orders and margin factor. 504 func CalcOrderMargins(positionSize int64, orders []*types.Order, positionFactor, marginFactor num.Decimal, auctionPrice *num.Uint) *num.Uint { 505 if len(orders) == 0 { 506 return num.UintZero() 507 } 508 buyOrders := []*types.Order{} 509 sellOrders := []*types.Order{} 510 // split orders by side 511 for _, o := range orders { 512 if o.Side == types.SideBuy { 513 buyOrders = append(buyOrders, o) 514 } else { 515 sellOrders = append(sellOrders, o) 516 } 517 } 518 // sort orders from best to worst 519 sort.Slice(buyOrders, func(i, j int) bool { return buyOrders[i].Price.GT(buyOrders[j].Price) }) 520 sort.Slice(sellOrders, func(i, j int) bool { return sellOrders[i].Price.LT(sellOrders[j].Price) }) 521 522 // calc the side margin 523 marginByBuy := calcOrderSideMargin(positionSize, buyOrders, positionFactor, marginFactor, auctionPrice) 524 marginBySell := calcOrderSideMargin(positionSize, sellOrders, positionFactor, marginFactor, auctionPrice) 525 orderMargin := marginByBuy 526 if marginBySell.GT(orderMargin) { 527 orderMargin = marginBySell 528 } 529 return orderMargin 530 } 531 532 // calcOrderSideMargin returns the amount of order margin needed given the current position and party orders. 533 // Given the sorted orders of the side for the party (sorted from best to worst) 534 // If the party currently has a position x, assign 0 margin requirement the first-to-trade x of volume on the opposite side as this 535 // would reduce their position (for example, if a party had a long position 10 and sell orders of 15 at a price of $100 and 10 536 // at a price of $150, the first 10 of the sell order at $100 would not require any order margin). 537 // For any remaining volume, sum side margin = limit price * size * margin factor for each price level, as this is 538 // the worst-case trade price of the remaining component. 539 func calcOrderSideMargin(currentPosition int64, orders []*types.Order, positionFactor, marginFactor num.Decimal, auctionPrice *num.Uint) *num.Uint { 540 margin := num.UintZero() 541 remainingCovered := int64Abs(currentPosition) 542 for _, o := range orders { 543 if o.Status != types.OrderStatusActive || o.PeggedOrder != nil { 544 continue 545 } 546 size := o.TrueRemaining() 547 // for long position we don't need to count margin for the top <currentPosition> size for sell orders 548 // for short position we don't need to count margin for the top <currentPosition> size for buy orders 549 if remainingCovered != 0 && (o.Side == types.SideBuy && currentPosition < 0) || (o.Side == types.SideSell && currentPosition > 0) { 550 if size >= remainingCovered { // part of the order doesn't require margin 551 size = size - remainingCovered 552 remainingCovered = 0 553 } else { // the entire order doesn't require margin 554 remainingCovered -= size 555 size = 0 556 } 557 } 558 if size > 0 { 559 // if we're in auction we need to use the larger between auction price (which is the max(indicativePrice, markPrice)) and the order price 560 p := o.Price 561 if auctionPrice != nil && auctionPrice.GT(p) { 562 p = auctionPrice 563 } 564 // add the margin for the given order 565 margin.AddSum(num.UintZero().Mul(num.NewUint(size), p)) 566 } 567 } 568 // factor the margin by margin factor and divide by position factor to get to the right decimals 569 margin, _ = num.UintFromDecimal(margin.ToDecimal().Mul(marginFactor).Div(positionFactor)) 570 return margin 571 } 572 573 // switching from cross margin to isolated margin or changing margin factor 574 // 1. For any active position, calculate average entry price * abs(position) * margin factor. 575 // Calculate the amount of funds which will be added to, or subtracted from, the general account in order to do this. 576 // If additional funds must be added which are not available, reject the transaction immediately. 577 // 2. For any active orders, calculate the quantity limit price * remaining size * margin factor which needs to be placed 578 // in the order margin account. Add this amount to the difference calculated in step 1. 579 // If this amount is less than or equal to the amount in the general account, 580 // perform the transfers (first move funds into/out of margin account, then move funds into the order margin account). 581 // If there are insufficient funds, reject the transaction. 582 // 3. Move account to isolated margin mode on this market 583 // 584 // If a party has no position nore orders and switches to isolated margin the function returns an empty slice. 585 func switchToIsolatedMargin(evt events.Margin, margin *types.MarginLevels, orders []*types.Order, marginFactor, positionFactor num.Decimal) ([]events.Risk, error) { 586 marginAccountBalance := evt.MarginBalance() 587 generalAccountBalance := evt.GeneralAccountBalance() 588 orderMarginAccountBalance := evt.OrderMarginBalance() 589 if orderMarginAccountBalance == nil { 590 orderMarginAccountBalance = num.UintZero() 591 } 592 totalOrderNotional := num.UintZero() 593 for _, o := range orders { 594 if o.Status == types.OrderStatusActive && o.PeggedOrder == nil { 595 totalOrderNotional = totalOrderNotional.AddSum(num.UintZero().Mul(o.Price, num.NewUint(o.TrueRemaining()))) 596 } 597 } 598 599 positionSize := int64Abs(evt.Size()) 600 requiredPositionMargin := num.UintZero().Mul(evt.AverageEntryPrice(), num.NewUint(positionSize)).ToDecimal().Mul(marginFactor).Div(positionFactor) 601 requireOrderMargin := totalOrderNotional.ToDecimal().Mul(marginFactor).Div(positionFactor) 602 603 // check that we have enough in the general account for any top up needed, i.e. 604 // topupNeeded = requiredPositionMargin + requireOrderMargin - marginAccountBalance 605 // if topupNeeded > generalAccountBalance => fail 606 if requiredPositionMargin.Add(requireOrderMargin).Sub(marginAccountBalance.ToDecimal()).Sub(orderMarginAccountBalance.ToDecimal()).GreaterThan(generalAccountBalance.ToDecimal()) { 607 return nil, fmt.Errorf("insufficient balance in general account to cover for required order margin") 608 } 609 610 // average entry price * current position * new margin factor (aka requiredPositionMargin) must be above the initial margin for the current position or the transaction will be rejected 611 if !requiredPositionMargin.IsZero() && !requiredPositionMargin.GreaterThan(margin.InitialMargin.ToDecimal()) { 612 return nil, fmt.Errorf("required position margin must be greater than initial margin") 613 } 614 615 // we're all good, just need to setup the transfers for collateral topup/release 616 uRequiredPositionMargin, _ := num.UintFromDecimal(requiredPositionMargin) 617 riskEvents := []events.Risk{} 618 if !uRequiredPositionMargin.EQ(marginAccountBalance) { 619 // need to topup or release margin <-> general 620 var amt *num.Uint 621 var tp types.TransferType 622 if uRequiredPositionMargin.GT(marginAccountBalance) { 623 amt = num.UintZero().Sub(uRequiredPositionMargin, marginAccountBalance) 624 tp = types.TransferTypeMarginLow 625 } else { 626 amt = num.UintZero().Sub(marginAccountBalance, uRequiredPositionMargin) 627 tp = types.TransferTypeMarginHigh 628 } 629 riskEvents = append(riskEvents, &marginChange{ 630 Margin: evt, 631 transfer: &types.Transfer{ 632 Owner: evt.Party(), 633 Type: tp, 634 MinAmount: amt, 635 Amount: &types.FinancialAmount{ 636 Asset: evt.Asset(), 637 Amount: amt.Clone(), 638 }, 639 }, 640 margins: margin, 641 }) 642 } 643 uRequireOrderMargin, _ := num.UintFromDecimal(requireOrderMargin) 644 if !uRequireOrderMargin.EQ(orderMarginAccountBalance) { 645 // need to topup or release orderMargin <-> general 646 var amt *num.Uint 647 var tp types.TransferType 648 if requireOrderMargin.GreaterThan(orderMarginAccountBalance.ToDecimal()) { 649 amt = num.UintZero().Sub(uRequireOrderMargin, orderMarginAccountBalance) 650 tp = types.TransferTypeOrderMarginLow 651 } else { 652 amt = num.UintZero().Sub(orderMarginAccountBalance, uRequireOrderMargin) 653 tp = types.TransferTypeOrderMarginHigh 654 } 655 riskEvents = append(riskEvents, &marginChange{ 656 Margin: evt, 657 transfer: &types.Transfer{ 658 Owner: evt.Party(), 659 Type: tp, 660 MinAmount: amt, 661 Amount: &types.FinancialAmount{ 662 Asset: evt.Asset(), 663 Amount: amt.Clone(), 664 }, 665 }, 666 margins: margin, 667 }) 668 } 669 return riskEvents, nil 670 } 671 672 // int64Abs returns the absolute uint64 value of the given int64 n. 673 func int64Abs(n int64) uint64 { 674 if n < 0 { 675 return uint64(-n) 676 } 677 return uint64(n) 678 }