/* * 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 System.Threading; using QuantConnect.Interfaces; using QuantConnect.Logging; using QuantConnect.Orders; using Python.Runtime; namespace QuantConnect.Securities { /// /// Algorithm Transactions Manager - Recording Transactions /// public class SecurityTransactionManager : IOrderProvider { private class TransactionRecordEntry { public decimal ProfitLoss; public bool IsWin; } private readonly Dictionary _transactionRecord; private readonly IAlgorithm _algorithm; private int _orderId; private int _groupOrderManagerId; private readonly SecurityManager _securities; private TimeSpan _marketOrderFillTimeout = TimeSpan.MinValue; private IOrderProcessor _orderProcessor; /// /// Gets the time the security information was last updated /// public DateTime UtcTime { get { return _securities.UtcTime; } } /// /// Initialise the transaction manager for holding and processing orders. /// public SecurityTransactionManager(IAlgorithm algorithm, SecurityManager security) { _algorithm = algorithm; //Private reference for processing transactions _securities = security; //Internal storage for transaction records: _transactionRecord = new Dictionary(); } /// /// Trade record of profits and losses for each trade statistics calculations /// /// Will return a shallow copy, modifying the returned container /// will have no effect public Dictionary TransactionRecord { get { lock (_transactionRecord) { return _transactionRecord.ToDictionary(x => x.Key, x => x.Value.ProfitLoss); } } } /// /// Gets the number or winning transactions /// public int WinCount { get { lock (_transactionRecord) { return _transactionRecord.Values.Count(x => x.IsWin); } } } /// /// Gets the number of losing transactions /// public int LossCount { get { lock (_transactionRecord) { return _transactionRecord.Values.Count(x => !x.IsWin); } } } /// /// Trade record of profits and losses for each trade statistics calculations that are considered winning trades /// public Dictionary WinningTransactions { get { lock (_transactionRecord) { return _transactionRecord.Where(x => x.Value.IsWin).ToDictionary(x => x.Key, x => x.Value.ProfitLoss); } } } /// /// Trade record of profits and losses for each trade statistics calculations that are considered losing trades /// public Dictionary LosingTransactions { get { lock (_transactionRecord) { return _transactionRecord.Where(x => !x.Value.IsWin).ToDictionary(x => x.Key, x => x.Value.ProfitLoss); } } } /// /// Configurable minimum order value to ignore bad orders, or orders with unrealistic sizes /// /// Default minimum order size is $0 value [Obsolete("MinimumOrderSize is obsolete and will not be used, please use Settings.MinimumOrderMarginPortfolioPercentage instead")] public decimal MinimumOrderSize { get; } /// /// Configurable minimum order size to ignore bad orders, or orders with unrealistic sizes /// /// Default minimum order size is 0 shares [Obsolete("MinimumOrderQuantity is obsolete and will not be used, please use Settings.MinimumOrderMarginPortfolioPercentage instead")] public int MinimumOrderQuantity { get; } /// /// Get the last order id. /// public int LastOrderId { get { return _orderId; } } /// /// Configurable timeout for market order fills /// /// Default value is 5 seconds public TimeSpan MarketOrderFillTimeout { get { return _marketOrderFillTimeout; } set { _marketOrderFillTimeout = value; } } /// /// Processes the order request /// /// The request to be processed /// The order ticket for the request public OrderTicket ProcessRequest(OrderRequest request) { if (_algorithm != null && _algorithm.IsWarmingUp) { throw new Exception(OrderResponse.WarmingUp(request).ToString()); } var submit = request as SubmitOrderRequest; if (submit != null) { SetOrderId(submit); } return _orderProcessor.Process(request); } /// /// Sets the order id for the specified submit request /// /// Request to set the order id for /// This method is public so we can request an order id from outside the assembly, for testing for example public void SetOrderId(SubmitOrderRequest request) { // avoid setting the order id if it's already been set if (request.OrderId < 1) { request.SetOrderId(GetIncrementOrderId()); } } /// /// Add an order to collection and return the unique order id or negative if an error. /// /// A request detailing the order to be submitted /// New unique, increasing orderid public OrderTicket AddOrder(SubmitOrderRequest request) { return ProcessRequest(request); } /// /// Update an order yet to be filled such as stop or limit orders. /// /// Request detailing how the order should be updated /// Does not apply if the order is already fully filled public OrderTicket UpdateOrder(UpdateOrderRequest request) { return ProcessRequest(request); } /// /// Added alias for RemoveOrder - /// /// Order id we wish to cancel /// Tag to indicate from where this method was called public OrderTicket CancelOrder(int orderId, string orderTag = null) { return RemoveOrder(orderId, orderTag); } /// /// Cancels all open orders for all symbols /// /// List containing the cancelled order tickets public List CancelOpenOrders() { if (_algorithm != null && _algorithm.IsWarmingUp) { throw new InvalidOperationException(Messages.SecurityTransactionManager.CancelOpenOrdersNotAllowedOnInitializeOrWarmUp); } var cancelledOrders = new List(); foreach (var ticket in GetOpenOrderTickets()) { ticket.Cancel(Messages.SecurityTransactionManager.OrderCanceledByCancelOpenOrders(_algorithm.UtcTime)); cancelledOrders.Add(ticket); } return cancelledOrders; } /// /// Cancels all open orders for the specified symbol /// /// The symbol whose orders are to be cancelled /// Custom order tag /// List containing the cancelled order tickets public List CancelOpenOrders(Symbol symbol, string tag = null) { if (_algorithm != null && _algorithm.IsWarmingUp) { throw new InvalidOperationException(Messages.SecurityTransactionManager.CancelOpenOrdersNotAllowedOnInitializeOrWarmUp); } var cancelledOrders = new List(); foreach (var ticket in GetOpenOrderTickets(x => x.Symbol == symbol)) { ticket.Cancel(tag); cancelledOrders.Add(ticket); } return cancelledOrders; } /// /// Remove this order from outstanding queue: user is requesting a cancel. /// /// Specific order id to remove /// Tag request public OrderTicket RemoveOrder(int orderId, string tag = null) { return ProcessRequest(new CancelOrderRequest(_securities.UtcTime, orderId, tag ?? string.Empty)); } /// /// Gets an enumerable of matching the specified /// /// The filter predicate used to find the required order tickets /// An enumerable of matching the specified public IEnumerable GetOrderTickets(Func filter = null) { return _orderProcessor.GetOrderTickets(filter ?? (x => true)); } /// /// Gets an enumerable of matching the specified /// /// The Python function filter used to find the required order tickets /// An enumerable of matching the specified public IEnumerable GetOrderTickets(PyObject filter) { return _orderProcessor.GetOrderTickets(filter.ConvertToDelegate>()); } /// /// Get an enumerable of open for the specified symbol /// /// The symbol for which to return the order tickets /// An enumerable of open . public IEnumerable GetOpenOrderTickets(Symbol symbol) { return GetOpenOrderTickets(x => x.Symbol == symbol); } /// /// Gets an enumerable of opened matching the specified /// /// The filter predicate used to find the required order tickets /// An enumerable of opened matching the specified public IEnumerable GetOpenOrderTickets(Func filter = null) { return _orderProcessor.GetOpenOrderTickets(filter ?? (x => true)); } /// /// Gets an enumerable of opened matching the specified /// However, this method can be confused with the override that takes a Symbol as parameter. For this reason /// it first checks if it can convert the parameter into a symbol. If that conversion cannot be aplied it /// assumes the parameter is a Python function object and not a Python representation of a Symbol. /// /// The Python function filter used to find the required order tickets /// An enumerable of opened matching the specified public IEnumerable GetOpenOrderTickets(PyObject filter) { Symbol pythonSymbol; if (filter.TryConvert(out pythonSymbol)) { return GetOpenOrderTickets(pythonSymbol); } return _orderProcessor.GetOpenOrderTickets(filter.ConvertToDelegate>()); } /// /// Gets the remaining quantity to be filled from open orders, i.e. order size minus quantity filled /// /// Filters the order tickets to be included in the aggregate quantity remaining to be filled /// Total quantity that hasn't been filled yet for all orders that were not filtered public decimal GetOpenOrdersRemainingQuantity(Func filter = null) { return GetOpenOrderTickets(filter) .Aggregate(0m, (d, t) => d + t.QuantityRemaining); } /// /// Gets the remaining quantity to be filled from open orders, i.e. order size minus quantity filled /// However, this method can be confused with the override that takes a Symbol as parameter. For this reason /// it first checks if it can convert the parameter into a symbol. If that conversion cannot be aplied it /// assumes the parameter is a Python function object and not a Python representation of a Symbol. /// /// Filters the order tickets to be included in the aggregate quantity remaining to be filled /// Total quantity that hasn't been filled yet for all orders that were not filtered public decimal GetOpenOrdersRemainingQuantity(PyObject filter) { Symbol pythonSymbol; if (filter.TryConvert(out pythonSymbol)) { return GetOpenOrdersRemainingQuantity(pythonSymbol); } return GetOpenOrderTickets(filter) .Aggregate(0m, (d, t) => d + t.QuantityRemaining); } /// /// Gets the remaining quantity to be filled from open orders for a Symbol, i.e. order size minus quantity filled /// /// Symbol to get the remaining quantity of currently open orders /// Total quantity that hasn't been filled yet for orders matching the Symbol public decimal GetOpenOrdersRemainingQuantity(Symbol symbol) { return GetOpenOrdersRemainingQuantity(t => t.Symbol == symbol); } /// /// Gets the order ticket for the specified order id. Returns null if not found /// /// The order's id /// The order ticket with the specified id, or null if not found public OrderTicket GetOrderTicket(int orderId) { return _orderProcessor.GetOrderTicket(orderId); } /// /// Wait for a specific order to be either Filled, Invalid or Canceled /// /// The id of the order to wait for /// True if we successfully wait for the fill, false if we were unable /// to wait. This may be because it is not a market order or because the timeout /// was reached public bool WaitForOrder(int orderId) { var orderTicket = GetOrderTicket(orderId); if (orderTicket == null) { Log.Error($@"SecurityTransactionManager.WaitForOrder(): { Messages.SecurityTransactionManager.UnableToLocateOrderTicket(orderId)}"); return false; } if (!orderTicket.OrderClosed.WaitOne(_marketOrderFillTimeout)) { if(_marketOrderFillTimeout > TimeSpan.Zero) { Log.Error($@"SecurityTransactionManager.WaitForOrder(): {Messages.SecurityTransactionManager.OrderNotFilledWithinExpectedTime(_marketOrderFillTimeout)}"); } return false; } return true; } /// /// Get a list of all open orders for a symbol. /// /// The symbol for which to return the orders /// List of open orders. public List GetOpenOrders(Symbol symbol) { return GetOpenOrders(x => x.Symbol == symbol); } /// /// Gets open orders matching the specified filter. Specifying null will return an enumerable /// of all open orders. /// /// Delegate used to filter the orders /// All filtered open orders this order provider currently holds public List GetOpenOrders(Func filter = null) { filter = filter ?? (x => true); return _orderProcessor.GetOpenOrders(x => filter(x)); } /// /// Gets open orders matching the specified filter. However, this method can be confused with the /// override that takes a Symbol as parameter. For this reason it first checks if it can convert /// the parameter into a symbol. If that conversion cannot be aplied it assumes the parameter is /// a Python function object and not a Python representation of a Symbol. /// /// Python function object used to filter the orders /// All filtered open orders this order provider currently holds public List GetOpenOrders(PyObject filter) { Symbol pythonSymbol; if (filter.TryConvert(out pythonSymbol)) { return GetOpenOrders(pythonSymbol); } Func csharpFilter = filter.ConvertToDelegate>(); return _orderProcessor.GetOpenOrders(x => csharpFilter(x)); } /// /// Gets the current number of orders that have been processed /// public int OrdersCount { get { return _orderProcessor.OrdersCount; } } /// /// Get the order by its id /// /// Order id to fetch /// A clone of the order with the specified id, or null if no match is found public Order GetOrderById(int orderId) { return _orderProcessor.GetOrderById(orderId); } /// /// Gets the order by its brokerage id /// /// The brokerage id to fetch /// The first order matching the brokerage id, or null if no match is found public List GetOrdersByBrokerageId(string brokerageId) { return _orderProcessor.GetOrdersByBrokerageId(brokerageId); } /// /// Gets all orders matching the specified filter. Specifying null will return an enumerable /// of all orders. /// /// Delegate used to filter the orders /// All orders this order provider currently holds by the specified filter public IEnumerable GetOrders(Func filter = null) { return _orderProcessor.GetOrders(filter ?? (x => true)); } /// /// Gets all orders matching the specified filter. /// /// Python function object used to filter the orders /// All orders this order provider currently holds by the specified filter public IEnumerable GetOrders(PyObject filter) { return _orderProcessor.GetOrders(filter.ConvertToDelegate>()); } /// /// Get a new order id, and increment the internal counter. /// /// New unique int order id. public int GetIncrementOrderId() { return Interlocked.Increment(ref _orderId); } /// /// Get a new group order manager id, and increment the internal counter. /// /// New unique int group order manager id. public int GetIncrementGroupOrderManagerId() { return Interlocked.Increment(ref _groupOrderManagerId); } /// /// Sets the used for fetching orders for the algorithm /// /// The to be used to manage fetching orders public void SetOrderProcessor(IOrderProcessor orderProvider) { _orderProcessor = orderProvider; } /// /// Record the transaction value and time in a list to later be processed for statistics creation. /// /// /// Bit of a hack -- but using datetime as dictionary key is dangerous as you can process multiple orders within a second. /// For the accounting / statistics generating purposes its not really critical to know the precise time, so just add a millisecond while there's an identical key. /// /// Time of order processed /// Profit Loss. /// /// Whether the transaction is a win. /// For options exercise, this might not depend only on the profit/loss value /// public void AddTransactionRecord(DateTime time, decimal transactionProfitLoss, bool isWin) { lock (_transactionRecord) { var clone = time; while (_transactionRecord.ContainsKey(clone)) { clone = clone.AddMilliseconds(1); } _transactionRecord.Add(clone, new TransactionRecordEntry { ProfitLoss = transactionProfitLoss, IsWin = isWin }); } } /// /// Set live mode state of the algorithm /// /// True, live mode is enabled public void SetLiveMode(bool isLiveMode) { if (isLiveMode) { if(MarketOrderFillTimeout == TimeSpan.MinValue) { // set default value in live trading MarketOrderFillTimeout = TimeSpan.FromSeconds(5); } } else { // always zero in backtesting, fills happen synchronously, there's no dedicated thread like in live MarketOrderFillTimeout = TimeSpan.Zero; } } /// /// Calculates the projected holdings for the specified security based on the current open orders. /// /// The security /// /// The projected holdings for the specified security, which is the sum of the current holdings /// plus the sum of the open orders quantity. /// public ProjectedHoldings GetProjectedHoldings(Security security) { return _orderProcessor.GetProjectedHoldings(security); } } }