/*
* 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);
}
}
}