code.vegaprotocol.io/vega@v0.79.0/core/execution/common/equity_shares.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 common
    17  
    18  import (
    19  	"fmt"
    20  	"sort"
    21  
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  )
    25  
    26  // lp holds LiquidityProvider stake and avg values.
    27  type lp struct {
    28  	stake  num.Decimal
    29  	share  num.Decimal
    30  	avg    num.Decimal
    31  	vStake num.Decimal
    32  }
    33  
    34  // EquityShares module controls the Equity sharing algorithm described on the spec:
    35  // https://github.com/vegaprotocol/product/blob/02af55e048a92a204e9ee7b7ae6b4475a198c7ff/specs/0042-setting-fees-and-rewarding-lps.md#calculating-liquidity-provider-equity-like-share
    36  type EquityShares struct {
    37  	// mvp is the MarketValueProxy
    38  	mvp num.Decimal
    39  	r   num.Decimal
    40  
    41  	totalVStake num.Decimal
    42  	totalPStake num.Decimal
    43  	// lps is a map of party id to lp (LiquidityProviders)
    44  	lps map[string]*lp
    45  	// used to restore own ELS from checkpoint
    46  	pendingLPs []*types.ELSShare
    47  
    48  	openingAuctionEnded bool
    49  }
    50  
    51  func NewEquityShares(mvp num.Decimal) *EquityShares {
    52  	return &EquityShares{
    53  		mvp:         mvp,
    54  		r:           num.DecimalZero(),
    55  		totalPStake: num.DecimalZero(),
    56  		totalVStake: num.DecimalZero(),
    57  		lps:         map[string]*lp{},
    58  	}
    59  }
    60  
    61  func (es *EquityShares) GetLPSCount() int {
    62  	return len(es.lps)
    63  }
    64  
    65  func (es *EquityShares) InheritELS(shares []*types.ELSShare) {
    66  	for _, els := range shares {
    67  		if current, ok := es.lps[els.PartyID]; ok {
    68  			update, _ := num.UintFromDecimal(current.stake)
    69  			// update the totals:
    70  			es.totalPStake = es.totalPStake.Sub(current.stake).Add(els.SuppliedStake)
    71  			es.totalVStake = es.totalVStake.Sub(current.vStake).Add(els.VStake)
    72  			// make it look like the old ELS data was part of this market...
    73  			current.stake = els.SuppliedStake
    74  			current.vStake = els.VStake
    75  			current.avg = els.Avg
    76  			current.share = els.Share
    77  			// then treat the current commitment as a change to the commitment amount:
    78  			es.SetPartyStake(els.PartyID, update)
    79  		}
    80  	}
    81  }
    82  
    83  func (es *EquityShares) RestoreELS(shares []*types.ELSShare) {
    84  	es.pendingLPs = shares
    85  }
    86  
    87  func (es *EquityShares) RollbackParentELS() {
    88  	// get all current stakes
    89  	current := es.lps
    90  	// clear current state:
    91  	es.lps = make(map[string]*lp, len(current))
    92  	es.totalPStake, es.totalVStake = num.DecimalZero(), num.DecimalZero()
    93  	// now add the commitments one by one as if they were just made
    94  
    95  	// make the iteration over parties deterministic
    96  	pids := make([]string, 0, len(current))
    97  	for k := range current {
    98  		pids = append(pids, k)
    99  	}
   100  	sort.Strings(pids)
   101  
   102  	for _, pid := range pids {
   103  		els := current[pid]
   104  		update, _ := num.UintFromDecimal(els.stake)
   105  		es.SetPartyStake(pid, update)
   106  	}
   107  	if len(pids) > 0 {
   108  		es.ResetAvgToLP(pids[len(pids)-1])
   109  	}
   110  }
   111  
   112  func (es *EquityShares) LpsToLiquidityProviderFeeShare(ls map[string]num.Decimal) []*types.LiquidityProviderFeeShare {
   113  	out := make([]*types.LiquidityProviderFeeShare, 0, len(es.lps))
   114  	for k, v := range es.lps {
   115  		out = append(out, &types.LiquidityProviderFeeShare{
   116  			Party:                 k,
   117  			EquityLikeShare:       v.share.String(),
   118  			AverageEntryValuation: v.avg.String(),
   119  			AverageScore:          ls[k].String(),
   120  			VirtualStake:          v.vStake.StringFixed(16),
   121  		})
   122  	}
   123  
   124  	// sort then so we produce the same output on all nodes
   125  	sort.SliceStable(out, func(i, j int) bool {
   126  		return out[i].Party < out[j].Party
   127  	})
   128  
   129  	return out
   130  }
   131  
   132  // OpeningAuctionEnded signal to the EquityShare that the
   133  // opening auction has ended.
   134  func (es *EquityShares) OpeningAuctionEnded() {
   135  	// we should never call this twice
   136  	if es.openingAuctionEnded {
   137  		panic("market already left opening auction")
   138  	}
   139  	if len(es.pendingLPs) > 0 {
   140  		es.InheritELS(es.pendingLPs)
   141  		es.pendingLPs = nil
   142  	}
   143  	es.openingAuctionEnded = true
   144  	es.r = num.DecimalZero()
   145  	// force recalc of vStake, ensuring ELS will be set properly
   146  	es.UpdateVStake()
   147  }
   148  
   149  func (es *EquityShares) UpdateVStake() {
   150  	total := num.DecimalZero()
   151  	factor := num.DecimalFromFloat(1.0).Add(es.r)
   152  	recalc := false
   153  	for _, v := range es.lps {
   154  		vStake := num.MaxD(v.stake, v.vStake.Mul(factor))
   155  		v.vStake = vStake
   156  		total = total.Add(vStake)
   157  		if !recalc && v.share.IsZero() {
   158  			recalc = true
   159  		}
   160  	}
   161  	if !total.Equal(es.totalVStake) {
   162  		// some vStake changed, force recalc of ELS values.
   163  		es.totalVStake = total
   164  		recalc = true
   165  	}
   166  	if recalc {
   167  		es.updateAllELS()
   168  	}
   169  }
   170  
   171  func (es *EquityShares) GetTotalVStake() num.Decimal {
   172  	return es.totalVStake
   173  }
   174  
   175  func (es *EquityShares) GetMarketGrowth() num.Decimal {
   176  	return es.r
   177  }
   178  
   179  func (es *EquityShares) AvgTradeValue(avg num.Decimal) *EquityShares {
   180  	if avg.IsZero() {
   181  		// if es.openingAuctionEnded {
   182  		// 	// this should not be possible IRL, however unit tests like amend_lp_orders
   183  		// 	// rely on the EndOpeningAuction call and can end opening auction without setting a price
   184  		// 	// ie -> end opening auction without a trade value
   185  		// 	panic("opening auction ended, and avg trade value hit zero somehow?")
   186  		// }
   187  		return es
   188  	}
   189  	if !es.mvp.IsZero() {
   190  		es.r = avg.Sub(es.mvp).Div(es.mvp)
   191  	} else {
   192  		es.r = num.DecimalZero()
   193  	}
   194  	es.UpdateVStake()
   195  	es.mvp = avg
   196  	return es
   197  }
   198  
   199  func (es *EquityShares) ResetAvgToLP(id string) {
   200  	avg := es.lps[id].avg
   201  	for _, lp := range es.lps {
   202  		lp.avg = avg
   203  	}
   204  }
   205  
   206  // SetPartyStake sets LP values for a given party.
   207  func (es *EquityShares) SetPartyStake(id string, newStakeU *num.Uint) {
   208  	v, found := es.lps[id]
   209  	if newStakeU == nil || newStakeU.IsZero() {
   210  		if found {
   211  			es.totalVStake = es.totalVStake.Sub(v.vStake)
   212  			es.totalPStake = es.totalPStake.Sub(v.stake)
   213  		}
   214  		delete(es.lps, id)
   215  		return
   216  	}
   217  	newStake := num.DecimalFromUint(newStakeU)
   218  	if found && newStake.Equals(v.stake) {
   219  		// stake didn't change? there's nothing to do really
   220  		return
   221  	}
   222  	// first time we set the newStake and mvp as avg.
   223  	if !found {
   224  		// this is technically a delta == new stake -> calculate avg accordingly
   225  		v = &lp{}
   226  		es.lps[id] = v
   227  		es.updateAvgPosDelta(v, newStake, newStake) // delta == new stake
   228  		return
   229  	}
   230  
   231  	delta := newStake.Sub(v.stake)
   232  	if newStake.LessThan(v.stake) {
   233  		// commitment decreased
   234  		es.totalVStake = es.totalVStake.Sub(v.vStake)
   235  		es.totalPStake = es.totalPStake.Sub(v.stake)
   236  		// vStake * (newStake/oldStake) or vStake * (old_stake + (-delta))/old_stake
   237  		// e.g. 8000 => 5000 == vStakle * ((8000 + (-3000))/8000) == vStake * (5000/8000)
   238  		v.vStake = v.vStake.Mul(newStake.Div(v.stake))
   239  		v.stake = newStake
   240  		es.totalVStake = es.totalVStake.Add(v.vStake)
   241  		es.totalPStake = es.totalPStake.Add(v.stake)
   242  		// recalculate ELS for this party
   243  		v.share, _ = es.equity(id)
   244  		return
   245  	}
   246  
   247  	es.updateAvgPosDelta(v, delta, newStake)
   248  }
   249  
   250  func (es *EquityShares) updateAvgPosDelta(v *lp, delta, newStake num.Decimal) {
   251  	// entry valuation == total Virtual stake (before delta is applied)
   252  	// (average entry valuation) <- (average entry valuation) x S / (S + Delta S) + (entry valuation) x (Delta S) / (S + Delta S)
   253  	// S being the LP's physical stake, Delta S being the amount by which the stake is increased
   254  	es.totalVStake = es.totalVStake.Add(delta)
   255  	es.totalPStake = es.totalPStake.Sub(v.stake).Add(newStake)
   256  	v.avg = v.avg.Mul(v.stake).Div(newStake).Add(es.totalVStake.Mul(delta).Div(newStake))
   257  	// this is the first LP -> no vStake yet
   258  	v.vStake = v.vStake.Add(delta)
   259  	v.stake = newStake
   260  }
   261  
   262  // AvgEntryValuation returns the Average Entry Valuation for a given party.
   263  func (es *EquityShares) AvgEntryValuation(id string) num.Decimal {
   264  	if v, ok := es.lps[id]; ok {
   265  		return v.avg
   266  	}
   267  	return num.DecimalZero()
   268  }
   269  
   270  // equity returns the following:
   271  // (LP i stake)(n) x market_value_proxy(n) / (LP i avg_entry_valuation)(n)
   272  // given a party id (i).
   273  //
   274  // Returns an error if the party has no stake.
   275  func (es *EquityShares) equity(id string) (num.Decimal, error) {
   276  	if v, ok := es.lps[id]; ok {
   277  		if es.totalVStake.IsZero() {
   278  			return num.DecimalZero(), nil
   279  		}
   280  		return v.vStake.Div(es.totalVStake), nil
   281  	}
   282  
   283  	return num.DecimalZero(), fmt.Errorf("party %s has no stake", id)
   284  }
   285  
   286  // AllShares returns the ratio of equity for each party on the market.
   287  func (es *EquityShares) AllShares() map[string]num.Decimal {
   288  	return es.SharesExcept(map[string]struct{}{})
   289  }
   290  
   291  // SharesFromParty returns the equity-like shares of a given party on the market.
   292  func (es *EquityShares) SharesFromParty(party string) num.Decimal {
   293  	totalEquity := num.DecimalZero()
   294  	partyELS := num.DecimalZero()
   295  	for id := range es.lps {
   296  		eq, err := es.equity(id)
   297  		if err != nil {
   298  			// since equity(id) returns an error when the party does not exist
   299  			// getting an error here means we are doing something wrong cause
   300  			// it should never happen unless `.equity()` behavior changes.
   301  			panic(err)
   302  		}
   303  		if id == party {
   304  			partyELS = eq
   305  		}
   306  		totalEquity = totalEquity.Add(eq)
   307  	}
   308  
   309  	if partyELS.Equal(num.DecimalZero()) || totalEquity.Equal(num.DecimalZero()) {
   310  		return num.DecimalZero()
   311  	}
   312  
   313  	return partyELS.Div(totalEquity)
   314  }
   315  
   316  // HasShares returns whether the given party is registered as an LP with ELS.
   317  func (es *EquityShares) HasShares(party string) bool {
   318  	_, ok := es.lps[party]
   319  	return ok
   320  }
   321  
   322  // SharesExcept returns the ratio of equity for each party on the market, except
   323  // the ones listed in parameter.
   324  func (es *EquityShares) SharesExcept(except map[string]struct{}) map[string]num.Decimal {
   325  	shares := make(map[string]num.Decimal, len(es.lps)-len(except))
   326  	allTotal, allTotalV := es.totalPStake, es.totalVStake
   327  	all := true
   328  	for k := range except {
   329  		if v, ok := es.lps[k]; ok {
   330  			es.totalPStake = es.totalPStake.Sub(v.stake)
   331  			es.totalVStake = es.totalVStake.Sub(v.vStake)
   332  			all = false
   333  		}
   334  	}
   335  	for id, v := range es.lps {
   336  		if _, ok := except[id]; ok {
   337  			continue
   338  		}
   339  		eq, err := es.equity(id)
   340  		if err != nil {
   341  			panic(err)
   342  		}
   343  		shares[id] = eq
   344  		if all && !v.share.Equals(eq) {
   345  			v.share = eq
   346  		}
   347  	}
   348  	if !all {
   349  		es.totalPStake = allTotal
   350  		es.totalVStake = allTotalV
   351  	}
   352  	return shares
   353  }
   354  
   355  func (es *EquityShares) updateAllELS() {
   356  	for id, v := range es.lps {
   357  		eq, err := es.equity(id)
   358  		if err != nil {
   359  			panic(err)
   360  		}
   361  		if !v.share.Equals(eq) {
   362  			v.share = eq
   363  		}
   364  	}
   365  }
   366  
   367  func (es *EquityShares) GetCPShares() []*types.ELSShare {
   368  	shares := make([]*types.ELSShare, 0, len(es.lps))
   369  	for id, els := range es.lps {
   370  		shares = append(shares, &types.ELSShare{
   371  			PartyID:       id,
   372  			Share:         els.share,
   373  			SuppliedStake: els.stake,
   374  			VStake:        els.vStake,
   375  			Avg:           els.avg,
   376  		})
   377  	}
   378  	// make sure data is sorted
   379  	sort.SliceStable(shares, func(i, j int) bool {
   380  		return shares[i].PartyID > shares[j].PartyID
   381  	})
   382  	return shares
   383  }
   384  
   385  func (es *EquityShares) SetCPShares(shares []*types.ELSShare) {
   386  	es.lps = make(map[string]*lp, len(shares))
   387  	es.totalPStake = num.DecimalZero()
   388  	es.totalVStake = num.DecimalZero()
   389  	for _, share := range shares {
   390  		lp := lp{
   391  			avg:    share.Avg,
   392  			share:  share.Share,
   393  			stake:  share.SuppliedStake,
   394  			vStake: share.VStake,
   395  		}
   396  		es.totalPStake = es.totalPStake.Add(lp.stake)
   397  		es.totalVStake = es.totalVStake.Add(lp.vStake)
   398  		es.lps[share.PartyID] = &lp
   399  	}
   400  }