public async Task HandleInternalTradesAsync(IReadOnlyCollection <InternalTrade> internalTrades)
        {
            foreach (InternalTrade internalTrade in internalTrades)
            {
                HedgeLimitOrder hedgeLimitOrder =
                    await _hedgeLimitOrderService.GetByIdAsync(internalTrade.LimitOrderId);

                if (hedgeLimitOrder == null)
                {
                    continue;
                }

                if (await _lykkeTradeService.ExistAsync(internalTrade.Id))
                {
                    _log.WarningWithDetails("Trade already registered.", internalTrade);
                    continue;
                }

                await _lykkeTradeService.RegisterAsync(internalTrade);

                await _positionService.UpdateAsync(hedgeLimitOrder.AssetId, Name, internalTrade.Type,
                                                   internalTrade.Volume, internalTrade.OppositeVolume);

                hedgeLimitOrder.ExecuteVolume(internalTrade.Volume);

                if (internalTrade.Status == TradeStatus.Fill)
                {
                    _hedgeLimitOrderService.Close(hedgeLimitOrder);
                }
            }
        }
        public async Task CreateLimitOrderAsync(string assetId, string exchange, LimitOrderType limitOrderType,
                                                decimal price, decimal volume, string userId)
        {
            AssetHedgeSettings assetHedgeSettings =
                await _assetHedgeSettingsService.GetByAssetIdAsync(assetId, exchange);

            if (assetHedgeSettings == null)
            {
                throw new InvalidOperationException("Asset hedge settings not found");
            }

            if (assetHedgeSettings.Mode != AssetHedgeMode.Manual)
            {
                throw new InvalidOperationException("Asset hedge settings mode should be 'Manual'");
            }

            if (!_exchangeAdapters.TryGetValue(assetHedgeSettings.Exchange, out IExchangeAdapter exchangeAdapter))
            {
                throw new InvalidOperationException("There is no exchange provider");
            }

            HedgeLimitOrder hedgeLimitOrder = HedgeLimitOrder.Create(assetHedgeSettings.Exchange,
                                                                     assetHedgeSettings.AssetId, assetHedgeSettings.AssetPairId, limitOrderType, PriceType.Limit, price,
                                                                     volume);

            hedgeLimitOrder.Context = new { price, volume, userId }.ToJson();

            _log.InfoWithDetails("Manual hedge limit order created", new { hedgeLimitOrder, userId });

            await exchangeAdapter.ExecuteLimitOrderAsync(hedgeLimitOrder);
        }
예제 #3
0
        public Task InsertAsync(HedgeLimitOrder hedgeLimitOrder)
        {
            var entity = new HedgeLimitOrderEntity(GetPartitionKey(hedgeLimitOrder.Id), GetRowKey(hedgeLimitOrder.Id));

            Mapper.Map(hedgeLimitOrder, entity);

            return(_storage.InsertAsync(entity));
        }
        public async Task ClosePositionAsync(string assetId, string exchange, string userId)
        {
            Position position = await _positionService.GetByAssetIdAsync(assetId, exchange);

            if (position == null)
            {
                throw new InvalidOperationException("Position not found");
            }

            AssetHedgeSettings assetHedgeSettings =
                await _assetHedgeSettingsService.GetByAssetIdAsync(assetId, exchange);

            if (assetHedgeSettings == null)
            {
                throw new InvalidOperationException("Asset hedge settings not found");
            }

            if (assetHedgeSettings.Mode == AssetHedgeMode.Auto)
            {
                throw new InvalidOperationException("Can not close position while asset hedge settings mode is 'Auto'");
            }

            if (!_exchangeAdapters.TryGetValue(assetHedgeSettings.Exchange, out IExchangeAdapter exchangeAdapter))
            {
                throw new InvalidOperationException("There is no exchange provider");
            }

            Quote quote = _quoteService.GetByAssetPairId(assetHedgeSettings.Exchange,
                                                         assetHedgeSettings.AssetPairId);

            if (quote == null)
            {
                throw new InvalidOperationException("No quote");
            }

            LimitOrderType limitOrderType = position.Volume > 0
                ? LimitOrderType.Sell
                : LimitOrderType.Buy;

            decimal price = limitOrderType == LimitOrderType.Sell
                ? quote.Ask
                : quote.Bid;

            decimal volume = Math.Abs(position.Volume);

            HedgeLimitOrder hedgeLimitOrder = HedgeLimitOrder.Create(assetHedgeSettings.Exchange,
                                                                     assetHedgeSettings.AssetId, assetHedgeSettings.AssetPairId, limitOrderType, PriceType.Limit, price,
                                                                     volume);

            hedgeLimitOrder.Context = new { userId }.ToJson();

            _log.InfoWithDetails("Manual hedge limit order created to closed position", new { hedgeLimitOrder, userId });

            await exchangeAdapter.ExecuteLimitOrderAsync(hedgeLimitOrder);
        }
        public async Task AddAsync(HedgeLimitOrder hedgeLimitOrder)
        {
            _cache.Set(hedgeLimitOrder);

            _memoryCache.Set(hedgeLimitOrder.Id, hedgeLimitOrder,
                             new MemoryCacheEntryOptions {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
            });

            await _hedgeLimitOrderRepository.InsertAsync(hedgeLimitOrder);
        }
        private async Task <IReadOnlyCollection <HedgeLimitOrder> > CreateLimitOrdersAsync(
            IEnumerable <AssetInvestment> assetInvestments)
        {
            HedgeSettings hedgeSettings = await _hedgeSettingsService.GetAsync();

            var hedgeLimitOrders = new List <HedgeLimitOrder>();

            foreach (AssetInvestment assetInvestment in assetInvestments)
            {
                AssetHedgeSettings assetHedgeSettings =
                    await _assetHedgeSettingsService.EnsureAsync(assetInvestment.AssetId);

                LimitOrderType limitOrderType = assetInvestment.RemainingAmount > 0
                    ? LimitOrderType.Sell
                    : LimitOrderType.Buy;

                if (!CanCreateHedgeLimitOrder(assetInvestment, assetHedgeSettings, hedgeSettings, limitOrderType))
                {
                    continue;
                }

                decimal commonThresholdUp = limitOrderType == LimitOrderType.Buy
                    ? hedgeSettings.ThresholdUpBuy
                    : hedgeSettings.ThresholdUpSell;

                LimitOrderPrice limitOrderPrice = LimitOrderPriceCalculator.Calculate(assetInvestment.Quote,
                                                                                      Math.Abs(assetInvestment.RemainingAmount), limitOrderType,
                                                                                      assetHedgeSettings.ThresholdUp ?? commonThresholdUp, hedgeSettings.MarketOrderMarkup);

                decimal price = limitOrderPrice.Price;

                decimal volume = Math.Abs(assetInvestment.RemainingAmount / assetInvestment.Quote.Mid);

                HedgeLimitOrder hedgeLimitOrder = HedgeLimitOrder.Create(assetHedgeSettings.Exchange,
                                                                         assetHedgeSettings.AssetId, assetHedgeSettings.AssetPairId, limitOrderType, limitOrderPrice.Type,
                                                                         price, volume);

                hedgeLimitOrder.Context = assetInvestment.ToJson();

                hedgeLimitOrders.Add(hedgeLimitOrder);
            }

            return(hedgeLimitOrders);
        }
        public async Task ExecuteLimitOrderAsync(HedgeLimitOrder hedgeLimitOrder)
        {
            VirtualTrade virtualTrade = VirtualTrade.Create(
                hedgeLimitOrder.Id,
                hedgeLimitOrder.AssetId,
                hedgeLimitOrder.AssetPairId,
                hedgeLimitOrder.Type == LimitOrderType.Sell
                    ? TradeType.Sell
                    : TradeType.Buy,
                hedgeLimitOrder.Timestamp,
                hedgeLimitOrder.Price,
                hedgeLimitOrder.Volume);

            await _positionService.UpdateAsync(virtualTrade.AssetId, Name, virtualTrade.Type, virtualTrade.Volume,
                                               virtualTrade.OppositeVolume);

            await _virtualTradeService.AddAsync(virtualTrade);

            _log.InfoWithDetails("Virtual trade executed", virtualTrade);
        }
        private async Task ApplyLimitOrdersAsync(IEnumerable <string> assets,
                                                 IReadOnlyCollection <HedgeLimitOrder> hedgeLimitOrders)
        {
            foreach (string assetId in assets)
            {
                AssetHedgeSettings assetHedgeSettings = await _assetHedgeSettingsService.GetByAssetIdAsync(assetId);

                HedgeLimitOrder hedgeLimitOrder = hedgeLimitOrders.SingleOrDefault(o => o.AssetId == assetId);

                if (_exchangeAdapters.TryGetValue(assetHedgeSettings.Exchange, out IExchangeAdapter exchangeAdapter))
                {
                    if (hedgeLimitOrder == null || hedgeLimitOrder.Error != LimitOrderError.None)
                    {
                        if (assetHedgeSettings.Mode != AssetHedgeMode.Manual)
                        {
                            await exchangeAdapter.CancelLimitOrderAsync(assetId);
                        }
                    }
                    else
                    {
                        if (assetHedgeSettings.Mode == AssetHedgeMode.Auto)
                        {
                            await exchangeAdapter.ExecuteLimitOrderAsync(hedgeLimitOrder);
                        }
                    }
                }
                else
                {
                    if (hedgeLimitOrder != null)
                    {
                        _log.WarningWithDetails("There is no exchange provider",
                                                new { assetHedgeSettings, hedgeLimitOrder });
                        // TODO: send health issue
                    }
                }
            }
        }
        public async Task ExecuteLimitOrderAsync(HedgeLimitOrder hedgeLimitOrder)
        {
            await _hedgeLimitOrderService.AddAsync(hedgeLimitOrder);

            AssetPairSettings assetPairSettings =
                await _instrumentService.GetAssetPairAsync(hedgeLimitOrder.AssetPairId, Name);

            if (assetPairSettings == null)
            {
                hedgeLimitOrder.Error        = LimitOrderError.Unknown;
                hedgeLimitOrder.ErrorMessage = "Instrument not configured";

                _log.WarningWithDetails("No settings for instrument", hedgeLimitOrder);

                return;
            }

            decimal price = hedgeLimitOrder.Price
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, hedgeLimitOrder.Type == LimitOrderType.Sell);

            decimal volume = Math.Round(hedgeLimitOrder.Volume, assetPairSettings.VolumeAccuracy);

            if (volume < assetPairSettings.MinVolume)
            {
                hedgeLimitOrder.Error = LimitOrderError.TooSmallVolume;
                return;
            }

            var limitOrder = new LimitOrder
            {
                Id     = hedgeLimitOrder.Id,
                Price  = price,
                Volume = volume,
                Type   = hedgeLimitOrder.Type
            };

            try
            {
                await _lykkeExchangeService.ApplyAsync(hedgeLimitOrder.AssetPairId, limitOrder);

                if (limitOrder.Error == LimitOrderError.None)
                {
                    _hedgeLimitOrders[hedgeLimitOrder.AssetId] = hedgeLimitOrder;
                }

                hedgeLimitOrder.Error        = limitOrder.Error;
                hedgeLimitOrder.ErrorMessage = limitOrder.ErrorMessage;
            }
            catch (ExchangeException exception)
            {
                hedgeLimitOrder.Error        = LimitOrderError.Unknown;
                hedgeLimitOrder.ErrorMessage = exception.Message;
            }
            catch (Exception exception)
            {
                hedgeLimitOrder.Error        = LimitOrderError.Unknown;
                hedgeLimitOrder.ErrorMessage = "Cannot create limit orders an unexpected error occurred";

                _log.WarningWithDetails("An error occurred during creating limit orders", exception, hedgeLimitOrder);
            }
        }
 private static string GetKey(HedgeLimitOrder hedgeLimitOrder)
 => GetKey(hedgeLimitOrder.AssetId);
 public void Close(HedgeLimitOrder hedgeLimitOrder)
 {
     _cache.Remove(GetKey(hedgeLimitOrder), o => o.Id == hedgeLimitOrder.Id);
 }
        public async Task CancelLimitOrderAsync(string assetId)
        {
            ExternalOrder externalOrder = await _externalOrderRepository.GetAsync(Name, assetId);

            if (externalOrder == null)
            {
                return;
            }

            ISpotController spotController = _exchangeAdapterClientFactory.GetSpotController(Name);

            try
            {
                await spotController.CancelLimitOrderAsync(new CancelLimitOrderRequest { OrderId = externalOrder.Id });

                HedgeLimitOrder hedgeLimitOrder =
                    await _hedgeLimitOrderService.GetByIdAsync(externalOrder.HedgeLimitOrderId);

                OrderModel order = await spotController.LimitOrderStatusAsync(externalOrder.Id);

                if (order == null)
                {
                    _log.WarningWithDetails("External order not found", externalOrder);
                    return;
                }

                if (order.ExecutionStatus == OrderStatus.Fill || order.ExecutionStatus == OrderStatus.Canceled)
                {
                    if (order.ExecutedVolume > 0)
                    {
                        await _positionService.UpdateAsync(hedgeLimitOrder.AssetId, hedgeLimitOrder.Exchange,
                                                           hedgeLimitOrder.Type == LimitOrderType.Sell
                                                           ?Domain.TradeType.Sell
                                                           : Domain.TradeType.Buy,
                                                           order.ExecutedVolume, order.ExecutedVolume *order.AvgExecutionPrice);

                        await _externalTradeService.RegisterAsync(new ExternalTrade
                        {
                            Id              = Guid.NewGuid().ToString("D"),
                            Exchange        = hedgeLimitOrder.Exchange,
                            LimitOrderId    = hedgeLimitOrder.Id,
                            ExchangeOrderId = externalOrder.Id,
                            AssetPairId     = hedgeLimitOrder.AssetPairId,
                            Type            = hedgeLimitOrder.Type == LimitOrderType.Sell
                                ? Domain.TradeType.Sell
                                : Domain.TradeType.Buy,
                            Timestamp = order.Timestamp,
                            Price     = order.AvgExecutionPrice,
                            Volume    = order.ExecutedVolume,
                            Status    = order.RemainingAmount > 0
                                ? TradeStatus.PartialFill
                                : TradeStatus.Fill,
                            OriginalVolume  = order.OriginalVolume,
                            RemainingVolume = order.RemainingAmount
                        });
                    }

                    await _externalOrderRepository.DeleteAsync(externalOrder.Exchange, externalOrder.Asset);

                    _hedgeLimitOrderService.Close(hedgeLimitOrder);
                }
                else
                {
                    _log.WarningWithDetails("Can not cancel external order in progress", externalOrder);
                }
            }
            catch (Exception exception)
            {
                _log.WarningWithDetails("An error occurred while canceling limit order", exception,
                                        externalOrder);
            }
        }
        public async Task ExecuteLimitOrderAsync(HedgeLimitOrder hedgeLimitOrder)
        {
            await _hedgeLimitOrderService.AddAsync(hedgeLimitOrder);

            ExternalOrder externalOrder =
                await _externalOrderRepository.GetAsync(hedgeLimitOrder.Exchange, hedgeLimitOrder.AssetId);

            if (externalOrder != null)
            {
                hedgeLimitOrder.Error        = LimitOrderError.Unknown;
                hedgeLimitOrder.ErrorMessage = "Already exists";

                return;
            }

            AssetPairSettings assetPairSettings =
                await _instrumentService.GetAssetPairAsync(hedgeLimitOrder.AssetPairId, hedgeLimitOrder.Exchange);

            if (assetPairSettings == null)
            {
                hedgeLimitOrder.Error        = LimitOrderError.Unknown;
                hedgeLimitOrder.ErrorMessage = "Instrument not configured";

                _log.WarningWithDetails("No settings for instrument", hedgeLimitOrder);

                return;
            }

            decimal price = hedgeLimitOrder.Price
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, hedgeLimitOrder.Type == LimitOrderType.Sell);

            decimal volume = Math.Round(hedgeLimitOrder.Volume, assetPairSettings.VolumeAccuracy);

            if (volume < assetPairSettings.MinVolume)
            {
                hedgeLimitOrder.Error = LimitOrderError.TooSmallVolume;
                return;
            }

            ISpotController spotController = _exchangeAdapterClientFactory.GetSpotController(Name);

            try
            {
                var assetPair = assetPairSettings.AssetPairId;
                // TODO: Remove this workaround
                if (Name == "NettingEngineDefault")
                {
                    assetPair = GetAssetPair(assetPair);
                }

                OrderIdResponse response = await spotController.CreateLimitOrderAsync(new LimitOrderRequest
                {
                    Instrument = assetPair,
                    TradeType  = hedgeLimitOrder.Type == LimitOrderType.Sell ? TradeType.Sell : TradeType.Buy,
                    Price      = price,
                    Volume     = volume
                });

                externalOrder = new ExternalOrder(response.OrderId, hedgeLimitOrder.Exchange,
                                                  hedgeLimitOrder.AssetId, hedgeLimitOrder.Id);

                await _externalOrderRepository.InsertAsync(externalOrder);

                _log.InfoWithDetails("External order created", externalOrder);
            }
            catch (Exception exception)
            {
                hedgeLimitOrder.Error        = LimitOrderError.Unknown;
                hedgeLimitOrder.ErrorMessage = exception.Message;

                _log.WarningWithDetails("An error occurred while creating external order", exception, hedgeLimitOrder);
            }
        }
        private async Task <IReadOnlyCollection <PositionReport> > CreateReports()
        {
            IReadOnlyCollection <Position> positions = await _positionService.GetAllAsync();

            IReadOnlyCollection <AssetInvestment> assetInvestments = _investmentService.GetAll();

            IReadOnlyCollection <HedgeLimitOrder> hedgeLimitOrders = _hedgeLimitOrderService.GetAll();

            HedgeSettings hedgeSettings = await _hedgeSettingsService.GetAsync();

            IReadOnlyCollection <AssetHedgeSettings>
            assetsHedgeSettings = await _assetHedgeSettingsService.GetAllAsync();

            string[] assets = positions.Select(o => o.AssetId)
                              .Union(assetInvestments.Select(o => o.AssetId))
                              .Union(assetsHedgeSettings.Select(o => o.AssetId))
                              .ToArray();

            var positionReports = new List <PositionReport>();

            foreach (string assetId in assets)
            {
                AssetHedgeSettings assetHedgeSettings = await _assetHedgeSettingsService.EnsureAsync(assetId);

                Position currentPosition = positions
                                           .SingleOrDefault(o => o.AssetId == assetId && o.Exchange == assetHedgeSettings.Exchange);

                HedgeLimitOrder hedgeLimitOrder = hedgeLimitOrders.SingleOrDefault(o => o.AssetId == assetId);

                AssetInvestment assetInvestment = assetInvestments.SingleOrDefault(o => o.AssetId == assetId);

                decimal?volumeInUsd = null;

                if (currentPosition != null)
                {
                    volumeInUsd = GetVolumeInUsd(currentPosition.AssetId, currentPosition.Exchange,
                                                 currentPosition.Volume);
                }

                Quote assetQuote;

                if (assetInvestment == null)
                {
                    assetQuote = _rateService.GetQuoteUsd(assetHedgeSettings.AssetId, assetHedgeSettings.Exchange);
                }
                else
                {
                    assetQuote = assetInvestment.Quote;
                }

                positionReports.Add(new PositionReport
                {
                    AssetId         = assetId,
                    Exchange        = assetHedgeSettings.Exchange,
                    Quote           = assetQuote,
                    Volume          = currentPosition?.Volume,
                    VolumeInUsd     = volumeInUsd,
                    OppositeVolume  = currentPosition?.OppositeVolume,
                    PnL             = volumeInUsd.HasValue ? currentPosition.OppositeVolume + volumeInUsd : null,
                    HedgeLimitOrder = hedgeLimitOrder,
                    AssetInvestment = assetInvestment,
                    Error           = ValidateAssetHedgeSettings(assetHedgeSettings)
                                      ?? ValidateInvestments(assetInvestment)
                                      ?? ValidateThresholdCritical(assetInvestment, hedgeSettings, assetHedgeSettings)
                                      ?? ValidateQuote(assetQuote)
                });

                IEnumerable <Position> otherPositions = positions
                                                        .Where(o => o.AssetId == assetId && o.Exchange != assetHedgeSettings.Exchange);

                foreach (Position position in otherPositions)
                {
                    Quote otherPositionQuote = _rateService.GetQuoteUsd(position.AssetId, position.Exchange);

                    volumeInUsd = GetVolumeInUsd(position.AssetId, position.Exchange, position.Volume);

                    positionReports.Add(new PositionReport
                    {
                        AssetId         = assetId,
                        Exchange        = position.Exchange,
                        Quote           = otherPositionQuote,
                        Volume          = position.Volume,
                        VolumeInUsd     = volumeInUsd,
                        OppositeVolume  = position.OppositeVolume,
                        PnL             = volumeInUsd.HasValue ? position.OppositeVolume + volumeInUsd : null,
                        HedgeLimitOrder = null,
                        AssetInvestment = null,
                        Error           = ValidateAssetHedgeSettings(assetHedgeSettings)
                                          ?? ValidateQuote(otherPositionQuote)
                    });
                }
            }

            foreach (PositionReport positionReport in positionReports)
            {
                if (positionReport.Exchange == ExchangeNames.Virtual)
                {
                    positionReport.ActualPnL = -1 * positionReport.PnL;
                }
                else
                {
                    positionReport.ActualPnL = positionReport.PnL;
                }
            }

            return(positionReports
                   .OrderBy(o => o.AssetId)
                   .ToArray());
        }