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