Beispiel #1
0
        private async Task HandleGenerationCycleAsync(string assetPair)
        {
            var lastCalculationTime = DateTime.UtcNow;

            var instrument = await _instrumentsAccessService.GetByAssetPairIdAsync(assetPair);

            var nextPack = await TryToCalculateNext(instrument.AssetPair, instrument.Volume, lastCalculationTime);

            var defaultSettings = await _settingsService.GetDefaultSettingsAsync();

            var lastPriceLifetime = instrument.PriceLifetime ?? defaultSettings.PriceLifetime;

            var recalculationThreshold = instrument.RecalculationInterval ?? defaultSettings.RecalculationInterval;

            while (_tokenSources[instrument.AssetPair] != null &&
                   !_tokenSources[instrument.AssetPair].IsCancellationRequested)
            {
                try
                {
                    instrument = await _instrumentsAccessService.GetByAssetPairIdAsync(assetPair);

                    defaultSettings = await _settingsService.GetDefaultSettingsAsync();

                    var priceLifetime = instrument.PriceLifetime ?? defaultSettings.PriceLifetime;

                    lastPriceLifetime = priceLifetime;

                    TryToPublish(instrument.AssetPair, nextPack);

                    await Task.Delay(
                        Min(priceLifetime - recalculationThreshold,
                            lastCalculationTime + priceLifetime - DateTime.UtcNow),
                        _tokenSources[instrument.AssetPair].Token);

                    recalculationThreshold = instrument.RecalculationInterval ?? defaultSettings.RecalculationInterval;

                    nextPack = await TryToCalculateNext(instrument.AssetPair, instrument.Volume,
                                                        lastCalculationTime + lastPriceLifetime);

                    await Task.Delay(
                        Max(lastCalculationTime + priceLifetime - DateTime.UtcNow, TimeSpan.Zero),
                        _tokenSources[instrument.AssetPair].Token);

                    lastCalculationTime += priceLifetime;
                }
                catch (TaskCanceledException)
                {
                }
                catch (Exception e)
                {
                    _log.Error(e, e.Message, instrument.AssetPair);
                }
            }
        }
        public async Task <Price> CreateAsync(string assetPair, OrderType type, decimal quotingVolume, DateTime validFrom)
        {
            var instrument = await _instrumentsAccessService.GetByAssetPairIdAsync(assetPair);

            if (instrument == null || instrument.State != InstrumentState.Active)
            {
                throw new FailedOperationException($"No active instrument {assetPair} was found.");
            }

            var orderBook = _orderBookService.GetByAssetPairId(instrument.Exchange, assetPair);

            if (type == OrderType.None)
            {
                throw new FailedOperationException($"Invalid order type {OrderType.None}");
            }

            var pair = await _assetsService.TryGetAssetPairAsync(assetPair);

            if (pair == null)
            {
                throw new FailedOperationException($"Pair {assetPair} not found.");
            }

            var baseAsset = await _assetsService.TryGetAssetAsync(pair.BaseAssetId);

            var quotingAsset = await _assetsService.TryGetAssetAsync(pair.QuotingAssetId);

            if (baseAsset == null)
            {
                throw new FailedOperationException($"Base asset {pair.BaseAssetId} not found.");
            }

            if (quotingAsset == null)
            {
                throw new FailedOperationException($"Quoting asset {pair.QuotingAssetId} not found.");
            }

            var orders = type == OrderType.Buy
                ? orderBook.SellLimitOrders.OrderBy(x => x.Price).ToList()
                : orderBook.BuyLimitOrders.OrderByDescending(x => x.Price).ToList();

            if (!orders.Any() || orders.Sum(x => x.Volume * x.Price) < quotingVolume)
            {
                throw new FailedOperationException("Not enough liquidity.");
            }

            var defaultSettings = await _settingsService.GetDefaultSettingsAsync();

            var markupCoefficient = instrument.Markup ?? defaultSettings.Markup;

            var midPrice = orderBook.GetMidPrice();

            if (!midPrice.HasValue)
            {
                throw new FailedOperationException("Not enough liquidity.");
            }

            var markupAbsolute = midPrice.Value * markupCoefficient;

            var volumes         = new List <decimal>();
            var remainingVolume = quotingVolume;

            for (var i = 0; i < orders.Count; i++)
            {
                var orderVolumeInUsd = orders[i].Volume * orders[i].Price;

                if (remainingVolume == 0m || remainingVolume <= (decimal)pair.MinVolume)
                {
                    break;
                }

                if (orderVolumeInUsd <= remainingVolume)
                {
                    volumes.Add(orders[i].Volume);

                    remainingVolume -= orderVolumeInUsd;
                }
                else
                {
                    volumes.Add(remainingVolume / orders[i].Price);

                    break;
                }
            }

            var volumeBase = Math.Round(volumes.Sum(), baseAsset.Accuracy);

            var originalPrice = (quotingVolume / volumes.Sum()).TruncateDecimalPlaces(pair.Accuracy, type == OrderType.Buy);

            var overallPrice =
                (originalPrice + markupAbsolute * (type == OrderType.Buy ? 1m : -1m))
                .TruncateDecimalPlaces(pair.Accuracy, type == OrderType.Buy);

            var validTo = validFrom + (instrument.PriceLifetime ?? defaultSettings.PriceLifetime);

            var allowedOverlap = instrument.OverlapTime ?? defaultSettings.OverlapTime;

            var price = new Price
            {
                Id             = Guid.NewGuid().ToString(),
                Value          = overallPrice,
                AssetPair      = assetPair,
                Exchange       = instrument.Exchange,
                BaseVolume     = volumeBase,
                QuotingVolume  = quotingVolume,
                Markup         = markupCoefficient,
                OriginalPrice  = originalPrice,
                Type           = type,
                ValidFrom      = validFrom,
                ValidTo        = validTo,
                AllowedOverlap = allowedOverlap
            };

            await _pricesRepository.InsertAsync(price);

            await UpdateIfLatestAsync(price);

            return(price);
        }
Beispiel #3
0
 public Task <Instrument> GetByAssetPairIdAsync(string assetPair)
 {
     return(_instrumentsAccessService.GetByAssetPairIdAsync(assetPair));
 }
        public async Task <Order> CreateAsync(string walletId, string priceId, decimal quotingVolume)
        {
            var price = await _pricesService.GetAsync(priceId);

            if (price == null)
            {
                throw new EntityNotFoundException();
            }

            var instrument = await _instrumentsAccessService.GetByAssetPairIdAsync(price.AssetPair);

            if (instrument.State != InstrumentState.Active)
            {
                throw new FailedOperationException($"Instrument {instrument.AssetPair} isn't active.");
            }

            var defaultSettings = await _settingsService.GetDefaultSettingsAsync();

            if (DateTime.UtcNow > price.ValidTo + (instrument.OverlapTime ?? defaultSettings.OverlapTime))
            {
                throw new FailedOperationException("Given price too old.");
            }

            if (quotingVolume > price.QuotingVolume)
            {
                throw new FailedOperationException("Requested volume higher than initial.");
            }

            var pair = await _assetsService.TryGetAssetPairAsync(price.AssetPair);

            var baseAsset = await _assetsService.TryGetAssetAsync(pair.BaseAssetId);

            var baseVolume = quotingVolume == price.QuotingVolume
                ? price.BaseVolume
                : (quotingVolume / price.Value).TruncateDecimalPlaces(baseAsset.Accuracy, price.Type == OrderType.Sell);

            var order = new Order
            {
                Id                   = Guid.NewGuid().ToString(),
                WalletId             = walletId,
                Type                 = price.Type,
                AssetPair            = price.AssetPair,
                QuotingVolume        = quotingVolume,
                BaseVolume           = baseVolume,
                PriceId              = priceId,
                CreatedTime          = DateTime.UtcNow,
                Status               = OrderStatus.New,
                ReserveTransferId    = Guid.NewGuid().ToString(),
                SettlementTransferId = Guid.NewGuid().ToString()
            };

            await _ordersRepository.InsertAsync(order);

            _log.Info("Order was created.", order);

            try
            {
                await _internalTransfersService.TransferAsync(
                    order.ReserveTransferId,
                    walletId,
                    await _settingsService.GetWalletIdAsync(),
                    order.Type == OrderType.Buy
                    ?pair.QuotingAssetId
                    : pair.BaseAssetId,
                    order.Type == OrderType.Buy
                    ?quotingVolume
                    : baseVolume);
            }
            catch (MeNotEnoughFundsException)
            {
                var rejectReason = "Client doesn't have enough funds.";

                await PersistWithStatusAsync(order, OrderStatus.Cancelled, rejectReason);

                throw new FailedOperationException(rejectReason);
            }
            catch (MeOperationException e)
            {
                var rejectReason = "ME call failed.";

                await PersistWithStatusAsync(order, OrderStatus.Cancelled, rejectReason);

                _log.Warning(rejectReason, priceId, e);

                throw new FailedOperationException(rejectReason);
            }
            catch (FailedOperationException e)
            {
                var rejectReason = "ME call failed.";

                await PersistWithStatusAsync(order, OrderStatus.Cancelled, rejectReason);

                _log.Warning(rejectReason, priceId, e);

                throw;
            }

            await PersistWithStatusAsync(order, OrderStatus.Reserved);

            return(order);
        }