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 =
null,
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 tag ??=
"Liquidated";
1242 foreach (var symbolToLiquidate
in symbols)
1249 var holdings =
Portfolio[symbolToLiquidate];
1250 if (holdings.Invested)
1253 quantity = holdings.Quantity;
1257 if (orders.Count == 1 && quantity != 0 && orders[0].Quantity == -quantity && orders[0].Type ==
OrderType.Market)
1263 var marketOrdersQuantity = 0m;
1264 foreach (var order
in orders)
1273 marketOrdersQuantity += ticket.
Quantity - ticket.QuantityFilled;
1286 var ticket =
Order(symbolToLiquidate, -quantity - marketOrdersQuantity, asynchronous: asynchronous, tag: tag, orderProperties: orderProperties);
1287 orderTickets.Add(ticket);
1291 return orderTickets;
1301 [Obsolete($
"This method is obsolete, please use Liquidate(symbol: symbolToLiquidate, tag: tag) method")]
1304 return Liquidate(symbol: symbolToLiquidate, tag: tag).Select(x => x.OrderId).ToList();
1332 public List<OrderTicket>
SetHoldings(List<PortfolioTarget> targets,
bool liquidateExistingHoldings =
false,
string tag =
null,
IOrderProperties orderProperties =
null)
1334 List<OrderTicket> orderTickets =
null;
1336 if (liquidateExistingHoldings)
1338 orderTickets =
Liquidate(GetSymbolsToLiquidate(targets.Select(t => t.Symbol)), tag: tag, orderProperties: orderProperties);
1340 orderTickets ??=
new List<OrderTicket>();
1342 foreach (var portfolioTarget
in targets
1345 .OrderTargetsByMarginImpact(
this, targetIsDelta:
true))
1347 var tickets = SetHoldingsImpl(portfolioTarget.Symbol, portfolioTarget.Quantity,
false, tag, orderProperties);
1348 orderTickets.AddRange(tickets);
1350 return orderTickets;
1366 return SetHoldings(symbol, percentage.SafeDecimalCast(), liquidateExistingHoldings, tag, orderProperties);
1382 return SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties);
1398 return SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag, orderProperties);
1417 return SetHoldingsImpl(symbol,
CalculateOrderQuantity(symbol, percentage), liquidateExistingHoldings, tag, orderProperties);
1423 private List<OrderTicket> SetHoldingsImpl(
Symbol symbol, decimal orderQuantity,
bool liquidateExistingHoldings =
false,
string tag =
null,
IOrderProperties orderProperties =
null)
1425 List<OrderTicket> orderTickets =
null;
1427 if (liquidateExistingHoldings)
1429 orderTickets =
Liquidate(GetSymbolsToLiquidate([symbol]), tag: tag, orderProperties: orderProperties);
1432 orderTickets ??=
new List<OrderTicket>();
1436 ticket => ticket.Symbol == symbol
1437 && (ticket.OrderType ==
OrderType.Market
1438 || ticket.OrderType ==
OrderType.MarketOnOpen))
1439 .Aggregate(0m, (d, ticket) => d + ticket.Quantity - ticket.QuantityFilled);
1442 var quantity = orderQuantity - marketOrdersQuantity;
1443 if (Math.Abs(quantity) > 0)
1448 Error($
"{symbol} not found in portfolio. Request this data when initializing the algorithm.");
1449 return orderTickets;
1454 if (security.Exchange.ExchangeOpen)
1456 ticket =
MarketOrder(symbol, quantity,
false, tag, orderProperties);
1462 orderTickets.Add(ticket);
1464 return orderTickets;
1472 private List<Symbol> GetSymbolsToLiquidate(IEnumerable<Symbol> symbols)
1474 var targetSymbols =
new HashSet<Symbol>(symbols);
1476 .Where(symbol => !targetSymbols.Contains(symbol))
1477 .OrderBy(symbol => symbol.
Value)
1479 return symbolsToLiquidate;
1488 [DocumentationAttribute(TradingAndOrders)]
1507 if (percent ==
null)
1525 [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.")]
1529 return Order(symbol, quantity, asynchronous, tag, orderProperties);
1539 [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.")]
1543 return Order(symbol, quantity);
1553 [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.")]
1557 return Order(symbol, (decimal)quantity);
1571 return security.IsMarketOpen(
false);
1573 return symbol.IsMarketOpen(
UtcTime,
false);
1577 IOrderProperties properties, decimal stopPrice = 0m, decimal limitPrice = 0m, decimal triggerPrice = 0m, decimal trailingAmount = 0m,
1580 return new SubmitOrderRequest(orderType, security.
Type, security.
Symbol, quantity, stopPrice, limitPrice, triggerPrice, trailingAmount,
1581 trailingAsPercentage,
UtcTime, tag, properties, groupOrderManager);
1584 private static void CheckComboOrderSizing(List<Leg> legs, decimal quantity)
1586 var greatestsCommonDivisor = Math.Abs(legs.Select(leg => leg.Quantity).GreatestCommonDivisor());
1588 if (greatestsCommonDivisor != 1)
1590 throw new ArgumentException(
1591 "The global combo quantity should be used to increase or reduce the size of the order, " +
1592 "while the leg quantities should be used to specify the ratio of the order. " +
1593 "The combo order quantities should be reduced " +
1594 $
"from {quantity}x({string.Join(",
", legs.Select(leg => $"{leg.Quantity} {leg.Symbol}
"))}) " +
1595 $
"to {quantity * greatestsCommonDivisor}x({string.Join(",
", legs.Select(leg => $"{leg.Quantity / greatestsCommonDivisor} {leg.Symbol}
"))}).");
1603 private void InvalidateGoodTilDateTimeInForce(
IOrderProperties orderProperties)
1610 if (!_isGtdTfiForMooAndMocOrdersValidationWarningSent)
1612 Debug(
"Warning: Good-Til-Date Time-In-Force is not supported for MOO and MOC orders. " +
1613 "The time-in-force will be reset to Good-Til-Canceled (GTC).");
1614 _isGtdTfiForMooAndMocOrdersValidationWarningSent =
true;