code.vegaprotocol.io/vega@v0.79.0/core/settlement/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 settlement
    17  
    18  import (
    19  	"context"
    20  	"sort"
    21  	"sync"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/events"
    25  	"code.vegaprotocol.io/vega/core/metrics"
    26  	"code.vegaprotocol.io/vega/core/products"
    27  	"code.vegaprotocol.io/vega/core/types"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/logging"
    30  )
    31  
    32  // MarketPosition ...
    33  //
    34  //go:generate go run github.com/golang/mock/mockgen -destination mocks/market_position_mock.go -package mocks code.vegaprotocol.io/vega/core/settlement MarketPosition
    35  type MarketPosition interface {
    36  	Party() string
    37  	Size() int64
    38  	Buy() int64
    39  	Sell() int64
    40  	Price() *num.Uint
    41  	BuySumProduct() *num.Uint
    42  	SellSumProduct() *num.Uint
    43  	VWBuy() *num.Uint
    44  	VWSell() *num.Uint
    45  	ClearPotentials()
    46  	AverageEntryPrice() *num.Uint
    47  }
    48  
    49  // Product ...
    50  //
    51  //go:generate go run github.com/golang/mock/mockgen -destination mocks/settlement_product_mock.go -package mocks code.vegaprotocol.io/vega/core/settlement Product
    52  type Product interface {
    53  	Settle(*num.Uint, *num.Uint, num.Decimal) (*types.FinancialAmount, bool, num.Decimal, error)
    54  	GetAsset() string
    55  }
    56  
    57  // TimeService.
    58  //
    59  //go:generate go run github.com/golang/mock/mockgen -destination mocks/time_service_mock.go -package mocks code.vegaprotocol.io/vega/core/settlement TimeService
    60  type TimeService interface {
    61  	GetTimeNow() time.Time
    62  }
    63  
    64  // Broker - the event bus broker, send events here.
    65  type Broker interface {
    66  	Send(event events.Event)
    67  	SendBatch(events []events.Event)
    68  }
    69  
    70  // Engine - the main type (of course).
    71  type Engine struct {
    72  	Config
    73  	log *logging.Logger
    74  
    75  	market          string
    76  	product         Product
    77  	settledPosition map[string]int64 // party -> last mark-to-market position
    78  	mu              *sync.Mutex
    79  	trades          map[string][]*settlementTrade
    80  	timeService     TimeService
    81  	broker          Broker
    82  	positionFactor  num.Decimal
    83  	lastMarkPrice   *num.Uint // price at last mark to market
    84  }
    85  
    86  // New instantiates a new instance of the settlement engine.
    87  func New(log *logging.Logger, conf Config, product Product, market string, timeService TimeService, broker Broker, positionFactor num.Decimal) *Engine {
    88  	// setup logger
    89  	log = log.Named(namedLogger)
    90  	log.SetLevel(conf.Level.Get())
    91  
    92  	return &Engine{
    93  		Config:          conf,
    94  		log:             log,
    95  		market:          market,
    96  		product:         product,
    97  		settledPosition: map[string]int64{},
    98  		mu:              &sync.Mutex{},
    99  		trades:          map[string][]*settlementTrade{},
   100  		timeService:     timeService,
   101  		broker:          broker,
   102  		positionFactor:  positionFactor,
   103  	}
   104  }
   105  
   106  func (e *Engine) UpdateProduct(product products.Product) {
   107  	e.product = product
   108  }
   109  
   110  // ReloadConf update the internal configuration of the settlement engined.
   111  func (e *Engine) ReloadConf(cfg Config) {
   112  	e.log.Info("reloading configuration")
   113  	if e.log.GetLevel() != cfg.Level.Get() {
   114  		e.log.Info("updating log level",
   115  			logging.String("old", e.log.GetLevel().String()),
   116  			logging.String("new", cfg.Level.String()),
   117  		)
   118  		e.log.SetLevel(cfg.Level.Get())
   119  	}
   120  
   121  	e.Config = cfg
   122  }
   123  
   124  // Update merely adds positions to the settlement engine, and won't be useful for a MTM settlement
   125  // this function is mainly used for testing, and should be used with extreme caution as a result
   126  // perhaps the tests should be refactored to use the Settle call to create positions first.
   127  func (e *Engine) Update(positions []events.MarketPosition) {
   128  	e.mu.Lock()
   129  	for _, p := range positions {
   130  		party := p.Party()
   131  		e.settledPosition[party] = p.Size()
   132  		e.lastMarkPrice = p.Price()
   133  	}
   134  	e.mu.Unlock()
   135  }
   136  
   137  // Settle run settlement over all the positions.
   138  func (e *Engine) Settle(t time.Time, settlementData *num.Uint) ([]*types.Transfer, *num.Uint, error) {
   139  	e.log.Debugf("Settling market, closed at %s", t.Format(time.RFC3339))
   140  	positions, round, err := e.settleAll(settlementData)
   141  	if err != nil {
   142  		e.log.Error(
   143  			"Something went wrong trying to settle positions",
   144  			logging.Error(err),
   145  		)
   146  		return nil, nil, err
   147  	}
   148  	return positions, round, nil
   149  }
   150  
   151  // AddTrade - this call is required to get the correct MTM settlement values
   152  // each change in position has to be calculated using the exact price of the trade.
   153  func (e *Engine) AddTrade(trade *types.Trade) {
   154  	e.mu.Lock()
   155  	defer e.mu.Unlock()
   156  	var buyerSize, sellerSize int64
   157  	// checking the len of cd shouldn't be required here, but it is needed in the second if
   158  	// in case the buyer and seller are one and the same...
   159  	if cd, ok := e.trades[trade.Buyer]; !ok || len(cd) == 0 {
   160  		e.trades[trade.Buyer] = []*settlementTrade{}
   161  		// check if the buyer already has a known position
   162  		if pos, ok := e.settledPosition[trade.Buyer]; ok {
   163  			buyerSize = pos
   164  		}
   165  	} else {
   166  		buyerSize = cd[len(cd)-1].newSize
   167  	}
   168  	if cd, ok := e.trades[trade.Seller]; !ok || len(cd) == 0 {
   169  		e.trades[trade.Seller] = []*settlementTrade{}
   170  		// check if seller has a known position
   171  		if pos, ok := e.settledPosition[trade.Seller]; ok {
   172  			sellerSize = pos
   173  		}
   174  	} else {
   175  		sellerSize = cd[len(cd)-1].newSize
   176  	}
   177  	size := int64(trade.Size)
   178  	// the parties both need to get a MTM settlement on the traded volume
   179  	// and this MTM part has to be based on the _actual_ trade value
   180  	price := trade.Price.Clone()
   181  	e.trades[trade.Buyer] = append(e.trades[trade.Buyer], &settlementTrade{
   182  		price:       price,
   183  		marketPrice: trade.MarketPrice,
   184  		size:        size,
   185  		newSize:     buyerSize + size,
   186  	})
   187  	e.trades[trade.Seller] = append(e.trades[trade.Seller], &settlementTrade{
   188  		price:       price.Clone(),
   189  		marketPrice: trade.MarketPrice,
   190  		size:        -size,
   191  		newSize:     sellerSize - size,
   192  	})
   193  }
   194  
   195  func (e *Engine) HasTraded() bool {
   196  	return len(e.trades) > 0
   197  }
   198  
   199  func (e *Engine) getFundingTransfer(mtmShare *num.Uint, neg bool, mpos events.MarketPosition, owner string) (*mtmTransfer, bool) {
   200  	tf := e.getMtmTransfer(mtmShare, neg, mpos, owner)
   201  	if tf.transfer == nil {
   202  		tf.transfer = &types.Transfer{
   203  			Type:  types.TransferTypePerpFundingWin,
   204  			Owner: owner,
   205  			Amount: &types.FinancialAmount{
   206  				Amount: mtmShare,
   207  				Asset:  e.product.GetAsset(),
   208  			},
   209  		}
   210  		return tf, false
   211  	}
   212  	if tf.transfer.Type == types.TransferTypeMTMLoss {
   213  		tf.transfer.Type = types.TransferTypePerpFundingLoss
   214  	} else {
   215  		tf.transfer.Type = types.TransferTypePerpFundingWin
   216  	}
   217  	return tf, true
   218  }
   219  
   220  func (e *Engine) getMtmTransfer(mtmShare *num.Uint, neg bool, mpos events.MarketPosition, owner string) *mtmTransfer {
   221  	if mtmShare.IsZero() {
   222  		return &mtmTransfer{
   223  			MarketPosition: mpos,
   224  			transfer:       nil,
   225  		}
   226  	}
   227  	typ := types.TransferTypeMTMWin
   228  	if neg {
   229  		typ = types.TransferTypeMTMLoss
   230  	}
   231  	return &mtmTransfer{
   232  		MarketPosition: mpos,
   233  		transfer: &types.Transfer{
   234  			Type:  typ,
   235  			Owner: owner,
   236  			Amount: &types.FinancialAmount{
   237  				Amount: mtmShare,
   238  				Asset:  e.product.GetAsset(),
   239  			},
   240  		},
   241  	}
   242  }
   243  
   244  func (e *Engine) winSocialisationUpdate(transfer *mtmTransfer, amt *num.Uint) {
   245  	if amt.IsZero() {
   246  		return
   247  	}
   248  	if transfer.transfer == nil {
   249  		transfer.transfer = &types.Transfer{
   250  			Type:  types.TransferTypeMTMWin,
   251  			Owner: transfer.Party(),
   252  			Amount: &types.FinancialAmount{
   253  				Amount: num.UintZero(),
   254  				Asset:  e.product.GetAsset(),
   255  			},
   256  		}
   257  	}
   258  	transfer.transfer.Amount.Amount.AddSum(amt)
   259  }
   260  
   261  func (e *Engine) SettleMTM(ctx context.Context, markPrice *num.Uint, positions []events.MarketPosition) []events.Transfer {
   262  	timer := metrics.NewTimeCounter("-", "settlement", "SettleOrder")
   263  	defer func() { e.lastMarkPrice = markPrice.Clone() }()
   264  	e.mu.Lock()
   265  	tCap := e.transferCap(positions)
   266  	transfers := make([]events.Transfer, 0, tCap)
   267  	// roughly half of the transfers should be wins, half losses
   268  	wins := make([]events.Transfer, 0, tCap/2)
   269  	trades := e.trades
   270  	e.trades = map[string][]*settlementTrade{} // remove here, once we've processed it all here, we're done
   271  	evts := make([]events.Event, 0, len(positions))
   272  	var (
   273  		largestShare  *mtmTransfer       // pointer to whomever gets the last remaining amount from the loss
   274  		zeroShares    = []*mtmTransfer{} // all zero shares for equal distribution if possible
   275  		zeroAmts      = false
   276  		mtmDec        = num.NewDecimalFromFloat(0)
   277  		lossTotal     = num.UintZero()
   278  		winTotal      = num.UintZero()
   279  		lossTotalDec  = num.NewDecimalFromFloat(0)
   280  		winTotalDec   = num.NewDecimalFromFloat(0)
   281  		appendLargest = false
   282  	)
   283  
   284  	// network is treated as a regular party
   285  	for _, evt := range positions {
   286  		party := evt.Party()
   287  		current, lastSettledPrice := e.getOrCreateCurrentPosition(party, evt.Size())
   288  		traded, hasTraded := trades[party]
   289  		tradeset := make([]events.TradeSettlement, 0, len(traded))
   290  		// empty position
   291  		skip := current == 0 && lastSettledPrice.IsZero() && evt.Buy() == 0 && evt.Sell() == 0
   292  		for _, t := range traded {
   293  			tradeset = append(tradeset, t)
   294  		}
   295  		// create (and add position to buffer)
   296  		evts = append(evts, events.NewSettlePositionEvent(ctx, party, e.market, evt.Price(), tradeset, e.timeService.GetTimeNow().UnixNano(), e.positionFactor))
   297  		// no changes in position, and the MTM price hasn't changed, we don't need to do anything
   298  		// or an empty position that isn't the result of the party closing itself out
   299  		if !hasTraded && (lastSettledPrice.EQ(markPrice) || skip) {
   300  			// no changes in position and markPrice hasn't changed -> nothing needs to be marked
   301  			continue
   302  		}
   303  		// calculate MTM value, we need the signed mark-price, the OLD open position/volume
   304  		// the new position is either the same, or accounted for by the traded var (added trades)
   305  		// and the old mark price at which the party held the position
   306  		// the trades slice contains all trade positions (position changes for the party)
   307  		// at their exact trade price, so we can MTM that volume correctly, too
   308  		mtmShare, mtmDShare, neg := calcMTM(markPrice, lastSettledPrice, current, traded, e.positionFactor)
   309  		// we've marked this party to market, their position can now reflect this
   310  		e.settledPosition[party] = evt.Size()
   311  		// we don't want to accidentally MTM a party who closed out completely when they open
   312  		// a new position at a later point, so remove if size == 0
   313  		if evt.Size() == 0 && evt.Buy() == 0 && evt.Sell() == 0 {
   314  			// broke this up into its own func for symmetry
   315  			e.rmPosition(party)
   316  		}
   317  
   318  		// there's still a subset of potential-only positions, their MTM will be zero
   319  		// but they don't hold an open position, and are excluded from win-socialisation.
   320  		skip = !hasTraded && evt.Size() == 0
   321  		posEvent := newPos(evt, markPrice)
   322  		mtmTransfer := e.getMtmTransfer(mtmShare.Clone(), neg, posEvent, party)
   323  
   324  		if !neg {
   325  			wins = append(wins, mtmTransfer)
   326  			winTotal.AddSum(mtmShare)
   327  			winTotalDec = winTotalDec.Add(mtmDShare)
   328  			if !skip && mtmShare.IsZero() {
   329  				zeroShares = append(zeroShares, mtmTransfer)
   330  				zeroAmts = true
   331  			}
   332  			if mtmDShare.GreaterThan(mtmDec) {
   333  				mtmDec = mtmDShare
   334  				largestShare = mtmTransfer
   335  			}
   336  		} else if mtmShare.IsZero() {
   337  			// zero value loss
   338  			wins = append(wins, mtmTransfer)
   339  			lossTotalDec = lossTotalDec.Add(mtmDShare)
   340  		} else {
   341  			transfers = append(transfers, mtmTransfer)
   342  			lossTotal.AddSum(mtmShare)
   343  			lossTotalDec = lossTotalDec.Add(mtmDShare)
   344  		}
   345  	}
   346  	// no need for this lock anymore
   347  	e.mu.Unlock()
   348  	delta := num.UintZero()
   349  	if lossTotal.GT(winTotal) {
   350  		delta.Sub(lossTotal, winTotal)
   351  	}
   352  	// make sure largests share is never nil
   353  	if largestShare == nil {
   354  		largestShare = &mtmTransfer{
   355  			MarketPosition: &npos{
   356  				price: markPrice.Clone(),
   357  			},
   358  		}
   359  		appendLargest = true
   360  	}
   361  	if !delta.IsZero() {
   362  		if zeroAmts {
   363  			if appendLargest {
   364  				zeroShares = append(zeroShares, largestShare)
   365  			}
   366  			zRound := num.DecimalFromInt64(int64(len(zeroShares)))
   367  			zeroShares = append(zeroShares, largestShare)
   368  			// there are more transfers from losses than we pay out to wins, but some winning parties have zero transfers
   369  			// this delta should == combined win decimals, let's sanity check this!
   370  			if winTotalDec.LessThan(lossTotalDec) && winTotalDec.LessThan(lossTotalDec.Sub(zRound)) {
   371  				e.log.Panic("There's less MTM wins than losses, even accounting for decimals",
   372  					logging.Decimal("total loss", lossTotalDec),
   373  					logging.Decimal("total wins", winTotalDec),
   374  				)
   375  			}
   376  			// parties with a zero win transfer should get AT MOST a transfer of value 1
   377  			// any remainder after that should go to the largest win share, unless we only have parties
   378  			// with a win share of 0. that shouldn't be possible however, and so we can ignore that case
   379  			// should this happen at any point, the collateral engine will panic on settlement balance > 0
   380  			// which is the correct behaviour
   381  
   382  			// start distributing the delta
   383  			one := num.NewUint(1)
   384  			for _, transfer := range zeroShares {
   385  				e.winSocialisationUpdate(transfer, one)
   386  				delta.Sub(delta, one)
   387  				if delta.IsZero() {
   388  					break // all done
   389  				}
   390  			}
   391  		}
   392  		// delta is whatever amount the largest share win party gets, this shouldn't be too much
   393  		// delta can be zero at this stage, which is fine
   394  		e.winSocialisationUpdate(largestShare, delta)
   395  	}
   396  	// append wins after loss transfers
   397  	transfers = append(transfers, wins...)
   398  	if len(transfers) > 0 && appendLargest && largestShare.transfer != nil {
   399  		transfers = append(transfers, largestShare)
   400  	}
   401  	if len(evts) > 0 {
   402  		e.broker.SendBatch(evts)
   403  	}
   404  	timer.EngineTimeCounterAdd()
   405  	return transfers
   406  }
   407  
   408  // RemoveDistressed - remove whatever settlement data we have for distressed parties
   409  // they are being closed out, and shouldn't be part of any MTM settlement or closing settlement.
   410  func (e *Engine) RemoveDistressed(ctx context.Context, evts []events.Margin) {
   411  	devts := make([]events.Event, 0, len(evts))
   412  	e.mu.Lock()
   413  	netSize := e.settledPosition[types.NetworkParty]
   414  	netTradeSize := netSize
   415  	netTrades := e.trades[types.NetworkParty]
   416  	if len(netTrades) > 0 {
   417  		netTradeSize = netTrades[len(netTrades)-1].newSize
   418  	}
   419  	for _, v := range evts {
   420  		key := v.Party()
   421  		margin := num.Sum(v.MarginBalance(), v.GeneralBalance())
   422  		devts = append(devts, events.NewSettleDistressed(ctx, key, e.market, v.Price(), margin, e.timeService.GetTimeNow().UnixNano()))
   423  		settled := e.settledPosition[key]
   424  		// first, set the base size for all trades to include the settled position
   425  		for i, t := range netTrades {
   426  			t.newSize += settled
   427  			netTrades[i] = t
   428  		}
   429  		// the last trade or settled position should include this value
   430  		netTradeSize += settled
   431  		// transfer trades from the distressed party over to the network
   432  		// update the new sizes accordingly
   433  		if trades := e.trades[key]; len(trades) > 0 {
   434  			for _, t := range trades {
   435  				t.newSize = netTradeSize + t.size
   436  				netTradeSize += t.size
   437  				netTrades = append(netTrades, t)
   438  			}
   439  		}
   440  		// the network settled size should be updated
   441  		netSize += settled
   442  		delete(e.settledPosition, key)
   443  		delete(e.trades, key)
   444  	}
   445  	e.settledPosition[types.NetworkParty] = netSize
   446  	if len(netTrades) > 0 {
   447  		e.trades[types.NetworkParty] = netTrades
   448  	}
   449  	e.mu.Unlock()
   450  	e.broker.SendBatch(devts)
   451  }
   452  
   453  // simplified settle call.
   454  func (e *Engine) settleAll(settlementData *num.Uint) ([]*types.Transfer, *num.Uint, error) {
   455  	e.mu.Lock()
   456  
   457  	// there should be as many positions as there are parties (obviously)
   458  	aggregated := make([]*types.Transfer, 0, len(e.settledPosition))
   459  	// parties who are in profit should be appended (collect first).
   460  	// The split won't always be 50-50, but it's a reasonable approximation
   461  	owed := make([]*types.Transfer, 0, len(e.settledPosition)/2)
   462  	// ensure we iterate over the positions in the same way by getting all the parties (keys)
   463  	// and sort them
   464  	keys := make([]string, 0, len(e.settledPosition))
   465  	for p := range e.settledPosition {
   466  		keys = append(keys, p)
   467  	}
   468  	sort.Strings(keys)
   469  	var delta num.Decimal
   470  	for _, party := range keys {
   471  		pos := e.settledPosition[party]
   472  		// this is possible now, with the Mark to Market stuff, it's possible we've settled any and all positions for a given party
   473  		if pos == 0 {
   474  			continue
   475  		}
   476  		e.log.Debug("Settling position for party", logging.String("party-id", party))
   477  		// @TODO - there was something here... the final amount had to be oracle - market or something
   478  		amt, neg, rem, err := e.product.Settle(e.lastMarkPrice, settlementData.Clone(), num.DecimalFromInt64(pos).Div(e.positionFactor))
   479  		// for now, product.Settle returns the total value, we need to only settle the delta between a parties current position
   480  		// and the final price coming from the oracle, so oracle_price - mark_price * volume (check with Tamlyn whether this should be absolute or not)
   481  		if err != nil {
   482  			e.log.Error(
   483  				"Failed to settle position for party",
   484  				logging.String("party-id", party),
   485  				logging.Error(err),
   486  			)
   487  			e.mu.Unlock()
   488  			return nil, nil, err
   489  		}
   490  		settlePos := &types.Transfer{
   491  			Owner:  party,
   492  			Amount: amt,
   493  		}
   494  		e.log.Debug(
   495  			"Settled position for party",
   496  			logging.String("party-id", party),
   497  			logging.String("amount", amt.Amount.String()),
   498  		)
   499  
   500  		if neg { // this is a loss transfer
   501  			settlePos.Type = types.TransferTypeLoss
   502  			aggregated = append(aggregated, settlePos)
   503  			// truncated loss amount will not be transferred to the settlement balance
   504  			// so remove it from the total delta (aka rounding)
   505  			delta = delta.Sub(rem)
   506  		} else { // this is a win transfer
   507  			settlePos.Type = types.TransferTypeWin
   508  			owed = append(owed, settlePos)
   509  			// Truncated win transfer won't be withdrawn from the settlement balance
   510  			// so add it to the total delta (aka rounding)
   511  			delta = delta.Add(rem)
   512  		}
   513  	}
   514  	// we only care about the int part
   515  	round := num.UintZero()
   516  	// if delta > 0, the settlement account will have a non-zero balance at the end
   517  	if !delta.IsNegative() {
   518  		round, _ = num.UintFromDecimal(delta)
   519  	}
   520  	// append the parties in profit to the end
   521  	aggregated = append(aggregated, owed...)
   522  	e.mu.Unlock()
   523  	return aggregated, round, nil
   524  }
   525  
   526  func (e *Engine) getOrCreateCurrentPosition(party string, size int64) (int64, *num.Uint) {
   527  	p, ok := e.settledPosition[party]
   528  	if !ok {
   529  		e.settledPosition[party] = size
   530  		return 0, num.UintZero()
   531  	}
   532  	return p, e.lastMarkPrice
   533  }
   534  
   535  func (e *Engine) HasPosition(party string) bool {
   536  	_, okPos := e.settledPosition[party]
   537  	_, okTrades := e.trades[party]
   538  
   539  	return okPos && okTrades
   540  }
   541  
   542  func (e *Engine) rmPosition(party string) {
   543  	delete(e.settledPosition, party)
   544  }
   545  
   546  // just get the max len as cap.
   547  func (e *Engine) transferCap(evts []events.MarketPosition) int {
   548  	curLen, evtLen := len(e.settledPosition), len(evts)
   549  	if curLen >= evtLen {
   550  		return curLen
   551  	}
   552  	return evtLen
   553  }
   554  
   555  // party.PREV_OPEN_VOLUME * (product.value(current_price) - product.value(prev_mark_price)) + SUM(from i=1 to new_trades.length)( new_trade(i).volume(party) * (product.value(current_price) - new_trade(i).price ) )
   556  // the sum bit is a worry, we do not have all the trades available at this point...
   557  
   558  // calcMTM only handles futures ATM. The formula is simple:
   559  // amount =  prev_vol * (current_price - prev_mark_price) + SUM(new_trade := range trades)( new_trade(i).volume(party)*(current_price - new_trade(i).price )
   560  // given that the new trades price will equal new mark price,  the sum(trades) bit will probably == 0 for nicenet
   561  // the size here is the _new_ position size, the price is the OLD price!!
   562  func calcMTM(markPrice, price *num.Uint, size int64, trades []*settlementTrade, positionFactor num.Decimal) (*num.Uint, num.Decimal, bool) {
   563  	delta, sign := num.UintZero().Delta(markPrice, price)
   564  	// this shouldn't be possible I don't think, but just in case
   565  	if size < 0 {
   566  		size = -size
   567  		// swap sign
   568  		sign = !sign
   569  	}
   570  	mtmShare := delta.Mul(delta, num.NewUint(uint64(size)))
   571  	for _, c := range trades {
   572  		delta, neg := num.UintZero().Delta(markPrice, c.price)
   573  		size := num.NewUint(uint64(c.size))
   574  		if c.size < 0 {
   575  			size = size.SetUint64(uint64(-c.size))
   576  			neg = !neg
   577  		}
   578  		add := delta.Mul(delta, size)
   579  		if mtmShare.IsZero() {
   580  			mtmShare.Set(add)
   581  			sign = neg
   582  		} else if neg == sign {
   583  			// both mtmShare and add are the same sign
   584  			mtmShare = mtmShare.Add(mtmShare, add)
   585  		} else if mtmShare.GTE(add) {
   586  			// regardless of sign, we just have to subtract
   587  			mtmShare = mtmShare.Sub(mtmShare, add)
   588  		} else {
   589  			// add > mtmShare, we don't care about signs here
   590  			// just subtract mtmShare and switch signs
   591  			mtmShare = add.Sub(add, mtmShare)
   592  			sign = neg
   593  		}
   594  	}
   595  
   596  	// as mtmShare was calculated with the volumes as integers (not decimals in pdp space) we need to divide by position factor
   597  	decShare := mtmShare.ToDecimal().Div(positionFactor)
   598  	res, _ := num.UintFromDecimal(decShare)
   599  	return res, decShare, sign
   600  }
   601  
   602  // SettleFundingPeriod takes positions and a funding-payement and returns a slice of transfers.
   603  // returns the slice of transfers to perform, and the max remainder on the settlement account due to rounding issues.
   604  func (e *Engine) SettleFundingPeriod(ctx context.Context, positions []events.MarketPosition, fundingPayment *num.Int) ([]events.Transfer, *num.Uint) {
   605  	if fundingPayment.IsZero() || len(positions) == 0 {
   606  		// nothing to do here
   607  		return nil, nil
   608  	}
   609  
   610  	// colletral engine expects all the losses before the wins
   611  	transfers := make([]events.Transfer, 0, len(positions))
   612  	wins := make([]events.Transfer, 0, len(positions))
   613  	zeroTransfers := make([]events.Transfer, 0, len(positions)/2)
   614  	totalW, totalL := num.UintZero(), num.UintZero()
   615  	var delta num.Decimal
   616  	for _, p := range positions {
   617  		// per-party cash flow is -openVolume * fundingPayment
   618  		flow, rem, neg := calcFundingFlow(fundingPayment, p, e.positionFactor)
   619  		if neg {
   620  			// amount of loss not collected, this never gets added to the settlement account
   621  			delta = delta.Sub(rem)
   622  		} else {
   623  			// amount of wins never collected, remains in the settlement account
   624  			delta = delta.Add(rem)
   625  		}
   626  
   627  		if tf, valid := e.getFundingTransfer(flow, neg, p, p.Party()); valid {
   628  			if tf.transfer.Type == types.TransferTypePerpFundingWin {
   629  				wins = append(wins, tf)
   630  				totalW.AddSum(flow)
   631  			} else {
   632  				transfers = append(transfers, tf)
   633  				totalL.AddSum(flow)
   634  			}
   635  		} else {
   636  			// we could use deltas to order these transfers to prioritise the right people
   637  			zeroTransfers = append(zeroTransfers, tf)
   638  		}
   639  		if e.log.IsDebug() {
   640  			e.log.Debug("cash flow", logging.String("mid", e.market), logging.String("pid", p.Party()), logging.String("flow", flow.String()))
   641  		}
   642  	}
   643  	// account for cases where the winning side never even accounts for an amount of 1
   644  	if len(wins) == 0 && len(zeroTransfers) > 0 {
   645  		wins = zeroTransfers
   646  	}
   647  	// profit and loss balances out perfectly, or profit > loss
   648  	if totalL.LTE(totalW) {
   649  		// this rounding shouldn't be needed, losses will be distributed in their entirety
   650  		round, _ := num.UintFromDecimal(delta.Abs())
   651  		return append(transfers, wins...), round
   652  	}
   653  	round := totalL.Sub(totalL, totalW) // loss - win is what will be left over
   654  	// we have a remainder, make sure it's an expected amount due to rounding
   655  	if dU, _ := num.UintFromDecimal(delta.Ceil().Abs()); dU.LT(round) {
   656  		e.log.Panic("Excess loss transfer amount found, cannot be explained by rounding",
   657  			logging.String("loss-win delta", round.String()),
   658  			logging.Decimal("rounding delta", delta.Abs()),
   659  		)
   660  	}
   661  	return append(transfers, wins...), round
   662  }
   663  
   664  func calcFundingFlow(fp *num.Int, p events.MarketPosition, posFac num.Decimal) (*num.Uint, num.Decimal, bool) {
   665  	// -openVolume * fundingPayment
   666  	// divide by position factor to account for position decimal places
   667  	flowD := num.DecimalFromInt64(-p.Size()).Mul(num.DecimalFromInt(fp)).Div(posFac)
   668  	neg := flowD.IsNegative()
   669  	flow, frac := num.UintFromDecimalWithFraction(flowD.Abs())
   670  	return flow, frac, neg
   671  }