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  }