code.vegaprotocol.io/vega@v0.79.0/core/plugins/positions.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 plugins
    17  
    18  import (
    19  	"context"
    20  	"sync"
    21  
    22  	"code.vegaprotocol.io/vega/core/events"
    23  	"code.vegaprotocol.io/vega/core/subscribers"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/protos/vega"
    27  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    28  
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  var ErrMarketNotFound = errors.New("could not find market")
    33  
    34  // FP FundingPaymentsEvent.
    35  type FP interface {
    36  	events.Event
    37  	MarketID() string
    38  	IsParty(id string) bool
    39  	FundingPayments() *eventspb.FundingPayments
    40  }
    41  
    42  // SE SettleEvent - common denominator between SPE & SDE.
    43  type SE interface {
    44  	events.Event
    45  	PartyID() string
    46  	MarketID() string
    47  	Price() *num.Uint
    48  	Timestamp() int64
    49  }
    50  
    51  // SPE SettlePositionEvent.
    52  type SPE interface {
    53  	SE
    54  	PositionFactor() num.Decimal
    55  	Trades() []events.TradeSettlement
    56  	Timestamp() int64
    57  }
    58  
    59  // SDE SettleDistressedEvent.
    60  type SDE interface {
    61  	SE
    62  	Margin() *num.Uint
    63  	Timestamp() int64
    64  }
    65  
    66  // LSE LossSocializationEvent.
    67  type LSE interface {
    68  	events.Event
    69  	PartyID() string
    70  	MarketID() string
    71  	Amount() *num.Int
    72  	Timestamp() int64
    73  	IsFunding() bool
    74  }
    75  
    76  // DOC DistressedOrdersClosedEvent.
    77  type DOC interface {
    78  	events.Event
    79  	MarketID() string
    80  	Parties() []string
    81  }
    82  
    83  // DPE DistressedPositionsEvent.
    84  type DPE interface {
    85  	events.Event
    86  	MarketID() string
    87  	DistressedParties() []string
    88  	SafeParties() []string
    89  }
    90  
    91  // SME SettleMarketEvent.
    92  type SME interface {
    93  	MarketID() string
    94  	SettledPrice() *num.Uint
    95  	PositionFactor() num.Decimal
    96  	TxHash() string
    97  }
    98  
    99  // TE TradeEvent.
   100  type TE interface {
   101  	MarketID() string
   102  	IsParty(id string) bool // we don't use this one, but it's to make sure we identify the event correctly
   103  	Trade() vega.Trade
   104  }
   105  
   106  // Positions plugin taking settlement data to build positions API data.
   107  type Positions struct {
   108  	*subscribers.Base
   109  	mu      *sync.RWMutex
   110  	data    map[string]map[string]Position
   111  	factors map[string]num.Decimal
   112  }
   113  
   114  func NewPositions(ctx context.Context) *Positions {
   115  	return &Positions{
   116  		Base:    subscribers.NewBase(ctx, 10, true),
   117  		mu:      &sync.RWMutex{},
   118  		data:    map[string]map[string]Position{},
   119  		factors: map[string]num.Decimal{},
   120  	}
   121  }
   122  
   123  func (p *Positions) Push(evts ...events.Event) {
   124  	if len(evts) == 0 {
   125  		return
   126  	}
   127  	// lock here, because some of these events are sent in batch (if not all of them)
   128  	p.mu.Lock()
   129  	for _, e := range evts {
   130  		switch te := e.(type) {
   131  		case SPE:
   132  			p.updatePosition(te)
   133  		case SDE:
   134  			p.updateSettleDestressed(te)
   135  		case LSE:
   136  			p.applyLossSocialization(te)
   137  		case DOC:
   138  			p.applyDistressedOrders(te)
   139  		case DPE:
   140  			p.applyDistressedPositions(te)
   141  		case SME:
   142  			p.handleSettleMarket(te)
   143  		case FP:
   144  			p.handleFundingPayments(te)
   145  		case TE:
   146  			p.handleTradeEvent(te)
   147  		}
   148  	}
   149  	p.mu.Unlock()
   150  }
   151  
   152  func (p *Positions) handleRegularTrade(e TE) {
   153  	trade := e.Trade()
   154  	if trade.Type == types.TradeTypeNetworkCloseOutBad {
   155  		return
   156  	}
   157  	marketID := e.MarketID()
   158  	partyPos, ok := p.data[marketID]
   159  	if !ok {
   160  		// @TODO should this be done?
   161  		return
   162  	}
   163  	buyerFee, sellerFee := getFeeAmounts(&trade)
   164  	buyer, ok := partyPos[trade.Buyer]
   165  	if !ok {
   166  		buyer = Position{
   167  			Position:            types.NewPosition(marketID, trade.Buyer),
   168  			AverageEntryPriceFP: num.DecimalZero(),
   169  			RealisedPnlFP:       num.DecimalZero(),
   170  			UnrealisedPnlFP:     num.DecimalZero(),
   171  		}
   172  	}
   173  	buyer.setFees(buyerFee)
   174  	seller, ok := partyPos[trade.Seller]
   175  	if !ok {
   176  		seller = Position{
   177  			Position:            types.NewPosition(marketID, trade.Seller),
   178  			AverageEntryPriceFP: num.DecimalZero(),
   179  			RealisedPnlFP:       num.DecimalZero(),
   180  			UnrealisedPnlFP:     num.DecimalZero(),
   181  		}
   182  	}
   183  	seller.setFees(sellerFee)
   184  	partyPos[trade.Buyer] = buyer
   185  	partyPos[trade.Seller] = seller
   186  	p.data[marketID] = partyPos
   187  }
   188  
   189  // handle trade event closing distressed parties.
   190  func (p *Positions) handleTradeEvent(e TE) {
   191  	trade := e.Trade()
   192  	if trade.Type != types.TradeTypeNetworkCloseOutBad {
   193  		p.handleRegularTrade(e)
   194  		return
   195  	}
   196  	marketID := e.MarketID()
   197  	partyPos, ok := p.data[marketID]
   198  	if !ok {
   199  		return
   200  	}
   201  	posFactor := num.DecimalOne()
   202  	// keep track of position factors
   203  	if pf, ok := p.factors[marketID]; ok {
   204  		posFactor = pf
   205  	}
   206  	mPrice, _ := num.UintFromString(trade.Price, 10)
   207  	markPriceDec := num.DecimalFromUint(mPrice)
   208  	size := int64(trade.Size)
   209  	pos, ok := partyPos[types.NetworkParty]
   210  	if !ok {
   211  		pos = Position{
   212  			Position:            types.NewPosition(marketID, types.NetworkParty),
   213  			AverageEntryPriceFP: num.DecimalZero(),
   214  			RealisedPnlFP:       num.DecimalZero(),
   215  			UnrealisedPnlFP:     num.DecimalZero(),
   216  		}
   217  	}
   218  	dParty := trade.Seller
   219  	networkFee, otherFee := getFeeAmounts(&trade)
   220  	if trade.Seller == types.NetworkParty {
   221  		size *= -1
   222  		dParty = trade.Buyer
   223  		networkFee, otherFee = otherFee, networkFee
   224  	}
   225  	other, ok := partyPos[dParty]
   226  	if !ok {
   227  		other = Position{
   228  			Position:            types.NewPosition(marketID, dParty),
   229  			AverageEntryPriceFP: num.DecimalZero(),
   230  			RealisedPnlFP:       num.DecimalZero(),
   231  			UnrealisedPnlFP:     num.DecimalZero(),
   232  		}
   233  	}
   234  	other.setFees(otherFee)
   235  	other.ResetSince()
   236  	pos.setFees(networkFee)
   237  	opened, closed := calculateOpenClosedVolume(pos.OpenVolume, size)
   238  	realisedPnlDelta := markPriceDec.Sub(pos.AverageEntryPriceFP).Mul(num.DecimalFromInt64(closed)).Div(posFactor)
   239  	pos.RealisedPnl = pos.RealisedPnl.Add(realisedPnlDelta)
   240  	pos.RealisedPnlFP = pos.RealisedPnlFP.Add(realisedPnlDelta)
   241  	// what was realised is no longer unrealised
   242  	pos.UnrealisedPnl = pos.UnrealisedPnl.Sub(realisedPnlDelta)
   243  	pos.UnrealisedPnlFP = pos.UnrealisedPnlFP.Sub(realisedPnlDelta)
   244  	pos.OpenVolume -= closed
   245  
   246  	pos.AverageEntryPriceFP = updateVWAP(pos.AverageEntryPriceFP, pos.OpenVolume, opened, mPrice)
   247  	pos.AverageEntryPrice, _ = num.UintFromDecimal(pos.AverageEntryPriceFP.Round(0))
   248  	pos.OpenVolume += opened
   249  	mtm(&pos, mPrice, posFactor)
   250  	partyPos[types.NetworkParty] = pos
   251  	partyPos[dParty] = other
   252  	p.data[marketID] = partyPos
   253  }
   254  
   255  func (p *Positions) handleFundingPayments(e FP) {
   256  	marketID := e.MarketID()
   257  	partyPos, ok := p.data[marketID]
   258  	if !ok {
   259  		return
   260  	}
   261  	payments := e.FundingPayments().Payments
   262  	for _, pay := range payments {
   263  		pos, ok := partyPos[pay.PartyId]
   264  		if !ok {
   265  			continue
   266  		}
   267  		amt, _ := num.DecimalFromString(pay.Amount)
   268  		iAmt, _ := num.IntFromDecimal(amt)
   269  		pos.RealisedPnl = pos.RealisedPnl.Add(amt)
   270  		pos.RealisedPnlFP = pos.RealisedPnlFP.Add(amt)
   271  		// add funding totals
   272  		pos.FundingPaymentAmount.Add(iAmt)
   273  		pos.FundingPaymentAmountSince.Add(iAmt)
   274  		partyPos[pay.PartyId] = pos
   275  	}
   276  	p.data[marketID] = partyPos
   277  }
   278  
   279  func (p *Positions) applyDistressedPositions(e DPE) {
   280  	marketID := e.MarketID()
   281  	partyPos, ok := p.data[marketID]
   282  	if !ok {
   283  		return
   284  	}
   285  	for _, party := range e.DistressedParties() {
   286  		if pos, ok := partyPos[party]; ok {
   287  			pos.state = vega.PositionStatus_POSITION_STATUS_DISTRESSED
   288  			partyPos[party] = pos
   289  		}
   290  	}
   291  	for _, party := range e.SafeParties() {
   292  		if pos, ok := partyPos[party]; ok {
   293  			pos.state = vega.PositionStatus_POSITION_STATUS_UNSPECIFIED
   294  			partyPos[party] = pos
   295  		}
   296  	}
   297  	p.data[marketID] = partyPos
   298  }
   299  
   300  func (p *Positions) applyDistressedOrders(e DOC) {
   301  	marketID, parties := e.MarketID(), e.Parties()
   302  	partyPos, ok := p.data[marketID]
   303  	if !ok {
   304  		return
   305  	}
   306  	for _, party := range parties {
   307  		if pos, ok := partyPos[party]; ok {
   308  			pos.state = vega.PositionStatus_POSITION_STATUS_ORDERS_CLOSED
   309  			partyPos[party] = pos
   310  		}
   311  	}
   312  	p.data[marketID] = partyPos
   313  }
   314  
   315  func (p *Positions) applyLossSocialization(e LSE) {
   316  	iAmt := e.Amount()
   317  	marketID, partyID, amountLoss := e.MarketID(), e.PartyID(), num.DecimalFromInt(iAmt)
   318  	pos, ok := p.data[marketID][partyID]
   319  	if !ok {
   320  		return
   321  	}
   322  	if amountLoss.IsNegative() {
   323  		pos.loss = pos.loss.Add(amountLoss)
   324  	} else {
   325  		pos.adjustment = pos.adjustment.Add(amountLoss)
   326  	}
   327  	if e.IsFunding() {
   328  		// adjust funding amounts if needed.
   329  		pos.FundingPaymentAmount.Add(iAmt)
   330  		pos.FundingPaymentAmountSince.Add(iAmt)
   331  	}
   332  	pos.RealisedPnlFP = pos.RealisedPnlFP.Add(amountLoss)
   333  	pos.RealisedPnl = pos.RealisedPnl.Add(amountLoss)
   334  
   335  	pos.Position.UpdatedAt = e.Timestamp()
   336  	p.data[marketID][partyID] = pos
   337  }
   338  
   339  func (p *Positions) updatePosition(e SPE) {
   340  	mID, tID := e.MarketID(), e.PartyID()
   341  	if _, ok := p.data[mID]; !ok {
   342  		p.data[mID] = map[string]Position{}
   343  	}
   344  	calc, ok := p.data[mID][tID]
   345  	if !ok {
   346  		calc = seToProto(e)
   347  	}
   348  	updateSettlePosition(&calc, e)
   349  	calc.Position.UpdatedAt = e.Timestamp()
   350  	p.data[mID][tID] = calc
   351  }
   352  
   353  func (p *Positions) updateSettleDestressed(e SDE) {
   354  	mID, tID := e.MarketID(), e.PartyID()
   355  	if _, ok := p.data[mID]; !ok {
   356  		p.data[mID] = map[string]Position{}
   357  	}
   358  	calc, ok := p.data[mID][tID]
   359  	if !ok {
   360  		calc = seToProto(e)
   361  	}
   362  	margin := e.Margin()
   363  	calc.RealisedPnl = calc.RealisedPnl.Add(calc.UnrealisedPnl)
   364  	calc.RealisedPnlFP = calc.RealisedPnlFP.Add(calc.UnrealisedPnlFP)
   365  	calc.OpenVolume = 0
   366  	calc.UnrealisedPnl = num.DecimalZero()
   367  	calc.AverageEntryPrice = num.UintZero()
   368  	// realised P&L includes whatever we had in margin account at this point
   369  	dMargin := num.DecimalFromUint(margin)
   370  	calc.RealisedPnl = calc.RealisedPnl.Sub(dMargin)
   371  	calc.RealisedPnlFP = calc.RealisedPnlFP.Sub(dMargin)
   372  	// @TODO average entry price shouldn't be affected(?)
   373  	// the volume now is zero, though, so we'll end up moving this position to storage
   374  	calc.UnrealisedPnlFP = num.DecimalZero()
   375  	calc.AverageEntryPriceFP = num.DecimalZero()
   376  	calc.Position.UpdatedAt = e.Timestamp()
   377  	calc.state = vega.PositionStatus_POSITION_STATUS_CLOSED_OUT
   378  	p.data[mID][tID] = calc
   379  }
   380  
   381  func (p *Positions) handleSettleMarket(e SME) {
   382  	market := e.MarketID()
   383  	posFactor := e.PositionFactor()
   384  	// keep track of position factors
   385  	if _, ok := p.factors[market]; !ok {
   386  		p.factors[market] = posFactor
   387  	}
   388  	markPriceDec := num.DecimalFromUint(e.SettledPrice())
   389  	mp, ok := p.data[market]
   390  	if !ok {
   391  		panic(ErrMarketNotFound)
   392  	}
   393  	for pid, pos := range mp {
   394  		openVolumeDec := num.DecimalFromInt64(pos.OpenVolume)
   395  
   396  		unrealisedPnl := openVolumeDec.Mul(markPriceDec.Sub(pos.AverageEntryPriceFP)).Div(posFactor).Round(0)
   397  		pos.RealisedPnl = pos.RealisedPnl.Add(unrealisedPnl)
   398  		pos.UnrealisedPnl = num.DecimalZero()
   399  		p.data[market][pid] = pos
   400  	}
   401  }
   402  
   403  // GetPositionsByMarketAndParty get the position of a single party in a given market.
   404  func (p *Positions) GetPositionsByMarketAndParty(market, party string) (*types.Position, error) {
   405  	p.mu.RLock()
   406  	defer p.mu.RUnlock()
   407  	mp, ok := p.data[market]
   408  	if !ok {
   409  		return nil, nil
   410  	}
   411  	pos, ok := mp[party]
   412  	if !ok {
   413  		return nil, nil
   414  	}
   415  	return &pos.Position, nil
   416  }
   417  
   418  func (p *Positions) GetStateByMarketAndParty(market, party string) (vega.PositionStatus, error) {
   419  	p.mu.RLock()
   420  	defer p.mu.RUnlock()
   421  	mp, ok := p.data[market]
   422  	if !ok {
   423  		return vega.PositionStatus_POSITION_STATUS_UNSPECIFIED, nil
   424  	}
   425  	if pos, ok := mp[party]; ok {
   426  		return pos.state, nil
   427  	}
   428  	return vega.PositionStatus_POSITION_STATUS_UNSPECIFIED, nil
   429  }
   430  
   431  // GetPositionsByParty get all positions for a given party.
   432  func (p *Positions) GetPositionsByParty(party string) ([]*types.Position, error) {
   433  	p.mu.RLock()
   434  	defer p.mu.RUnlock()
   435  	// at most, party is active in all markets
   436  	positions := make([]*types.Position, 0, len(p.data))
   437  	for _, parties := range p.data {
   438  		if pos, ok := parties[party]; ok {
   439  			positions = append(positions, &pos.Position)
   440  		}
   441  	}
   442  	if len(positions) == 0 {
   443  		return nil, nil
   444  		// return nil, ErrPartyNotFound
   445  	}
   446  	return positions, nil
   447  }
   448  
   449  func (p *Positions) GetPositionStatesByParty(party string) ([]vega.PositionStatus, error) {
   450  	p.mu.RLock()
   451  	defer p.mu.RUnlock()
   452  	// max 1 state per market
   453  	states := make([]vega.PositionStatus, 0, len(p.data))
   454  	for _, parties := range p.data {
   455  		if pos, ok := parties[party]; ok {
   456  			states = append(states, pos.state)
   457  		}
   458  	}
   459  	return states, nil
   460  }
   461  
   462  // GetAllPositions returns all positions, across markets.
   463  func (p *Positions) GetAllPositions() ([]*types.Position, error) {
   464  	p.mu.RLock()
   465  	defer p.mu.RUnlock()
   466  	var pos []*types.Position
   467  	for k := range p.data {
   468  		// guesstimate what the slice cap ought to be: number of markets * number of parties in 1 market
   469  		pos = make([]*types.Position, 0, len(p.data)*len(p.data[k]))
   470  		break
   471  	}
   472  	for _, parties := range p.data {
   473  		for _, tp := range parties {
   474  			tp := tp
   475  			pos = append(pos, &tp.Position)
   476  		}
   477  	}
   478  	return pos, nil
   479  }
   480  
   481  // GetPositionsByMarket get all party positions in a given market.
   482  func (p *Positions) GetPositionsByMarket(market string) ([]*types.Position, error) {
   483  	p.mu.RLock()
   484  	defer p.mu.RUnlock()
   485  	mp, ok := p.data[market]
   486  	if !ok {
   487  		return nil, ErrMarketNotFound
   488  	}
   489  	s := make([]*types.Position, 0, len(mp))
   490  	for _, tp := range mp {
   491  		tp := tp
   492  		s = append(s, &tp.Position)
   493  	}
   494  	return s, nil
   495  }
   496  
   497  func calculateOpenClosedVolume(currentOpenVolume, tradedVolume int64) (int64, int64) {
   498  	if currentOpenVolume != 0 && ((currentOpenVolume > 0) != (tradedVolume > 0)) {
   499  		var closedVolume int64
   500  		if absUint64(tradedVolume) > absUint64(currentOpenVolume) {
   501  			closedVolume = currentOpenVolume
   502  		} else {
   503  			closedVolume = -tradedVolume
   504  		}
   505  		return tradedVolume + closedVolume, closedVolume
   506  	}
   507  	return tradedVolume, 0
   508  }
   509  
   510  func closeV(p *Position, closedVolume int64, tradedPrice *num.Uint, positionFactor num.Decimal) num.Decimal {
   511  	if closedVolume == 0 {
   512  		return num.DecimalZero()
   513  	}
   514  	realisedPnlDelta := num.DecimalFromUint(tradedPrice).Sub(p.AverageEntryPriceFP).Mul(num.DecimalFromInt64(closedVolume)).Div(positionFactor)
   515  	p.RealisedPnlFP = p.RealisedPnlFP.Add(realisedPnlDelta)
   516  	p.OpenVolume -= closedVolume
   517  	return realisedPnlDelta
   518  }
   519  
   520  func updateVWAP(vwap num.Decimal, volume int64, addVolume int64, addPrice *num.Uint) num.Decimal {
   521  	if volume+addVolume == 0 {
   522  		return num.DecimalZero()
   523  	}
   524  
   525  	volumeDec := num.DecimalFromInt64(volume)
   526  	addVolumeDec := num.DecimalFromInt64(addVolume)
   527  	addPriceDec := num.DecimalFromUint(addPrice)
   528  
   529  	//	return ((vwap * float64(volume)) + (float64(addPrice) * float64(addVolume))) / (float64(volume) + float64(addVolume))
   530  	return vwap.Mul(volumeDec).Add(addPriceDec.Mul(addVolumeDec)).Div(volumeDec.Add(addVolumeDec))
   531  }
   532  
   533  func openV(p *Position, openedVolume int64, tradedPrice *num.Uint) {
   534  	// calculate both average entry price here.
   535  	p.AverageEntryPriceFP = updateVWAP(p.AverageEntryPriceFP, p.OpenVolume, openedVolume, tradedPrice)
   536  	p.OpenVolume += openedVolume
   537  }
   538  
   539  func mtm(p *Position, markPrice *num.Uint, positionFactor num.Decimal) {
   540  	if p.OpenVolume == 0 {
   541  		p.UnrealisedPnlFP = num.DecimalZero()
   542  		p.UnrealisedPnl = num.DecimalZero()
   543  		return
   544  	}
   545  	markPriceDec := num.DecimalFromUint(markPrice)
   546  	openVolumeDec := num.DecimalFromInt64(p.OpenVolume)
   547  
   548  	//	p.UnrealisedPnlFP = float64(p.OpenVolume) * (float64(markPrice) - p.AverageEntryPriceFP)
   549  	p.UnrealisedPnlFP = openVolumeDec.Mul(markPriceDec.Sub(p.AverageEntryPriceFP)).Div(positionFactor)
   550  }
   551  
   552  func updateSettlePosition(p *Position, e SPE) {
   553  	for _, t := range e.Trades() {
   554  		reset := p.OpenVolume == 0
   555  		pr := t.Price()
   556  		openedVolume, closedVolume := calculateOpenClosedVolume(p.OpenVolume, t.Size())
   557  		_ = closeV(p, closedVolume, pr, e.PositionFactor())
   558  		before := p.OpenVolume
   559  		openV(p, openedVolume, pr)
   560  		// was the position zero, or did the position flip sides?
   561  		if reset || (before < 0 && p.OpenVolume > 0) || (before > 0 && p.OpenVolume < 0) {
   562  			p.ResetSince()
   563  		}
   564  		p.AverageEntryPrice, _ = num.UintFromDecimal(p.AverageEntryPriceFP.Round(0))
   565  
   566  		p.RealisedPnl = p.RealisedPnlFP.Round(0)
   567  	}
   568  	mtm(p, e.Price(), e.PositionFactor())
   569  	p.UnrealisedPnl = p.UnrealisedPnlFP.Round(0)
   570  }
   571  
   572  type Position struct {
   573  	types.Position
   574  	AverageEntryPriceFP num.Decimal
   575  	RealisedPnlFP       num.Decimal
   576  	UnrealisedPnlFP     num.Decimal
   577  
   578  	// what the party lost because of loss socialization
   579  	loss num.Decimal
   580  	// what a party was missing which triggered loss socialization
   581  	adjustment num.Decimal
   582  	state      vega.PositionStatus
   583  }
   584  
   585  func seToProto(e SE) Position {
   586  	return Position{
   587  		Position:            types.NewPosition(e.MarketID(), e.PartyID()),
   588  		AverageEntryPriceFP: num.DecimalZero(),
   589  		RealisedPnlFP:       num.DecimalZero(),
   590  		UnrealisedPnlFP:     num.DecimalZero(),
   591  	}
   592  }
   593  
   594  func absUint64(v int64) uint64 {
   595  	if v < 0 {
   596  		v *= -1
   597  	}
   598  	return uint64(v)
   599  }
   600  
   601  func (p *Positions) Types() []events.Type {
   602  	return []events.Type{
   603  		events.SettlePositionEvent,
   604  		events.SettleDistressedEvent,
   605  		events.LossSocializationEvent,
   606  		events.DistressedOrdersClosedEvent,
   607  		events.DistressedPositionsEvent,
   608  		events.SettleMarketEvent,
   609  		events.FundingPaymentsEvent,
   610  		events.TradeEvent,
   611  	}
   612  }
   613  
   614  func (p *Position) setFees(fee *feeAmounts) {
   615  	p.TakerFeesPaid.AddSum(fee.taker)
   616  	p.TakerFeesPaidSince.AddSum(fee.taker)
   617  	p.MakerFeesReceived.AddSum(fee.maker)
   618  	p.MakerFeesReceivedSince.AddSum(fee.maker)
   619  	p.FeesPaid.AddSum(fee.other)
   620  	p.FeesPaidSince.AddSum(fee.other)
   621  }