Example #1
0
        public decimal GetInitMarginForOrder(Order order)
        {
            var accountAsset =
                _tradingInstrumentsCache.GetTradingInstrument(order.TradingConditionId, order.AssetPairId);
            var marginRate = _cfdCalculatorService.GetQuoteRateForBaseAsset(order.AccountAssetId, order.AssetPairId,
                                                                            order.LegalEntity, order.Direction == OrderDirection.Buy);
            var accountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId);

            return(Math.Round(
                       GetMargins(accountAsset, Math.Abs(order.Volume), marginRate).MarginInit,
                       accountBaseAssetAccuracy));
        }
Example #2
0
        public void SetCommissionRates(string tradingConditionId, Position position)
        {
            var tradingInstrument = _accountAssetsCacheService
                                    .GetTradingInstrument(tradingConditionId, position.AssetPairId);

            //TODO: understand what to do with comissions
        }
Example #3
0
        private bool CheckIfNetVolumeCanBeLiquidated(string accountId, string assetPairId, Position[] positions,
                                                     out string details)
        {
            var netPositionVolume = positions.Sum(p => p.Volume);

            var account           = _accountsCache.Get(accountId);
            var tradingInstrument = _tradingInstrumentsCacheService.GetTradingInstrument(account.TradingConditionId,
                                                                                         assetPairId);

            if (tradingInstrument.LiquidationThreshold > 0 &&
                Math.Abs(netPositionVolume) > tradingInstrument.LiquidationThreshold)
            {
                details = $"Threshold exceeded. Net volume : {netPositionVolume}. " +
                          $"Threshold : {tradingInstrument.LiquidationThreshold}.";
                return(false);
            }

            //TODO: discuss and handle situation with different MEs for different positions
            //at the current moment all positions has the same asset pair
            //and every asset pair can be processed only by one ME
            var anyPosition = positions.First();
            var me          = _matchingEngineRouter.GetMatchingEngineForClose(anyPosition.OpenMatchingEngineId);
            //the same for externalProvider..
            var externalProvider = anyPosition.ExternalProviderId;

            if (me.GetPriceForClose(assetPairId, netPositionVolume, externalProvider) == null)
            {
                details = $"Not enough depth of orderbook. Net volume : {netPositionVolume}.";
                return(false);
            }

            details = string.Empty;
            return(true);
        }
Example #4
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.");
        }
Example #5
0
        public async Task <(Order order, List <Order> relatedOrders)> ValidateRequestAndCreateOrders(
            OrderPlaceRequest request)
        {
            #region Validate properties

            if (request.Volume == 0)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume can not be 0");
            }

            var assetPair = GetAssetPairIfAvailableForTrading(request.InstrumentId, request.Type.ToType <OrderType>(),
                                                              request.ForceOpen, false);

            if (request.Type != OrderTypeContract.Market || request.AdditionalInfo.IsCancellationTrade(out _))
            {
                if (!request.Price.HasValue)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice,
                                                     $"Price is required for {request.Type} order");
                }
                else
                {
                    request.Price = Math.Round(request.Price.Value, assetPair.Accuracy);

                    if (request.Price <= 0)
                    {
                        throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice,
                                                         $"Price should be more than 0");
                    }
                }
            }
            else
            {
                //always ignore price for market orders
                request.Price = null;
            }

            var account = _accountsCacheService.TryGet(request.AccountId);

            if (account == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidAccount, "Account not found");
            }

            ValidateValidity(request.Validity, request.Type.ToType <OrderType>());

            ITradingInstrument tradingInstrument;
            try
            {
                tradingInstrument =
                    _tradingInstrumentsCache.GetTradingInstrument(account.TradingConditionId, assetPair.Id);
            }
            catch
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 $"Instrument {assetPair.Id} is not available for trading on selected account");
            }

            if (tradingInstrument.DealMinLimit > 0 && Math.Abs(request.Volume) < tradingInstrument.DealMinLimit)
            {
                throw new ValidateOrderException(OrderRejectReason.MinOrderSizeLimit,
                                                 $"The minimum volume of a single order is limited to {tradingInstrument.DealMinLimit} {tradingInstrument.Instrument}.");
            }

            #endregion

            var equivalentSettings = GetReportingEquivalentPricesSettings(account.LegalEntity);

            if (request.Type == OrderTypeContract.StopLoss ||
                request.Type == OrderTypeContract.TakeProfit ||
                request.Type == OrderTypeContract.TrailingStop)
            {
                var order = await ValidateAndGetSlOrTpOrder(request, request.Type, request.Price, equivalentSettings,
                                                            null);

                return(order, new List <Order>());
            }

            var initialParameters = await GetOrderInitialParameters(request.InstrumentId, account.LegalEntity,
                                                                    equivalentSettings, account.BaseAssetId);

            var volume = request.Direction == OrderDirectionContract.Sell ? -Math.Abs(request.Volume)
                : request.Direction == OrderDirectionContract.Buy ? Math.Abs(request.Volume)
                : request.Volume;

            var originator = GetOriginator(request.Originator);

            var baseOrder = new Order(initialParameters.Id, initialParameters.Code, request.InstrumentId, volume,
                                      initialParameters.Now, initialParameters.Now, request.Validity, account.Id, account.TradingConditionId,
                                      account.BaseAssetId, request.Price, equivalentSettings.EquivalentAsset, OrderFillType.FillOrKill,
                                      string.Empty, account.LegalEntity, request.ForceOpen, request.Type.ToType <OrderType>(),
                                      request.ParentOrderId, null, originator, initialParameters.EquivalentPrice,
                                      initialParameters.FxPrice, initialParameters.FxAssetPairId, initialParameters.FxToAssetPairDirection,
                                      OrderStatus.Placed, request.AdditionalInfo);

            ValidateBaseOrderPrice(baseOrder, baseOrder.Price);

            var relatedOrders = new List <Order>();

            if (request.StopLoss.HasValue)
            {
                request.StopLoss = Math.Round(request.StopLoss.Value, assetPair.Accuracy);

                if (request.StopLoss <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidStoploss,
                                                     $"StopLoss should be more then 0");
                }

                var orderType = request.UseTrailingStop ? OrderTypeContract.TrailingStop : OrderTypeContract.StopLoss;

                var sl = await ValidateAndGetSlOrTpOrder(request, orderType, request.StopLoss,
                                                         equivalentSettings, baseOrder);

                if (sl != null)
                {
                    relatedOrders.Add(sl);
                }
            }

            if (request.TakeProfit.HasValue)
            {
                request.TakeProfit = Math.Round(request.TakeProfit.Value, assetPair.Accuracy);

                if (request.TakeProfit <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit,
                                                     $"TakeProfit should be more then 0");
                }

                var tp = await ValidateAndGetSlOrTpOrder(request, OrderTypeContract.TakeProfit, request.TakeProfit,
                                                         equivalentSettings, baseOrder);

                if (tp != null)
                {
                    relatedOrders.Add(tp);
                }
            }

            await ValidateProductComplexityConfirmation(request, account);

            return(baseOrder, relatedOrders);
        }