public void EnrichOpeningOrder(Order order)
        {
            try
            {
                order.EquivalentAsset = GetEquivalentAsset(order.ClientId, order.AccountId);

                order.OpenPriceEquivalent = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset,
                                                                                            order.Instrument, order.LegalEntity);
            }
            catch (Exception e)
            {
                _log.WriteError("EnrichOpeningOrder", order.ToJson(), e);
            }
        }
Beispiel #2
0
        public void UpdateOrderFpl(IOrder order, FplData fplData)
        {
            fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId);
            fplData.QuoteRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, order.Instrument);

            var fpl = order.GetOrderType() == OrderDirection.Buy
                ? (order.ClosePrice - order.OpenPrice) * fplData.QuoteRate * order.GetMatchedVolume()
                : (order.OpenPrice - order.ClosePrice) * fplData.QuoteRate * order.GetMatchedVolume();

            fplData.Fpl = Math.Round(fpl, fplData.AccountBaseAssetAccuracy);

            var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument);

            fplData.MarginInit        = Math.Round(order.ClosePrice * order.GetMatchedVolume() * fplData.QuoteRate / accountAsset.LeverageInit, fplData.AccountBaseAssetAccuracy);
            fplData.MarginMaintenance = Math.Round(order.ClosePrice * order.GetMatchedVolume() * fplData.QuoteRate / accountAsset.LeverageMaintenance, fplData.AccountBaseAssetAccuracy);

            fplData.OpenCrossPrice  = Math.Round(order.OpenPrice * fplData.QuoteRate, order.AssetAccuracy);
            fplData.CloseCrossPrice = Math.Round(order.ClosePrice * fplData.QuoteRate, order.AssetAccuracy);

            fplData.OpenPrice     = order.OpenPrice;
            fplData.ClosePrice    = order.ClosePrice;
            fplData.SwapsSnapshot = order.GetSwaps();

            fplData.CalculatedHash = fplData.ActualHash;

            fplData.TotalFplSnapshot = order.GetTotalFpl(fplData.SwapsSnapshot);

            var account = _accountsCacheService.Get(order.ClientId, order.AccountId);

            account.CacheNeedsToBeUpdated();
        }
Beispiel #3
0
        private async Task <OrderInitialParameters> GetOrderInitialParameters(string assetPairId, string legalEntity,
                                                                              ReportingEquivalentPricesSettings equivalentSettings, string accountAssetId)
        {
            var fxAssetPairIdAndDirection = _cfdCalculatorService.GetFxAssetPairIdAndDirection(accountAssetId,
                                                                                               assetPairId, legalEntity);

            return(new OrderInitialParameters
            {
                Id = _identityGenerator.GenerateAlphanumericId(),
                Code = await _identityGenerator.GenerateIdAsync(nameof(Order)),
                Now = _dateService.Now(),
                EquivalentPrice = _cfdCalculatorService.GetQuoteRateForQuoteAsset(equivalentSettings.EquivalentAsset,
                                                                                  assetPairId, legalEntity),
                FxPrice = _cfdCalculatorService.GetQuoteRateForQuoteAsset(accountAssetId,
                                                                          assetPairId, legalEntity),
                FxAssetPairId = fxAssetPairIdAndDirection.id,
                FxToAssetPairDirection = fxAssetPairIdAndDirection.direction,
            });
        }
Beispiel #4
0
        public void Is_GetQuoteRateForQuoteAsset_Correct()
        {
            const string instrument = "USDCHF";

            _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(new InstrumentBidAskPair {
                Instrument = "USDCHF", Ask = 0.9982M, Bid = 0.9980M
            }));

            var quote = _quoteCacheService.GetQuote(instrument);

            Assert.IsNotNull(quote);
            Assert.AreEqual(0.9980, quote.Bid);
            Assert.AreEqual(0.9982, quote.Ask);

            var quoteRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(Accounts[0].BaseAssetId, instrument, "LYKKEVU");

            Assert.AreEqual(1.002004008016032064128256513, quoteRate);

            quoteRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(Accounts[0].BaseAssetId, instrument, "LYKKEVU", false);

            Assert.AreEqual(1.0018032458425165297535564015, quoteRate);
        }
Beispiel #5
0
        public void Is_GetQuoteRateForQuoteAsset_Correct()
        {
            const string instrument = "BTCUSD";

            _bestPriceConsumer.SendEvent(this, new BestPriceChangeEventArgs(new InstrumentBidAskPair {
                Instrument = instrument, Ask = 905.35M, Bid = 905.1M
            }));

            var quote = _quoteCacheService.GetQuote(instrument);

            Assert.IsNotNull(quote);
            Assert.AreEqual(905.1, quote.Bid);
            Assert.AreEqual(905.35, quote.Ask);

            var quoteRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(Accounts[0].BaseAssetId, instrument);

            Assert.AreEqual(1, quoteRate);
        }
Beispiel #6
0
        private decimal GetSwaps(string accountAssetId, string instrument, DateTime?openDate, DateTime?closeDate,
                                 decimal volume, decimal swapRate, string legalEntity)
        {
            decimal result = 0;

            if (openDate.HasValue)
            {
                var close   = closeDate ?? DateTime.UtcNow;
                var seconds = (decimal)(close - openDate.Value).TotalSeconds;

                const int secondsInYear = 31536000;
                var       quote         = _cfdCalculatorService.GetQuoteRateForQuoteAsset(accountAssetId, instrument, legalEntity,
                                                                                          swapRate > 0);
                var swaps = quote * volume * swapRate * seconds / secondsInYear;
                result = Math.Round(swaps, _assetsCache.GetAssetAccuracy(accountAssetId));
            }

            return(result);
        }
Beispiel #7
0
        private void UpdateOrderFplData(IOrder order, FplData fplData)
        {
            fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId);
            fplData.FplRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, order.Instrument,
                                                                              order.LegalEntity, order.Volume * (order.ClosePrice - order.OpenPrice) > 0);

            var fpl = (order.ClosePrice - order.OpenPrice) * fplData.FplRate * order.Volume;

            fplData.Fpl = Math.Round(fpl, fplData.AccountBaseAssetAccuracy);

            CalculateMargin(order, fplData);

            fplData.OpenPrice     = order.OpenPrice;
            fplData.ClosePrice    = order.ClosePrice;
            fplData.SwapsSnapshot = order.GetSwaps();

            fplData.CalculatedHash = fplData.ActualHash;

            fplData.TotalFplSnapshot = order.GetTotalFpl(fplData.SwapsSnapshot);

            _accountsCacheService.Get(order.ClientId, order.AccountId).CacheNeedsToBeUpdated();
        }
Beispiel #8
0
        public void CheckIsEnoughBalance(Order order, IMatchingEngineBase matchingEngine)
        {
            var orderMargin            = _fplService.GetInitMarginForOrder(order);
            var accountMarginAvailable = _accountsCacheService.Get(order.AccountId).GetMarginAvailable();

            var quote = _quoteCacheService.GetQuote(order.AssetPairId);

            var openPrice         = order.Price ?? 0;
            var closePrice        = 0m;
            var directionForClose = order.Volume.GetClosePositionOrderDirection();

            if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume) &&
                quote.GetVolumeForOrderDirection(directionForClose) >= Math.Abs(order.Volume))
            {
                closePrice = quote.GetPriceForOrderDirection(directionForClose);

                if (openPrice == 0)
                {
                    openPrice = quote.GetPriceForOrderDirection(order.Direction);
                }
            }
            else
            {
                var openPriceInfo  = matchingEngine.GetBestPriceForOpen(order.AssetPairId, order.Volume);
                var closePriceInfo =
                    matchingEngine.GetPriceForClose(order.AssetPairId, order.Volume, openPriceInfo.externalProviderId);

                if (openPriceInfo.price == null || closePriceInfo == null)
                {
                    throw new ValidateOrderException(OrderRejectReason.NoLiquidity,
                                                     "Price for open/close can not be calculated");
                }

                closePrice = closePriceInfo.Value;

                if (openPrice == 0)
                {
                    openPrice = openPriceInfo.price.Value;
                }
            }

            var pnlInTradingCurrency = (closePrice - openPrice) * order.Volume;
            var fxRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId,
                                                                         order.AssetPairId, order.LegalEntity,
                                                                         pnlInTradingCurrency > 0);
            var pnl = pnlInTradingCurrency * fxRate;

            // just in case... is should be always negative
            if (pnl > 0)
            {
                _log.WriteWarning(nameof(CheckIsEnoughBalance), order.ToJson(),
                                  $"Theoretical PnL at the moment of order execution is positive");
                pnl = 0;
            }

            if (accountMarginAvailable + pnl < orderMargin)
            {
                throw new ValidateOrderException(OrderRejectReason.NotEnoughBalance,
                                                 MtMessages.Validation_NotEnoughBalance,
                                                 $"Account available margin: {accountMarginAvailable}, order margin: {orderMargin}, pnl: {pnl} " +
                                                 $"(open price: {openPrice}, close price: {closePrice}, fx rate: {fxRate})");
            }
        }
Beispiel #9
0
        public void CheckIsEnoughBalance(Order order, IMatchingEngineBase matchingEngine, decimal additionalMargin)
        {
            _log.WriteInfo(nameof(CheckIsEnoughBalance), new { Order = order, additionalMargin }.ToJson(),
                           "Start checking if account balance is enough ...");

            var orderMargin = _fplService.GetInitMarginForOrder(order);

            _log.WriteInfo(nameof(CheckIsEnoughBalance), new { Order = order, orderMargin }.ToJson(),
                           "Order margin calculated");

            var account = _accountsProvider.GetAccountById(order.AccountId);
            var accountMarginAvailable = account.GetMarginAvailable() + additionalMargin;

            _log.WriteInfo(nameof(CheckIsEnoughBalance), new { Order = order, Account = account, accountMarginAvailable }.ToJson(),
                           "Account margin available calculated");

            var quote = _quoteCacheService.GetQuote(order.AssetPairId);

            decimal openPrice;
            decimal closePrice;
            var     directionForClose = order.Volume.GetClosePositionOrderDirection();

            if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume) &&
                quote.GetVolumeForOrderDirection(directionForClose) >= Math.Abs(order.Volume))
            {
                closePrice = quote.GetPriceForOrderDirection(directionForClose);
                openPrice  = quote.GetPriceForOrderDirection(order.Direction);
            }
            else
            {
                var openPriceInfo  = matchingEngine.GetBestPriceForOpen(order.AssetPairId, order.Volume);
                var closePriceInfo =
                    matchingEngine.GetPriceForClose(order.AssetPairId, order.Volume, openPriceInfo.externalProviderId);

                if (openPriceInfo.price == null || closePriceInfo == null)
                {
                    throw new ValidateOrderException(OrderRejectReason.NoLiquidity,
                                                     "Price for open/close can not be calculated");
                }

                closePrice = closePriceInfo.Value;
                openPrice  = openPriceInfo.price.Value;
            }
            _log.WriteInfo(nameof(CheckIsEnoughBalance), new { Order = order, Quote = quote, openPrice, closePrice }.ToJson(),
                           "Open and close prices calculated");


            var pnlInTradingCurrency = (closePrice - openPrice) * order.Volume;
            var fxRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId,
                                                                         order.AssetPairId, order.LegalEntity,
                                                                         pnlInTradingCurrency > 0);
            var pnl = pnlInTradingCurrency * fxRate;

            // just in case... is should be always negative
            if (pnl > 0)
            {
                _log.WriteWarning(nameof(CheckIsEnoughBalance), order.ToJson(),
                                  $"Theoretical PnL at the moment of order execution is positive");
                pnl = 0;
            }
            _log.WriteInfo(nameof(CheckIsEnoughBalance), new { Order = order, pnlInTradingCurrency, fxRate, pnl }.ToJson(),
                           "PNL calculated");

            var assetType = _assetPairsCache.GetAssetPairById(order.AssetPairId).AssetType;

            if (!_clientProfileSettingsCache.TryGetValue(account.TradingConditionId, assetType, out var clientProfileSettings))
            {
                throw new InvalidOperationException($"Client profile settings for [{account.TradingConditionId}] and asset type [{assetType}] were not found in cache");
            }

            var tradingInstrument =
                _tradingInstrumentsCache.GetTradingInstrument(account.TradingConditionId, order.AssetPairId);

            var entryCost = CostHelper.CalculateEntryCost(
                order.Price,
                order.Direction == OrderDirection.Buy ? Lykke.Snow.Common.Costs.OrderDirection.Buy : Lykke.Snow.Common.Costs.OrderDirection.Sell,
                quote.Ask,
                quote.Bid,
                fxRate,
                tradingInstrument.Spread,
                tradingInstrument.HedgeCost,
                _marginTradingSettings.BrokerDefaultCcVolume,
                _marginTradingSettings.BrokerDonationShare);

            _log.WriteInfo(nameof(CheckIsEnoughBalance),
                           new
            {
                OrderPrice = order.Price, OrderDirection = order.Direction, quote.Ask, quote.Bid, fxRate,
                tradingInstrument.Spread, tradingInstrument.HedgeCost, _marginTradingSettings.BrokerDefaultCcVolume,
                _marginTradingSettings.BrokerDonationShare, CalculatedEntryCost = entryCost
            }.ToJson(),
                           "Entry cost calculated");

            var exitCost = CostHelper.CalculateExitCost(
                order.Price,
                order.Direction == OrderDirection.Buy ? Lykke.Snow.Common.Costs.OrderDirection.Buy : Lykke.Snow.Common.Costs.OrderDirection.Sell,
                quote.Ask,
                quote.Bid,
                fxRate,
                tradingInstrument.Spread,
                tradingInstrument.HedgeCost,
                _marginTradingSettings.BrokerDefaultCcVolume,
                _marginTradingSettings.BrokerDonationShare);

            _log.WriteInfo(nameof(CheckIsEnoughBalance),
                           new
            {
                OrderPrice = order.Price, OrderDirection = order.Direction, quote.Ask, quote.Bid, fxRate,
                tradingInstrument.Spread, tradingInstrument.HedgeCost, _marginTradingSettings.BrokerDefaultCcVolume,
                _marginTradingSettings.BrokerDonationShare, CalculatedExitCost = exitCost
            }.ToJson(),
                           "Exit cost calculated");

            if (accountMarginAvailable + pnl - entryCost - exitCost < orderMargin)
            {
                throw new ValidateOrderException(OrderRejectReason.NotEnoughBalance,
                                                 MtMessages.Validation_NotEnoughBalance,
                                                 $"Account available margin: {accountMarginAvailable}, order margin: {orderMargin}, pnl: {pnl}, entry cost: {entryCost}, exit cost: {exitCost} " +
                                                 $"(open price: {openPrice}, close price: {closePrice}, fx rate: {fxRate})");
            }

            _log.WriteInfo(nameof(CheckIsEnoughBalance), new { Order = order, accountMarginAvailable, pnl, entryCost, exitCost, orderMargin }.ToJson(),
                           "Account balance is enough, validation succeeded.");
        }
Beispiel #10
0
        private async Task <Order> ExecuteOrderByMatchingEngineAsync(Order order, IMatchingEngineBase matchingEngine,
                                                                     bool checkStopout, OrderModality modality = OrderModality.Regular)
        {
            //TODO: think how not to execute one order twice!!!

            var now = _dateService.Now();

            //just in case )
            if (order.OrderType != OrderType.Market &&
                order.Validity.HasValue &&
                now.Date > order.Validity.Value.Date)
            {
                order.Expire(now);
                _orderCancelledEventChannel.SendEvent(this,
                                                      new OrderCancelledEventArgs(order,
                                                                                  new OrderCancelledMetadata {
                    Reason = OrderCancellationReasonContract.Expired
                }));
                return(order);
            }

            order.StartExecution(_dateService.Now(), matchingEngine.Id);

            _orderExecutionStartedEvenChannel.SendEvent(this, new OrderExecutionStartedEventArgs(order));

            if (order.PositionsToBeClosed.Any())
            {
                var netVolume    = 0M;
                var rejectReason = default(OrderRejectReason?);
                foreach (var positionId in order.PositionsToBeClosed)
                {
                    if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position))
                    {
                        rejectReason = OrderRejectReason.ParentPositionDoesNotExist;
                        continue;
                    }
                    if (position.Status != PositionStatus.Active)
                    {
                        rejectReason = OrderRejectReason.ParentPositionIsNotActive;
                        continue;
                    }

                    netVolume += position.Volume;

                    position.StartClosing(_dateService.Now(), order.OrderType.GetCloseReason(), order.Originator, "");
                }

                if (netVolume == 0M && rejectReason.HasValue)
                {
                    order.Reject(rejectReason.Value,
                                 rejectReason.Value == OrderRejectReason.ParentPositionDoesNotExist
                        ? "Related position does not exist"
                        : "Related position is not active", "", _dateService.Now());
                    _orderRejectedEventChannel.SendEvent(this, new OrderRejectedEventArgs(order));
                    return(order);
                }

                // there is no any global lock of positions / orders, that's why it is possible to have concurrency
                // in position close process
                // since orders, that have not empty PositionsToBeClosed should close positions and not open new ones
                // volume of executed order should be equal to position volume, but should have opposite sign
                if (order.Volume != -netVolume)
                {
                    var metadata = new OrderChangedMetadata
                    {
                        OldValue        = order.Volume.ToString("F2"),
                        UpdatedProperty = OrderChangedProperty.Volume
                    };
                    order.ChangeVolume(-netVolume, _dateService.Now(), order.Originator);
                    _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(order, metadata));
                }
            }

            var equivalentRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset,
                                                                                 order.AssetPairId, order.LegalEntity);
            var fxRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId,
                                                                         order.AssetPairId, order.LegalEntity);

            order.SetRates(equivalentRate, fxRate);

            var shouldOpenNewPosition = ShouldOpenNewPosition(order);

            if (modality == OrderModality.Regular && order.Originator != OriginatorType.System)
            {
                try
                {
                    _validateOrderService.MakePreTradeValidation(
                        order,
                        shouldOpenNewPosition,
                        matchingEngine);
                }
                catch (ValidateOrderException ex)
                {
                    RejectOrder(order, ex.RejectReason, ex.Message, ex.Comment);
                    return(order);
                }
            }

            var matchedOrders = await matchingEngine.MatchOrderAsync(order, shouldOpenNewPosition, modality);

            if (!matchedOrders.Any())
            {
                RejectOrder(order, OrderRejectReason.NoLiquidity, "No orders to match", "");
                return(order);
            }

            if (matchedOrders.SummaryVolume < Math.Abs(order.Volume))
            {
                if (order.FillType == OrderFillType.FillOrKill)
                {
                    RejectOrder(order, OrderRejectReason.NoLiquidity, "Not fully matched", "");
                    return(order);
                }
                else
                {
                    order.PartiallyExecute(_dateService.Now(), matchedOrders);
                    _ordersCache.InProgress.Add(order);
                    return(order);
                }
            }

            if (order.Status == OrderStatus.ExecutionStarted)
            {
                var accuracy = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId)?.Accuracy ??
                               AssetPairsCache.DefaultAssetPairAccuracy;

                order.Execute(_dateService.Now(), matchedOrders, accuracy);

                _orderExecutedEventChannel.SendEvent(this, new OrderExecutedEventArgs(order));

                if (checkStopout)
                {
                    var account      = _accountsCacheService.Get(order.AccountId);
                    var accountLevel = account.GetAccountLevel();

                    if (accountLevel == AccountLevel.StopOut)
                    {
                        CommitStopOut(account, null);
                    }
                    else if (accountLevel > AccountLevel.None)
                    {
                        _marginCallEventChannel.SendEvent(this, new MarginCallEventArgs(account, accountLevel));
                    }
                }
            }

            return(order);
        }
Beispiel #11
0
 public void EnrichOpeningOrder(Order order)
 {
     order.EquivalentAsset     = GetEquivalentAsset();
     order.OpenPriceEquivalent = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset,
                                                                                 order.Instrument);
 }