code.vegaprotocol.io/vega@v0.79.0/core/positions/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 positions
    17  
    18  import (
    19  	"context"
    20  	"encoding/binary"
    21  	"fmt"
    22  	"sort"
    23  	"sync"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/libs/crypto"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/logging"
    30  
    31  	"golang.org/x/exp/maps"
    32  )
    33  
    34  // Broker (no longer need to mock this, use the broker/mocks wrapper).
    35  type Broker interface {
    36  	Send(event events.Event)
    37  	SendBatch(events []events.Event)
    38  }
    39  
    40  // Engine represents the positions engine.
    41  type Engine struct {
    42  	marketID string
    43  	log      *logging.Logger
    44  	Config
    45  
    46  	cfgMu sync.Mutex
    47  	// partyID -> MarketPosition
    48  	positions map[string]*MarketPosition
    49  
    50  	// this is basically tracking all position to
    51  	// not perform a copy when positions a retrieved by other engines
    52  	// the pointer is hidden behind the interface, and do not expose
    53  	// any function to mutate them, so we can consider it safe to return
    54  	// this slice.
    55  	positionsCpy []events.MarketPosition
    56  
    57  	// keep track of the position updated during the current block to avoid sending
    58  	updatedPositions map[string]struct{}
    59  	positionUpdated  func(context.Context, *MarketPosition)
    60  
    61  	broker Broker
    62  
    63  	// keep track of open, but distressed positions, this speeds things up when creating the event data
    64  	// and when generating snapshots
    65  	distressedPos map[string]struct{}
    66  
    67  	// keeps track of the lowest openInterest
    68  	// per party over a whole epoch
    69  	// should be reseted by the market on new epochs
    70  	partiesHighestVolume map[string]*openVolumeRecord
    71  
    72  	// keep track of the traded volume during the epoch
    73  	// will be reset
    74  	partiesTradedSize map[string]uint64
    75  }
    76  
    77  type openVolumeRecord struct {
    78  	Latest  uint64
    79  	Highest uint64
    80  }
    81  
    82  // RecordLatest will save the new openInterest for a party
    83  // but also register it as the lowest if it goes below
    84  // the existing lowest openInterest.
    85  func (o *openVolumeRecord) RecordLatest(size int64) {
    86  	new := size
    87  	if size < 0 {
    88  		new = -size
    89  	}
    90  
    91  	o.Latest = uint64(new)
    92  	if o.Highest < uint64(new) {
    93  		o.Highest = uint64(new)
    94  	}
    95  }
    96  
    97  // Reset will be used at the end of the epoch,
    98  // in order to set the lowest for the epoch to
    99  // be the latest of the previous epoch.
   100  func (o *openVolumeRecord) Reset() uint64 {
   101  	highest := o.Highest
   102  	o.Highest = o.Latest
   103  
   104  	return highest
   105  }
   106  
   107  // New instantiates a new positions engine.
   108  func New(log *logging.Logger, config Config, marketID string, broker Broker) *Engine {
   109  	// setup logger
   110  	log = log.Named(namedLogger)
   111  	log.SetLevel(config.Level.Get())
   112  
   113  	e := &Engine{
   114  		marketID:             marketID,
   115  		Config:               config,
   116  		log:                  log,
   117  		positions:            map[string]*MarketPosition{},
   118  		positionsCpy:         []events.MarketPosition{},
   119  		broker:               broker,
   120  		updatedPositions:     map[string]struct{}{},
   121  		distressedPos:        map[string]struct{}{},
   122  		partiesHighestVolume: map[string]*openVolumeRecord{},
   123  		partiesTradedSize:    map[string]uint64{},
   124  	}
   125  	e.positionUpdated = e.bufferPosition
   126  	if config.StreamPositionVerbose {
   127  		e.positionUpdated = e.sendPosition
   128  	}
   129  
   130  	return e
   131  }
   132  
   133  func (e *Engine) FlushPositionEvents(ctx context.Context) {
   134  	if e.StreamPositionVerbose || len(e.updatedPositions) <= 0 {
   135  		return
   136  	}
   137  
   138  	e.sendBufferedPosition(ctx)
   139  }
   140  
   141  func (e *Engine) bufferPosition(_ context.Context, pos *MarketPosition) {
   142  	e.updatedPositions[pos.partyID] = struct{}{}
   143  }
   144  
   145  func (e *Engine) sendBufferedPosition(ctx context.Context) {
   146  	parties := maps.Keys(e.updatedPositions)
   147  	sort.Strings(parties)
   148  	evts := make([]events.Event, 0, len(parties))
   149  
   150  	for _, v := range parties {
   151  		// ensure the position exists,
   152  		// party might have been distressed or something
   153  		if pos, ok := e.positions[v]; ok {
   154  			evts = append(evts, events.NewPositionStateEvent(ctx, pos, e.marketID))
   155  		}
   156  	}
   157  
   158  	e.broker.SendBatch(evts)
   159  	e.updatedPositions = make(map[string]struct{}, len(e.updatedPositions))
   160  }
   161  
   162  func (e *Engine) sendPosition(ctx context.Context, pos *MarketPosition) {
   163  	e.broker.Send(events.NewPositionStateEvent(ctx, pos, e.marketID))
   164  }
   165  
   166  func (e *Engine) Hash() []byte {
   167  	// Fields * FieldSize = (8 * 3)
   168  	// Prices = 32 * 2
   169  	output := make([]byte, len(e.positionsCpy)*((8*3)+(32*2)))
   170  	var i int
   171  	for _, p := range e.positionsCpy {
   172  		values := []uint64{
   173  			uint64(p.Size()),
   174  			uint64(p.Buy()),
   175  			uint64(p.Sell()),
   176  		}
   177  
   178  		for _, v := range values {
   179  			binary.BigEndian.PutUint64(output[i:], v)
   180  			i += 8
   181  		}
   182  
   183  		// Add bytes for VWBuy and VWSell here
   184  		b := p.BuySumProduct().Bytes()
   185  		copy(output[i:], b[:])
   186  		i += 32
   187  		s := p.SellSumProduct().Bytes()
   188  		copy(output[i:], s[:])
   189  		i += 32
   190  	}
   191  
   192  	return crypto.Hash(output)
   193  }
   194  
   195  // ReloadConf update the internal configuration of the positions engine.
   196  func (e *Engine) ReloadConf(cfg Config) {
   197  	e.log.Info("reloading configuration")
   198  	if e.log.GetLevel() != cfg.Level.Get() {
   199  		e.log.Info("updating log level",
   200  			logging.String("old", e.log.GetLevel().String()),
   201  			logging.String("new", cfg.Level.String()),
   202  		)
   203  		e.log.SetLevel(cfg.Level.Get())
   204  	}
   205  
   206  	e.cfgMu.Lock()
   207  	e.Config = cfg
   208  	e.cfgMu.Unlock()
   209  }
   210  
   211  // ValidateOrder checks that the given order can be registered.
   212  func (e *Engine) ValidateOrder(order *types.Order) error {
   213  	pos, found := e.positions[order.Party]
   214  	if !found {
   215  		pos = NewMarketPosition(order.Party)
   216  	}
   217  	return pos.ValidateOrderRegistration(order.TrueRemaining(), order.Side)
   218  }
   219  
   220  // ValidateAmendOrder checks that replace the given order with a new order will no cause issues with position tracking.
   221  func (e *Engine) ValidateAmendOrder(order *types.Order, newOrder *types.Order) error {
   222  	pos, found := e.positions[order.Party]
   223  	if !found {
   224  		pos = NewMarketPosition(order.Party)
   225  	}
   226  
   227  	if order.TrueRemaining() >= newOrder.TrueRemaining() {
   228  		return nil
   229  	}
   230  	return pos.ValidateOrderRegistration(newOrder.TrueRemaining()-order.TrueRemaining(), order.Side)
   231  }
   232  
   233  // RegisterOrder updates the potential positions for a submitted order, as though
   234  // the order were already accepted.
   235  // It returns the updated position.
   236  // The margins+risk engines need the updated position to determine whether the
   237  // order should be accepted.
   238  func (e *Engine) RegisterOrder(ctx context.Context, order *types.Order) *MarketPosition {
   239  	pos, found := e.positions[order.Party]
   240  	if !found {
   241  		pos = NewMarketPosition(order.Party)
   242  		e.positions[order.Party] = pos
   243  		// append the pointer to the slice as well
   244  		e.positionsCpy = append(e.positionsCpy, pos)
   245  		// create the entry in the open interest map
   246  		e.partiesHighestVolume[order.Party] = &openVolumeRecord{}
   247  	}
   248  
   249  	pos.RegisterOrder(e.log, order)
   250  	e.positionUpdated(ctx, pos)
   251  	return pos
   252  }
   253  
   254  // UnregisterOrder undoes the actions of RegisterOrder. It is used when an order
   255  // has been rejected by the Risk Engine, or when an order is amended or canceled.
   256  func (e *Engine) UnregisterOrder(ctx context.Context, order *types.Order) *MarketPosition {
   257  	pos, found := e.positions[order.Party]
   258  	if !found {
   259  		e.log.Panic("could not find position in engine when unregistering order",
   260  			logging.Order(*order))
   261  	}
   262  	pos.UnregisterOrder(e.log, order)
   263  	e.positionUpdated(ctx, pos)
   264  	return pos
   265  }
   266  
   267  // AmendOrder unregisters the original order and then registers the newly amended order
   268  // this method is a quicker way of handling separate unregister+register pairs.
   269  func (e *Engine) AmendOrder(ctx context.Context, originalOrder, newOrder *types.Order) *MarketPosition {
   270  	pos, found := e.positions[originalOrder.Party]
   271  	if !found {
   272  		e.log.Panic("could not find position in engine when amending order",
   273  			logging.Order(*originalOrder),
   274  			logging.Order(*newOrder))
   275  	}
   276  
   277  	pos.AmendOrder(e.log, originalOrder, newOrder)
   278  	e.positionUpdated(ctx, pos)
   279  	return pos
   280  }
   281  
   282  func (e *Engine) updatePartiesTradedSize(party string, size uint64) {
   283  	e.partiesTradedSize[party] = e.partiesTradedSize[party] + size
   284  }
   285  
   286  // Update pushes the previous positions on the channel + the updated open volumes of buyer/seller.
   287  func (e *Engine) Update(ctx context.Context, trade *types.Trade, passiveOrder, aggressiveOrder *types.Order) []events.MarketPosition {
   288  	buyer, ok := e.positions[trade.Buyer]
   289  	if !ok {
   290  		e.log.Panic("could not find buyer position",
   291  			logging.Trade(*trade))
   292  	}
   293  
   294  	seller, ok := e.positions[trade.Seller]
   295  	if !ok {
   296  		e.log.Panic("could not find seller position",
   297  			logging.Trade(*trade))
   298  	}
   299  
   300  	// now we check if the trade is possible based on the potential positions
   301  	// this should always be true, no trade can happen without the equivalent
   302  	// potential position
   303  	if buyer.buy < int64(trade.Size) {
   304  		e.log.Panic("trade with a potential buy position < to the trade size",
   305  			logging.PartyID(trade.Buyer),
   306  			logging.Int64("potential-buy", buyer.buy),
   307  			logging.Trade(*trade))
   308  	}
   309  	if seller.sell < int64(trade.Size) {
   310  		e.log.Panic("trade with a potential sell position < to the trade size",
   311  			logging.PartyID(trade.Seller),
   312  			logging.Int64("potential-sell", buyer.sell),
   313  			logging.Trade(*trade))
   314  	}
   315  
   316  	// Update long/short actual position for buyer and seller.
   317  	// The buyer's position increases and the seller's position decreases.
   318  	buyer.averageEntryPrice = CalcVWAP(buyer.averageEntryPrice, buyer.size, int64(trade.Size), trade.Price)
   319  	buyer.size += int64(trade.Size)
   320  	seller.averageEntryPrice = CalcVWAP(seller.averageEntryPrice, -seller.size, int64(trade.Size), trade.Price)
   321  	seller.size -= int64(trade.Size)
   322  
   323  	aggressive := buyer
   324  	passive := seller
   325  	if aggressiveOrder.Side == types.SideSell {
   326  		aggressive = seller
   327  		passive = buyer
   328  	}
   329  
   330  	// Update potential positions & vwaps. Potential positions decrease for both buyer and seller.
   331  	aggressive.UpdateOnOrderChange(e.log, aggressiveOrder.Side, aggressiveOrder.Price, trade.Size, false)
   332  	passive.UpdateOnOrderChange(e.log, passiveOrder.Side, passiveOrder.Price, trade.Size, false)
   333  	// if the network opens a position here, the price will not be set, which breaks the snapshot
   334  	// we know the network will trade at the current mark price, and therefore it will hold the position at this price.
   335  	// so we should just make sure the price is set correctly here.
   336  	if aggressive.partyID == types.NetworkParty && (aggressive.price == nil || aggressive.price.IsZero()) {
   337  		aggressive.price = trade.Price.Clone()
   338  	}
   339  
   340  	ret := []events.MarketPosition{
   341  		*buyer.Clone(),
   342  		*seller.Clone(),
   343  	}
   344  
   345  	e.positionUpdated(ctx, buyer)
   346  	e.positionUpdated(ctx, seller)
   347  
   348  	e.partiesHighestVolume[buyer.partyID].RecordLatest(buyer.size)
   349  	e.updatePartiesTradedSize(buyer.partyID, trade.Size)
   350  	e.partiesHighestVolume[seller.partyID].RecordLatest(seller.size)
   351  	e.updatePartiesTradedSize(seller.partyID, trade.Size)
   352  
   353  	if e.log.GetLevel() == logging.DebugLevel {
   354  		e.log.Debug("Positions Updated for trade",
   355  			logging.Trade(*trade),
   356  			logging.String("buyer-position", fmt.Sprintf("%+v", buyer)),
   357  			logging.String("seller-position", fmt.Sprintf("%+v", seller)))
   358  	}
   359  	return ret
   360  }
   361  
   362  // RemoveDistressed Removes positions for distressed parties, and returns the most up to date positions we have.
   363  func (e *Engine) RemoveDistressed(parties []events.MarketPosition) []events.MarketPosition {
   364  	ret := make([]events.MarketPosition, 0, len(parties))
   365  	for _, party := range parties {
   366  		e.log.Warn("removing party from positions engine",
   367  			logging.String("party-id", party.Party()))
   368  
   369  		party := party.Party()
   370  		if current, ok := e.positions[party]; ok {
   371  			ret = append(ret, current)
   372  		}
   373  		// remove from the map
   374  		delete(e.positions, party)
   375  		delete(e.distressedPos, party)
   376  		delete(e.partiesHighestVolume, party)
   377  		// remove from the slice
   378  		for i := range e.positionsCpy {
   379  			if e.positionsCpy[i].Party() == party {
   380  				e.log.Warn("removing party from positions engine (cpy slice)",
   381  					logging.String("party-id", party))
   382  				e.positionsCpy = append(e.positionsCpy[:i], e.positionsCpy[i+1:]...)
   383  
   384  				break
   385  			}
   386  		}
   387  	}
   388  	return ret
   389  }
   390  
   391  // UpdateMarkPrice update the mark price on all positions and return a slice
   392  // of the updated positions.
   393  func (e *Engine) UpdateMarkPrice(markPrice *num.Uint) []events.MarketPosition {
   394  	for _, pos := range e.positions {
   395  		pos.price.Set(markPrice)
   396  	}
   397  	return e.positionsCpy
   398  }
   399  
   400  func (e *Engine) GetOpenInterest() uint64 {
   401  	openInterest := uint64(0)
   402  	for _, pos := range e.positions {
   403  		if pos.size > 0 {
   404  			openInterest += uint64(pos.size)
   405  		}
   406  	}
   407  	return openInterest
   408  }
   409  
   410  func (e *Engine) GetOpenInterestGivenTrades(trades []*types.Trade) uint64 {
   411  	oi := e.GetOpenInterest()
   412  	d := int64(0)
   413  	// Store changes to positions across trades locally
   414  	posLocal := make(map[string]int64)
   415  	var ok bool
   416  	for _, t := range trades {
   417  		if t.Seller == t.Buyer {
   418  			// ignore wash trade
   419  			continue
   420  		}
   421  		bPos, sPos := int64(0), int64(0)
   422  		if bPos, ok = posLocal[t.Buyer]; !ok {
   423  			if p, ok := e.positions[t.Buyer]; ok {
   424  				bPos = p.size
   425  			}
   426  		}
   427  		if sPos, ok = posLocal[t.Seller]; !ok {
   428  			if p, ok := e.positions[t.Seller]; ok {
   429  				sPos = p.size
   430  			}
   431  		}
   432  
   433  		bPosNew := bPos + int64(t.Size)
   434  		sPosNew := sPos - int64(t.Size)
   435  		posLocal[t.Buyer] = bPosNew
   436  		posLocal[t.Seller] = sPosNew
   437  		// Change in open interest due to trades equals change in longs for both parties
   438  		d += max(0, bPosNew) - max(0, bPos) + max(0, sPosNew) - max(0, sPos)
   439  	}
   440  	if d < 0 {
   441  		return oi - uint64(-d)
   442  	}
   443  	return oi + uint64(d)
   444  }
   445  
   446  //nolint:unparam
   447  func max(a int64, b int64) int64 {
   448  	if a >= b {
   449  		return a
   450  	}
   451  	return b
   452  }
   453  
   454  // Positions is just the logic to update buyer, will eventually return the MarketPosition we need to push.
   455  func (e *Engine) Positions() []events.MarketPosition {
   456  	return e.positionsCpy
   457  }
   458  
   459  // MarkDistressed - mark any distressed parties as such, returns the IDs of the parties who weren't distressed before.
   460  func (e *Engine) MarkDistressed(closed []string) ([]string, []string) {
   461  	// assume number of distressed parties is roughly equal, it isn't but should overall reduce reallocs
   462  	// create a copy, otherwise newly distressed positions are unmarked
   463  	prevDistressed := make(map[string]struct{}, len(e.distressedPos))
   464  	for k := range e.distressedPos {
   465  		prevDistressed[k] = struct{}{}
   466  	}
   467  	nIDs := make([]string, 0, len(closed))
   468  	for _, c := range closed {
   469  		if _, ok := prevDistressed[c]; ok {
   470  			// this party was already known to be distressed, leave it as-is
   471  			// delete from this map, so we are left with only parties who were distressed, but aren't anymore
   472  			delete(prevDistressed, c)
   473  			continue
   474  		}
   475  		// not in distressed map -> get position, mark as distressed
   476  		e.positions[c].distressed = true
   477  		// add the new distressed party to the map
   478  		e.distressedPos[c] = struct{}{}
   479  		// add the ID to the slice of distressed ID's for the event
   480  		nIDs = append(nIDs, c)
   481  	}
   482  	// if we didn't have any previously distressed parties to update, we're done
   483  	if len(prevDistressed) == 0 {
   484  		return nIDs, nil
   485  	}
   486  	// mark parties who are no longer distressed accoringly
   487  	nd := make([]string, 0, len(prevDistressed))
   488  	for k := range prevDistressed {
   489  		// mark as no longer distressed
   490  		e.positions[k].distressed = false
   491  		// remove from the map
   492  		delete(e.distressedPos, k)
   493  		nd = append(nd, k)
   494  	}
   495  	sort.Strings(nIDs)
   496  	sort.Strings(nd)
   497  	return nIDs, nd
   498  }
   499  
   500  // GetPositionByPartyID - return current position for a given party, it's used in margin checks during auctions
   501  // we're not specifying an interface of the return type, and we return a pointer to a copy for the nil.
   502  func (e *Engine) GetPositionByPartyID(partyID string) (*MarketPosition, bool) {
   503  	pos, ok := e.positions[partyID]
   504  	if !ok {
   505  		return nil, false
   506  	}
   507  	cpy := pos.Clone()
   508  	// return a copy
   509  	return cpy, true
   510  }
   511  
   512  func (e *Engine) GetPositionsByParty(ids ...string) []events.MarketPosition {
   513  	ret := make([]events.MarketPosition, 0, len(ids))
   514  	for _, id := range ids {
   515  		pos, ok := e.positions[id]
   516  		if ok {
   517  			ret = append(ret, pos.Clone())
   518  		}
   519  	}
   520  	return ret
   521  }
   522  
   523  // Parties returns a list of all the parties in the position engine.
   524  func (e *Engine) Parties() []string {
   525  	parties := make([]string, 0, len(e.positions))
   526  	for _, v := range e.positions {
   527  		parties = append(parties, v.Party())
   528  	}
   529  	return parties
   530  }
   531  
   532  func (e *Engine) GetClosedPositions() []events.MarketPosition {
   533  	out := []events.MarketPosition{}
   534  
   535  	for _, v := range e.positions {
   536  		if v.Closed() {
   537  			e.remove(v)
   538  			out = append(out, v)
   539  		}
   540  	}
   541  
   542  	sort.Slice(out, func(i, j int) bool { return out[i].Party() < out[j].Party() })
   543  
   544  	return out
   545  }
   546  
   547  // GetOpenPositionCount returns the number of open positions in the market.
   548  func (e *Engine) GetOpenPositionCount() uint64 {
   549  	var total uint64
   550  	for _, mp := range e.positionsCpy {
   551  		if mp.Size() != 0 {
   552  			total++
   553  		}
   554  	}
   555  	return total
   556  }
   557  
   558  // GetPartiesLowestOpenInterestForEpoch will return a map of parties
   559  // and minimal open interest for the epoch, it is meant to be called
   560  // at the end of the epoch, and will reset the lowest open interest
   561  // of the party to the latest open interest recored for the epoch.
   562  func (e *Engine) GetPartiesLowestOpenInterestForEpoch() map[string]uint64 {
   563  	out := map[string]uint64{}
   564  
   565  	for party, oi := range e.partiesHighestVolume {
   566  		out[party] = oi.Reset()
   567  	}
   568  
   569  	return out
   570  }
   571  
   572  // GetPartiesTradedVolumeForEpoch will return a map of parties
   573  // and their traded volume recorded during this epoch.
   574  func (e *Engine) GetPartiesTradedVolumeForEpoch() (out map[string]uint64) {
   575  	out, e.partiesTradedSize = e.partiesTradedSize, map[string]uint64{}
   576  	return
   577  }
   578  
   579  func (e *Engine) remove(p *MarketPosition) {
   580  	// delete from the map first
   581  	delete(e.positions, p.partyID)
   582  	delete(e.partiesHighestVolume, p.partyID)
   583  	delete(e.distressedPos, p.partyID) // in case party was previously flagged as distressed
   584  
   585  	// remove from the slice
   586  	for i := range e.positionsCpy {
   587  		if e.positionsCpy[i].Party() == p.partyID {
   588  			e.positionsCpy = append(e.positionsCpy[:i], e.positionsCpy[i+1:]...)
   589  			break
   590  		}
   591  	}
   592  }
   593  
   594  // CalcVWAP calculates the volume weighted average entry price.
   595  func CalcVWAP(vwap *num.Uint, pos int64, addVolume int64, addPrice *num.Uint) *num.Uint {
   596  	if pos+addVolume == 0 || addPrice == nil {
   597  		return num.UintZero()
   598  	}
   599  
   600  	newPos := pos + addVolume
   601  	if newPos*pos < 0 { // switching from short/long or long/short
   602  		if newPos < 0 {
   603  			newPos = -newPos
   604  		}
   605  		newAcquiredPositionUint := num.NewUint(uint64(newPos))
   606  		if pos < 0 {
   607  			pos = -pos
   608  		}
   609  		posUint := num.NewUint(uint64(pos))
   610  		// cost of closing the old position
   611  		closePositionCost := num.UintZero().Mul(posUint, vwap)
   612  		// cost of opening the new position
   613  		openNewPositionCost := num.UintZero().Mul(newAcquiredPositionUint, addPrice)
   614  		return num.UintZero().Div(num.Sum(closePositionCost, openNewPositionCost), num.Sum(posUint, newAcquiredPositionUint))
   615  	}
   616  	// only decreasing position
   617  	if (pos > 0 && addVolume < 0) || (pos < 0 && addVolume > 0) {
   618  		return vwap
   619  	}
   620  	// increasing position
   621  	if pos < 0 {
   622  		pos = -pos
   623  	}
   624  	posUint := num.NewUint(uint64(pos))
   625  	if addVolume < 0 {
   626  		addVolume = -addVolume
   627  	}
   628  	addVolumeUint := num.NewUint(uint64(addVolume))
   629  	if newPos < 0 {
   630  		newPos = -newPos
   631  	}
   632  	newPosUint := num.NewUint(uint64(newPos))
   633  	numerator := num.UintZero().Mul(vwap, posUint).AddSum(num.UintZero().Mul(addPrice, addVolumeUint))
   634  	return num.UintZero().Div(numerator, newPosUint)
   635  }