/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Interfaces; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Securities.Option; using static QuantConnect.StringExtensions; namespace QuantConnect.Algorithm { public partial class QCAlgorithm { private int _maxOrders = 10000; private bool _isMarketOnOpenOrderWarningSent = false; /// /// Transaction Manager - Process transaction fills and order management. /// public SecurityTransactionManager Transactions { get; set; } /// /// Buy Stock (Alias of Order) /// /// string Symbol of the asset to trade /// int Quantity of the asset to trade /// public OrderTicket Buy(Symbol symbol, int quantity) { return Order(symbol, (decimal)Math.Abs(quantity)); } /// /// Buy Stock (Alias of Order) /// /// string Symbol of the asset to trade /// double Quantity of the asset to trade /// public OrderTicket Buy(Symbol symbol, double quantity) { return Order(symbol, Math.Abs(quantity).SafeDecimalCast()); } /// /// Buy Stock (Alias of Order) /// /// string Symbol of the asset to trade /// decimal Quantity of the asset to trade /// public OrderTicket Buy(Symbol symbol, decimal quantity) { return Order(symbol, Math.Abs(quantity)); } /// /// Buy Stock (Alias of Order) /// /// string Symbol of the asset to trade /// float Quantity of the asset to trade /// public OrderTicket Buy(Symbol symbol, float quantity) { return Order(symbol, (decimal)Math.Abs(quantity)); } /// /// Sell stock (alias of Order) /// /// string Symbol of the asset to trade /// int Quantity of the asset to trade /// public OrderTicket Sell(Symbol symbol, int quantity) { return Order(symbol, (decimal)Math.Abs(quantity) * -1); } /// /// Sell stock (alias of Order) /// /// String symbol to sell /// Quantity to order /// int Order Id. public OrderTicket Sell(Symbol symbol, double quantity) { return Order(symbol, Math.Abs(quantity).SafeDecimalCast() * -1m); } /// /// Sell stock (alias of Order) /// /// String symbol /// Quantity to sell /// int order id public OrderTicket Sell(Symbol symbol, float quantity) { return Order(symbol, (decimal)Math.Abs(quantity) * -1m); } /// /// Sell stock (alias of Order) /// /// String symbol to sell /// Quantity to sell /// Int Order Id. public OrderTicket Sell(Symbol symbol, decimal quantity) { return Order(symbol, Math.Abs(quantity) * -1); } /// /// Issue an order/trade for asset: Alias wrapper for Order(string, int); /// /// public OrderTicket Order(Symbol symbol, double quantity) { return Order(symbol, quantity.SafeDecimalCast()); } /// /// Issue an order/trade for asset /// /// public OrderTicket Order(Symbol symbol, int quantity) { return MarketOrder(symbol, (decimal)quantity); } /// /// Issue an order/trade for asset /// /// public OrderTicket Order(Symbol symbol, decimal quantity) { return MarketOrder(symbol, quantity); } /// /// Wrapper for market order method: submit a new order for quantity of symbol using type order. /// /// Symbol of the MarketType Required. /// Number of shares to request. /// Send the order asynchronously (false). Otherwise we'll block until it fills /// Place a custom order property or tag (e.g. indicator data). /// public OrderTicket Order(Symbol symbol, decimal quantity, bool asynchronous = false, string tag = "") { return MarketOrder(symbol, quantity, asynchronous, tag); } /// /// Market order implementation: Send a market order and wait for it to be filled. /// /// Symbol of the MarketType Required. /// Number of shares to request. /// Send the order asynchronously (false). Otherwise we'll block until it fills /// Place a custom order property or tag (e.g. indicator data). /// int Order id public OrderTicket MarketOrder(Symbol symbol, int quantity, bool asynchronous = false, string tag = "") { return MarketOrder(symbol, (decimal)quantity, asynchronous, tag); } /// /// Market order implementation: Send a market order and wait for it to be filled. /// /// Symbol of the MarketType Required. /// Number of shares to request. /// Send the order asynchronously (false). Otherwise we'll block until it fills /// Place a custom order property or tag (e.g. indicator data). /// int Order id public OrderTicket MarketOrder(Symbol symbol, double quantity, bool asynchronous = false, string tag = "") { return MarketOrder(symbol, quantity.SafeDecimalCast(), asynchronous, tag); } /// /// Market order implementation: Send a market order and wait for it to be filled. /// /// Symbol of the MarketType Required. /// Number of shares to request. /// Send the order asynchronously (false). Otherwise we'll block until it fills /// Place a custom order property or tag (e.g. indicator data). /// int Order id public OrderTicket MarketOrder(Symbol symbol, decimal quantity, bool asynchronous = false, string tag = "") { var security = Securities[symbol]; // check the exchange is open before sending a market order, if it's not open // then convert it into a market on open order if (!security.Exchange.ExchangeOpen) { var mooTicket = MarketOnOpenOrder(security.Symbol, quantity, tag); if (!_isMarketOnOpenOrderWarningSent) { var anyNonDailySubscriptions = security.Subscriptions.Any(x => x.Resolution != Resolution.Daily); if (mooTicket.SubmitRequest.Response.IsSuccess && !anyNonDailySubscriptions) { Debug("Warning: all market orders sent using daily data, or market orders sent after hours are automatically converted into MarketOnOpen orders."); _isMarketOnOpenOrderWarningSent = true; } } return mooTicket; } var request = CreateSubmitOrderRequest(OrderType.Market, security, quantity, tag, DefaultOrderProperties?.Clone()); // If warming up, do not submit if (IsWarmingUp) { return OrderTicket.InvalidWarmingUp(Transactions, request); } //Initialize the Market order parameters: var preOrderCheckResponse = PreOrderChecks(request); if (preOrderCheckResponse.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, preOrderCheckResponse); } //Add the order and create a new order Id. var ticket = Transactions.AddOrder(request); // Wait for the order event to process, only if the exchange is open if (!asynchronous) { Transactions.WaitForOrder(ticket.OrderId); } return ticket; } /// /// Market on open order implementation: Send a market order when the exchange opens /// /// The symbol to be ordered /// The number of shares to required /// Place a custom order property or tag (e.g. indicator data). /// The order ID public OrderTicket MarketOnOpenOrder(Symbol symbol, double quantity, string tag = "") { return MarketOnOpenOrder(symbol, quantity.SafeDecimalCast(), tag); } /// /// Market on open order implementation: Send a market order when the exchange opens /// /// The symbol to be ordered /// The number of shares to required /// Place a custom order property or tag (e.g. indicator data). /// The order ID public OrderTicket MarketOnOpenOrder(Symbol symbol, int quantity, string tag = "") { return MarketOnOpenOrder(symbol, (decimal)quantity, tag); } /// /// Market on open order implementation: Send a market order when the exchange opens /// /// The symbol to be ordered /// The number of shares to required /// Place a custom order property or tag (e.g. indicator data). /// The order ID public OrderTicket MarketOnOpenOrder(Symbol symbol, decimal quantity, string tag = "") { var security = Securities[symbol]; var request = CreateSubmitOrderRequest(OrderType.MarketOnOpen, security, quantity, tag, DefaultOrderProperties?.Clone()); var response = PreOrderChecks(request); if (response.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, response); } return Transactions.AddOrder(request); } /// /// Market on close order implementation: Send a market order when the exchange closes /// /// The symbol to be ordered /// The number of shares to required /// Place a custom order property or tag (e.g. indicator data). /// The order ID public OrderTicket MarketOnCloseOrder(Symbol symbol, int quantity, string tag = "") { return MarketOnCloseOrder(symbol, (decimal)quantity, tag); } /// /// Market on close order implementation: Send a market order when the exchange closes /// /// The symbol to be ordered /// The number of shares to required /// Place a custom order property or tag (e.g. indicator data). /// The order ID public OrderTicket MarketOnCloseOrder(Symbol symbol, double quantity, string tag = "") { return MarketOnCloseOrder(symbol, quantity.SafeDecimalCast(), tag); } /// /// Market on close order implementation: Send a market order when the exchange closes /// /// The symbol to be ordered /// The number of shares to required /// Place a custom order property or tag (e.g. indicator data). /// The order ID public OrderTicket MarketOnCloseOrder(Symbol symbol, decimal quantity, string tag = "") { var security = Securities[symbol]; var request = CreateSubmitOrderRequest(OrderType.MarketOnClose, security, quantity, tag, DefaultOrderProperties?.Clone()); var response = PreOrderChecks(request); if (response.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, response); } return Transactions.AddOrder(request); } /// /// Send a limit order to the transaction handler: /// /// String symbol for the asset /// Quantity of shares for limit order /// Limit price to fill this order /// String tag for the order (optional) /// Order id public OrderTicket LimitOrder(Symbol symbol, int quantity, decimal limitPrice, string tag = "") { return LimitOrder(symbol, (decimal)quantity, limitPrice, tag); } /// /// Send a limit order to the transaction handler: /// /// String symbol for the asset /// Quantity of shares for limit order /// Limit price to fill this order /// String tag for the order (optional) /// Order id public OrderTicket LimitOrder(Symbol symbol, double quantity, decimal limitPrice, string tag = "") { return LimitOrder(symbol, quantity.SafeDecimalCast(), limitPrice, tag); } /// /// Send a limit order to the transaction handler: /// /// String symbol for the asset /// Quantity of shares for limit order /// Limit price to fill this order /// String tag for the order (optional) /// Order id public OrderTicket LimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, string tag = "") { var security = Securities[symbol]; var request = CreateSubmitOrderRequest(OrderType.Limit, security, quantity, tag, limitPrice: limitPrice, properties: DefaultOrderProperties?.Clone()); var response = PreOrderChecks(request); if (response.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, response); } return Transactions.AddOrder(request); } /// /// Create a stop market order and return the newly created order id; or negative if the order is invalid /// /// String symbol for the asset we're trading /// Quantity to be traded /// Price to fill the stop order /// Optional string data tag for the order /// Int orderId for the new order. public OrderTicket StopMarketOrder(Symbol symbol, int quantity, decimal stopPrice, string tag = "") { return StopMarketOrder(symbol, (decimal)quantity, stopPrice, tag); } /// /// Create a stop market order and return the newly created order id; or negative if the order is invalid /// /// String symbol for the asset we're trading /// Quantity to be traded /// Price to fill the stop order /// Optional string data tag for the order /// Int orderId for the new order. public OrderTicket StopMarketOrder(Symbol symbol, double quantity, decimal stopPrice, string tag = "") { return StopMarketOrder(symbol, quantity.SafeDecimalCast(), stopPrice, tag); } /// /// Create a stop market order and return the newly created order id; or negative if the order is invalid /// /// String symbol for the asset we're trading /// Quantity to be traded /// Price to fill the stop order /// Optional string data tag for the order /// Int orderId for the new order. public OrderTicket StopMarketOrder(Symbol symbol, decimal quantity, decimal stopPrice, string tag = "") { var security = Securities[symbol]; var request = CreateSubmitOrderRequest(OrderType.StopMarket, security, quantity, tag, stopPrice: stopPrice, properties: DefaultOrderProperties?.Clone()); var response = PreOrderChecks(request); if (response.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, response); } return Transactions.AddOrder(request); } /// /// Send a stop limit order to the transaction handler: /// /// String symbol for the asset /// Quantity of shares for limit order /// Stop price for this order /// Limit price to fill this order /// String tag for the order (optional) /// Order id public OrderTicket StopLimitOrder(Symbol symbol, int quantity, decimal stopPrice, decimal limitPrice, string tag = "") { return StopLimitOrder(symbol, (decimal)quantity, stopPrice, limitPrice, tag); } /// /// Send a stop limit order to the transaction handler: /// /// String symbol for the asset /// Quantity of shares for limit order /// Stop price for this order /// Limit price to fill this order /// String tag for the order (optional) /// Order id public OrderTicket StopLimitOrder(Symbol symbol, double quantity, decimal stopPrice, decimal limitPrice, string tag = "") { return StopLimitOrder(symbol, quantity.SafeDecimalCast(), stopPrice, limitPrice, tag); } /// /// Send a stop limit order to the transaction handler: /// /// String symbol for the asset /// Quantity of shares for limit order /// Stop price for this order /// Limit price to fill this order /// String tag for the order (optional) /// Order id public OrderTicket StopLimitOrder(Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, string tag = "") { var security = Securities[symbol]; var request = CreateSubmitOrderRequest(OrderType.StopLimit, security, quantity, tag, stopPrice: stopPrice, limitPrice: limitPrice, properties: DefaultOrderProperties?.Clone()); var response = PreOrderChecks(request); if (response.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, response); } //Add the order and create a new order Id. return Transactions.AddOrder(request); } /// /// Send an exercise order to the transaction handler /// /// String symbol for the option position /// Quantity of options contracts /// Send the order asynchronously (false). Otherwise we'll block until it fills /// String tag for the order (optional) public OrderTicket ExerciseOption(Symbol optionSymbol, int quantity, bool asynchronous = false, string tag = "") { var option = (Option)Securities[optionSymbol]; var request = CreateSubmitOrderRequest(OrderType.OptionExercise, option, quantity, tag, DefaultOrderProperties?.Clone()); // If warming up, do not submit if (IsWarmingUp) { return OrderTicket.InvalidWarmingUp(Transactions, request); } //Initialize the exercise order parameters var preOrderCheckResponse = PreOrderChecks(request); if (preOrderCheckResponse.IsError) { return OrderTicket.InvalidSubmitRequest(Transactions, request, preOrderCheckResponse); } //Add the order and create a new order Id. var ticket = Transactions.AddOrder(request); // Wait for the order event to process, only if the exchange is open if (!asynchronous) { Transactions.WaitForOrder(ticket.OrderId); } return ticket; } // Support for option strategies trading /// /// Buy Option Strategy (Alias of Order) /// /// Specification of the strategy to trade /// Quantity of the strategy to trade /// Sequence of order ids public IEnumerable Buy(OptionStrategy strategy, int quantity) { return Order(strategy, Math.Abs(quantity)); } /// /// Sell Option Strategy (alias of Order) /// /// Specification of the strategy to trade /// Quantity of the strategy to trade /// Sequence of order ids public IEnumerable Sell(OptionStrategy strategy, int quantity) { return Order(strategy, Math.Abs(quantity) * -1); } /// /// Issue an order/trade for buying/selling an option strategy /// /// Specification of the strategy to trade /// Quantity of the strategy to trade /// Sequence of order ids public IEnumerable Order(OptionStrategy strategy, int quantity) { return GenerateOrders(strategy, quantity); } private IEnumerable GenerateOrders(OptionStrategy strategy, int strategyQuantity) { var orders = new List(); // setting up the tag text for all orders of one strategy var strategyTag = $"{strategy.Name} ({strategyQuantity.ToStringInvariant()})"; // walking through all option legs and issuing orders if (strategy.OptionLegs != null) { foreach (var optionLeg in strategy.OptionLegs) { var optionSeq = Securities.Where(kv => kv.Key.Underlying == strategy.Underlying && kv.Key.ID.OptionRight == optionLeg.Right && kv.Key.ID.Date == optionLeg.Expiration && kv.Key.ID.StrikePrice == optionLeg.Strike); if (optionSeq.Count() != 1) { throw new InvalidOperationException("Couldn't find the option contract in algorithm securities list. " + Invariant($"Underlying: {strategy.Underlying}, option {optionLeg.Right}, strike {optionLeg.Strike}, ") + Invariant($"expiration: {optionLeg.Expiration}")); } var option = optionSeq.First().Key; switch (optionLeg.OrderType) { case OrderType.Market: var marketOrder = MarketOrder(option, optionLeg.Quantity * strategyQuantity, tag: strategyTag); orders.Add(marketOrder); break; case OrderType.Limit: var limitOrder = LimitOrder(option, optionLeg.Quantity * strategyQuantity, optionLeg.OrderPrice, tag: strategyTag); orders.Add(limitOrder); break; default: throw new InvalidOperationException("Order type is not supported in option strategy: " + optionLeg.OrderType.ToString()); } } } // walking through all underlying legs and issuing orders if (strategy.UnderlyingLegs != null) { foreach (var underlyingLeg in strategy.UnderlyingLegs) { if (!Securities.ContainsKey(strategy.Underlying)) { var error = $"Couldn't find the option contract underlying in algorithm securities list. Underlying: {strategy.Underlying}"; throw new InvalidOperationException(error); } switch (underlyingLeg.OrderType) { case OrderType.Market: var marketOrder = MarketOrder(strategy.Underlying, underlyingLeg.Quantity * strategyQuantity, tag: strategyTag); orders.Add(marketOrder); break; case OrderType.Limit: var limitOrder = LimitOrder(strategy.Underlying, underlyingLeg.Quantity * strategyQuantity, underlyingLeg.OrderPrice, tag: strategyTag); orders.Add(limitOrder); break; default: throw new InvalidOperationException("Order type is not supported in option strategy: " + underlyingLeg.OrderType.ToString()); } } } return orders; } /// /// Perform pre-order checks to ensure we have sufficient capital, /// the market is open, and we haven't exceeded maximum realistic orders per day. /// /// OrderResponse. If no error, order request is submitted. private OrderResponse PreOrderChecks(SubmitOrderRequest request) { var response = PreOrderChecksImpl(request); if (response.IsError) { Error(response.ErrorMessage); } return response; } /// /// Perform pre-order checks to ensure we have sufficient capital, /// the market is open, and we haven't exceeded maximum realistic orders per day. /// /// OrderResponse. If no error, order request is submitted. private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request) { if (IsWarmingUp) { return OrderResponse.WarmingUp(request); } //Most order methods use security objects; so this isn't really used. // todo: Left here for now but should review Security security; if (!Securities.TryGetValue(request.Symbol, out security)) { return OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity, "You haven't requested " + request.Symbol.ToString() + " data. Add this with AddSecurity() in the Initialize() Method."); } //Ordering 0 is useless. if (request.Quantity == 0) { return OrderResponse.ZeroQuantity(request); } if (Math.Abs(request.Quantity) < security.SymbolProperties.LotSize) { return OrderResponse.Error(request, OrderResponseErrorCode.OrderQuantityLessThanLoteSize, Invariant($"Unable to {request.OrderRequestType.ToLower()} order with id {request.OrderId} which ") + Invariant($"quantity ({Math.Abs(request.Quantity)}) is less than lot ") + Invariant($"size ({security.SymbolProperties.LotSize}).") ); } if (!security.IsTradable) { return OrderResponse.Error(request, OrderResponseErrorCode.NonTradableSecurity, "The security with symbol '" + request.Symbol.ToString() + "' is marked as non-tradable."); } var price = security.Price; //Check the exchange is open before sending a market on close orders if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen) { return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open."); } //Check the exchange is open before sending a exercise orders if (request.OrderType == OrderType.OptionExercise && !security.Exchange.ExchangeOpen) { return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open."); } if (price == 0) { return OrderResponse.Error(request, OrderResponseErrorCode.SecurityPriceZero, request.Symbol.GetZeroPriceMessage()); } // check quote currency existence/conversion rate on all orders Cash quoteCash; var quoteCurrency = security.QuoteCurrency.Symbol; if (!Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash)) { return OrderResponse.Error(request, OrderResponseErrorCode.QuoteCurrencyRequired, request.Symbol.Value + ": requires " + quoteCurrency + " in the cashbook to trade."); } if (security.QuoteCurrency.ConversionRate == 0m) { return OrderResponse.Error(request, OrderResponseErrorCode.ConversionRateZero, request.Symbol.Value + ": requires " + quoteCurrency + " to have a non-zero conversion rate. This can be caused by lack of data."); } // need to also check base currency existence/conversion rate on forex orders if (security.Type == SecurityType.Forex || security.Type == SecurityType.Crypto) { Cash baseCash; var baseCurrency = ((IBaseCurrencySymbol)security).BaseCurrencySymbol; if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash)) { return OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " in the cashbook to trade."); } if (baseCash.ConversionRate == 0m) { return OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " to have non-zero conversion rates. This can be caused by lack of data."); } } //Make sure the security has some data: if (!security.HasData) { return OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData, "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."); } // We've already processed too many orders: max 10k if (!LiveMode && Transactions.OrdersCount > _maxOrders) { Status = AlgorithmStatus.Stopped; return OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders, $"You have exceeded maximum number of orders ({_maxOrders.ToStringInvariant()}), for unlimited orders upgrade your account." ); } if (request.OrderType == OrderType.OptionExercise) { if (security.Type != SecurityType.Option) return OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity, "The security with symbol '" + request.Symbol.ToString() + "' is not exercisable."); if (security.Holdings.IsShort) return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType, "The security with symbol '" + request.Symbol.ToString() + "' has a short option position. Only long option positions are exercisable."); if (request.Quantity > security.Holdings.Quantity) return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType, "Cannot exercise more contracts of '" + request.Symbol.ToString() + "' than is currently available in the portfolio. "); if (request.Quantity <= 0.0m) OrderResponse.ZeroQuantity(request); } if (request.OrderType == OrderType.MarketOnClose) { var nextMarketClose = security.Exchange.Hours.GetNextMarketClose(security.LocalTime, false); // must be submitted with at least 10 minutes in trading day, add buffer allow order submission var latestSubmissionTime = nextMarketClose.Subtract(Orders.MarketOnCloseOrder.DefaultSubmissionTimeBuffer); if (!security.Exchange.ExchangeOpen || Time > latestSubmissionTime) { // tell the user we require a 16 minute buffer, on minute data in live a user will receive the 3:44->3:45 bar at 3:45, // this is already too late to submit one of these orders, so make the user do it at the 3:43->3:44 bar so it's submitted // to the brokerage before 3:45. return OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate, "MarketOnClose orders must be placed with at least a 16 minute buffer before market close."); } } // passes all initial order checks return OrderResponse.Success(request); } /// /// Liquidate all holdings and cancel open orders. Called at the end of day for tick-strategies. /// /// Symbols we wish to liquidate /// Custom tag to know who is calling this. /// Array of order ids for liquidated symbols /// public List Liquidate(Symbol symbolToLiquidate = null, string tag = "Liquidated") { var orderIdList = new List(); if (!Settings.LiquidateEnabled) { Debug("Liquidate() is currently disabled by settings. To re-enable please set 'Settings.LiquidateEnabled' to true"); return orderIdList; } IEnumerable toLiquidate; if (symbolToLiquidate != null) { toLiquidate = Securities.ContainsKey(symbolToLiquidate) ? new[] { symbolToLiquidate } : Enumerable.Empty(); } else { toLiquidate = Securities.Keys.OrderBy(x => x.Value); } foreach (var symbol in toLiquidate) { // get open orders var orders = Transactions.GetOpenOrders(symbol); // get quantity in portfolio var quantity = Portfolio[symbol].Quantity; // if there is only one open market order that would close the position, do nothing if (orders.Count == 1 && quantity != 0 && orders[0].Quantity == -quantity && orders[0].Type == OrderType.Market) continue; // cancel all open orders var marketOrdersQuantity = 0m; foreach (var order in orders) { if (order.Type == OrderType.Market) { // pending market order var ticket = Transactions.GetOrderTicket(order.Id); if (ticket != null) { // get remaining quantity marketOrdersQuantity += ticket.Quantity - ticket.QuantityFilled; } } else { Transactions.CancelOrder(order.Id, tag); } } // Liquidate at market price if (quantity != 0) { // calculate quantity for closing market order var ticket = Order(symbol, -quantity - marketOrdersQuantity, tag: tag); if (ticket.Status == OrderStatus.Filled) { orderIdList.Add(ticket.OrderId); } } } return orderIdList; } /// /// Maximum number of orders for the algorithm /// /// public void SetMaximumOrders(int max) { if (!_locked) { _maxOrders = max; } } /// /// Sets holdings for a collection of targets. /// The implementation will order the provided targets executing first those that /// reduce a position, freeing margin. /// /// The portfolio desired quantities as percentages /// True will liquidate existing holdings /// public void SetHoldings(List targets, bool liquidateExistingHoldings = false) { foreach (var portfolioTarget in targets // we need to create targets with quantities for OrderTargetsByMarginImpact .Select(target => new PortfolioTarget(target.Symbol, CalculateOrderQuantity(target.Symbol, target.Quantity))) .OrderTargetsByMarginImpact(this, targetIsDelta:true)) { SetHoldingsImpl(portfolioTarget.Symbol, portfolioTarget.Quantity, liquidateExistingHoldings); } } /// /// Alias for SetHoldings to avoid the M-decimal errors. /// /// string symbol we wish to hold /// double percentage of holdings desired /// liquidate existing holdings if necessary to hold this stock /// public void SetHoldings(Symbol symbol, double percentage, bool liquidateExistingHoldings = false) { SetHoldings(symbol, percentage.SafeDecimalCast(), liquidateExistingHoldings); } /// /// Alias for SetHoldings to avoid the M-decimal errors. /// /// string symbol we wish to hold /// float percentage of holdings desired /// bool liquidate existing holdings if necessary to hold this stock /// Tag the order with a short string. /// public void SetHoldings(Symbol symbol, float percentage, bool liquidateExistingHoldings = false, string tag = "") { SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag); } /// /// Alias for SetHoldings to avoid the M-decimal errors. /// /// string symbol we wish to hold /// float percentage of holdings desired /// bool liquidate existing holdings if necessary to hold this stock /// Tag the order with a short string. /// public void SetHoldings(Symbol symbol, int percentage, bool liquidateExistingHoldings = false, string tag = "") { SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag); } /// /// Automatically place a market order which will set the holdings to between 100% or -100% of *PORTFOLIO VALUE*. /// E.g. SetHoldings("AAPL", 0.1); SetHoldings("IBM", -0.2); -> Sets portfolio as long 10% APPL and short 20% IBM /// E.g. SetHoldings("AAPL", 2); -> Sets apple to 2x leveraged with all our cash. /// If the market is closed, place a market on open order. /// /// Symbol indexer /// decimal fraction of portfolio to set stock /// bool flag to clean all existing holdings before setting new faction. /// Tag the order with a short string. /// public void SetHoldings(Symbol symbol, decimal percentage, bool liquidateExistingHoldings = false, string tag = "") { SetHoldingsImpl(symbol, CalculateOrderQuantity(symbol, percentage), liquidateExistingHoldings, tag); } /// /// Set holdings implementation, which uses order quantities (delta) not percentage nor target final quantity /// private void SetHoldingsImpl(Symbol symbol, decimal orderQuantity, bool liquidateExistingHoldings = false, string tag = "") { //If they triggered a liquidate if (liquidateExistingHoldings) { foreach (var kvp in Portfolio) { var holdingSymbol = kvp.Key; var holdings = kvp.Value; if (holdingSymbol != symbol && holdings.AbsoluteQuantity > 0) { //Go through all existing holdings [synchronously], market order the inverse quantity: var liquidationQuantity = CalculateOrderQuantity(holdingSymbol, 0m); Order(holdingSymbol, liquidationQuantity, false, tag); } } } //Calculate total unfilled quantity for open market orders var marketOrdersQuantity = Transactions.GetOpenOrderTickets( ticket => ticket.Symbol == symbol && (ticket.OrderType == OrderType.Market || ticket.OrderType == OrderType.MarketOnOpen)) .Aggregate(0m, (d, ticket) => d + ticket.Quantity - ticket.QuantityFilled); //Only place trade if we've got > 1 share to order. var quantity = orderQuantity - marketOrdersQuantity; if (Math.Abs(quantity) > 0) { Security security; if (!Securities.TryGetValue(symbol, out security)) { Error($"{symbol} not found in portfolio. Request this data when initializing the algorithm."); return; } //Check whether the exchange is open to send a market order. If not, send a market on open order instead if (security.Exchange.ExchangeOpen) { MarketOrder(symbol, quantity, false, tag); } else { MarketOnOpenOrder(symbol, quantity, tag); } } } /// /// Calculate the order quantity to achieve target-percent holdings. /// /// Security object we're asking for /// Target percentage holdings /// Order quantity to achieve this percentage public decimal CalculateOrderQuantity(Symbol symbol, double target) { return CalculateOrderQuantity(symbol, target.SafeDecimalCast()); } /// /// Calculate the order quantity to achieve target-percent holdings. /// /// Security object we're asking for /// Target percentage holdings, this is an unleveraged value, so /// if you have 2x leverage and request 100% holdings, it will utilize half of the /// available margin /// Order quantity to achieve this percentage public decimal CalculateOrderQuantity(Symbol symbol, decimal target) { var percent = PortfolioTarget.Percent(this, symbol, target, true); if (percent == null) { return 0; } return percent.Quantity; } /// /// Obsolete implementation of Order method accepting a OrderType. This was deprecated since it /// was impossible to generate other orders via this method. Any calls to this method will always default to a Market Order. /// /// Symbol we want to purchase /// Quantity to buy, + is long, - short. /// Order Type /// Don't wait for the response, just submit order and move on. /// Custom data for this order /// Integer Order ID. [Obsolete("This Order method has been made obsolete, use Order(string, int, bool, string) method instead. Calls to the obsolete method will only generate market orders.")] public OrderTicket Order(Symbol symbol, int quantity, OrderType type, bool asynchronous = false, string tag = "") { return Order(symbol, quantity, asynchronous, tag); } /// /// Obsolete method for placing orders. /// /// /// /// [Obsolete("This Order method has been made obsolete, use the specialized Order helper methods instead. Calls to the obsolete method will only generate market orders.")] public OrderTicket Order(Symbol symbol, decimal quantity, OrderType type) { return Order(symbol, quantity); } /// /// Obsolete method for placing orders. /// /// /// /// [Obsolete("This Order method has been made obsolete, use the specialized Order helper methods instead. Calls to the obsolete method will only generate market orders.")] public OrderTicket Order(Symbol symbol, int quantity, OrderType type) { return Order(symbol, (decimal)quantity); } /// /// Determines if the exchange for the specified symbol is open at the current time. /// /// The symbol /// True if the exchange is considered open at the current time, false otherwise public bool IsMarketOpen(Symbol symbol) { var exchangeHours = MarketHoursDatabase .FromDataFolder() .GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType); var time = UtcTime.ConvertFromUtc(exchangeHours.TimeZone); return exchangeHours.IsOpen(time, false); } private SubmitOrderRequest CreateSubmitOrderRequest(OrderType orderType, Security security, decimal quantity, string tag, IOrderProperties properties, decimal stopPrice = 0m, decimal limitPrice = 0m) { return new SubmitOrderRequest(orderType, security.Type, security.Symbol, quantity, stopPrice, limitPrice, UtcTime, tag, properties); } } }