21 using System.Collections.Generic;
29 public partial class QCAlgorithm
31 private int _maxOrders = 10000;
32 private bool _isMarketOnOpenOrderWarningSent;
33 private bool _isMarketOnOpenOrderRestrictedForFuturesWarningSent;
34 private bool _isGtdTfiForMooAndMocOrdersValidationWarningSent;
35 private bool _isOptionsOrderOnStockSplitWarningSent;
40 [DocumentationAttribute(TradingAndOrders)]
53 return Order(symbol, (decimal)Math.Abs(quantity));
66 return Order(symbol, Math.Abs(quantity).SafeDecimalCast());
79 return Order(symbol, Math.Abs(quantity));
92 return Order(symbol, (decimal)Math.Abs(quantity));
106 return Order(symbol, (decimal)Math.Abs(quantity) * -1);
118 return Order(symbol, Math.Abs(quantity).SafeDecimalCast() * -1m);
130 return Order(symbol, (decimal)Math.Abs(quantity) * -1m);
142 return Order(symbol, Math.Abs(quantity) * -1);
155 return Order(symbol, quantity.SafeDecimalCast());
195 return MarketOrder(symbol, quantity, asynchronous, tag, orderProperties);
210 return MarketOrder(symbol, (decimal)quantity, asynchronous, tag, orderProperties);
225 return MarketOrder(symbol, quantity.SafeDecimalCast(), asynchronous, tag, orderProperties);
241 return MarketOrder(security, quantity, asynchronous, tag, orderProperties);
261 if (!_isMarketOnOpenOrderWarningSent)
264 if (mooTicket.SubmitRequest.Response.IsSuccess && !anyNonDailySubscriptions)
266 Debug(
"Warning: all market orders sent using daily data, or market orders sent after hours are automatically converted into MarketOnOpen orders.");
267 _isMarketOnOpenOrderWarningSent =
true;
279 if (ticket.Status !=
OrderStatus.Invalid && !asynchronous)
298 return MarketOnOpenOrder(symbol, quantity.SafeDecimalCast(), tag, orderProperties);
327 InvalidateGoodTilDateTimeInForce(properties);
330 var request = CreateSubmitOrderRequest(
OrderType.MarketOnOpen, security, quantity, tag, properties);
375 InvalidateGoodTilDateTimeInForce(properties);
378 var request = CreateSubmitOrderRequest(
OrderType.MarketOnClose, security, quantity, tag, properties);
395 return LimitOrder(symbol, (decimal)quantity, limitPrice, tag, orderProperties);
410 return LimitOrder(symbol, quantity.SafeDecimalCast(), limitPrice, tag, orderProperties);
443 return StopMarketOrder(symbol, (decimal)quantity, stopPrice, tag, orderProperties);
458 return StopMarketOrder(symbol, quantity.SafeDecimalCast(), stopPrice, tag, orderProperties);
494 return TrailingStopOrder(symbol, (decimal)quantity, trailingAmount, trailingAsPercentage, tag, orderProperties);
512 return TrailingStopOrder(symbol, quantity.SafeDecimalCast(), trailingAmount, trailingAsPercentage, tag, orderProperties);
531 var stopPrice = Orders.TrailingStopOrder.CalculateStopPrice(security.Price, trailingAmount, trailingAsPercentage,
533 return TrailingStopOrder(symbol, quantity, stopPrice, trailingAmount, trailingAsPercentage, tag, orderProperties);
551 return TrailingStopOrder(symbol, (decimal)quantity, stopPrice, trailingAmount, trailingAsPercentage, tag, orderProperties);
569 return TrailingStopOrder(symbol, quantity.SafeDecimalCast(), stopPrice, trailingAmount, trailingAsPercentage, tag, orderProperties);
588 var request = CreateSubmitOrderRequest(
593 stopPrice: stopPrice,
594 trailingAmount: trailingAmount,
595 trailingAsPercentage: trailingAsPercentage,
614 return StopLimitOrder(symbol, (decimal)quantity, stopPrice, limitPrice, tag, orderProperties);
630 return StopLimitOrder(symbol, quantity.SafeDecimalCast(), stopPrice, limitPrice, tag, orderProperties);
647 var request = CreateSubmitOrderRequest(
OrderType.StopLimit, security, quantity, tag, stopPrice: stopPrice, limitPrice: limitPrice, properties: orderProperties ??
DefaultOrderProperties?.
Clone());
665 return LimitIfTouchedOrder(symbol, (decimal)quantity, triggerPrice, limitPrice, tag, orderProperties);
681 return LimitIfTouchedOrder(symbol, quantity.SafeDecimalCast(), triggerPrice, limitPrice, tag, orderProperties);
698 var request = CreateSubmitOrderRequest(
OrderType.LimitIfTouched, security, quantity, tag, triggerPrice: triggerPrice, limitPrice: limitPrice, properties: orderProperties ??
DefaultOrderProperties?.
Clone());
722 var preOrderCheckResponse = PreOrderChecks(request);
723 if (preOrderCheckResponse.IsError)
754 return Order(strategy, Math.Abs(quantity), asynchronous, tag, orderProperties);
769 return Order(strategy, Math.Abs(quantity) * -1, asynchronous, tag, orderProperties);
784 return GenerateOptionStrategyOrders(strategy, quantity, asynchronous, tag, orderProperties);
799 return SubmitComboOrder(legs, quantity, 0, asynchronous, tag, orderProperties);
814 if (legs.Any(x => x.OrderPrice ==
null || x.OrderPrice == 0))
816 throw new ArgumentException(
"ComboLegLimitOrder requires a limit price for each leg");
819 return SubmitComboOrder(legs, quantity, 0, asynchronous:
true, tag, orderProperties);
838 throw new ArgumentException(
"ComboLimitOrder requires a limit price");
841 if (legs.Any(x => x.OrderPrice !=
null && x.OrderPrice != 0))
843 throw new ArgumentException(
"ComboLimitOrder does not support limit prices for individual legs");
846 return SubmitComboOrder(legs, quantity, limitPrice, asynchronous:
true, tag, orderProperties);
849 private IEnumerable<OrderTicket> GenerateOptionStrategyOrders(
OptionStrategy strategy,
int strategyQuantity,
bool asynchronous,
string tag,
IOrderProperties orderProperties)
856 tag ??= $
"{strategy.Name} ({strategyQuantity.ToStringInvariant()})";
861 foreach (var optionLeg
in strategy.
OptionLegs)
871 leg =
new Leg {
Symbol = option, OrderPrice = optionLeg.OrderPrice, Quantity = optionLeg.Quantity };
878 throw new InvalidOperationException(
"Couldn't find the option contract in algorithm securities list. " +
879 Invariant($
"Underlying: {strategy.Underlying}, option {optionLeg.Right}, strike {optionLeg.Strike}, ") +
880 Invariant($
"expiration: {optionLeg.Expiration}")
886 return SubmitComboOrder(legs, strategyQuantity, 0, asynchronous, tag, orderProperties);
889 private List<OrderTicket> SubmitComboOrder(List<Leg> legs, decimal quantity, decimal limitPrice,
bool asynchronous,
string tag,
IOrderProperties orderProperties)
891 CheckComboOrderSizing(legs, quantity);
902 List<OrderTicket> orderTickets =
new(capacity: legs.Count);
903 List<SubmitOrderRequest> submitRequests =
new(capacity: legs.Count);
904 foreach (var leg
in legs)
908 if (leg.OrderPrice.HasValue)
911 limitPrice = leg.OrderPrice.Value;
914 var request = CreateSubmitOrderRequest(
917 ((decimal)leg.Quantity).GetOrderLegGroupQuantity(groupOrderManager),
920 groupOrderManager: groupOrderManager,
921 limitPrice: limitPrice);
924 var response = PreOrderChecks(request);
925 if (response.IsError)
931 submitRequests.Add(request);
934 foreach (var request
in submitRequests)
943 foreach (var ticket
in orderTickets)
945 if (ticket.Status.IsOpen())
961 [DocumentationAttribute(TradingAndOrders)]
964 var response = PreOrderChecks(request);
965 if (response.IsError)
981 var response = PreOrderChecksImpl(request);
982 if (response.IsError)
984 Error(response.ErrorMessage);
1015 if (Math.Abs(request.
Quantity) < security.SymbolProperties.LotSize)
1018 Invariant($
"Unable to {request.OrderRequestType.ToLower()} order with id {request.OrderId} which ") +
1019 Invariant($
"quantity ({Math.Abs(request.Quantity)}) is less than lot ") +
1020 Invariant($
"size ({security.SymbolProperties.LotSize}).")
1024 if (!security.IsTradable)
1027 $
"The security with symbol '{request.Symbol}' is marked as non-tradable."
1031 var price = security.Price;
1034 if (request.
OrderType ==
OrderType.OptionExercise && !security.Exchange.ExchangeOpen)
1037 $
"{request.OrderType} order and exchange not open."
1044 if (!_isMarketOnOpenOrderRestrictedForFuturesWarningSent)
1046 Debug(
"Warning: Market-On-Open orders are not allowed for futures and future options. Consider using limit orders during extended market hours.");
1047 _isMarketOnOpenOrderRestrictedForFuturesWarningSent =
true;
1051 $
"{request.OrderType} orders not supported for {security.Type}."
1061 var quoteCurrency = security.QuoteCurrency.Symbol;
1065 $
"{request.Symbol.Value}: requires {quoteCurrency} in the cashbook to trade."
1068 if (security.QuoteCurrency.ConversionRate == 0m)
1071 $
"{request.Symbol.Value}: requires {quoteCurrency} to have a non-zero conversion rate. This can be caused by lack of data."
1082 $
"{request.Symbol.Value}: requires {baseCurrency} and {quoteCurrency} in the cashbook to trade."
1085 if (baseCash.ConversionRate == 0m)
1088 $
"{request.Symbol.Value}: requires {baseCurrency} and {quoteCurrency} to have non-zero conversion rates. This can be caused by lack of data."
1094 if (!security.HasData)
1097 "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."
1106 Invariant($
"You have exceeded maximum number of orders ({_maxOrders}), for unlimited orders upgrade your account.")
1112 if (!security.Type.IsOption())
1115 $
"The security with symbol '{request.Symbol}' is not exercisable."
1119 if ((security as
Option).Style ==
OptionStyle.European &&
UtcTime.Date < security.Symbol.ID.Date.ConvertToUtc(security.Exchange.TimeZone).Date)
1122 $
"Cannot exercise European style option with symbol '{request.Symbol}' before its expiration date."
1126 if (security.Holdings.IsShort)
1129 $
"The security with symbol '{request.Symbol}' has a short option position. Only long option positions are exercisable."
1133 if (Math.Abs(request.
Quantity) > security.Holdings.Quantity)
1136 $
"Cannot exercise more contracts of '{request.Symbol}' than is currently available in the portfolio. "
1143 if (security.Exchange.Hours.IsMarketAlwaysOpen)
1145 throw new InvalidOperationException($
"Market never closes for this symbol {security.Symbol}, can no submit a {nameof(OrderType.MarketOnOpen)} order.");
1150 if (security.Exchange.Hours.IsMarketAlwaysOpen)
1152 throw new InvalidOperationException($
"Market never closes for this symbol {security.Symbol}, can no submit a {nameof(OrderType.MarketOnClose)} order.");
1155 var nextMarketClose = security.Exchange.Hours.GetNextMarketClose(security.LocalTime,
false);
1158 var latestSubmissionTimeUtc = nextMarketClose
1159 .ConvertToUtc(security.Exchange.TimeZone)
1160 .Subtract(Orders.MarketOnCloseOrder.SubmissionTimeBuffer);
1161 if (
UtcTime > latestSubmissionTimeUtc)
1167 $
"MarketOnClose orders must be placed within {Orders.MarketOnCloseOrder.SubmissionTimeBuffer} before market close." +
1168 " Override this TimeSpan buffer by setting Orders.MarketOnCloseOrder.SubmissionTimeBuffer in QCAlgorithm.Initialize()."
1176 throw new ArgumentException(
"Can not set a limit price using market combo orders");
1186 if (!_isOptionsOrderOnStockSplitWarningSent)
1188 Debug(
"Warning: Options orders are not allowed when a split occurred for its underlying stock");
1189 _isOptionsOrderOnStockSplitWarningSent =
true;
1193 "Options orders are not allowed when a split occurred for its underlying stock");
1207 [DocumentationAttribute(TradingAndOrders)]
1210 IEnumerable<Symbol> toLiquidate;
1214 ?
new[] { symbol } : Enumerable.Empty<
Symbol>();
1221 return Liquidate(toLiquidate, asynchronous, tag, orderProperties);
1232 public List<OrderTicket>
Liquidate(IEnumerable<Symbol> symbols,
bool asynchronous =
false,
string tag =
"Liquidated",
IOrderProperties orderProperties =
null)
1234 var orderTickets =
new List<OrderTicket>();
1237 Debug(
"Liquidate() is currently disabled by settings. To re-enable please set 'Settings.LiquidateEnabled' to true");
1238 return orderTickets;
1241 foreach (var symbolToLiquidate
in symbols)
1248 var holdings =
Portfolio[symbolToLiquidate];
1249 if (holdings.Invested)
1252 quantity = holdings.Quantity;
1256 if (orders.Count == 1 && quantity != 0 && orders[0].Quantity == -quantity && orders[0].Type ==
OrderType.Market)
1262 var marketOrdersQuantity = 0m;
1263 foreach (var order
in orders)
1272 marketOrdersQuantity += ticket.
Quantity - ticket.QuantityFilled;
1285 var ticket =
Order(symbolToLiquidate, -quantity - marketOrdersQuantity, asynchronous: asynchronous, tag: tag, orderProperties: orderProperties);
1286 orderTickets.Add(ticket);
1290 return orderTickets;
1300 [Obsolete($
"This method is obsolete, please use Liquidate(symbol: symbolToLiquidate, tag: tag) method")]
1303 return Liquidate(symbol: symbolToLiquidate, tag:tag).Select(x => x.OrderId).ToList();
1330 public void SetHoldings(List<PortfolioTarget> targets,
bool liquidateExistingHoldings =
false,
string tag =
"",
IOrderProperties orderProperties =
null)
1333 if (liquidateExistingHoldings)
1335 LiquidateExistingHoldings(targets.Select(x => x.Symbol).ToHashSet(), tag, orderProperties);
1338 foreach (var portfolioTarget
in targets
1341 .OrderTargetsByMarginImpact(
this, targetIsDelta:
true))
1343 SetHoldingsImpl(portfolioTarget.Symbol, portfolioTarget.Quantity,
false, tag, orderProperties);
1359 SetHoldings(symbol, percentage.SafeDecimalCast(), liquidateExistingHoldings, tag, orderProperties);
1374 SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties);
1389 SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties);
1407 SetHoldingsImpl(symbol,
CalculateOrderQuantity(symbol, percentage), liquidateExistingHoldings, tag, orderProperties);
1413 private void SetHoldingsImpl(
Symbol symbol, decimal orderQuantity,
bool liquidateExistingHoldings =
false,
string tag =
"",
IOrderProperties orderProperties =
null)
1416 if (liquidateExistingHoldings)
1418 LiquidateExistingHoldings(
new HashSet<Symbol> { symbol }, tag, orderProperties);
1423 ticket => ticket.Symbol == symbol
1424 && (ticket.OrderType ==
OrderType.Market
1425 || ticket.OrderType ==
OrderType.MarketOnOpen))
1426 .Aggregate(0m, (d, ticket) => d + ticket.Quantity - ticket.QuantityFilled);
1429 var quantity = orderQuantity - marketOrdersQuantity;
1430 if (Math.Abs(quantity) > 0)
1435 Error($
"{symbol} not found in portfolio. Request this data when initializing the algorithm.");
1440 if (security.Exchange.ExchangeOpen)
1442 MarketOrder(symbol, quantity,
false, tag, orderProperties);
1457 private void LiquidateExistingHoldings(HashSet<Symbol> symbols,
string tag =
"",
IOrderProperties orderProperties =
null)
1461 var holdingSymbol = kvp.Key;
1462 var holdings = kvp.Value;
1463 if (!symbols.Contains(holdingSymbol) && holdings.AbsoluteQuantity > 0)
1467 Order(holdingSymbol, liquidationQuantity,
false, tag, orderProperties);
1478 [DocumentationAttribute(TradingAndOrders)]
1497 if (percent ==
null)
1515 [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.")]
1519 return Order(symbol, quantity, asynchronous, tag, orderProperties);
1529 [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.")]
1533 return Order(symbol, quantity);
1543 [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.")]
1547 return Order(symbol, (decimal)quantity);
1561 return security.IsMarketOpen(
false);
1563 return symbol.IsMarketOpen(
UtcTime,
false);
1567 IOrderProperties properties, decimal stopPrice = 0m, decimal limitPrice = 0m, decimal triggerPrice = 0m, decimal trailingAmount = 0m,
1570 return new SubmitOrderRequest(orderType, security.
Type, security.
Symbol, quantity, stopPrice, limitPrice, triggerPrice, trailingAmount,
1571 trailingAsPercentage,
UtcTime, tag, properties, groupOrderManager);
1574 private static void CheckComboOrderSizing(List<Leg> legs, decimal quantity)
1576 var greatestsCommonDivisor = Math.Abs(legs.Select(leg => leg.Quantity).GreatestCommonDivisor());
1578 if (greatestsCommonDivisor != 1)
1580 throw new ArgumentException(
1581 "The global combo quantity should be used to increase or reduce the size of the order, " +
1582 "while the leg quantities should be used to specify the ratio of the order. " +
1583 "The combo order quantities should be reduced " +
1584 $
"from {quantity}x({string.Join(",
", legs.Select(leg => $"{leg.Quantity} {leg.Symbol}
"))}) " +
1585 $
"to {quantity * greatestsCommonDivisor}x({string.Join(",
", legs.Select(leg => $"{leg.Quantity / greatestsCommonDivisor} {leg.Symbol}
"))}).");
1593 private void InvalidateGoodTilDateTimeInForce(
IOrderProperties orderProperties)
1600 if (!_isGtdTfiForMooAndMocOrdersValidationWarningSent)
1602 Debug(
"Warning: Good-Til-Date Time-In-Force is not supported for MOO and MOC orders. " +
1603 "The time-in-force will be reset to Good-Til-Canceled (GTC).");
1604 _isGtdTfiForMooAndMocOrdersValidationWarningSent =
true;