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); }
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); }