/* * 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 QuantConnect.Data; using QuantConnect.Interfaces; using QuantConnect.Orders; using System.Linq; namespace QuantConnect.Algorithm.CSharp { /// /// In this algorithm we submit/update/cancel each order type /// /// /// /// /// /// public class OrderTicketDemoAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition { private const string symbol = "SPY"; private readonly List _openMarketOnOpenOrders = new List(); private readonly List _openMarketOnCloseOrders = new List(); private readonly List _openLimitOrders = new List(); private readonly List _openStopMarketOrders = new List(); private readonly List _openStopLimitOrders = new List(); private readonly List _openTrailingStopOrders = new List(); /// /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. /// public override void Initialize() { SetStartDate(2013, 10, 7); //Set Start Date SetEndDate(2013, 10, 11); //Set End Date SetCash(100000); //Set Strategy Cash // Find more symbols here: http://quantconnect.com/data AddSecurity(SecurityType.Equity, symbol, Resolution.Minute); } /// /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// /// Slice object keyed by symbol containing the stock data public override void OnData(Slice slice) { // MARKET ORDERS MarketOrders(); // LIMIT ORDERS LimitOrders(); // STOP MARKET ORDERS StopMarketOrders(); // STOP LIMIT ORDERS StopLimitOrders(); // TRAILING STOP ORDERS TrailingStopOrders(); // MARKET ON OPEN ORDERS MarketOnOpenOrders(); // MARKET ON CLOSE ORDERS MarketOnCloseOrders(); } /// /// MarketOrders are the only orders that are processed synchronously by default, so /// they'll fill by the next line of code. This behavior equally applies to live mode. /// You can opt out of this behavior by specifying the 'asynchronous' parameter as true. /// private void MarketOrders() { if (TimeIs(7, 9, 31)) { Log("Submitting MarketOrder"); // submit a market order to buy 10 shares, this function returns an OrderTicket object // we submit the order with asynchronous:false, so it block until it is filled var newTicket = MarketOrder(symbol, 10, asynchronous: false); if (newTicket.Status != OrderStatus.Filled) { Log("Synchronous market order was not filled synchronously!"); Quit(); } // we can also submit the ticket asynchronously. In a backtest, we'll still perform // the fill before the next time events for your algorithm. here we'll submit the order // asynchronously and try to cancel it, sometimes it will, sometimes it will be filled // first. newTicket = MarketOrder(symbol, 10, asynchronous: true); var response = newTicket.Cancel("Attempt to cancel async order"); if (response.IsSuccess) { Log("Successfully canceled async market order: " + newTicket.OrderId); } else { Log("Unable to cancel async market order: " + response.ErrorCode); } } } /// /// LimitOrders are always processed asynchronously. Limit orders are used to /// set 'good' entry points for an order. For example, you may wish to go /// long a stock, but want a good price, so can place a LimitOrder to buy with /// a limit price below the current market price. Likewise the opposite is true /// when selling, you can place a LimitOrder to sell with a limit price above the /// current market price to get a better sale price. /// You can submit requests to update or cancel the LimitOrder at any time. /// The 'LimitPrice' for an order can be retrieved from the ticket using the /// OrderTicket.Get(OrderField) method, for example: /// /// var currentLimitPrice = orderTicket.Get(OrderField.LimitPrice); /// /// private void LimitOrders() { if (TimeIs(7, 12, 0)) { Log("Submitting LimitOrder"); // submit a limit order to buy 10 shares at .1% below the bar's close var close = Securities[symbol].Close; var newTicket = LimitOrder(symbol, 10, close * .999m); _openLimitOrders.Add(newTicket); // submit another limit order to sell 10 shares at .1% above the bar's close newTicket = LimitOrder(symbol, -10, close * 1.001m); _openLimitOrders.Add(newTicket); } // when we submitted new limit orders we placed them into this list, // so while there's two entries they're still open and need processing if (_openLimitOrders.Count == 2) { var openOrders = _openLimitOrders; // check if either is filled and cancel the other var longOrder = openOrders[0]; var shortOrder = openOrders[1]; if (CheckPairOrdersForFills(longOrder, shortOrder)) { _openLimitOrders.Clear(); return; } // if neither order has filled, bring in the limits by a penny var newLongLimit = longOrder.Get(OrderField.LimitPrice) + 0.01m; var newShortLimit = shortOrder.Get(OrderField.LimitPrice) - 0.01m; Log($"Updating limits - Long: {newLongLimit.ToStringInvariant("0.00")} Short: {newShortLimit.ToStringInvariant("0.00")}"); longOrder.Update(new UpdateOrderFields { // we could change the quantity, but need to specify it //Quantity = LimitPrice = newLongLimit, Tag = "Update #" + (longOrder.UpdateRequests.Count + 1) }); shortOrder.Update(new UpdateOrderFields { LimitPrice = newShortLimit, Tag = "Update #" + (shortOrder.UpdateRequests.Count + 1) }); } } /// /// StopMarketOrders work in the opposite way that limit orders do. /// When placing a long trade, the stop price must be above current /// market price. In this way it's a 'stop loss' for a short trade. /// When placing a short trade, the stop price must be below current /// market price. In this way it's a 'stop loss' for a long trade. /// You can submit requests to update or cancel the StopMarketOrder at any time. /// The 'StopPrice' for an order can be retrieved from the ticket using the /// OrderTicket.Get(OrderField) method, for example: /// /// var currentStopPrice = orderTicket.Get(OrderField.StopPrice); /// /// private void StopMarketOrders() { if (TimeIs(7, 12 + 4, 0)) { Log("Submitting StopMarketOrder"); // a long stop is triggered when the price rises above the value // so we'll set a long stop .25% above the current bar's close var close = Securities[symbol].Close; var stopPrice = close * 1.0025m; var newTicket = StopMarketOrder(symbol, 10, stopPrice); _openStopMarketOrders.Add(newTicket); // a short stop is triggered when the price falls below the value // so we'll set a short stop .25% below the current bar's close stopPrice = close * .9975m; newTicket = StopMarketOrder(symbol, -10, stopPrice); _openStopMarketOrders.Add(newTicket); } // when we submitted new stop market orders we placed them into this list, // so while there's two entries they're still open and need processing if (_openStopMarketOrders.Count == 2) { // check if either is filled and cancel the other var longOrder = _openStopMarketOrders[0]; var shortOrder = _openStopMarketOrders[1]; if (CheckPairOrdersForFills(longOrder, shortOrder)) { _openStopMarketOrders.Clear(); return; } // if neither order has filled, bring in the stops by a penny var newLongStop = longOrder.Get(OrderField.StopPrice) - 0.01m; var newShortStop = shortOrder.Get(OrderField.StopPrice) + 0.01m; Log($"Updating stops - Long: {newLongStop.ToStringInvariant("0.00")} Short: {newShortStop.ToStringInvariant("0.00")}"); longOrder.Update(new UpdateOrderFields { // we could change the quantity, but need to specify it //Quantity = StopPrice = newLongStop, Tag = "Update #" + (longOrder.UpdateRequests.Count + 1) }); shortOrder.Update(new UpdateOrderFields { StopPrice = newShortStop, Tag = "Update #" + (shortOrder.UpdateRequests.Count + 1) }); } } /// /// StopLimitOrders work as a combined stop and limit order. First, the /// price must pass the stop price in the same way a StopMarketOrder works, /// but then we're also guaranteed a fill price at least as good as the /// limit price. This order type can be beneficial in gap down scenarios /// where a StopMarketOrder would have triggered and given the not as beneficial /// gapped down price, whereas the StopLimitOrder could protect you from /// getting the gapped down price through prudent placement of the limit price. /// You can submit requests to update or cancel the StopLimitOrder at any time. /// The 'StopPrice' or 'LimitPrice' for an order can be retrieved from the ticket /// using the OrderTicket.Get(OrderField) method, for example: /// /// var currentStopPrice = orderTicket.Get(OrderField.StopPrice); /// var currentLimitPrice = orderTicket.Get(OrderField.LimitPrice); /// /// private void StopLimitOrders() { if (TimeIs(8, 12, 1)) { Log("Submitting StopLimitOrder"); // a long stop is triggered when the price rises above the value // so we'll set a long stop .25% above the current bar's close // now we'll also be setting a limit, this means we are guaranteed // to get at least the limit price for our fills, so make the limit // price a little softer than the stop price var close = Securities[symbol].Close; var stopPrice = close * 1.001m; var limitPrice = close - 0.03m; var newTicket = StopLimitOrder(symbol, 10, stopPrice, limitPrice); _openStopLimitOrders.Add(newTicket); // a short stop is triggered when the price falls below the value // so we'll set a short stop .25% below the current bar's close // now we'll also be setting a limit, this means we are guaranteed // to get at least the limit price for our fills, so make the limit // price a little softer than the stop price stopPrice = close * .999m; limitPrice = close + 0.03m; newTicket = StopLimitOrder(symbol, -10, stopPrice, limitPrice); _openStopLimitOrders.Add(newTicket); } // when we submitted new stop limit orders we placed them into this list, // so while there's two entries they're still open and need processing if (_openStopLimitOrders.Count == 2) { // check if either is filled and cancel the other var longOrder = _openStopLimitOrders[0]; var shortOrder = _openStopLimitOrders[1]; if (CheckPairOrdersForFills(longOrder, shortOrder)) { _openStopLimitOrders.Clear(); return; } // if neither order has filled, bring in the stops/limits in by a penny var newLongStop = longOrder.Get(OrderField.StopPrice) - 0.01m; var newLongLimit = longOrder.Get(OrderField.LimitPrice) + 0.01m; var newShortStop = shortOrder.Get(OrderField.StopPrice) + 0.01m; var newShortLimit = shortOrder.Get(OrderField.LimitPrice) - 0.01m; Log($"Updating stops - Long: {newLongStop.ToStringInvariant("0.00")} Short: {newShortStop.ToStringInvariant("0.00")}"); Log($"Updating limits - Long: {newLongLimit.ToStringInvariant("0.00")} Short: {newShortLimit.ToStringInvariant("0.00")}"); longOrder.Update(new UpdateOrderFields { // we could change the quantity, but need to specify it //Quantity = StopPrice = newLongStop, LimitPrice = newLongLimit, Tag = "Update #" + (longOrder.UpdateRequests.Count + 1) }); shortOrder.Update(new UpdateOrderFields { StopPrice = newShortStop, LimitPrice = newShortLimit, Tag = "Update #" + (shortOrder.UpdateRequests.Count + 1) }); } } /// /// TrailingStopOrders work the same way as StopMarketOrders, except /// their stop price is adjusted to a certain amount, keeping it a certain /// fixed distance from/to the market price, depending on the order direction, /// which allows to preserve profits and protecting against losses. /// The stop price can be accessed just as with StopMarketOrders, and /// the trailing amount can be accessed with the OrderTicket.Get(OrderField), for example: /// /// var currentTrailingAmount = orderTicket.Get(OrderField.StopPrice); /// var trailingAsPercentage = orderTicket.Get(OrderField.TrailingAsPercentage); /// /// private void TrailingStopOrders() { if (TimeIs(7, 12, 0)) { Log("Submitting TrailingStopOrder"); // a long stop is triggered when the price rises above the value // so we'll set a long stop .25% above the current bar's close var close = Securities[symbol].Close; var stopPrice = close * 1.0025m; var newTicket = TrailingStopOrder(symbol, 10, stopPrice, trailingAmount: 0.0025m, trailingAsPercentage: true); _openTrailingStopOrders.Add(newTicket); // a short stop is triggered when the price falls below the value // so we'll set a short stop .25% below the current bar's close stopPrice = close * .9975m; newTicket = TrailingStopOrder(symbol, -10, stopPrice, trailingAmount: 0.0025m, trailingAsPercentage: true); _openTrailingStopOrders.Add(newTicket); } // when we submitted new stop market orders we placed them into this list, // so while there's two entries they're still open and need processing else if (_openTrailingStopOrders.Count == 2) { // check if either is filled and cancel the other var longOrder = _openTrailingStopOrders[0]; var shortOrder = _openTrailingStopOrders[1]; if (CheckPairOrdersForFills(longOrder, shortOrder)) { _openTrailingStopOrders.Clear(); return; } // if neither order has filled in the last 5 minutes, bring in the trailing percentage by 0.01% if ((UtcTime - longOrder.Time).TotalMinutes % 5 != 0) { return; } var longTrailingPercentage = longOrder.Get(OrderField.TrailingAmount); var newLongTrailingPercentage = Math.Max(longTrailingPercentage - 0.0001m, 0.0001m); var shortTrailingPercentage = shortOrder.Get(OrderField.TrailingAmount); var newShortTrailingPercentage = Math.Max(shortTrailingPercentage - 0.0001m, 0.0001m); Log($"Updating trailing percentages - Long: {newLongTrailingPercentage.ToStringInvariant("0.000")} Short: {newShortTrailingPercentage.ToStringInvariant("0.000")}"); longOrder.Update(new UpdateOrderFields { // we could change the quantity, but need to specify it //Quantity = TrailingAmount = newLongTrailingPercentage, Tag = "Update #" + (longOrder.UpdateRequests.Count + 1) }); shortOrder.Update(new UpdateOrderFields { TrailingAmount = newShortTrailingPercentage, Tag = "Update #" + (shortOrder.UpdateRequests.Count + 1) }); } } /// /// MarketOnCloseOrders are always executed at the next market's closing /// price. The only properties that can be updated are the quantity and /// order tag properties. /// private void MarketOnCloseOrders() { if (TimeIs(9, 12, 0)) { Log("Submitting MarketOnCloseOrder"); // open a new position or triple our existing position var qty = Portfolio[symbol].Quantity; qty = qty == 0 ? 100 : 2*qty; var newTicket = MarketOnCloseOrder(symbol, qty); _openMarketOnCloseOrders.Add(newTicket); } if (_openMarketOnCloseOrders.Count == 1 && Time.Minute == 59) { var ticket = _openMarketOnCloseOrders[0]; // check for fills if (ticket.Status == OrderStatus.Filled) { _openMarketOnCloseOrders.Clear(); return; } var quantity = ticket.Quantity + 1; Log("Updating quantity - New Quantity: " + quantity); // we can update the quantity and tag ticket.Update(new UpdateOrderFields { Quantity = quantity, Tag = "Update #" + (ticket.UpdateRequests.Count + 1) }); } if (TimeIs(EndDate.Day, 12 + 3, 45)) { Log("Submitting MarketOnCloseOrder to liquidate end of algorithm"); MarketOnCloseOrder(symbol, -Portfolio[symbol].Quantity, "Liquidate end of algorithm"); } } /// /// MarketOnOpenOrders are always executed at the next market's opening /// price. The only properties that can be updated are the quantity and /// order tag properties. /// private void MarketOnOpenOrders() { if (TimeIs(8, 14 + 2, 0)) { Log("Submitting MarketOnOpenOrder"); // its EOD, let's submit a market on open order to short even more! var newTicket = MarketOnOpenOrder(symbol, 50); _openMarketOnOpenOrders.Add(newTicket); } if (_openMarketOnOpenOrders.Count == 1 && Time.Minute == 59) { var ticket = _openMarketOnOpenOrders[0]; // check for fills if (ticket.Status == OrderStatus.Filled) { _openMarketOnOpenOrders.Clear(); return; } var quantity = ticket.Quantity + 1; Log("Updating quantity - New Quantity: " + quantity); // we can update the quantity and tag ticket.Update(new UpdateOrderFields { Quantity = quantity, Tag = "Update #" + (ticket.UpdateRequests.Count + 1) }); } } public override void OnOrderEvent(OrderEvent orderEvent) { var order = Transactions.GetOrderById(orderEvent.OrderId); Log($"{Time}: {order.Type}: {orderEvent}"); if (orderEvent.Quantity == 0) { throw new RegressionTestException("OrderEvent quantity is Not expected to be 0, it should hold the current order Quantity"); } if (orderEvent.Quantity != order.Quantity) { throw new RegressionTestException("OrderEvent quantity should hold the current order Quantity"); } if (order is LimitOrder && orderEvent.LimitPrice == 0 || order is StopLimitOrder && orderEvent.LimitPrice == 0) { throw new RegressionTestException("OrderEvent LimitPrice is Not expected to be 0 for LimitOrder and StopLimitOrder"); } if (order is StopMarketOrder && orderEvent.StopPrice == 0) { throw new RegressionTestException("OrderEvent StopPrice is Not expected to be 0 for StopMarketOrder"); } // We can access the order ticket from the order event if (orderEvent.Ticket == null) { throw new RegressionTestException("OrderEvent Ticket was not set"); } if (orderEvent.OrderId != orderEvent.Ticket.OrderId) { throw new RegressionTestException("OrderEvent.OrderId and orderEvent.Ticket.OrderId do not match"); } } private bool CheckPairOrdersForFills(OrderTicket longOrder, OrderTicket shortOrder) { if (longOrder.Status == OrderStatus.Filled) { Log(shortOrder.OrderType + ": Cancelling short order, long order is filled."); shortOrder.Cancel("Long filled."); return true; } if (shortOrder.Status == OrderStatus.Filled) { Log(longOrder.OrderType + ": Cancelling long order, short order is filled."); longOrder.Cancel("Short filled"); return true; } return false; } private bool TimeIs(int day, int hour, int minute) { return Time.Day == day && Time.Hour == hour && Time.Minute == minute; } public override void OnEndOfAlgorithm() { Func basicOrderTicketFilter = x => x.Symbol == symbol; var filledOrders = Transactions.GetOrders(x => x.Status == OrderStatus.Filled); var orderTickets = Transactions.GetOrderTickets(basicOrderTicketFilter); var openOrders = Transactions.GetOpenOrders(x => x.Symbol == symbol); var openOrderTickets = Transactions.GetOpenOrderTickets(basicOrderTicketFilter); var remainingOpenOrders = Transactions.GetOpenOrdersRemainingQuantity(basicOrderTicketFilter); if (filledOrders.Count() != 9 || orderTickets.Count() != 12) { throw new RegressionTestException($"There were expected 9 filled orders and 12 order tickets"); } if (openOrders.Count != 0 || openOrderTickets.Any()) { throw new RegressionTestException($"No open orders or tickets were expected"); } if (remainingOpenOrders != 0m) { throw new RegressionTestException($"No remaining quantity to be filled from open orders was expected"); } var symbolOpenOrders = Transactions.GetOpenOrders(symbol).Count; var symbolOpenOrdersTickets = Transactions.GetOpenOrderTickets(symbol).Count(); var symbolOpenOrdersRemainingQuantity = Transactions.GetOpenOrdersRemainingQuantity(symbol); if (symbolOpenOrders != 0 || symbolOpenOrdersTickets != 0) { throw new RegressionTestException($"No open orders or tickets were expected"); } if (symbolOpenOrdersRemainingQuantity != 0) { throw new RegressionTestException($"No remaining quantity to be filled from open orders was expected"); } var defaultOrders = Transactions.GetOrders(); var defaultOrderTickets = Transactions.GetOrderTickets(); var defaultOpenOrders = Transactions.GetOpenOrders(); var defaultOpenOrderTickets = Transactions.GetOpenOrderTickets(); var defaultOpenOrdersRemaining = Transactions.GetOpenOrdersRemainingQuantity(); if (defaultOrders.Count() != 12 || defaultOrderTickets.Count() != 12) { throw new RegressionTestException($"There were expected 12 orders and 12 order tickets"); } if (defaultOpenOrders.Count != 0 || defaultOpenOrderTickets.Any()) { throw new RegressionTestException($"No open orders or tickets were expected"); } if (defaultOpenOrdersRemaining != 0m) { throw new RegressionTestException($"No remaining quantity to be filled from open orders was expected"); } } /// /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. /// public bool CanRunLocally { get; } = true; /// /// This is used by the regression test system to indicate which languages this algorithm is written in. /// public List Languages { get; } = new() { Language.CSharp, Language.Python }; /// /// Data Points count of all timeslices of algorithm /// public long DataPoints => 3943; /// /// Data Points count of the algorithm history /// public int AlgorithmHistoryDataPoints => 0; /// /// Final status of the algorithm /// public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; /// /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm /// public Dictionary ExpectedStatistics => new Dictionary { {"Total Orders", "12"}, {"Average Win", "0%"}, {"Average Loss", "-0.01%"}, {"Compounding Annual Return", "73.899%"}, {"Drawdown", "0.100%"}, {"Expectancy", "-1"}, {"Start Equity", "100000"}, {"End Equity", "100709.93"}, {"Net Profit", "0.710%"}, {"Sharpe Ratio", "12.469"}, {"Sortino Ratio", "429.347"}, {"Probabilistic Sharpe Ratio", "99.495%"}, {"Loss Rate", "100%"}, {"Win Rate", "0%"}, {"Profit-Loss Ratio", "0"}, {"Alpha", "0.188"}, {"Beta", "0.189"}, {"Annual Standard Deviation", "0.045"}, {"Annual Variance", "0.002"}, {"Information Ratio", "-7.802"}, {"Tracking Error", "0.181"}, {"Treynor Ratio", "2.971"}, {"Total Fees", "$9.00"}, {"Estimated Strategy Capacity", "$50000000.00"}, {"Lowest Capacity Asset", "SPY R735QTJ8XC9X"}, {"Portfolio Turnover", "7.01%"}, {"Drawdown Recovery", "2"}, {"OrderListHash", "236df5da8408fdd11445a88425ea9ab7"} }; } }