Example #1
0
        private async Task CreateEvent(LimitQueueItem.LimitOrderWithTrades limitOrderWithTrades, OrderStatus status)
        {
            var order     = limitOrderWithTrades.Order;
            var type      = order.Volume > 0 ? OrderType.Buy : OrderType.Sell;
            var assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(order.AssetPairId);

            var date = status == OrderStatus.InOrderBook ? limitOrderWithTrades.Order.CreatedAt : DateTime.UtcNow;

            var insertRequest = new LimitTradeEventInsertRequest
            {
                Volume    = order.Volume,
                Type      = type,
                OrderId   = order.Id,
                Status    = status,
                AssetId   = assetPair?.BaseAssetId,
                ClientId  = order.ClientId,
                Price     = order.Price,
                AssetPair = order.AssetPairId,
                DateTime  = date
            };

            await _limitTradeEventsRepositoryClient.CreateAsync(insertRequest);

            _log.Info(nameof(OperationHistoryProjection), $"Client {order.ClientId}. Limit trade {order.Id}. State has changed -> {status}", JsonConvert.SerializeObject(insertRequest, Formatting.Indented));
        }
Example #2
0
        public async Task <decimal?> ConvertPriceAsync(string assetPairId, decimal price)
        {
            AssetPair assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(assetPairId);

            if (assetPair.QuotingAssetId == UsdAssetId)
            {
                return(price);
            }

            IReadOnlyCollection <CrossRateInstrument> crossInstruments = await GetAllAsync();

            CrossRateInstrument crossInstrument = crossInstruments.SingleOrDefault(o => o.AssetPairId == assetPairId);

            if (crossInstrument == null)
            {
                _log.WarningWithDetails("Cross instrument not found", assetPairId);
                return(null);
            }

            Quote quote = await _quoteService
                          .GetAsync(crossInstrument.QuoteSource, crossInstrument.ExternalAssetPairId);

            if (quote == null)
            {
                _log.WarningWithDetails("No quote for cross instrument", crossInstrument);
                return(null);
            }

            (decimal crossPrice, decimal _) =
                Calculator.CalculateCrossMidPrice(price, quote, crossInstrument.IsInverse);

            return(crossPrice);
        }
 public Task <AssetPair> TryGetAssetPairAsync(string assetPairId)
 {
     return(Policy
            .Handle <Exception>()
            .WaitAndRetryForeverAsync(
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                (exception, timespan) => _log.Error(exception, context: new { assetPairId = assetPairId }))
            .ExecuteAsync(() => _apiService.TryGetAssetPairAsync(assetPairId)));
 }
Example #4
0
 public Task <AssetPair> TryGetAssetPairAsync(string assetPairId)
 {
     return(Policy
            .Handle <Exception>()
            .WaitAndRetryForeverAsync(
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                (exception, timespan) => _log.WriteErrorAsync("Get asset pair with retry", assetPairId, exception))
            .ExecuteAsync(() => _apiService.TryGetAssetPairAsync(assetPairId)));
 }
Example #5
0
        private async Task <ClientTrade[]> CreateTrades(LimitQueueItem.LimitOrderWithTrades limitOrderWithTrades)
        {
            if (limitOrderWithTrades.Trades == null || limitOrderWithTrades.Trades.Count == 0)
            {
                return(new ClientTrade[0]);
            }

            var assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(limitOrderWithTrades.Order.AssetPairId);

            var trades = limitOrderWithTrades.ToDomain(assetPair);

            return(trades.ToArray());
        }
        private async Task <bool> ValidateBalanceAsync(InternalOrder internalOrder)
        {
            Instrument instrument = await _instrumentService.TryGetByAssetPairIdAsync(internalOrder.AssetPairId);

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

            Asset   asset;
            decimal amount;

            if (internalOrder.Type == LimitOrderType.Sell)
            {
                asset = await _assetsServiceWithCache.TryGetAssetAsync(assetPair.QuotingAssetId);

                amount = (internalOrder.Volume * internalOrder.Price).TruncateDecimalPlaces(asset.Accuracy, true);
            }
            else
            {
                asset = await _assetsServiceWithCache.TryGetAssetAsync(assetPair.BaseAssetId);

                amount = internalOrder.Volume;
            }

            Balance balance = await _balanceService.GetByAssetIdAsync(ExchangeNames.Lykke, asset.Id);

            if (balance.Amount < amount)
            {
                internalOrder.Status       = InternalOrderStatus.Rejected;
                internalOrder.RejectReason = Errors.NotEnoughLiquidity;

                await _internalOrderRepository.UpdateAsync(internalOrder);

                return(false);
            }

            return(true);
        }
        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);
        }
Example #8
0
        public async Task <ClientTrade[]> Create(TradeQueueItem.MarketOrder order, List <TradeQueueItem.TradeInfo> trades, string clientId)
        {
            var assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(order.AssetPairId);

            var trade        = trades[0];
            var price        = ConvertExtensions.CalcEffectivePrice(trades, assetPair, order.Volume > 0);
            var marketVolume = trades.Sum(x => x.MarketVolume);
            var limitVolume  = trades.Sum(x => x.LimitVolume);

            var marketAssetRecord = CreateCommonPartForTradeRecord(trade, order.Id, price, order.AssetPairId);
            var limitAssetRecord  = CreateCommonPartForTradeRecord(trade, order.Id, price, order.AssetPairId);

            marketAssetRecord.ClientId = limitAssetRecord.ClientId = clientId;

            marketAssetRecord.Amount = marketVolume;
            if (Math.Sign(marketVolume) == Math.Sign(limitVolume))
            {
                marketAssetRecord.Amount *= -1;
            }
            marketAssetRecord.AssetId = trade.MarketAsset;

            limitAssetRecord.Amount  = limitVolume;
            limitAssetRecord.AssetId = trade.LimitAsset;

            foreach (var t in trades)
            {
                var transfer = t.Fees?.FirstOrDefault()?.Transfer;

                if (transfer != null)
                {
                    if (marketAssetRecord.AssetId == transfer.Asset)
                    {
                        marketAssetRecord.FeeSize += (double)transfer.Volume;
                        marketAssetRecord.FeeType  = FeeType.Absolute;
                    }
                    else
                    {
                        limitAssetRecord.FeeSize += (double)transfer.Volume;
                        limitAssetRecord.FeeType  = FeeType.Absolute;
                    }
                }
            }

            marketAssetRecord.Id = Core.Domain.CashOperations.Utils.GenerateRecordId(marketAssetRecord.DateTime);
            limitAssetRecord.Id  = Core.Domain.CashOperations.Utils.GenerateRecordId(limitAssetRecord.DateTime);

            return(new[] { marketAssetRecord, limitAssetRecord });
        }
        public async Task <decimal> GetFiatEquityMarkup(string assetPairId)
        {
            AssetPair lykkeAssetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(assetPairId);

            Domain.AssetSettings quoteAssetSettings = await _assetSettingsService.GetByLykkeIdAsync(lykkeAssetPair.QuotingAssetId);

            if (quoteAssetSettings == null)
            {
                _log.WarningWithDetails("Can't find asset settings while calculating fiat equity.", lykkeAssetPair.QuotingAssetId);

                return(0);
            }

            if (quoteAssetSettings.IsCrypto)
            {
                return(0);
            }

            decimal fiatEquity = GetFiatEquity();

            if (fiatEquity >= 0)
            {
                return(0);
            }

            MarketMakerSettings marketMakerSettings = await _marketMakerSettingsService.GetAsync();

            decimal thresholdFrom = marketMakerSettings.FiatEquityThresholdFrom;
            decimal thresholdTo   = marketMakerSettings.FiatEquityThresholdTo;
            decimal markupFrom    = marketMakerSettings.FiatEquityMarkupFrom;
            decimal markupTo      = marketMakerSettings.FiatEquityMarkupTo;

            if (thresholdFrom >= thresholdTo)
            {
                return(0);
            }

            decimal markup = CalculateMarkup(fiatEquity, thresholdFrom, thresholdTo, markupFrom, markupTo);

            return(markup);
        }
        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);
        }
Example #11
0
        public async Task <ValidationResult> ValidateLimitOrderAsync(string walletId, string assetPairId, OrderAction side, decimal price, decimal volume)
        {
            if (price <= 0)
            {
                return(new ValidationResult
                {
                    Message = ErrorMessages.LessThanZero(nameof(price)),
                    FieldName = nameof(price)
                });
            }

            if (volume <= 0)
            {
                return(new ValidationResult
                {
                    Message = ErrorMessages.LessThanZero(nameof(volume)),
                    FieldName = nameof(volume)
                });
            }

            var assetPair = await _assetsService.TryGetAssetPairAsync(assetPairId);

            if (assetPair == null)
            {
                return(new ValidationResult
                {
                    Message = ErrorMessages.AssetPairNotFound,
                    FieldName = nameof(assetPairId)
                });
            }

            if (volume < (decimal)assetPair.MinVolume)
            {
                return(new ValidationResult
                {
                    Message = ErrorMessages.MustBeGreaterThan(nameof(volume), assetPair.MinVolume.ToString(CultureInfo.InvariantCulture)),
                    FieldName = nameof(volume)
                });
            }

            decimal totalVolume;
            string  asset;

            if (side == OrderAction.Buy)
            {
                asset       = assetPair.QuotingAssetId;
                totalVolume = price * volume;
            }
            else
            {
                asset       = assetPair.BaseAssetId;
                totalVolume = volume;
            }

            var assetBalance = await _balancesClient.GetClientBalanceByAssetId(
                new ClientBalanceByAssetIdModel
            {
                ClientId = walletId,
                AssetId  = asset
            });

            if (assetBalance == null || assetBalance.Balance - assetBalance.Reserved < totalVolume)
            {
                return(new ValidationResult
                {
                    Message = ErrorMessages.NotEnoughFunds,
                    FieldName = nameof(volume)
                });
            }

            return(null);
        }
        private async Task CheckApi(string key, ILiquidityEngineClient client)
        {
            if (_lastTime == default(DateTime))
            {
                _lastTime = DateTime.UtcNow.Subtract(Interval);
            }

            var fromDate = _lastTime;
            var toDate   = DateTime.UtcNow;

            Log.Info($"Check api Start. summary. Api: {key}. LastTime: {_lastTime:yyyy-MM-dd HH:mm:ss}");

            var sb = new StringBuilder();

            sb.AppendLine($"=== {fromDate:yyyy/MM/dd HH:mm:ss} ===");
            sb.AppendLine($"=== {toDate:yyyy/MM/dd HH:mm:ss} ===");
            sb.Append(Environment.NewLine);
            sb.AppendLine("Liquidity Engine Summary Statistics");
            sb.Append(Environment.NewLine);

            var countTrade = 0;

            try
            {
                var data = await client.Reports.GetPositionsReportAsync(fromDate, toDate, int.MaxValue);

                var report = data
                             .Where(x => x.IsClosed)
                             .Where(x => x.CloseDate > fromDate)
                             .Where(x => x.CloseDate <= toDate)
                             .ToList();

                var summary = await client.Reports.GetBalanceIndicatorsReportAsync();

                var markups = await client.InstrumentMarkupsApi.GetAllAsync();

                var grouped = report.GroupBy(x => x.AssetPairId).OrderBy(x => x.Key);

                var result        = new List <string>();
                var totalPl       = 0m;
                var totalTurnover = 0m;
                foreach (var assetPairTrades in grouped)
                {
                    var assetPairId = assetPairTrades.Key;
                    var count       = assetPairTrades.Count();
                    var buyVolume   = assetPairTrades.Where(x => x.Type == PositionType.Long).Sum(x => x.Volume);
                    var sellVolume  = assetPairTrades.Where(x => x.Type == PositionType.Short).Sum(x => x.Volume);
                    var isPnLInUsd  = assetPairTrades.All(x => x.PnLUsd.HasValue);
                    var pnLInUsd    = isPnLInUsd ? assetPairTrades.Sum(x => x.PnLUsd ?? 0) : (decimal?)null;
                    var pnL         = assetPairTrades.Sum(x => x.PnL ?? 0);

                    var assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(assetPairId);

                    var quoteAssetId = assetPair?.QuotingAssetId;
                    var asset        = await _assetsServiceWithCache.TryGetAssetAsync(quoteAssetId);

                    var quoteAssetDisplayId = quoteAssetId == null ? null : asset.Id;
                    var quoteAssetStr       = string.IsNullOrWhiteSpace(quoteAssetDisplayId) ? "[quote asset]" : quoteAssetDisplayId;

                    var pnLStr = pnLInUsd.HasValue ? $"{Math.Round(pnLInUsd.Value, 4)}$" : $"{Math.Round(pnL, 4)} {quoteAssetStr}";

                    var lastTrade    = assetPairTrades.Where(e => e.PnLUsd.HasValue && e.PnLUsd.Value > 0).OrderByDescending(e => e.ClosePrice).FirstOrDefault();
                    var latsUsdPrice = (lastTrade != null && lastTrade.PnLUsd.HasValue && lastTrade.PnL.HasValue)
                        ? lastTrade.PnLUsd.Value / lastTrade.PnL.Value
                        : 0m;
                    decimal turnover    = assetPairTrades.Sum(e => e.Volume * e.ClosePrice) ?? 0m;
                    var     turnoverUsd = turnover * latsUsdPrice;

                    var assetPairMessage = $"{assetPairId}; " +
                                           $"PL={pnLStr}; " +
                                           $"Turnover: {Math.Round(turnoverUsd, 0)} $; ";

                    result.Add(assetPairMessage);

                    assetPairMessage = $". Count: {count}; " +
                                       $"Sell: {Math.Round(sellVolume, 6)}; " +
                                       $"Buy: {Math.Round(buyVolume, 6)}; ";

                    var markup = markups.SingleOrDefault(x => x.AssetPairId == assetPairId);
                    if (markup != null)
                    {
                        var bidMarkup = $"{markup.TotalBidMarkup*100:0.##}%";
                        var askMarkup = markup.TotalAskMarkup == -1 ? "stopped" : $"{markup.TotalAskMarkup*100:0.##}%";

                        assetPairMessage += $"BidMarkup: {bidMarkup}, AskMarkup: {askMarkup}";
                    }

                    result.Add(assetPairMessage);

                    totalPl       += pnLInUsd ?? 0m;
                    totalTurnover += turnoverUsd;
                }

                sb.AppendLine(string.Join(Environment.NewLine, result));
                sb.Append($"");

                var fee = totalTurnover > 0 ? totalPl / totalTurnover : 0m;

                sb.Append($"Total PL: {Math.Round(totalPl, 2)} $, Turnover: {Math.Round(totalTurnover, 2)} $, fee: {Math.Round(fee * 100, 2)} %");

                _lastTime = DateTime.UtcNow;

                await SendMessage(sb.ToString());

                var summaryMessage = $"Equity: {(int) summary.Equity} $.  RiskExposure: {(int) summary.RiskExposure} $";
                await SendMessage(summaryMessage);

                var fiatSettlement = await client.Trades.GetSettlementTradesAsync();

                if (fiatSettlement.Any(e => !e.IsCompleted))
                {
                    sb.Clear();
                    sb.AppendLine("Detect fiat trades without settlement in lykke: ");
                    foreach (var model in fiatSettlement.Where(e => !e.IsCompleted))
                    {
                        sb.AppendLine($"{model.AssetPair}, {model.Type}, price: {model.Price}, volume: {model.Volume}");
                    }
                    await SendMessage(sb.ToString());
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }

            Log.Info($"Check api complete. Found: {countTrade} asset pairs. Api: {key}. LastTime: {_lastTime:yyyy-MM-dd HH:mm:ss}");
        }
Example #13
0
        private async Task <OrderBook> CalculateDirectOrderBookAsync(Instrument instrument)
        {
            Quote[] quotes = _b2C2OrderBookService.GetQuotes(instrument.AssetPairId);

            if (quotes == null || quotes.Length != 2)
            {
                _log.WarningWithDetails("No quotes for instrument", instrument.AssetPairId);
                return(null);
            }

            Balance  baseAssetBalance   = null;
            Balance  quoteAssetBalance  = null;
            TimeSpan timeSinceLastTrade = TimeSpan.Zero;

            if (instrument.AllowSmartMarkup)
            {
                AssetPairLink assetPairLink =
                    await _assetPairLinkService.GetByInternalAssetPairIdAsync(instrument.AssetPairId);

                if (assetPairLink != null && !assetPairLink.IsEmpty())
                {
                    baseAssetBalance =
                        await _balanceService.GetByAssetIdAsync(ExchangeNames.B2C2, assetPairLink.ExternalBaseAssetId);

                    quoteAssetBalance =
                        await _balanceService.GetByAssetIdAsync(ExchangeNames.B2C2, assetPairLink.ExternalQuoteAssetId);

                    timeSinceLastTrade =
                        DateTime.UtcNow - _tradeService.GetLastInternalTradeTime(instrument.AssetPairId);
                }
                else
                {
                    _log.WarningWithDetails("The asset pair link does not configured", new { instrument.AssetPairId });
                }
            }

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

            Asset baseAsset = await _assetsServiceWithCache.TryGetAssetAsync(assetPair.BaseAssetId);

            MarketMakerSettings marketMakerSettings = await _marketMakerSettingsService.GetAsync();

            decimal pnLStopLossMarkup = await _pnLStopLossEngineService.GetTotalMarkupByAssetPairIdAsync(assetPair.Id);

            decimal fiatEquityStopLossMarkup = await _fiatEquityStopLossService.GetFiatEquityMarkup(assetPair.Id);

            decimal stopLossMarkup = await _noFreshQuotesStopLossService.GetNoFreshQuotesMarkup(assetPair.Id);

            IReadOnlyCollection <LimitOrder> limitOrders = Calculator.CalculateLimitOrders(
                quotes[0],
                quotes[1],
                instrument.Levels.ToArray(),
                baseAssetBalance?.Amount ?? 0,
                quoteAssetBalance?.Amount ?? 0,
                (int)timeSinceLastTrade.TotalSeconds,
                instrument.HalfLifePeriod,
                instrument.AllowSmartMarkup,
                marketMakerSettings.LimitOrderPriceMarkup,
                pnLStopLossMarkup,
                fiatEquityStopLossMarkup,
                stopLossMarkup,
                assetPair.Accuracy,
                baseAsset.Accuracy);

            await ValidateQuoteTimeoutAsync(limitOrders, quotes[0]);

            await ValidateQuoteTimeoutAsync(limitOrders, quotes[1]);

            ValidateMinVolume(limitOrders, (decimal)assetPair.MinVolume);

            await ValidatePriceAsync(limitOrders);

            await ValidateBalanceAsync(limitOrders, assetPair);

            WriteInfoLog(instrument.AssetPairId, quotes, timeSinceLastTrade, limitOrders,
                         "Direct limit orders calculated");

            return(new OrderBook
            {
                AssetPairId = instrument.AssetPairId,
                Time = DateTime.UtcNow,
                LimitOrders = limitOrders,
                IsDirect = true
            });
        }
Example #14
0
        public async Task <IActionResult> PlaceMarketOrder([FromBody] MarketOrderRequest request)
        {
            var id = Guid.NewGuid();

            var asset = await _assetsServiceWithCache.TryGetAssetAsync(request.AssetId);

            var pair = await _assetsServiceWithCache.TryGetAssetPairAsync(request.AssetPairId);

            if (asset == null)
            {
                return(NotFound($"Asset '{request.AssetId}' not found."));
            }

            if (pair == null)
            {
                return(NotFound($"Asset pair '{request.AssetPairId}' not found."));
            }

            if (pair.IsDisabled)
            {
                return(BadRequest($"Asset pair '{request.AssetPairId}' disabled."));
            }

            if (request.AssetId != pair.BaseAssetId && request.AssetId != pair.QuotingAssetId)
            {
                return(BadRequest());
            }

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

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

            var tradingSession = await _clientSessionsClient.GetTradingSession(_lykkePrincipal.GetToken());

            var confirmationRequired = _baseSettings.EnableSessionValidation && !(tradingSession?.Confirmed ?? false);

            if (confirmationRequired)
            {
                return(BadRequest("Session confirmation is required"));
            }

            var command = new CreateMarketOrderCommand
            {
                AssetId   = request.AssetId,
                AssetPair = new AssetPairModel
                {
                    Id                = request.AssetPairId,
                    BaseAsset         = ConvertAssetToAssetModel(baseAsset),
                    QuotingAsset      = ConvertAssetToAssetModel(quotingAsset),
                    MinVolume         = pair.MinVolume,
                    MinInvertedVolume = pair.MinInvertedVolume
                },
                Volume      = Math.Abs(request.Volume),
                OrderAction = request.OrderAction == Models.Orders.OrderAction.Buy
                                         ? Lykke.Service.Operations.Contracts.OrderAction.Buy
                                         : Lykke.Service.Operations.Contracts.OrderAction.Sell,
                Client         = await GetClientModel(),
                GlobalSettings = GetGlobalSettings()
            };

            try
            {
                await _operationsClient.PlaceMarketOrder(id, command);
            }
            catch (HttpOperationException e)
            {
                if (e.Response.StatusCode == HttpStatusCode.BadRequest)
                {
                    return(BadRequest(JObject.Parse(e.Response.Content)));
                }

                throw;
            }

            return(Created(Url.Action("Get", "Operations", new { id }), id));
        }
Example #15
0
        private async Task CheckApi(string key, IReportsApi reportsApi)
        {
            if (_lastTime == default(DateTime))
            {
                _lastTime = DateTime.UtcNow;
                return;
            }

            var fromDate = _lastTime;
            var toDate   = DateTime.UtcNow;

            Log.Info($"Check api Start. summary. Api: {key}. LastTime: {_lastTime:yyyy-MM-dd HH:mm:ss}");

            var sb = new StringBuilder();

            sb.AppendLine($"=== {fromDate:yyyy/MM/dd HH:mm:ss} ===");
            sb.AppendLine($"=== {toDate:yyyy/MM/dd HH:mm:ss} ===");
            sb.Append(Environment.NewLine);
            sb.AppendLine("Liquidity Engine Summary Statistics");
            sb.Append(Environment.NewLine);

            var countTrade = 0;

            try
            {
                var report = (await reportsApi.GetPositionsReportAsync(fromDate, toDate, int.MaxValue))
                             .Where(x => x.IsClosed)
                             .Where(x => x.CloseDate > fromDate)
                             .Where(x => x.CloseDate <= toDate)
                             .ToList();

                if (report.Count == 0)
                {
                    return;
                }

                var grouped = report.GroupBy(x => x.AssetPairId).OrderBy(x => x.Key);

                var result = new List <string>();
                foreach (var assetPairTrades in grouped)
                {
                    var assetPairId = assetPairTrades.Key;
                    var count       = assetPairTrades.Count();
                    var buyVolume   = assetPairTrades.Where(x => x.Type == PositionType.Long).Sum(x => x.Volume);
                    var sellVolume  = assetPairTrades.Where(x => x.Type == PositionType.Short).Sum(x => x.Volume);
                    var isPnLInUsd  = assetPairTrades.All(x => x.PnLUsd.HasValue);
                    var pnLInUsd    = isPnLInUsd ? assetPairTrades.Sum(x => x.PnLUsd.Value) : (decimal?)null;
                    var pnL         = assetPairTrades.Sum(x => x.PnL.Value);

                    var assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(assetPairId);

                    var quoteAssetId = assetPair?.QuotingAssetId;
                    var asset        = await _assetsServiceWithCache.TryGetAssetAsync(quoteAssetId);

                    var quoteAssetDisplayId = quoteAssetId == null ? null : asset.Id;
                    var quoteAssetStr       = string.IsNullOrWhiteSpace(quoteAssetDisplayId) ? "[quote asset]" : quoteAssetDisplayId;

                    var pnLStr = pnLInUsd.HasValue ? $"{Math.Round(pnLInUsd.Value, 4)}$" : $"{Math.Round(pnL, 4)} {quoteAssetStr}";

                    var assetPairMessage = $"{assetPairId}; " +
                                           $"PL={pnLStr}; " +
                                           $"Count: {count}; " +
                                           $"Sell: {Math.Round(sellVolume, 6)}; " +
                                           $"Buy: {Math.Round(buyVolume, 6)}; ";
                    result.Add(assetPairMessage);
                }

                sb.AppendLine(string.Join(Environment.NewLine, result));

                _lastTime = DateTime.UtcNow;

                await TelegramSender.SendTextMessageAsync(PublisherSettings.ChatId, sb.ToString());
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }

            Log.Info($"Check api complete. Found: {countTrade} asset pairs. Api: {key}. LastTime: {_lastTime:yyyy-MM-dd HH:mm:ss}");
        }
        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);
        }
        private async Task CheckApi(string key, ILiquidityEngineClient client)
        {
            var fromDate = DateTime.UtcNow.Date.AddDays(-1);
            var toDate   = DateTime.UtcNow.Date.AddDays(+1);

            if (!_lastClose.TryGetValue(key, out var lastClose))
            {
                lastClose       = DateTime.UtcNow;
                _lastClose[key] = lastClose;
            }

            Log.Info($"Check api started. Trades. Api: {key}. LastTime: {lastClose:yyyy-MM-dd HH:mm:ss}");

            var countTrade = 0;

            try
            {
                var data = await client.Reports.GetPositionsReportAsync(fromDate, toDate, 5000);

                var markups = await client.InstrumentMarkupsApi.GetAllAsync();

                var positions = data.Where(e => e.CloseDate > lastClose).ToList();

                foreach (var position in positions.OrderBy(e => e.CloseDate))
                {
                    var assetPair = await _assetsServiceWithCache.TryGetAssetPairAsync(position.AssetPairId);

                    var quoteAssetId = assetPair?.QuotingAssetId;
                    var asset        = await _assetsServiceWithCache.TryGetAssetAsync(quoteAssetId);

                    var quoteAssetDisplayId = quoteAssetId == null ? null : asset.Id;
                    var quoteAssetStr       = string.IsNullOrWhiteSpace(quoteAssetDisplayId) ? "[quote asset]" : quoteAssetDisplayId;

                    var markupModel = markups.Single(x => x.AssetPairId == position.AssetPairId);
                    var markupValue = position.Type == PositionType.Short ? markupModel.TotalAskMarkup : markupModel.TotalBidMarkup;
                    var markup      = markupValue == -1 ? "stopped" : (markupValue * 100).ToString("0.##") + "%";

                    var pnL        = position.PnL ?? 0;
                    var closePrice = position.ClosePrice ?? 0;
                    var pnLStr     = position.PnLUsd.HasValue ? $"{Math.Round(position.PnLUsd.Value, 4)}$" : $"{Math.Round(pnL, 4)} {quoteAssetStr}";

                    var message =
                        $"{position.AssetPairId}; " +
                        $"PL={pnLStr}; " +
                        $"{(position.Type == PositionType.Short ? "Sell" : "Buy")}; " +
                        $"Volume: {Math.Round(position.Volume, 6)}; " +
                        $"OpenPrice: {Math.Round(position.Price, 6)}; " +
                        $"ClosePrice: {Math.Round(closePrice, 6)}; " +
                        $"Close: {position.CloseDate:MM-dd HH:mm:ss}; " +
                        $"Markup: {markup}";

                    if (positions.Count >= 4800)
                    {
                        message += "; !!!max count of position in day, please add limit!!!";
                    }
                    await TelegramSender.SendTextMessageAsync(PublisherSettings.ChatId, message);

                    if (position.CloseDate.HasValue)
                    {
                        _lastClose[key] = position.CloseDate.Value;
                    }

                    countTrade++;
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex);
                _lastClose[key] = DateTime.UtcNow;
            }

            Log.Info($"Check api completed. Found: {countTrade} trades. Api: {key}. LastTime: {lastClose:yyyy-MM-dd HH:mm:ss}");
        }
Example #18
0
        private async Task <OrderBook> CalculateDirectOrderBookAsync(Instrument instrument, DateTime iterationDateTime)
        {
            Quote[] quotes = _b2C2OrderBookService.GetQuotes(instrument.AssetPairId);

            if (quotes == null || quotes.Length != 2)
            {
                _log.WarningWithDetails("No quotes for instrument", instrument.AssetPairId);
                return(null);
            }

            Balance  baseAssetBalance   = null;
            Balance  quoteAssetBalance  = null;
            TimeSpan timeSinceLastTrade = TimeSpan.Zero;

            if (instrument.AllowSmartMarkup)
            {
                AssetPairLink assetPairLink =
                    await _assetPairLinkService.GetByInternalAssetPairIdAsync(instrument.AssetPairId);

                if (assetPairLink != null && !assetPairLink.IsEmpty())
                {
                    baseAssetBalance =
                        await _balanceService.GetByAssetIdAsync(ExchangeNames.B2C2, assetPairLink.ExternalBaseAssetId);

                    quoteAssetBalance =
                        await _balanceService.GetByAssetIdAsync(ExchangeNames.B2C2, assetPairLink.ExternalQuoteAssetId);

                    timeSinceLastTrade =
                        DateTime.UtcNow - _tradeService.GetLastInternalTradeTime(instrument.AssetPairId);
                }
                else
                {
                    _log.WarningWithDetails("The asset pair link does not configured", new { instrument.AssetPairId });
                }
            }

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

            Asset baseAsset = await _assetsServiceWithCache.TryGetAssetAsync(assetPair.BaseAssetId);

            MarketMakerSettings marketMakerSettings = await _marketMakerSettingsService.GetAsync();

            decimal globalMarkup = marketMakerSettings.LimitOrderPriceMarkup;

            decimal noQuotesMarkup = await _noFreshQuotesStopLossService.GetNoFreshQuotesMarkup(assetPair.Id);

            decimal pnLStopLossMarkup = await _pnLStopLossEngineService.GetTotalMarkupByAssetPairIdAsync(assetPair.Id);

            decimal fiatEquityStopLossMarkup = await _fiatEquityStopLossService.GetFiatEquityMarkup(assetPair.Id);

            _log.InfoWithDetails("Arguments for Calculator.CalculateLimitOrders(...).", new
            {
                instrument.AssetPairId,
                quotes,
                levels                                   = instrument.Levels.ToArray(),
                baseAmountBalance                        = baseAssetBalance?.Amount ?? 0,
                quoteAmountBalance                       = quoteAssetBalance?.Amount ?? 0,
                timeSinceLastTradeTotalSeconds           = (int)timeSinceLastTrade.TotalSeconds,
                instrumentHalfLifePeriod                 = instrument.HalfLifePeriod,
                instrumentAllowSmartMarkup               = instrument.AllowSmartMarkup,
                marketMakerSettingsLimitOrderPriceMarkup = globalMarkup,
                pnLStopLossMarkup,
                fiatEquityStopLossMarkup,
                noQuotesMarkup,
                assetPairAccuracy = assetPair.Accuracy,
                baseAssetAccuracy = baseAsset.Accuracy,
                instrument
            });

            OrderBookUpdateReport orderBookUpdateReport = null;

            if (_isOrderBooksUpdateReportEnabled)
            {
                orderBookUpdateReport                    = new OrderBookUpdateReport(iterationDateTime);
                orderBookUpdateReport.AssetPair          = instrument.AssetPairId;
                orderBookUpdateReport.FirstQuoteAsk      = quotes[0].Ask;
                orderBookUpdateReport.FirstQuoteBid      = quotes[0].Bid;
                orderBookUpdateReport.SecondQuoteAsk     = quotes[1].Ask;
                orderBookUpdateReport.SecondQuoteBid     = quotes[1].Bid;
                orderBookUpdateReport.QuoteDateTime      = quotes[0].Time;
                orderBookUpdateReport.GlobalMarkup       = globalMarkup;
                orderBookUpdateReport.NoFreshQuoteMarkup = noQuotesMarkup;
                orderBookUpdateReport.PnLStopLossMarkup  = pnLStopLossMarkup;
                orderBookUpdateReport.FiatEquityMarkup   = fiatEquityStopLossMarkup;
            }

            IReadOnlyCollection <LimitOrder> limitOrders = Calculator.CalculateLimitOrders(
                quotes[0],
                quotes[1],
                instrument.Levels.ToArray(),
                baseAssetBalance?.Amount ?? 0,
                quoteAssetBalance?.Amount ?? 0,
                (int)timeSinceLastTrade.TotalSeconds,
                instrument.HalfLifePeriod,
                instrument.AllowSmartMarkup,
                globalMarkup,
                pnLStopLossMarkup,
                fiatEquityStopLossMarkup,
                noQuotesMarkup,
                assetPair.Accuracy,
                baseAsset.Accuracy,
                orderBookUpdateReport);

            await ValidateQuoteTimeoutAsync(limitOrders, quotes[0]);

            await ValidateQuoteTimeoutAsync(limitOrders, quotes[1]);

            ValidateMinVolume(limitOrders, (decimal)assetPair.MinVolume);

            await ValidatePriceAsync(limitOrders);

            await ValidateBalanceAsync(limitOrders, assetPair);

            WriteInfoLog(instrument.AssetPairId, quotes, timeSinceLastTrade, limitOrders,
                         "Direct limit orders calculated");

            if (orderBookUpdateReport != null)
            {
                await _orderBooksUpdatesReportPublisher.PublishAsync(orderBookUpdateReport);
            }

            return(new OrderBook
            {
                AssetPairId = instrument.AssetPairId,
                Time = DateTime.UtcNow,
                LimitOrders = limitOrders,
                IsDirect = true
            });
        }
        public async Task <IActionResult> PlaceMarketOrder([FromBody] MarketOrderRequest request)
        {
            var clientId = _requestContext.ClientId;

            var pair = await _assetsServiceWithCache.TryGetAssetPairAsync(request.AssetPairId);

            if (pair == null)
            {
                return(NotFound());
            }

            if (pair.IsDisabled)
            {
                return(BadRequest());
            }

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

            if (baseAsset == null)
            {
                return(NotFound());
            }

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

            if (quotingAsset == null)
            {
                return(BadRequest());
            }

            if (request.AssetId != baseAsset.Id && request.AssetId != baseAsset.Name &&
                request.AssetId != quotingAsset.Id && request.AssetId != quotingAsset.Name)
            {
                return(BadRequest());
            }

            var straight = request.AssetId == baseAsset.Id || request.AssetId == baseAsset.Name;
            var volume   =
                request.Volume.TruncateDecimalPlaces(straight ? baseAsset.Accuracy : quotingAsset.Accuracy);

            if (Math.Abs(volume) < double.Epsilon)
            {
                return(BadRequest(CreateErrorMessage("Required volume is less than asset accuracy")));
            }

            var order = new MarketOrderModel
            {
                Id                  = Guid.NewGuid().ToString(),
                AssetPairId         = pair.Id,
                ClientId            = clientId,
                ReservedLimitVolume = null,
                Straight            = straight,
                Volume              = Math.Abs(volume),
                OrderAction         = ToMeOrderAction(request.OrderAction)
            };

            if (_baseSettings.EnableFees)
            {
                order.Fees = new[]
                { await GetMarketOrderFee(clientId, request.AssetPairId, request.AssetId, request.OrderAction) }
            }
            ;

            var response = await _matchingEngineClient.HandleMarketOrderAsync(order);

            if (response == null)
            {
                throw new Exception("ME unavailable");
            }

            if (response.Status != MeStatusCodes.Ok)
            {
                return(BadRequest(CreateErrorMessage($"ME responded: {response.Status}")));
            }

            return(Ok(order.Id));
        }
Example #20
0
 public Task <AssetPair> GetAssetPairAsync(string assetPairId)
 {
     return(_assetsServiceWithCache.TryGetAssetPairAsync(assetPairId));
 }