예제 #1
0
        /// <summary>
        /// Check if there is sufficient buying power to execute this order.
        /// </summary>
        /// <param name="model">The <see cref="IBuyingPowerModel"/></param>
        /// <param name="portfolio">The algorithm's portfolio</param>
        /// <param name="security">The security to be traded</param>
        /// <param name="order">The order</param>
        /// <returns>Returns buying power information for an order</returns>
        public static HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(
            this IBuyingPowerModel model,
            SecurityPortfolioManager portfolio,
            Security security,
            Order order
            )
        {
            var parameters = new HasSufficientBuyingPowerForOrderParameters(portfolio, security, order);

            return(model.HasSufficientBuyingPowerForOrder(parameters));
        }
예제 #2
0
        /// <summary>
        /// Check if there is sufficient buying power to execute this order.
        /// </summary>
        /// <param name="parameters">An object containing the portfolio, the security and the order</param>
        /// <returns>Returns buying power information for an order</returns>
        public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters)
        {
            // short circuit the div 0 case
            if (parameters.Order.Quantity == 0)
            {
                return(new HasSufficientBuyingPowerForOrderResult(true));
            }

            var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id);

            if (ticket == null)
            {
                var reason = $"Null order ticket for id: {parameters.Order.Id}";
                Log.Error($"SecurityMarginModel.HasSufficientBuyingPowerForOrder(): {reason}");
                return(new HasSufficientBuyingPowerForOrderResult(false, reason));
            }

            if (parameters.Order.Type == OrderType.OptionExercise)
            {
                // for option assignment and exercise orders we look into the requirements to process the underlying security transaction
                var option     = (Option.Option)parameters.Security;
                var underlying = option.Underlying;

                if (option.IsAutoExercised(underlying.Close))
                {
                    var quantity = option.GetExerciseQuantity(parameters.Order.Quantity);

                    var newOrder = new LimitOrder
                    {
                        Id         = parameters.Order.Id,
                        Time       = parameters.Order.Time,
                        LimitPrice = option.StrikePrice,
                        Symbol     = underlying.Symbol,
                        Quantity   = option.Symbol.ID.OptionRight == OptionRight.Call ? quantity : -quantity
                    };

                    // we continue with this call for underlying
                    return(underlying.BuyingPowerModel.HasSufficientBuyingPowerForOrder(
                               new HasSufficientBuyingPowerForOrderParameters(parameters.Portfolio, underlying, newOrder)));
                }

                return(new HasSufficientBuyingPowerForOrderResult(true));
            }

            // When order only reduces or closes a security position, capital is always sufficient
            if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity))
            {
                return(new HasSufficientBuyingPowerForOrderResult(true));
            }

            var freeMargin = GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction);
            var initialMarginRequiredForOrder = GetInitialMarginRequiredForOrder(
                new InitialMarginRequiredForOrderParameters(parameters.Portfolio.CashBook,
                                                            parameters.Security,
                                                            parameters.Order));

            // pro-rate the initial margin required for order based on how much has already been filled
            var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity);
            var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder;

            if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin)
            {
                var reason = $"Id: {parameters.Order.Id}, " +
                             $"Initial Margin: {initialMarginRequiredForRemainderOfOrder.Normalize()}, " +
                             $"Free Margin: {freeMargin.Normalize()}";

                Log.Error($"SecurityMarginModel.HasSufficientBuyingPowerForOrder(): {reason}");
                return(new HasSufficientBuyingPowerForOrderResult(false, reason));
            }

            return(new HasSufficientBuyingPowerForOrderResult(true));
        }
예제 #3
0
        /// <summary>
        /// Check if there is sufficient buying power to execute this order.
        /// </summary>
        /// <param name="parameters">An object containing the portfolio, the security and the order</param>
        /// <returns>Returns buying power information for an order</returns>
        public override HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters)
        {
            var baseCurrency = parameters.Security as IBaseCurrencySymbol;

            if (baseCurrency == null)
            {
                return(new HasSufficientBuyingPowerForOrderResult(false, $"The '{parameters.Security.Symbol.Value}' security is not supported by this cash model. Currently only SecurityType.Crypto and SecurityType.Forex are supported."));
            }

            decimal totalQuantity;
            decimal orderQuantity;

            if (parameters.Order.Direction == OrderDirection.Buy)
            {
                // quantity available for buying in quote currency
                totalQuantity = parameters.Portfolio.CashBook[parameters.Security.QuoteCurrency.Symbol].Amount;
                orderQuantity = parameters.Order.AbsoluteQuantity * GetOrderPrice(parameters.Security, parameters.Order);
            }
            else
            {
                // quantity available for selling in base currency
                totalQuantity = parameters.Portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount;
                orderQuantity = parameters.Order.AbsoluteQuantity;
            }

            // calculate reserved quantity for open orders (in quote or base currency depending on direction)
            var openOrdersReservedQuantity = GetOpenOrdersReservedQuantity(parameters.Portfolio, parameters.Security, parameters.Order);

            bool isSufficient;
            var  reason = string.Empty;

            if (parameters.Order.Direction == OrderDirection.Sell)
            {
                // can sell available and non-reserved quantities
                isSufficient = orderQuantity <= totalQuantity - openOrdersReservedQuantity;
                if (!isSufficient)
                {
                    reason = $"Your portfolio holds {totalQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}, {openOrdersReservedQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol} of which are reserved for open orders, but your Sell order is for {orderQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Cash Modeling trading does not permit short holdings so ensure you only sell what you have, including any additional open orders.";
                }

                return(new HasSufficientBuyingPowerForOrderResult(isSufficient, reason));
            }

            if (parameters.Order.Type == OrderType.Market)
            {
                // include existing holdings (in quote currency)
                var holdingsValue =
                    parameters.Portfolio.CashBook.Convert(
                        parameters.Portfolio.CashBook[baseCurrency.BaseCurrencySymbol].Amount, baseCurrency.BaseCurrencySymbol, parameters.Security.QuoteCurrency.Symbol);

                // find a target value in account currency for buy market orders
                var targetValue =
                    parameters.Portfolio.CashBook.ConvertToAccountCurrency(totalQuantity - openOrdersReservedQuantity + holdingsValue,
                                                                           parameters.Security.QuoteCurrency.Symbol);

                // convert the target into a percent in relation to TPV
                var targetPercent = parameters.Portfolio.TotalPortfolioValue == 0 ? 0 : targetValue / parameters.Portfolio.TotalPortfolioValue;

                // maximum quantity that can be bought (in quote currency)
                var maximumQuantity =
                    GetMaximumOrderQuantityForTargetValue(
                        new GetMaximumOrderQuantityForTargetValueParameters(parameters.Portfolio, parameters.Security, targetPercent)).Quantity *GetOrderPrice(parameters.Security, parameters.Order);

                isSufficient = orderQuantity <= Math.Abs(maximumQuantity);
                if (!isSufficient)
                {
                    reason = $"Your portfolio holds {totalQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, {openOrdersReservedQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol} of which are reserved for open orders, but your Buy order is for {parameters.Order.AbsoluteQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Your order requires a total value of {orderQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, but only a total value of {Math.Abs(maximumQuantity).Normalize()} {parameters.Security.QuoteCurrency.Symbol} is available.";
                }

                return(new HasSufficientBuyingPowerForOrderResult(isSufficient, reason));
            }

            // for limit orders, add fees to the order cost
            var orderFee = 0m;

            if (parameters.Order.Type == OrderType.Limit)
            {
                orderFee = parameters.Security.FeeModel.GetOrderFee(parameters.Security, parameters.Order);
                orderFee = parameters.Portfolio.CashBook.Convert(orderFee, CashBook.AccountCurrency, parameters.Security.QuoteCurrency.Symbol);
            }

            isSufficient = orderQuantity <= totalQuantity - openOrdersReservedQuantity - orderFee;
            if (!isSufficient)
            {
                reason = $"Your portfolio holds {totalQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, {openOrdersReservedQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol} of which are reserved for open orders, but your Buy order is for {parameters.Order.AbsoluteQuantity.Normalize()} {baseCurrency.BaseCurrencySymbol}. Your order requires a total value of {orderQuantity.Normalize()} {parameters.Security.QuoteCurrency.Symbol}, but only a total value of {(totalQuantity - openOrdersReservedQuantity - orderFee).Normalize()} {parameters.Security.QuoteCurrency.Symbol} is available.";
            }

            return(new HasSufficientBuyingPowerForOrderResult(isSufficient, reason));
        }
예제 #4
0
        private HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters, OrderTicket ticket,
                                                                                        decimal?freeMarginToUse = null, decimal?initialMarginRequired = null)
        {
            // When order only reduces or closes a security position, capital is always sufficient
            if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity))
            {
                return(parameters.Sufficient());
            }

            var freeMargin = freeMarginToUse ?? GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction);
            var initialMarginRequiredForOrder = initialMarginRequired ?? GetInitialMarginRequiredForOrder(
                new InitialMarginRequiredForOrderParameters(
                    parameters.Portfolio.CashBook, parameters.Security, parameters.Order
                    ));

            // pro-rate the initial margin required for order based on how much has already been filled
            var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity);
            var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder;

            if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin)
            {
                return(parameters.Insufficient(Invariant($"Id: {parameters.Order.Id}, ") +
                                               Invariant($"Initial Margin: {initialMarginRequiredForRemainderOfOrder.Normalize()}, ") +
                                               Invariant($"Free Margin: {freeMargin.Normalize()}")
                                               ));
            }

            return(parameters.Sufficient());
        }
예제 #5
0
        /// <summary>
        /// Check if there is sufficient buying power to execute this order.
        /// </summary>
        /// <param name="parameters">An object containing the portfolio, the security and the order</param>
        /// <returns>Returns buying power information for an order</returns>
        public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters)
        {
            // short circuit the div 0 case
            if (parameters.Order.Quantity == 0)
            {
                return(parameters.Sufficient());
            }

            var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id);

            if (ticket == null)
            {
                return(parameters.Insufficient(
                           $"Null order ticket for id: {parameters.Order.Id}"
                           ));
            }

            if (parameters.Order.Type == OrderType.OptionExercise)
            {
                // for option assignment and exercise orders we look into the requirements to process the underlying security transaction
                var option     = (Option.Option)parameters.Security;
                var underlying = option.Underlying;

                if (option.IsAutoExercised(underlying.Close) && underlying.IsTradable)
                {
                    var quantity = option.GetExerciseQuantity(parameters.Order.Quantity);

                    var newOrder = new LimitOrder
                    {
                        Id         = parameters.Order.Id,
                        Time       = parameters.Order.Time,
                        LimitPrice = option.StrikePrice,
                        Symbol     = underlying.Symbol,
                        Quantity   = quantity
                    };

                    // we continue with this call for underlying
                    var parametersForUnderlying = parameters.ForUnderlying(newOrder);

                    var freeMargin = underlying.BuyingPowerModel.GetBuyingPower(parametersForUnderlying.Portfolio, parametersForUnderlying.Security, parametersForUnderlying.Order.Direction);
                    // we add the margin used by the option itself
                    freeMargin += GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(option, -parameters.Order.Quantity));

                    var initialMarginRequired = underlying.BuyingPowerModel.GetInitialMarginRequiredForOrder(
                        new InitialMarginRequiredForOrderParameters(parameters.Portfolio.CashBook, underlying, newOrder));

                    return(HasSufficientBuyingPowerForOrder(parametersForUnderlying, ticket, freeMargin, initialMarginRequired));
                }

                return(parameters.Sufficient());
            }

            return(HasSufficientBuyingPowerForOrder(parameters, ticket));
        }