/* * 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.Collections.Concurrent; using QuantConnect.Orders; namespace QuantConnect.Lean.Engine.TransactionHandlers { /// /// Class used to keep track of CancelPending orders and their original or updated status /// public class CancelPendingOrders { private readonly ConcurrentDictionary _cancelPendingOrders = new ConcurrentDictionary(); /// /// Amount of CancelPending Orders /// public int GetCancelPendingOrdersSize => _cancelPendingOrders.Count; /// /// Adds an order which will be canceled and we want to keep track of it Status in case of fallback /// /// The order id /// The order Status, before the cancel request public void Set(int orderId, OrderStatus status) { _cancelPendingOrders[orderId] = new CancelPendingOrder { Status = status }; } /// /// Updates an order that is pending to be canceled. /// /// The new status of the order. If its OrderStatus.Canceled or OrderStatus.Filled it will be removed /// The id of the order public void UpdateOrRemove(int orderId, OrderStatus newStatus) { CancelPendingOrder cancelPendingOrder; if (_cancelPendingOrders.TryGetValue(orderId, out cancelPendingOrder)) { // The purpose of this pattern 'trygetvalue/lock/if containskey' is to guarantee that threads working on the same order will be correctly synchronized // Thread 1 at HandleCancelOrderRequest() processing a failed cancel request will call RemoveAndFallback() and revert order status // Thread 2 at HandleOrderEvent() with a filled order event will call UpdateOrRemove() and remove the order, ignoring its 'saved' status. lock (cancelPendingOrder) { if (newStatus.IsClosed()) { RemoveOrderFromCollection(orderId); } else if (newStatus != OrderStatus.CancelPending) { // check again because it might of been removed by the failed to cancel event if (_cancelPendingOrders.ContainsKey(orderId)) { _cancelPendingOrders[orderId].Status = newStatus; } } } } } /// /// Removes an order which we failed to cancel and falls back the order Status to previous value /// /// The order that failed to be canceled public void RemoveAndFallback(Order order) { CancelPendingOrder cancelPendingOrder; if (_cancelPendingOrders.TryGetValue(order.Id, out cancelPendingOrder)) { // The purpose of this pattern 'trygetvalue/lock/if containskey' is to guarantee that threads working on the same order will be correctly synchronized // Thread 1 at HandleCancelOrderRequest() processing a failed cancel request will call RemoveAndFallback() and revert order status // Thread 2 at HandleOrderEvent() with a filled order event will call UpdateOrRemove() and remove the order, ignoring its 'saved' status. lock (cancelPendingOrder) { if (_cancelPendingOrders.ContainsKey(order.Id)) { // update Status before removing from _cancelPendingOrders order.Status = _cancelPendingOrders[order.Id].Status; RemoveOrderFromCollection(order.Id); } } } } private void RemoveOrderFromCollection(int orderId) { CancelPendingOrder cancelPendingOrderTrash; _cancelPendingOrders.TryRemove(orderId, out cancelPendingOrderTrash); } private class CancelPendingOrder { public OrderStatus Status { get; set; } } } }