public void Set(AssetRealisedPnL assetRealisedPnL)
        {
            lock (_sync)
            {
                if (!_cache.ContainsKey(assetRealisedPnL.WalletId))
                {
                    _cache[assetRealisedPnL.WalletId] = new Dictionary <string, AssetRealisedPnL>();
                }

                _cache[assetRealisedPnL.WalletId][assetRealisedPnL.AssetId] = assetRealisedPnL;
            }
        }
        public async Task InitializeAsync(string walletId, string assetId, double amount)
        {
            if (Math.Abs(amount) <= Double.Epsilon)
            {
                return;
            }

            IReadOnlyCollection <AssetPair> assetPairs = await _assetsServiceWithCache.GetAllAssetPairsAsync();

            AssetPair assetPair = assetPairs
                                  .SingleOrDefault(o => o.BaseAssetId == assetId && o.QuotingAssetId == QuoteAssetId);

            if (assetPair == null)
            {
                assetPair = assetPairs
                            .SingleOrDefault(o => o.BaseAssetId == QuoteAssetId && o.QuotingAssetId == assetId);
            }

            if (assetPair == null)
            {
                throw new InvalidOperationException($"Asset pair found by asset '{assetId}' and '{QuoteAssetId}'");
            }

            Quote quote = await _quoteService.GetAsync(assetId, QuoteAssetId);

            var tradeData = new TradeData
            {
                Id                   = Guid.Empty.ToString("D"),
                Exchange             = ExchangeNames.Lykke,
                AssetPair            = assetPair.Id,
                BaseAsset            = assetId,
                QuoteAsset           = QuoteAssetId,
                Price                = quote.Mid,
                Volume               = (decimal)Math.Abs(amount),
                Type                 = amount < 0 ? TradeType.Sell : TradeType.Buy,
                Time                 = DateTime.UtcNow,
                LimitOrderId         = Guid.Empty.ToString("D"),
                OppositeClientId     = null,
                OppositeLimitOrderId = null
            };

            AssetRealisedPnL assetRealisedPnL = await CalculateAsync(tradeData, walletId, assetId);

            await _assetRealisedPnLRepository.InsertAsync(assetRealisedPnL);

            _log.InfoWithDetails("Realised PnL initialized", new { walletId, assetId, amount });

            _cache.Set(assetRealisedPnL);
        }
        public async Task CalculateAsync(LykkeTrade lykkeTrade)
        {
            WalletSettings walletSettings = await _walletSettingsService.GetWalletAsync(lykkeTrade.ClientId);

            if (walletSettings == null || !walletSettings.Enabled)
            {
                return;
            }

            AssetPair assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(lykkeTrade.AssetPairId);

            string[] assets = walletSettings.Assets
                              .Intersect(new[] { assetPair.BaseAssetId, assetPair.QuotingAssetId })
                              .ToArray();

            if (!assets.Any())
            {
                return;
            }

            var tradeData = new TradeData
            {
                Id                   = lykkeTrade.Id,
                Exchange             = ExchangeNames.Lykke,
                AssetPair            = assetPair.Id,
                BaseAsset            = assetPair.BaseAssetId,
                QuoteAsset           = assetPair.QuotingAssetId,
                Price                = lykkeTrade.Price,
                Volume               = lykkeTrade.Volume,
                Type                 = lykkeTrade.Type,
                Time                 = lykkeTrade.Time,
                LimitOrderId         = lykkeTrade.LimitOrderId,
                OppositeClientId     = lykkeTrade.OppositeClientId,
                OppositeLimitOrderId = lykkeTrade.OppositeLimitOrderId
            };

            foreach (string assetId in assets)
            {
                AssetRealisedPnL assetRealisedPnL =
                    await CalculateAsync(tradeData, walletSettings.Id, assetId);

                await _assetRealisedPnLRepository.InsertAsync(assetRealisedPnL);

                _cache.Set(assetRealisedPnL);
            }

            _log.InfoWithDetails("Lykke trade handled", tradeData);
        }
        public async Task CalculateAsync(ExternalTrade externalTrade)
        {
            IReadOnlyCollection <WalletSettings> walletsSettings = await _walletSettingsService.GetWalletsAsync();

            walletsSettings = walletsSettings.Where(o => o.Enabled && o.HandleExternalTrades).ToArray();

            if (!walletsSettings.Any())
            {
                return;
            }

            var tradeData = new TradeData
            {
                Id                   = externalTrade.OrderId,
                Exchange             = externalTrade.Exchange,
                AssetPair            = externalTrade.AssetPairId,
                BaseAsset            = externalTrade.BaseAssetId,
                QuoteAsset           = externalTrade.QuoteAssetId,
                Price                = externalTrade.Price,
                Volume               = externalTrade.Volume,
                Type                 = externalTrade.Type,
                Time                 = externalTrade.Time,
                LimitOrderId         = externalTrade.OrderId,
                OppositeClientId     = null,
                OppositeLimitOrderId = null
            };

            foreach (WalletSettings walletSettings in walletsSettings)
            {
                string[] assets = walletSettings.Assets
                                  .Intersect(new[] { externalTrade.BaseAssetId, externalTrade.QuoteAssetId })
                                  .ToArray();

                foreach (string assetId in assets)
                {
                    AssetRealisedPnL assetRealisedPnL =
                        await CalculateAsync(tradeData, walletSettings.Id, assetId);

                    await _assetRealisedPnLRepository.InsertAsync(assetRealisedPnL);

                    _cache.Set(assetRealisedPnL);
                }
            }

            _log.InfoWithDetails("External trade handled", tradeData);
        }
        private async Task <AssetRealisedPnL> CalculateAsync(TradeData tradeData, string walletId, string assetId)
        {
            AssetRealisedPnL prevAssetPnL = await _assetRealisedPnLRepository.GetLastAsync(walletId, assetId) ??
                                            new AssetRealisedPnL();

            bool inverted = tradeData.QuoteAsset == assetId;

            string crossAssetId = inverted
                ? tradeData.BaseAsset
                : tradeData.QuoteAsset;

            Quote quote = await _quoteService.GetAsync(assetId, QuoteAssetId);

            Quote crossQuote = await _quoteService.GetAsync(crossAssetId, QuoteAssetId);

            RealisedPnLResult realisedPnLResult = RealisedPnLCalculator.Calculate(
                tradeData.Price,
                tradeData.Volume,
                inverted,
                tradeData.Type == TradeType.Sell ? -1 : 1,
                prevAssetPnL.CumulativeVolume,
                prevAssetPnL.CumulativeOppositeVolume,
                quote.Mid,
                prevAssetPnL.AvgPrice,
                crossQuote.Mid);

            return(new AssetRealisedPnL
            {
                WalletId = walletId,
                AssetId = assetId,
                Time = tradeData.Time,
                Exchange = tradeData.Exchange,

                TradeId = tradeData.Id,
                TradeAssetPair = tradeData.AssetPair,
                TradePrice = tradeData.Price,
                TradeVolume = tradeData.Volume,
                TradeType = tradeData.Type,

                CrossAssetPair = crossQuote.AssetPair,
                CrossPrice = crossQuote.Mid,

                Price = realisedPnLResult.Price,
                Volume = realisedPnLResult.Volume,
                OppositeVolume = realisedPnLResult.OppositeVolume,
                Inverted = inverted,

                PrevAvgPrice = prevAssetPnL.AvgPrice,
                PrevCumulativeVolume = prevAssetPnL.CumulativeVolume,
                PrevCumulativeOppositeVolume = prevAssetPnL.CumulativeOppositeVolume,

                OpenPrice = prevAssetPnL.AvgPrice,
                ClosePrice = realisedPnLResult.Price,
                CloseVolume = realisedPnLResult.ClosedVolume,
                RealisedPnL = realisedPnLResult.RealisedPnL,

                AvgPrice = realisedPnLResult.AvgPrice,
                CumulativeVolume = realisedPnLResult.CumulativeVolume,
                CumulativeOppositeVolume = realisedPnLResult.CumulativeOppositeVolume,
                CumulativeRealisedPnL = prevAssetPnL.CumulativeRealisedPnL + realisedPnLResult.RealisedPnL,

                Rate = quote.Mid,
                UnrealisedPnL = realisedPnLResult.UnrealisedPnL,

                LimitOrderId = tradeData.LimitOrderId,
                OppositeClientId = tradeData.OppositeClientId,
                OppositeLimitOrderId = tradeData.OppositeLimitOrderId
            });
        }