private IStakingPower MapStakingPower(IStakeSettings stakeInfo, DataStakingPower stakePower, CurrencyCode currencyCode) { return(new BusinessStakingPower() { Symbol = stakeInfo.Symbol, Date = stakePower?.Date ?? stakeInfo.InceptionDate, Power = CurrencyConverter.Convert(stakePower?.Power ?? decimal.Zero, currencyCode), Summary = stakePower?.Summary .Select(fs => new BusinessStakingPowerSummary() { Symbol = GetFunds() .Single(x => x.ContractAddress.Address.Equals(fs.ContractAddress, StringComparison.OrdinalIgnoreCase)) .Symbol, Power = CurrencyConverter.Convert(fs.Power, currencyCode), }) .ToList() ?? new List <BusinessStakingPowerSummary>(), Breakdown = stakePower?.Breakdown .Select(fp => new BusinessStakingPowerFund() { Symbol = GetFunds() .Single(x => x.ContractAddress.Address.Equals(fp.ContractAddress, StringComparison.OrdinalIgnoreCase)) .Symbol, FundModifier = fp.FundModifier, Quantity = fp.Events.Sum(x => x.Quantity), ModifiedQuantity = fp.Events.Sum(x => x.Quantity * x.TimeModifier * fp.FundModifier), Power = CurrencyConverter.Convert(fp.PricePerToken * fp.Events.Sum(x => x.Quantity * x.TimeModifier * fp.FundModifier), currencyCode), }) .ToList() ?? new List <BusinessStakingPowerFund>() }); }
private async Task SyncStakingPowerAsync( IFundService fundService, IStakeService stakeService, IStakingPowerRepository stakingRepository, IStakeSettings stake, DateTimeOffset startDate, DateTimeOffset endDate, CancellationToken cancellationToken) { var currentDate = GetHourlyDate(startDate.UtcDateTime).UtcDateTime; var lastStakingPower = await stakingRepository.GetStakingPowerAsync(stake.ContractAddress, currentDate.AddHours(-1)); var stakingEvents = await stakeService.ListStakeEventsAsync(stake.Symbol, startDate.UtcDateTime, endDate.UtcDateTime) .ToListAsync(cancellationToken); var hourlyGroups = stakingEvents .OrderBy(x => x.ConfirmedAt) .GroupBy(x => GetHourlyDate(x.ConfirmedAt)) .ToList(); do { try { var fundPowers = new List <DataStakingPowerFund>(); var hourlyEvents = hourlyGroups.SingleOrDefault(g => g.Key == currentDate) ?? Enumerable.Empty <IStakeEvent>(); foreach (var symbol in stake.FundMultipliers.Keys) { var fund = AppSettings.Funds .Cast <IFundSettings>() .Single(x => x.Symbol == symbol); var events = lastStakingPower?.Breakdown.SingleOrDefault(x => x.ContractAddress.Equals(fund.ContractAddress.Address, StringComparison.OrdinalIgnoreCase)) ?.Events.ToList() ?? new List <DataStakingEvent>(); events.AddRange(hourlyEvents .Where(e => e.ContractAddress == fund.ContractAddress && e.Type == StakeEventType.Lockup) .Select(e => new DataStakingEvent() { UserAddress = e.UserAddress, StakedAt = e.ConfirmedAt, Quantity = e.Change, ExpiresAt = e.Lock.ExpiresAt, TimeModifier = stake.TimeMultipliers .SingleOrDefault(tm => tm.RangeMin <= e.Lock.Duration.Days && tm.RangeMax >= e.Lock.Duration.Days) ?.Multiplier ?? 1 })); foreach (var releaseEvent in hourlyEvents .Where(e => e.ContractAddress == fund.ContractAddress && e.Type != StakeEventType.Lockup)) { var approximateQuantity = releaseEvent.Release.Quantity + (releaseEvent.Release.FeeQuantity ?? decimal.Zero); var userStakes = events .Where(e => e.UserAddress.Equals(releaseEvent.UserAddress.Address, StringComparison.OrdinalIgnoreCase)) .ToList(); var lockUp = userStakes.Count > 0 ? userStakes.Count == 1 ? userStakes.Single() : userStakes .OrderBy(x => x.StakedAt) .FirstOrDefault(e => Math.Abs(e.Quantity - approximateQuantity) <= Precision) : null; if (lockUp != null) { events.Remove(lockUp); } } if (events.Any()) { var prices = await fundService.ListPerformanceAsync( symbol, PriceMode.Raw, currentDate.Date.AddDays(-1), currentDate.Date.AddDays(2), CurrencyCode.USD) .ToListAsync(cancellationToken); var closestPrice = prices .OrderBy(i => Math.Abs( new DateTimeOffset(i.Date, TimeSpan.Zero).ToUnixTimeSeconds() - new DateTimeOffset(currentDate, TimeSpan.Zero).ToUnixTimeSeconds())) .FirstOrDefault() ?? throw new PermanentException($"No Price data could be found for date {currentDate}"); fundPowers.Add(new DataStakingPowerFund() { ContractAddress = fund.ContractAddress, FundModifier = stake.FundMultipliers[symbol], PricePerToken = closestPrice.NetAssetValuePerToken, Events = events }); } } lastStakingPower = new DataStakingPower() { Address = stake.ContractAddress, Date = currentDate.AddHours(1), Power = fundPowers.Sum(fp => fp.PricePerToken * fp.Events.Sum(fpe => fpe.Quantity * fpe.TimeModifier * fp.FundModifier)), Breakdown = fundPowers, Summary = fundPowers .Select(fp => new DataStakingPowerSummary() { ContractAddress = fp.ContractAddress, Power = fp.PricePerToken * fp.Events.Sum(fpe => fpe.Quantity * fpe.TimeModifier * fp.FundModifier) }) .ToList() }; await stakingRepository.UploadItemsAsync(lastStakingPower); } finally { currentDate = currentDate.AddHours(1); } }while (!cancellationToken.IsCancellationRequested && currentDate < endDate.Round()); }