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 }