code.vegaprotocol.io/vega@v0.79.0/core/risk/engine.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  	"errors"
    21  	"sort"
    22  	"sync"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/core/types/statevar"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/logging"
    30  
    31  	"golang.org/x/exp/maps"
    32  )
    33  
    34  //go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/risk Orderbook,AuctionState,TimeService,StateVarEngine,Model
    35  
    36  var (
    37  	ErrInsufficientFundsForInitialMargin          = errors.New("insufficient funds for initial margin")
    38  	ErrInsufficientFundsForMaintenanceMargin      = errors.New("insufficient funds for maintenance margin")
    39  	ErrInsufficientFundsForOrderMargin            = errors.New("insufficient funds for order margin")
    40  	ErrInsufficientFundsForMarginInGeneralAccount = errors.New("insufficient funds to cover margin in general margin")
    41  	ErrRiskFactorsNotAvailableForAsset            = errors.New("risk factors not available for the specified asset")
    42  	ErrInsufficientFundsToCoverTradeFees          = errors.New("insufficient funds to cover fees")
    43  )
    44  
    45  const RiskFactorStateVarName = "risk-factors"
    46  
    47  // Orderbook represent an abstraction over the orderbook.
    48  type Orderbook interface {
    49  	GetIndicativePrice() *num.Uint
    50  }
    51  
    52  // AuctionState represents the current auction state of the market, previously we got this information from the matching engine, but really... that's not its job.
    53  type AuctionState interface {
    54  	InAuction() bool
    55  	CanLeave() bool
    56  }
    57  
    58  // TimeService.
    59  type TimeService interface {
    60  	GetTimeNow() time.Time
    61  }
    62  
    63  // Broker the event bus broker.
    64  type Broker interface {
    65  	Send(events.Event)
    66  	SendBatch([]events.Event)
    67  }
    68  
    69  type StateVarEngine interface {
    70  	RegisterStateVariable(asset, market, name string, converter statevar.Converter, startCalculation func(string, statevar.FinaliseCalculation), trigger []statevar.EventType, result func(context.Context, statevar.StateVariableResult) error) error
    71  	NewEvent(asset, market string, eventType statevar.EventType)
    72  }
    73  
    74  type marginChange struct {
    75  	events.Margin // previous event that caused this change
    76  	transfer      *types.Transfer
    77  	margins       *types.MarginLevels
    78  }
    79  
    80  // Engine is the risk engine.
    81  type Engine struct {
    82  	Config
    83  	marginCalculator        *types.MarginCalculator
    84  	scalingFactorsUint      *scalingFactorsUint
    85  	log                     *logging.Logger
    86  	cfgMu                   sync.RWMutex
    87  	model                   Model
    88  	factors                 *types.RiskFactor
    89  	waiting                 bool
    90  	ob                      Orderbook
    91  	as                      AuctionState
    92  	timeSvc                 TimeService
    93  	broker                  Broker
    94  	riskFactorsInitialised  bool
    95  	mktID                   string
    96  	asset                   string
    97  	positionFactor          num.Decimal
    98  	linearSlippageFactor    num.Decimal
    99  	quadraticSlippageFactor num.Decimal
   100  
   101  	// a map of margin levels events to be send
   102  	// should be flushed after the processing of every transaction
   103  	// partyId -> MarginLevelsEvent
   104  	marginLevelsUpdates map[string]*events.MarginLevels
   105  	updateMarginLevels  func(...*events.MarginLevels)
   106  }
   107  
   108  // NewEngine instantiate a new risk engine.
   109  func NewEngine(log *logging.Logger,
   110  	config Config,
   111  	marginCalculator *types.MarginCalculator,
   112  	model Model,
   113  	ob Orderbook,
   114  	as AuctionState,
   115  	timeSvc TimeService,
   116  	broker Broker,
   117  	mktID string,
   118  	asset string,
   119  	stateVarEngine StateVarEngine,
   120  	positionFactor num.Decimal,
   121  	riskFactorsInitialised bool,
   122  	initialisedRiskFactors *types.RiskFactor, // if restored from snapshot, will be nil otherwise
   123  	linearSlippageFactor num.Decimal,
   124  	quadraticSlippageFactor num.Decimal,
   125  ) *Engine {
   126  	// setup logger
   127  	log = log.Named(namedLogger)
   128  	log.SetLevel(config.Level.Get())
   129  
   130  	sfUint := scalingFactorsUintFromDecimals(marginCalculator.ScalingFactors)
   131  	e := &Engine{
   132  		log:                     log,
   133  		Config:                  config,
   134  		marginCalculator:        marginCalculator,
   135  		model:                   model,
   136  		waiting:                 false,
   137  		ob:                      ob,
   138  		as:                      as,
   139  		timeSvc:                 timeSvc,
   140  		broker:                  broker,
   141  		mktID:                   mktID,
   142  		asset:                   asset,
   143  		scalingFactorsUint:      sfUint,
   144  		factors:                 model.DefaultRiskFactors(),
   145  		riskFactorsInitialised:  riskFactorsInitialised,
   146  		positionFactor:          positionFactor,
   147  		linearSlippageFactor:    linearSlippageFactor,
   148  		quadraticSlippageFactor: quadraticSlippageFactor,
   149  		marginLevelsUpdates:     map[string]*events.MarginLevels{},
   150  	}
   151  	stateVarEngine.RegisterStateVariable(asset, mktID, RiskFactorStateVarName, FactorConverter{}, e.startRiskFactorsCalculation, []statevar.EventType{statevar.EventTypeMarketEnactment, statevar.EventTypeMarketUpdated}, e.updateRiskFactor)
   152  
   153  	if initialisedRiskFactors != nil {
   154  		e.cfgMu.Lock()
   155  		e.factors = initialisedRiskFactors
   156  		e.cfgMu.Unlock()
   157  		// we've restored from snapshot, we don't need want to trigger a MarketEnactment event
   158  	} else {
   159  		// trigger the calculation of risk factors for the market
   160  		stateVarEngine.NewEvent(asset, mktID, statevar.EventTypeMarketEnactment)
   161  	}
   162  
   163  	e.updateMarginLevels = e.bufferMarginLevels
   164  	if e.StreamMarginLevelsVerbose {
   165  		e.updateMarginLevels = e.sendMarginLevels
   166  	}
   167  
   168  	return e
   169  }
   170  
   171  func (e *Engine) FlushMarginLevelsEvents() {
   172  	if e.StreamMarginLevelsVerbose || len(e.marginLevelsUpdates) <= 0 {
   173  		return
   174  	}
   175  
   176  	e.sendBufferedMarginLevels()
   177  }
   178  
   179  func (e *Engine) sendBufferedMarginLevels() {
   180  	parties := maps.Keys(e.marginLevelsUpdates)
   181  	sort.Strings(parties)
   182  	evts := make([]events.Event, 0, len(parties))
   183  
   184  	for _, v := range parties {
   185  		evts = append(evts, e.marginLevelsUpdates[v])
   186  	}
   187  
   188  	e.broker.SendBatch(evts)
   189  	e.marginLevelsUpdates = make(map[string]*events.MarginLevels, len(e.marginLevelsUpdates))
   190  }
   191  
   192  func (e *Engine) sendMarginLevels(m ...*events.MarginLevels) {
   193  	evts := make([]events.Event, 0, len(m))
   194  	for _, ml := range m {
   195  		evts = append(evts, ml)
   196  	}
   197  
   198  	e.broker.SendBatch(evts)
   199  }
   200  
   201  func (e *Engine) bufferMarginLevels(mls ...*events.MarginLevels) {
   202  	for _, m := range mls {
   203  		e.marginLevelsUpdates[m.PartyID()] = m
   204  	}
   205  }
   206  
   207  func (e *Engine) OnMarginScalingFactorsUpdate(sf *types.ScalingFactors) error {
   208  	if sf.CollateralRelease.LessThan(sf.InitialMargin) || sf.InitialMargin.LessThanOrEqual(sf.SearchLevel) {
   209  		return errors.New("incompatible margins scaling factors")
   210  	}
   211  
   212  	e.marginCalculator.ScalingFactors = sf
   213  	e.scalingFactorsUint = scalingFactorsUintFromDecimals(sf)
   214  	return nil
   215  }
   216  
   217  func (e *Engine) UpdateModel(
   218  	stateVarEngine StateVarEngine,
   219  	calculator *types.MarginCalculator,
   220  	model Model,
   221  	linearSlippageFactor num.Decimal,
   222  	quadraticSlippageFactor num.Decimal,
   223  ) {
   224  	e.scalingFactorsUint = scalingFactorsUintFromDecimals(calculator.ScalingFactors)
   225  	e.cfgMu.Lock()
   226  	e.factors = model.DefaultRiskFactors()
   227  	e.cfgMu.Unlock()
   228  	e.model = model
   229  	e.linearSlippageFactor = linearSlippageFactor
   230  	e.quadraticSlippageFactor = quadraticSlippageFactor
   231  	stateVarEngine.NewEvent(e.asset, e.mktID, statevar.EventTypeMarketUpdated)
   232  }
   233  
   234  // ReloadConf update the internal configuration of the risk engine.
   235  func (e *Engine) ReloadConf(cfg Config) {
   236  	e.log.Info("reloading configuration")
   237  	if e.log.GetLevel() != cfg.Level.Get() {
   238  		e.log.Info("updating log level",
   239  			logging.String("old", e.log.GetLevel().String()),
   240  			logging.String("new", cfg.Level.String()),
   241  		)
   242  		e.log.SetLevel(cfg.Level.Get())
   243  	}
   244  
   245  	e.cfgMu.Lock()
   246  	e.Config = cfg
   247  	e.cfgMu.Unlock()
   248  }
   249  
   250  // GetRiskFactors returns risk factors per specified asset.
   251  func (e *Engine) GetRiskFactors() *types.RiskFactor {
   252  	e.cfgMu.RLock()
   253  	defer e.cfgMu.RUnlock()
   254  	return e.factors
   255  }
   256  
   257  func (e *Engine) GetScalingFactors() *types.ScalingFactors {
   258  	return e.marginCalculator.ScalingFactors
   259  }
   260  
   261  func (e *Engine) GetSlippage() num.Decimal {
   262  	return e.linearSlippageFactor
   263  }
   264  
   265  func (e *Engine) UpdateMarginAuction(ctx context.Context, evts []events.Margin, price *num.Uint, increment num.Decimal, auctionPrice *num.Uint) ([]events.Risk, []events.Margin) {
   266  	if len(evts) == 0 {
   267  		return nil, nil
   268  	}
   269  	revts := make([]events.Risk, 0, len(evts))
   270  	// parties with insufficient margin to meet required level, return the event passed as arg
   271  	low := []events.Margin{}
   272  	eventBatch := make([]*events.MarginLevels, 0, len(evts))
   273  	// for now, we can assume a single asset for all events
   274  	rFactors := *e.factors
   275  	nowTS := e.timeSvc.GetTimeNow().UnixNano()
   276  	for _, evt := range evts {
   277  		levels := e.calculateMargins(evt, price, rFactors, true, true, increment, auctionPrice)
   278  		if levels == nil {
   279  			continue
   280  		}
   281  
   282  		levels.Party = evt.Party()
   283  		levels.Asset = e.asset // This is assuming there's a single asset at play here
   284  		levels.Timestamp = nowTS
   285  		levels.MarketID = e.mktID
   286  
   287  		curMargin := evt.MarginBalance()
   288  		if num.Sum(curMargin, evt.GeneralBalance()).LT(levels.InitialMargin) {
   289  			low = append(low, evt)
   290  			continue
   291  		}
   292  		eventBatch = append(eventBatch, events.NewMarginLevelsEvent(ctx, *levels))
   293  		// party has sufficient margin, no need to transfer funds
   294  		if curMargin.GTE(levels.InitialMargin) {
   295  			continue
   296  		}
   297  		minAmount := num.UintZero()
   298  		if levels.MaintenanceMargin.GT(curMargin) {
   299  			minAmount.Sub(levels.MaintenanceMargin, curMargin)
   300  		}
   301  		amt := num.UintZero().Sub(levels.InitialMargin, curMargin) // we know curBalace is less than initial
   302  		t := &types.Transfer{
   303  			Owner: evt.Party(),
   304  			Type:  types.TransferTypeMarginLow,
   305  			Amount: &types.FinancialAmount{
   306  				Asset:  e.asset,
   307  				Amount: amt,
   308  			},
   309  			MinAmount: minAmount,
   310  		}
   311  		revts = append(revts, &marginChange{
   312  			Margin:   evt,
   313  			transfer: t,
   314  			margins:  levels,
   315  		})
   316  	}
   317  	e.updateMarginLevels(eventBatch...)
   318  	return revts, low
   319  }
   320  
   321  // UpdateMarginOnNewOrder calculate the new margin requirement for a single order
   322  // this is intended to be used when a new order is created in order to ensure the
   323  // party margin account is at least at the InitialMargin level before the order is added to the book.
   324  func (e *Engine) UpdateMarginOnNewOrder(ctx context.Context, evt events.Margin, markPrice *num.Uint, increment num.Decimal, auctionPrice *num.Uint) (events.Risk, events.Margin, error) {
   325  	if evt == nil {
   326  		return nil, nil, nil
   327  	}
   328  	auction := e.as.InAuction() && !e.as.CanLeave()
   329  	margins := e.calculateMargins(evt, markPrice, *e.factors, true, auction, increment, auctionPrice)
   330  
   331  	// no margins updates, nothing to do then
   332  	if margins == nil {
   333  		return nil, nil, nil
   334  	}
   335  
   336  	// update other fields for the margins
   337  	margins.Party = evt.Party()
   338  	margins.Asset = evt.Asset()
   339  	margins.Timestamp = e.timeSvc.GetTimeNow().UnixNano()
   340  	margins.MarketID = e.mktID
   341  
   342  	curMarginBalance := evt.MarginBalance()
   343  
   344  	if num.Sum(curMarginBalance, evt.GeneralBalance()).LT(margins.InitialMargin) {
   345  		// there's not enough monies in the accounts of the party
   346  		// and the order does not reduce party's exposure,
   347  		// we break from here. The minimum requirement is INITIAL.
   348  		return nil, nil, ErrInsufficientFundsForInitialMargin
   349  	}
   350  
   351  	// propagate margins levels to the buffer
   352  	e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins))
   353  
   354  	// margins are sufficient, nothing to update
   355  	if curMarginBalance.GTE(margins.InitialMargin) {
   356  		return nil, nil, nil
   357  	}
   358  
   359  	minAmount := num.UintZero()
   360  	if margins.MaintenanceMargin.GT(curMarginBalance) {
   361  		minAmount.Sub(margins.MaintenanceMargin, curMarginBalance)
   362  	}
   363  
   364  	// margin is < that InitialMargin so we create a transfer request to top it up.
   365  	trnsfr := &types.Transfer{
   366  		Owner: evt.Party(),
   367  		Type:  types.TransferTypeMarginLow,
   368  		Amount: &types.FinancialAmount{
   369  			Asset:  evt.Asset(),
   370  			Amount: num.UintZero().Sub(margins.InitialMargin, curMarginBalance),
   371  		},
   372  		MinAmount: minAmount, // minimal amount == maintenance
   373  	}
   374  
   375  	change := &marginChange{
   376  		Margin:   evt,
   377  		transfer: trnsfr,
   378  		margins:  margins,
   379  	}
   380  	// we don't have enough in general + margin accounts to cover initial margin level, so we'll be dipping into our bond account
   381  	// we have to return the margin event to signal that
   382  	nonBondFunds := num.Sum(curMarginBalance, evt.GeneralBalance())
   383  	nonBondFunds.Sub(nonBondFunds, evt.BondBalance())
   384  	if nonBondFunds.LT(margins.InitialMargin) {
   385  		return change, evt, nil
   386  	}
   387  	return change, nil, nil
   388  }
   389  
   390  // UpdateMarginsOnSettlement ensure the margin requirement over all positions.
   391  // margins updates are based on the following requirement
   392  //
   393  //	---------------------------------------------------------------------------------------
   394  //
   395  // | 1 | SearchLevel < CurMargin < InitialMargin | nothing to do / no risk for the network |
   396  // | 2 | CurMargin < SearchLevel                 | set margin to InitialLevel              |
   397  // | 3 | CurMargin > ReleaseLevel                | release up to the InitialLevel          |
   398  //
   399  //	---------------------------------------------------------------------------------------
   400  //
   401  // In the case where the CurMargin is smaller to the MaintenanceLevel after trying to
   402  // move monies later, we'll need to close out the party but that cannot be figured out
   403  // now only in later when we try to move monies from the general account.
   404  func (e *Engine) UpdateMarginsOnSettlement(ctx context.Context, evts []events.Margin, markPrice *num.Uint, increment num.Decimal, auctionPrice *num.Uint) []events.Risk {
   405  	ret := make([]events.Risk, 0, len(evts))
   406  	now := e.timeSvc.GetTimeNow().UnixNano()
   407  
   408  	// var err error
   409  	// this will keep going until we've closed this channel
   410  	// this can be the result of an error, or being "finished"
   411  	for _, evt := range evts {
   412  		// before we do anything, see if the position is 0 now, but the margin balance is still set
   413  		// in which case the only response is to release the margin balance.
   414  		if evt.Size() == 0 && evt.Buy() == 0 && evt.Sell() == 0 && !evt.MarginBalance().IsZero() {
   415  			amt := evt.MarginBalance()
   416  			trnsfr := &types.Transfer{
   417  				Owner: evt.Party(),
   418  				Type:  types.TransferTypeMarginHigh,
   419  				Amount: &types.FinancialAmount{
   420  					Asset:  evt.Asset(),
   421  					Amount: amt,
   422  				},
   423  				MinAmount: amt.Clone(),
   424  			}
   425  			margins := types.MarginLevels{
   426  				MaintenanceMargin:      num.UintZero(),
   427  				SearchLevel:            num.UintZero(),
   428  				InitialMargin:          num.UintZero(),
   429  				CollateralReleaseLevel: num.UintZero(),
   430  				OrderMargin:            num.UintZero(),
   431  				Party:                  evt.Party(),
   432  				MarketID:               evt.MarketID(),
   433  				Asset:                  evt.Asset(),
   434  				Timestamp:              now,
   435  				MarginMode:             types.MarginModeCrossMargin,
   436  				MarginFactor:           num.DecimalZero(),
   437  			}
   438  			e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, margins))
   439  			ret = append(ret, &marginChange{
   440  				Margin:   evt,
   441  				transfer: trnsfr,
   442  				margins:  &margins,
   443  			})
   444  			continue
   445  		}
   446  		// channel is closed, and we've got a nil interface
   447  		auction := e.as.InAuction() && !e.as.CanLeave()
   448  		margins := e.calculateMargins(evt, markPrice, *e.factors, true, auction, increment, auctionPrice)
   449  
   450  		// no margins updates, nothing to do then
   451  		if margins == nil {
   452  			continue
   453  		}
   454  
   455  		// update other fields for the margins
   456  		margins.Timestamp = now
   457  		margins.MarketID = e.mktID
   458  		margins.Party = evt.Party()
   459  		margins.Asset = evt.Asset()
   460  
   461  		if e.log.GetLevel() == logging.DebugLevel {
   462  			e.log.Debug("margins calculated on settlement",
   463  				logging.String("party-id", evt.Party()),
   464  				logging.String("market-id", evt.MarketID()),
   465  				logging.Reflect("margins", *margins),
   466  			)
   467  		}
   468  
   469  		curMargin := evt.MarginBalance()
   470  		// case 1 -> nothing to do margins are sufficient
   471  		if curMargin.GTE(margins.SearchLevel) && curMargin.LT(margins.CollateralReleaseLevel) {
   472  			// propagate margins then continue
   473  			e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins))
   474  			continue
   475  		}
   476  
   477  		var trnsfr *types.Transfer
   478  		minAmount := num.UintZero()
   479  		// if we don't even have margin levels, the minimum amount must be non-zero.
   480  		if curMargin.LT(margins.MaintenanceMargin) {
   481  			minAmount.Sub(margins.MaintenanceMargin, curMargin)
   482  		}
   483  		// minAmount := num.UintZero().Sub(margins.MaintenanceMargin, curMargin)
   484  		// case 2 -> not enough margin
   485  		if curMargin.LT(margins.SearchLevel) {
   486  			// then the rest is common if we are before or after MaintenanceLevel,
   487  			// we try to reach the InitialMargin level
   488  			trnsfr = &types.Transfer{
   489  				Owner: evt.Party(),
   490  				Type:  types.TransferTypeMarginLow,
   491  				Amount: &types.FinancialAmount{
   492  					Asset:  evt.Asset(),
   493  					Amount: num.UintZero().Sub(margins.InitialMargin, curMargin),
   494  				},
   495  				MinAmount: minAmount,
   496  			}
   497  		} else { // case 3 -> release some collateral
   498  			// collateral not relased in auction
   499  			if e.as.InAuction() && !e.as.CanLeave() {
   500  				// propagate margins then continue
   501  				e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins))
   502  				continue
   503  			}
   504  			trnsfr = &types.Transfer{
   505  				Owner: evt.Party(),
   506  				Type:  types.TransferTypeMarginHigh,
   507  				Amount: &types.FinancialAmount{
   508  					Asset:  evt.Asset(),
   509  					Amount: num.UintZero().Sub(curMargin, margins.InitialMargin),
   510  				},
   511  				MinAmount: num.UintZero().Sub(curMargin, margins.CollateralReleaseLevel),
   512  			}
   513  		}
   514  
   515  		// propage margins to the buffers
   516  		e.updateMarginLevels(events.NewMarginLevelsEvent(ctx, *margins))
   517  
   518  		risk := &marginChange{
   519  			Margin:   evt,
   520  			transfer: trnsfr,
   521  			margins:  margins,
   522  		}
   523  		ret = append(ret, risk)
   524  	}
   525  	return ret
   526  }
   527  
   528  // ExpectMargins is used in the case some parties are in a distressed positions
   529  // in this situation we will only check if the party margin is > to the maintenance margin.
   530  func (e *Engine) ExpectMargins(evts []events.Margin, markPrice *num.Uint, increment num.Decimal, auctionPrice *num.Uint) (okMargins []events.Margin, distressedPositions []events.Margin) {
   531  	okMargins = make([]events.Margin, 0, len(evts)/2)
   532  	distressedPositions = make([]events.Margin, 0, len(evts)/2)
   533  	auction := e.as.InAuction() && !e.as.CanLeave()
   534  	for _, evt := range evts {
   535  		margins := e.calculateMargins(evt, markPrice, *e.factors, false, auction, increment, auctionPrice)
   536  		// no margins updates, nothing to do then
   537  		if margins == nil {
   538  			okMargins = append(okMargins, evt)
   539  			continue
   540  		}
   541  		if e.log.GetLevel() == logging.DebugLevel {
   542  			e.log.Debug("margins calculated",
   543  				logging.String("party-id", evt.Party()),
   544  				logging.String("market-id", evt.MarketID()),
   545  				logging.Reflect("margins", *margins),
   546  			)
   547  		}
   548  
   549  		curMargin := evt.MarginBalance()
   550  		if curMargin.GT(margins.MaintenanceMargin) {
   551  			okMargins = append(okMargins, evt)
   552  		} else {
   553  			distressedPositions = append(distressedPositions, evt)
   554  		}
   555  	}
   556  
   557  	return okMargins, distressedPositions
   558  }
   559  
   560  func (m marginChange) Amount() *num.Uint {
   561  	if m.transfer == nil {
   562  		return nil
   563  	}
   564  	return m.transfer.Amount.Amount.Clone()
   565  }
   566  
   567  // Transfer - it's actually part of the embedded interface already, but we have to mask it, because this type contains another transfer.
   568  func (m marginChange) Transfer() *types.Transfer {
   569  	return m.transfer
   570  }
   571  
   572  func (m marginChange) MarginLevels() *types.MarginLevels {
   573  	return m.margins
   574  }