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