code.vegaprotocol.io/vega@v0.79.0/datanode/service/party_stats.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 service
    17  
    18  import (
    19  	"context"
    20  
    21  	"code.vegaprotocol.io/vega/datanode/entities"
    22  	"code.vegaprotocol.io/vega/libs/num"
    23  	"code.vegaprotocol.io/vega/libs/ptr"
    24  	v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2"
    25  	"code.vegaprotocol.io/vega/protos/vega"
    26  )
    27  
    28  // dependencies
    29  
    30  // EpochStore is used to get the last epoch from DB.
    31  type EpochStore interface {
    32  	GetCurrent(ctx context.Context) (entities.Epoch, error)
    33  }
    34  
    35  // ReferralSetStore gets the referral set data, consider adding a custom method without the noise.
    36  type ReferralSetStore interface {
    37  	GetReferralSetStats(ctx context.Context, setID *entities.ReferralSetID, atEpoch *uint64, referee *entities.PartyID, pagination entities.CursorPagination) ([]entities.FlattenReferralSetStats, entities.PageInfo, error)
    38  }
    39  
    40  // VDSStore is Volume Discount Stats storage, again custom methods might need to be added.
    41  type VDSStore interface {
    42  	Stats(ctx context.Context, atEpoch *uint64, partyID *string, pagination entities.CursorPagination) ([]entities.FlattenVolumeDiscountStats, entities.PageInfo, error)
    43  	LatestStats(ctx context.Context, partyID string) (entities.VolumeDiscountStats, error)
    44  }
    45  
    46  // VRSStore is Volume Rebate Stats, may need custom methods.
    47  type VRSStore interface {
    48  	Stats(ctx context.Context, atEpoch *uint64, partyID *string, pagination entities.CursorPagination) ([]entities.FlattenVolumeRebateStats, entities.PageInfo, error)
    49  }
    50  
    51  // MktStore is a duplicate interface at this point, but again: custom method fetching list of markets would be handy.
    52  type MktStore interface {
    53  	GetByIDs(ctx context.Context, marketID []string) ([]entities.Market, error)
    54  	// NB: although it returns Market entity, all it has is id and fees. Trying to access anything else on it will get NPE.
    55  	GetAllFees(ctx context.Context) ([]entities.Market, error)
    56  }
    57  
    58  type VRStore interface {
    59  	GetCurrentVolumeRebateProgram(ctx context.Context) (entities.VolumeRebateProgram, error)
    60  }
    61  
    62  type RPStore interface {
    63  	GetCurrentReferralProgram(ctx context.Context) (entities.ReferralProgram, error)
    64  }
    65  
    66  type VDStore interface {
    67  	GetCurrentVolumeDiscountProgram(ctx context.Context) (entities.VolumeDiscountProgram, error)
    68  }
    69  
    70  // PSvc the actual service combining data from all dependencies.
    71  type PSvc struct {
    72  	epoch EpochStore
    73  	ref   ReferralSetStore
    74  	vds   VDSStore
    75  	vrs   VRSStore
    76  	mkt   MktStore
    77  	rp    RPStore
    78  	vd    VDStore
    79  	vr    VRStore
    80  }
    81  
    82  type partyFeeFactors struct {
    83  	maker     num.Decimal
    84  	infra     num.Decimal
    85  	liquidity num.Decimal
    86  }
    87  
    88  func NewPartyStatsService(epoch EpochStore, ref ReferralSetStore, vds VDSStore, vrs VRSStore, mkt MktStore, rp RPStore, vd VDStore, vr VRStore) *PSvc {
    89  	return &PSvc{
    90  		epoch: epoch,
    91  		ref:   ref,
    92  		vds:   vds,
    93  		vrs:   vrs,
    94  		mkt:   mkt,
    95  		rp:    rp,
    96  		vd:    vd,
    97  		vr:    vr,
    98  	}
    99  }
   100  
   101  func (s *PSvc) GetPartyStats(ctx context.Context, partyID string, markets []string) (*v2.GetPartyDiscountStatsResponse, error) {
   102  	// first up, last epoch to get the stats:
   103  	epoch, err := s.epoch.GetCurrent(ctx)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	// then get the markets:
   108  	var mkts []entities.Market
   109  	if len(markets) > 0 {
   110  		mkts, err = s.mkt.GetByIDs(ctx, markets)
   111  	} else {
   112  		mkts, err = s.mkt.GetAllFees(ctx)
   113  	}
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	lastE := uint64(epoch.ID - 1)
   118  
   119  	data := &v2.GetPartyDiscountStatsResponse{}
   120  	rfDiscountFactors := partyFeeFactors{}
   121  	rfRewardFactors := partyFeeFactors{}
   122  	vdDiscountFactors := partyFeeFactors{}
   123  
   124  	// now that we've gotten the epoch and all markets, get the party stats.
   125  	// 1. referral set stats.
   126  	refStats, _, err := s.ref.GetReferralSetStats(ctx, nil, &lastE, ptr.From(entities.PartyID(partyID)), entities.DefaultCursorPagination(true))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	if len(refStats) > 0 {
   131  		tier, err := s.getReferralTier(ctx, refStats[0])
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  		if err := setRefFeeFactors(&rfRewardFactors, &rfDiscountFactors, refStats[0]); err != nil {
   136  			return nil, err
   137  		}
   138  		if tier != nil {
   139  			data.ReferralDiscountTier = *tier.TierNumber
   140  		}
   141  	}
   142  	// 2. volume discount stats.
   143  	vdStats, _, err := s.vds.Stats(ctx, &lastE, &partyID, entities.DefaultCursorPagination(true))
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	if len(vdStats) > 0 {
   148  		tier, err := s.getVolumeDiscountTier(ctx, vdStats[0])
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  		if err := setVolFeeFactors(&vdDiscountFactors, vdStats[0]); err != nil {
   153  			return nil, err
   154  		}
   155  		if tier != nil {
   156  			data.VolumeDiscountTier = *tier.TierNumber
   157  		}
   158  	}
   159  	// 3. Volume Rebate stats.
   160  	vrStats, _, err := s.vrs.Stats(ctx, &lastE, &partyID, entities.DefaultCursorPagination(true))
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	var rebate num.Decimal
   165  	if len(vrStats) > 0 {
   166  		tier, err := s.getVolumeRebateTier(ctx, vrStats[0])
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  		rebate, err = num.DecimalFromString(vrStats[0].AdditionalRebate)
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  		if tier != nil {
   175  			data.VolumeRebateTier = *tier.TierNumber
   176  		}
   177  	}
   178  	for _, mkt := range mkts {
   179  		// @TODO ensure non-nil slice!
   180  		if err := setMarketFees(data, mkt, rfDiscountFactors, rfRewardFactors, vdDiscountFactors, rebate); err != nil {
   181  			return nil, err
   182  		}
   183  	}
   184  	return data, nil
   185  }
   186  
   187  func setMarketFees(data *v2.GetPartyDiscountStatsResponse, mkt entities.Market, rfDiscount, rfRewards, vdFactors partyFeeFactors, rebate num.Decimal) error {
   188  	maker, infra, liquidity, bb, treasury, err := feeFactors(mkt)
   189  	if err != nil {
   190  		return err
   191  	}
   192  	// undiscounted
   193  	base := num.DecimalZero().Add(maker).Add(infra).Add(liquidity).Add(bb).Add(treasury)
   194  	// discounted
   195  	discountedMaker := maker.Mul(num.DecimalOne().Sub(rfDiscount.maker)).Mul(num.DecimalOne().Sub(vdFactors.maker)).Mul(num.DecimalOne().Sub(rfRewards.maker))
   196  	discountedInfra := infra.Mul(num.DecimalOne().Sub(rfDiscount.infra)).Mul(num.DecimalOne().Sub(vdFactors.infra)).Mul(num.DecimalOne().Sub(rfRewards.infra))
   197  	discountedLiquidity := liquidity.Mul(num.DecimalOne().Sub(rfDiscount.liquidity)).Mul(num.DecimalOne().Sub(vdFactors.liquidity)).Mul(num.DecimalOne().Sub(rfRewards.liquidity))
   198  	discounted := discountedMaker.Add(discountedInfra).Add(discountedLiquidity).Add(bb).Add(treasury)
   199  
   200  	effRebate := num.MinD(rebate, bb.Add(treasury))
   201  
   202  	data.PartyMarketFees = append(data.PartyMarketFees, &v2.MarketFees{
   203  		MarketId:             mkt.ID.String(),
   204  		UndiscountedTakerFee: base.String(),
   205  		DiscountedTakerFee:   discounted.String(),
   206  		BaseMakerRebate:      maker.String(),
   207  		UserMakerRebate:      maker.Add(effRebate).String(),
   208  	})
   209  	return nil
   210  }
   211  
   212  func feeFactors(mkt entities.Market) (maker, infra, liquidity, bb, treasury num.Decimal, err error) {
   213  	if maker, err = num.DecimalFromString(mkt.Fees.Factors.MakerFee); err != nil {
   214  		return
   215  	}
   216  	if infra, err = num.DecimalFromString(mkt.Fees.Factors.InfrastructureFee); err != nil {
   217  		return
   218  	}
   219  	if liquidity, err = num.DecimalFromString(mkt.Fees.Factors.LiquidityFee); err != nil {
   220  		return
   221  	}
   222  	if bb, err = num.DecimalFromString(mkt.Fees.Factors.BuyBackFee); err != nil {
   223  		return
   224  	}
   225  	if treasury, err = num.DecimalFromString(mkt.Fees.Factors.TreasuryFee); err != nil {
   226  		return
   227  	}
   228  
   229  	return
   230  }
   231  
   232  func setRefFeeFactors(rewards, discounts *partyFeeFactors, stats entities.FlattenReferralSetStats) error {
   233  	maker, err := num.DecimalFromString(stats.RewardFactors.MakerRewardFactor)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	rewards.maker = maker
   238  	infra, err := num.DecimalFromString(stats.RewardFactors.InfrastructureRewardFactor)
   239  	if err != nil {
   240  		return err
   241  	}
   242  	rewards.infra = infra
   243  	liquidity, err := num.DecimalFromString(stats.RewardFactors.LiquidityRewardFactor)
   244  	if err != nil {
   245  		return err
   246  	}
   247  	rewards.liquidity = liquidity
   248  
   249  	dmaker, err := num.DecimalFromString(stats.DiscountFactors.MakerDiscountFactor)
   250  	if err != nil {
   251  		return err
   252  	}
   253  	discounts.maker = dmaker
   254  	dinfra, err := num.DecimalFromString(stats.DiscountFactors.InfrastructureDiscountFactor)
   255  	if err != nil {
   256  		return err
   257  	}
   258  	discounts.infra = dinfra
   259  	dliquidity, err := num.DecimalFromString(stats.DiscountFactors.LiquidityDiscountFactor)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	discounts.liquidity = dliquidity
   264  
   265  	return nil
   266  }
   267  
   268  func setVolFeeFactors(ff *partyFeeFactors, stats entities.FlattenVolumeDiscountStats) error {
   269  	maker, err := num.DecimalFromString(stats.DiscountFactors.MakerDiscountFactor)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	ff.maker = maker
   274  	infra, err := num.DecimalFromString(stats.DiscountFactors.InfrastructureDiscountFactor)
   275  	if err != nil {
   276  		return err
   277  	}
   278  	ff.infra = infra
   279  	liquidity, err := num.DecimalFromString(stats.DiscountFactors.LiquidityDiscountFactor)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	ff.liquidity = liquidity
   284  	return nil
   285  }
   286  
   287  func (s *PSvc) getReferralTier(ctx context.Context, stats entities.FlattenReferralSetStats) (*vega.BenefitTier, error) {
   288  	if stats.RewardFactors == nil {
   289  		return nil, nil
   290  	}
   291  	current, err := s.rp.GetCurrentReferralProgram(ctx)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  	for i, bt := range current.BenefitTiers {
   296  		if bt.ReferralRewardFactors.InfrastructureRewardFactor == stats.RewardFactors.InfrastructureRewardFactor &&
   297  			bt.ReferralRewardFactors.LiquidityRewardFactor == stats.RewardFactors.LiquidityRewardFactor &&
   298  			bt.ReferralRewardFactors.MakerRewardFactor == stats.RewardFactors.MakerRewardFactor {
   299  			tierNumber := uint64(i)
   300  			bt.TierNumber = &tierNumber
   301  			return bt, nil
   302  		}
   303  	}
   304  	return nil, nil
   305  }
   306  
   307  func (s *PSvc) getVolumeDiscountTier(ctx context.Context, stats entities.FlattenVolumeDiscountStats) (*vega.VolumeBenefitTier, error) {
   308  	if stats.DiscountFactors == nil {
   309  		return nil, nil
   310  	}
   311  	vol, err := num.DecimalFromString(stats.RunningVolume)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	current, err := s.vd.GetCurrentVolumeDiscountProgram(ctx)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  	if len(current.BenefitTiers) == 0 {
   320  		return nil, nil
   321  	}
   322  	for i := len(current.BenefitTiers) - 1; i >= 0; i-- {
   323  		dt := current.BenefitTiers[i]
   324  		minV, _ := num.DecimalFromString(dt.MinimumRunningNotionalTakerVolume)
   325  		if vol.GreaterThanOrEqual(minV) {
   326  			dt.TierNumber = ptr.From(uint64(i))
   327  			return dt, nil
   328  		}
   329  	}
   330  	return nil, nil
   331  }
   332  
   333  func (s *PSvc) getVolumeRebateTier(ctx context.Context, stats entities.FlattenVolumeRebateStats) (*vega.VolumeRebateBenefitTier, error) {
   334  	current, err := s.vr.GetCurrentVolumeRebateProgram(ctx)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	vf, err := num.DecimalFromString(stats.MakerVolumeFraction)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  	if len(current.BenefitTiers) == 0 {
   343  		return nil, nil
   344  	}
   345  	for i := len(current.BenefitTiers) - 1; i >= 0; i-- {
   346  		bt := current.BenefitTiers[i]
   347  		minF, _ := num.DecimalFromString(bt.MinimumPartyMakerVolumeFraction)
   348  		if vf.GreaterThanOrEqual(minF) {
   349  			bt.TierNumber = ptr.From(uint64(i))
   350  			return bt, nil
   351  		}
   352  	}
   353  	return nil, nil
   354  }