17 using System.Collections.Concurrent;
18 using System.Collections.Generic;
20 using System.Runtime.CompilerServices;
21 using System.Threading;
42 private bool _brokerageIsBacktesting;
43 private bool _loggedFeeAdjustmentWarning;
46 private int _totalOrderCount;
49 private bool _firstRoundOffMessage =
false;
52 private long _lastFillTimeTicks;
54 private const int MaxCashSyncAttempts = 5;
55 private int _failedCashSyncAttempts;
63 private Thread _processingThread;
64 private readonly CancellationTokenSource _cancellationTokenSource =
new CancellationTokenSource();
66 private readonly ConcurrentQueue<OrderEvent> _orderEvents =
new ConcurrentQueue<OrderEvent>();
72 private readonly ConcurrentDictionary<int, Order> _completeOrders =
new ConcurrentDictionary<int, Order>();
78 private readonly ConcurrentDictionary<int, Order> _openOrders =
new ConcurrentDictionary<int, Order>();
85 private readonly ConcurrentDictionary<int, OrderTicket> _openOrderTickets =
new ConcurrentDictionary<int, OrderTicket>();
92 private readonly ConcurrentDictionary<int, OrderTicket> _completeOrderTickets =
new ConcurrentDictionary<int, OrderTicket>();
97 private readonly Dictionary<Symbol, DataNormalizationMode> _priceAdjustmentModes =
new Dictionary<Symbol, DataNormalizationMode>();
106 private readonly
object _lockHandleOrderEvent =
new object();
116 public ConcurrentDictionary<int, Order>
Orders
120 return _completeOrders;
132 public ConcurrentDictionary<int, OrderTicket>
OrderTickets
136 return _completeOrderTickets;
153 if (brokerage ==
null)
155 throw new ArgumentNullException(nameof(brokerage));
160 _resultHandler = resultHandler;
162 _brokerage = brokerage;
167 HandleOrderEvents(orderEvents);
172 HandleAccountChanged(account);
177 HandlePositionAssigned(fill);
182 HandleOptionNotification(e);
187 HandleNewBrokerageSideOrder(e);
192 HandleDelistingNotification(e);
197 HandlerBrokerageOrderIdChangedEvent(e);
202 HandleOrderUpdated(e);
207 _algorithm = algorithm;
217 _processingThread =
new Thread(
Run) { IsBackground =
true, Name =
"Transaction Thread" };
218 _processingThread.Start();
227 #region Order Request Processing
237 Log.
Trace(
"BrokerageTransactionHandler.Process(): " + request);
254 throw new ArgumentOutOfRangeException();
269 var shortable =
true;
277 var message = GetShortableErrorMessage(request.
Symbol, request.
Quantity);
281 _algorithm.
Debug($
"Warning: {message}");
292 Interlocked.Increment(ref _totalOrderCount);
294 if (response.IsSuccess)
296 _openOrderTickets.TryAdd(ticket.OrderId, ticket);
297 _completeOrderTickets.TryAdd(ticket.OrderId, ticket);
310 ?
"Algorithm warming up."
311 : response.ErrorMessage;
314 var security = _algorithm.
Securities[order.Symbol];
315 order.PriceCurrency = security.SymbolProperties.QuoteCurrency;
318 order.Tag = orderTag;
319 ticket.SetOrder(order);
320 _completeOrderTickets.TryAdd(ticket.OrderId, ticket);
321 _completeOrders.TryAdd(order.Id, order);
338 if (!ticket.
OrderSet.WaitOne(orderSetTimeout))
340 Log.
Error(
"BrokerageTransactionHandler.WaitForOrderSubmission(): " +
341 $
"The order request (Id={ticket.OrderId}) was not submitted within {orderSetTimeout.TotalSeconds} second(s).");
353 if (!_completeOrderTickets.TryGetValue(request.
OrderId, out ticket))
358 ticket.AddUpdateRequest(request);
363 var order = GetOrderByIdInternal(request.
OrderId);
364 var orderQuantity = request.
Quantity ?? ticket.Quantity;
366 var shortable =
true;
369 shortable = _algorithm.
Shortable(ticket.Symbol, orderQuantity, order.Id);
371 if (_algorithm.
LiveMode && !shortable)
376 _algorithm.
Debug($
"Warning: {GetShortableErrorMessage(ticket.Symbol, ticket.Quantity)}");
383 Log.
Error(
"BrokerageTransactionHandler.Update(): Cannot update a null order");
389 Log.
Error(
"BrokerageTransactionHandler.Update(): Cannot update a pending submit order with status " + order.Status);
395 Log.
Error(
"BrokerageTransactionHandler.Update(): Cannot update closed order with status " + order.Status);
409 GetShortableErrorMessage(ticket.Symbol, ticket.Quantity));
419 catch (Exception err)
435 if (!_completeOrderTickets.TryGetValue(request.
OrderId, out ticket))
437 Log.
Error(
"BrokerageTransactionHandler.CancelOrder(): Unable to locate ticket for order.");
445 if (!ticket.TrySetCancelRequest(request))
453 var order = GetOrderByIdInternal(request.
OrderId);
454 if (order !=
null && request.
Tag !=
null)
456 order.Tag = request.
Tag;
460 Log.
Error(
"BrokerageTransactionHandler.CancelOrder(): Cannot find this id.");
465 Log.
Error(
"BrokerageTransactionHandler.CancelOrder(): Cannot cancel order with status: " + order.Status);
468 else if (order.Status.IsClosed())
470 Log.
Error(
"BrokerageTransactionHandler.CancelOrder(): Cannot cancel order already " + order.Status);
493 catch (Exception err)
507 public IEnumerable<OrderTicket>
GetOrderTickets(Func<OrderTicket, bool> filter =
null)
509 return _completeOrderTickets.Select(x => x.Value).Where(filter ?? (x =>
true));
519 return _openOrderTickets.Select(x => x.Value).Where(filter ?? (x =>
true));
530 _completeOrderTickets.TryGetValue(orderId, out ticket);
543 Order order = GetOrderByIdInternal(orderId);
544 return order?.
Clone();
547 private Order GetOrderByIdInternal(
int orderId)
550 return _completeOrders.TryGetValue(orderId, out order) ? order :
null;
562 if (openOrders.Count > 0
564 && (openOrders[0].GroupOrderManager ==
null || openOrders[0].GroupOrderManager.Count == openOrders.Count))
572 private static List<Order>
GetOrdersByBrokerageId(
string brokerageId, ConcurrentDictionary<int, Order> orders)
575 .Where(x => x.Value.BrokerId.Contains(brokerageId))
576 .Select(kvp => kvp.Value.Clone())
586 public IEnumerable<Order>
GetOrders(Func<Order, bool> filter =
null)
591 return _completeOrders.Select(x => x.Value).Where(filter).Select(x => x.Clone());
593 return _completeOrders.Select(x => x.Value).Select(x => x.Clone());
606 return _openOrders.Select(x => x.Value).Where(filter).Select(x => x.Clone()).ToList();
608 return _openOrders.Select(x => x.Value).Select(x => x.Clone()).ToList();
618 foreach (var request
in _orderRequestQueue.GetConsumingEnumerable(_cancellationTokenSource.Token))
624 catch (Exception err)
627 _algorithm.SetRuntimeError(err,
"HandleOrderRequest");
630 if (_processingThread !=
null)
632 Log.
Trace(
"BrokerageTransactionHandler.Run(): Ending Thread...");
657 Log.
Error(
"BrokerageTransactionHandler.ProcessSynchronousEvents(): Timed out waiting for request queue to finish processing.");
670 if (++_failedCashSyncAttempts >= MaxCashSyncAttempts)
672 throw new Exception(
"The maximum number of attempts for brokerage cash sync has been reached.");
679 const int maxOrdersToKeep = 10000;
680 if (_completeOrders.Count < maxOrdersToKeep + 1)
685 Log.
Debug(
"BrokerageTransactionHandler.ProcessSynchronousEvents(): Start removing old orders...");
686 var max = _completeOrders.Max(x => x.Key);
687 var lowestOrderIdToKeep = max - maxOrdersToKeep;
688 foreach (var item
in _completeOrders.Where(x => x.Key <= lowestOrderIdToKeep))
692 _completeOrders.TryRemove(item.Key, out value);
693 _completeOrderTickets.TryRemove(item.Key, out ticket);
696 Log.
Debug($
"BrokerageTransactionHandler.ProcessSynchronousEvents(): New order count {_completeOrders.Count}. Exit");
717 var orderTicket = order.ToOrderTicket(algorithm.
Transactions);
719 SetPriceAdjustmentMode(order, algorithm);
721 _openOrders.AddOrUpdate(order.
Id, order, (i, o) => order);
722 _completeOrders.AddOrUpdate(order.
Id, order, (i, o) => order);
723 _openOrderTickets.AddOrUpdate(order.
Id, orderTicket);
724 _completeOrderTickets.AddOrUpdate(order.
Id, orderTicket);
726 Interlocked.Increment(ref _totalOrderCount);
735 var timeout = TimeSpan.FromSeconds(60);
736 if (_processingThread !=
null)
741 Log.
Error(
"BrokerageTransactionHandler.Exit(): Exceed timeout: " + (
int)(timeout.TotalSeconds) +
" seconds.");
745 _processingThread?.StopSafely(timeout, _cancellationTokenSource);
747 _cancellationTokenSource.DisposeSafely();
770 throw new ArgumentOutOfRangeException();
786 var security = _algorithm.
Securities[order.Symbol];
787 order.PriceCurrency = security.SymbolProperties.QuoteCurrency;
788 if (
string.IsNullOrEmpty(order.Tag))
790 order.Tag = order.GetDefaultTag();
796 if (!_openOrders.TryAdd(order.Id, order) || !_completeOrders.TryAdd(order.Id, order))
798 Log.
Error(
"BrokerageTransactionHandler.HandleSubmitOrderRequest(): Unable to add new order, order not processed.");
801 if (!_completeOrderTickets.TryGetValue(order.Id, out ticket))
803 Log.
Error(
"BrokerageTransactionHandler.HandleSubmitOrderRequest(): Unable to retrieve order ticket, order not processed.");
807 var comboIsReady = order.TryGetGroupOrders(TryGetOrder, out var orders);
808 var comboSecuritiesFound = orders.TryGetGroupOrdersSecurities(_algorithm.
Portfolio, out var securities);
814 order.OrderSubmissionData =
new OrderSubmissionData(security.BidPrice, security.AskPrice, security.Close);
817 SetPriceAdjustmentMode(order, _algorithm);
820 ticket.SetOrder(order);
828 if (orders.Any(o => o.Quantity == 0))
831 _algorithm.
Error(response.ErrorMessage);
833 InvalidateOrders(orders, response.ErrorMessage);
837 if (!comboSecuritiesFound)
840 _algorithm.
Error(response.ErrorMessage);
842 InvalidateOrders(orders, response.ErrorMessage);
852 catch (Exception err)
855 _algorithm.
Error($
"Order Error: id: {order.Id.ToStringInvariant()}, Error executing margin models: {err.Message}");
859 "Error executing margin models"));
865 var errorMessage = securities.GetErrorMessage(hasSufficientBuyingPowerResult);
866 _algorithm.
Error(errorMessage);
868 InvalidateOrders(orders, errorMessage);
873 foreach (var kvp
in securities)
877 var errorMessage = $
"BrokerageModel declared unable to submit order: [{string.Join(",
", orders.Select(o => o.Id))}]";
883 InvalidateOrders(orders, response.ErrorMessage);
884 _algorithm.
Error(response.ErrorMessage);
893 orderPlaced = orders.All(o => _brokerage.
PlaceOrder(o));
895 catch (Exception err)
904 var errorMessage = $
"Brokerage failed to place orders: [{string.Join(",
", orders.Select(o => o.Id))}]";
906 InvalidateOrders(orders, errorMessage);
907 _algorithm.
Error(errorMessage);
921 if (!_completeOrders.TryGetValue(request.
OrderId, out order) || !_completeOrderTickets.TryGetValue(request.
OrderId, out ticket))
923 Log.
Error(
"BrokerageTransactionHandler.HandleUpdateOrderRequest(): Unable to update order with ID " + request.
OrderId);
932 var isClosedOrderUpdate =
false;
934 if (order.Status.IsClosed())
941 isClosedOrderUpdate =
true;
945 var security = _algorithm.
Securities[order.Symbol];
954 _algorithm.
Error(response.ErrorMessage);
958 "BrokerageModel declared unable to update order"));
963 order.ApplyUpdateOrderRequest(request);
968 ticket.SetOrder(order);
971 if (isClosedOrderUpdate)
981 catch (Exception err)
984 orderUpdated =
false;
991 var errorMessage =
"Brokerage failed to update order with id " + request.
OrderId;
992 _algorithm.
Error(errorMessage);
996 "Brokerage failed to update order"));
1010 if (!_completeOrders.TryGetValue(request.
OrderId, out order) || !_completeOrderTickets.TryGetValue(request.
OrderId, out ticket))
1012 Log.
Error(
"BrokerageTransactionHandler.HandleCancelOrderRequest(): Unable to cancel order with ID " + request.
OrderId +
".");
1023 if (order.Status.IsClosed())
1029 ticket.SetOrder(order);
1036 catch (Exception err)
1039 orderCanceled =
false;
1045 var message =
"Brokerage failed to cancel order with id " + order.Id;
1046 _algorithm.
Error(message);
1051 if (request.
Tag !=
null)
1054 order.Tag = request.
Tag;
1060 private void HandleOrderEvents(List<OrderEvent> orderEvents)
1062 lock (_lockHandleOrderEvent)
1065 var orders =
new List<Order>(orderEvents.Count);
1067 for (var i = 0; i < orderEvents.Count; i++)
1069 var orderEvent = orderEvents[i];
1071 if (orderEvent.Status.IsClosed() && _openOrders.TryRemove(orderEvent.OrderId, out var order))
1073 _completeOrders[orderEvent.OrderId] = order;
1075 else if (!_completeOrders.TryGetValue(orderEvent.OrderId, out order))
1077 Log.
Error(
"BrokerageTransactionHandler.HandleOrderEvents(): Unable to locate open Combo Order with id " + orderEvent.OrderId);
1078 LogOrderEvent(orderEvent);
1083 if (orderEvent.Status.IsClosed() && _openOrderTickets.TryRemove(orderEvent.OrderId, out var ticket))
1085 _completeOrderTickets[orderEvent.OrderId] = ticket;
1087 else if (!_completeOrderTickets.TryGetValue(orderEvent.OrderId, out ticket))
1089 Log.
Error(
"BrokerageTransactionHandler.HandleOrderEvents(): Unable to resolve open ticket: " + orderEvent.OrderId);
1090 LogOrderEvent(orderEvent);
1093 orderEvent.Ticket = ticket;
1096 var fillsToProcess =
new List<OrderEvent>(orderEvents.Count);
1099 for (var i = 0; i < orderEvents.Count; i++)
1101 var orderEvent = orderEvents[i];
1102 var order = orders[i];
1103 var ticket = orderEvent.Ticket;
1111 order.Status = orderEvent.Status;
1114 orderEvent.Id = order.GetNewId();
1117 switch (orderEvent.Status)
1120 order.CanceledTime = orderEvent.UtcTime;
1125 order.LastFillTime = orderEvent.UtcTime;
1128 if (orderEvent.Status ==
OrderStatus.Filled && !
string.IsNullOrWhiteSpace(orderEvent.Message))
1130 if (
string.IsNullOrWhiteSpace(order.Tag))
1132 order.Tag = orderEvent.Message;
1136 order.Tag +=
" - " + orderEvent.Message;
1144 if (ticket.UpdateRequests.Count > 0)
1146 order.LastUpdateTime = orderEvent.UtcTime;
1153 orderEvent.Quantity = order.Quantity;
1162 orderEvent.
LimitPrice = legLimitOrder.LimitPrice;
1166 orderEvent.
StopPrice = marketOrder.StopPrice;
1170 orderEvent.
LimitPrice = stopLimitOrder.LimitPrice;
1171 orderEvent.StopPrice = stopLimitOrder.StopPrice;
1175 orderEvent.
StopPrice = trailingStopOrder.StopPrice;
1176 orderEvent.TrailingAmount = trailingStopOrder.TrailingAmount;
1180 orderEvent.
LimitPrice = limitIfTouchedOrder.LimitPrice;
1181 orderEvent.TriggerPrice = limitIfTouchedOrder.TriggerPrice;
1188 fillsToProcess.Add(orderEvent);
1189 Interlocked.Exchange(ref _lastFillTimeTicks,
CurrentTimeUtc.Ticks);
1191 var security = _algorithm.
Securities[orderEvent.Symbol];
1193 if (orderEvent.Symbol.SecurityType ==
SecurityType.Crypto
1196 && orderEvent.OrderFee.Value.Currency == baseCurrency)
1200 orderEvent.FillQuantity -= orderEvent.OrderFee.Value.Amount;
1201 orderEvent.OrderFee =
new ModifiedFillQuantityOrderFee(orderEvent.OrderFee.Value, quoteCurrency, security.SymbolProperties.ContractMultiplier);
1203 if (!_loggedFeeAdjustmentWarning)
1205 _loggedFeeAdjustmentWarning =
true;
1206 const string message =
"When buying currency pairs, using Cash account types, fees in base currency" +
1207 " will be deducted from the filled quantity so virtual positions reflect actual holdings.";
1208 Log.
Trace($
"BrokerageTransactionHandler.HandleOrderEvent(): {message}");
1209 _algorithm.
Debug(message);
1220 catch (Exception err)
1223 _algorithm.
Error($
"Fill error: error in TradeBuilder.ProcessFill: {err.Message}");
1227 for (var i = 0; i < orderEvents.Count; i++)
1229 var orderEvent = orderEvents[i];
1233 var security = _algorithm.
Securities[orderEvent.Symbol];
1235 var multiplier = security.SymbolProperties.ContractMultiplier;
1236 var securityConversionRate = security.QuoteCurrency.ConversionRate;
1244 securityConversionRate,
1245 feeInAccountCurrency,
1248 catch (Exception err)
1255 orderEvent.Ticket.AddOrderEvent(orderEvent);
1260 for (var i = 0; i < orderEvents.Count; i++)
1262 var orderEvent = orderEvents[i];
1266 _orderEvents.Enqueue(orderEvent);
1278 catch (Exception err)
1281 _algorithm.SetRuntimeError(err,
"Order Event Handler");
1285 LogOrderEvent(orderEvent);
1289 private void HandleOrderEvent(
OrderEvent orderEvent)
1291 HandleOrderEvents(
new List<OrderEvent> { orderEvent });
1296 if (!_completeOrders.TryGetValue(e.
OrderId, out var order))
1298 Log.
Error(
"BrokerageTransactionHandler.HandleOrderUpdated(): Unable to locate open order with id " + e.
OrderId);
1317 private void SetPriceAdjustmentMode(
Order order,
IAlgorithm algorithm)
1326 if (!_priceAdjustmentModes.TryGetValue(order.
Symbol, out var mode))
1329 .GetSubscriptionDataConfigs(order.
Symbol, includeInternalConfigs:
true);
1330 if (configs.Count == 0)
1332 throw new InvalidOperationException($
"Unable to locate subscription data config for {order.Symbol}");
1335 mode = configs[0].DataNormalizationMode;
1336 _priceAdjustmentModes[order.
Symbol] = mode;
1346 private static void LogOrderEvent(
OrderEvent e)
1350 Log.
Debug(
"BrokerageTransactionHandler.LogOrderEvent(): " + e);
1359 private void HandleAccountChanged(
AccountEvent account)
1365 Log.
Trace($
"BrokerageTransactionHandler.HandleAccountChanged(): {account.CurrencySymbol} Cash Lean: {existingCashBalance} Brokerage: {account.CashBalance}. Will update: {_brokerage.AccountInstantlyUpdated}");
1381 var originalOrder = GetOrderByIdInternal(brokerageOrderIdChangedEvent.
OrderId);
1383 if (originalOrder ==
null)
1386 Log.
Error($
"BrokerageTransactionHandler.HandlerBrokerageOrderIdChangedEvent(): Lean order id {brokerageOrderIdChangedEvent.OrderId} not found");
1391 originalOrder.BrokerId = brokerageOrderIdChangedEvent.
BrokerId;
1397 private void HandlePositionAssigned(
OrderEvent fill)
1408 if (_algorithm.
LiveMode || security.Holdings.Quantity != 0)
1411 $
"BrokerageTransactionHandler.HandleDelistingNotification(): UtcTime: {CurrentTimeUtc} clearing position for delisted holding: " +
1412 $
"Symbol: {e.Symbol.Value}, " +
1413 $
"Quantity: {security.Holdings.Quantity}");
1417 var quantity = -security.Holdings.Quantity;
1420 var tag =
"Liquidate from delisting";
1429 FillPrice = security.Price,
1431 FillQuantity = order.Quantity
1435 HandleOrderEvent(fill);
1450 lock (_lockHandleOrderEvent)
1457 if (_algorithm.
LiveMode || security.Holdings.Quantity != 0)
1460 $
"BrokerageTransactionHandler.HandleOptionNotification(): UtcTime: {CurrentTimeUtc} clearing position for expired option holding: " +
1461 $
"Symbol: {e.Symbol.Value}, " +
1462 $
"Holdings: {security.Holdings.Quantity}");
1465 var quantity = -security.Holdings.Quantity;
1470 var exerciseOrder = GenerateOptionExerciseOrder(security, quantity, e.
Tag);
1472 EmitOptionNotificationEvents(security, exerciseOrder);
1477 Log.
Error(
"BrokerageTransactionHandler.HandleOptionNotification(): " +
1478 $
"unexpected position ({e.Position} instead of zero) " +
1479 $
"for expired option contract: {e.Symbol.Value}");
1485 if (Math.Abs(e.
Position) < security.Holdings.AbsoluteQuantity)
1487 Log.
Trace(
"BrokerageTransactionHandler.HandleOptionNotification(): " +
1488 $
"Symbol {e.Symbol.Value} EventQuantity {e.Position} Holdings {security.Holdings.Quantity}");
1491 if (security.Holdings.IsLong)
1500 EmitOptionNotificationEvents(security, exerciseOrder);
1505 else if (security.Holdings.IsShort)
1521 const int orderWindowSeconds = 10;
1525 if (_brokerageIsBacktesting ||
1528 && (x.Status.IsOpen() || x.Status.IsFill() &&
1529 (Math.Abs((x.Time - nowUtc).TotalSeconds) < orderWindowSeconds
1530 || (x.LastUpdateTime.HasValue && Math.Abs((x.LastUpdateTime.Value - nowUtc).TotalSeconds) < orderWindowSeconds)
1531 || (x.LastFillTime.HasValue && Math.Abs((x.LastFillTime.Value - nowUtc).TotalSeconds) < orderWindowSeconds)))).Any())
1533 var quantity = e.
Position - security.Holdings.Quantity;
1535 var exerciseOrder = GenerateOptionExerciseOrder(security, quantity, e.
Tag);
1537 EmitOptionNotificationEvents(security, exerciseOrder);
1551 void onError(IReadOnlyCollection<SecurityType> supportedSecurityTypes) =>
1552 _algorithm.
Debug($
"Warning: New brokerage-side order could not be processed due to " +
1553 $
"it's security not being supported. Supported security types are {string.Join(",
", supportedSecurityTypes)}");
1556 _algorithm.GetOrAddUnrequestedSecurity(e.
Order.
Symbol, out _, onError))
1578 var option = (
Option)security;
1579 var orderEvents = option.OptionExerciseModel.OptionExercise(option, order);
1581 foreach (var orderEvent
in orderEvents)
1583 HandleOrderEvent(orderEvent);
1585 if (orderEvent.IsAssignment)
1587 orderEvent.Message = order.
Tag;
1588 HandlePositionAssigned(orderEvent);
1597 CurrentTimeUtc -
new DateTime(Interlocked.Read(ref _lastFillTimeTicks));
1611 if (orderLotMod != 0)
1615 if (!_firstRoundOffMessage)
1617 _algorithm.
Error(
"Warning: Due to brokerage limitations, orders will be rounded to " +
1618 $
"the nearest lot size of {security.SymbolProperties.LotSize.ToStringInvariant()}"
1620 _firstRoundOffMessage =
true;
1638 var comboIsReady = order.TryGetGroupOrders(TryGetOrder, out var orders);
1639 orders.TryGetGroupOrdersSecurities(_algorithm.
Portfolio, out var securities);
1657 RoundOrderPrice(security, limitOrder.LimitPrice,
"LimitPrice", (roundedPrice) => limitOrder.LimitPrice = roundedPrice);
1664 RoundOrderPrice(security, stopMarketOrder.StopPrice,
"StopPrice", (roundedPrice) => stopMarketOrder.StopPrice = roundedPrice);
1671 RoundOrderPrice(security, stopLimitOrder.LimitPrice,
"LimitPrice", (roundedPrice) => stopLimitOrder.LimitPrice = roundedPrice);
1672 RoundOrderPrice(security, stopLimitOrder.StopPrice,
"StopPrice", (roundedPrice) => stopLimitOrder.StopPrice = roundedPrice);
1679 RoundOrderPrice(security, trailingStopOrder.StopPrice,
"StopPrice",
1680 (roundedPrice) => trailingStopOrder.StopPrice = roundedPrice);
1682 if (!trailingStopOrder.TrailingAsPercentage)
1684 RoundOrderPrice(security, trailingStopOrder.TrailingAmount,
"TrailingAmount",
1685 (roundedAmount) => trailingStopOrder.TrailingAmount = roundedAmount);
1693 RoundOrderPrice(security, limitIfTouchedOrder.LimitPrice,
"LimitPrice",
1694 (roundedPrice) => limitIfTouchedOrder.LimitPrice = roundedPrice);
1695 RoundOrderPrice(security, limitIfTouchedOrder.TriggerPrice,
"TriggerPrice",
1696 (roundedPrice) => limitIfTouchedOrder.TriggerPrice = roundedPrice);
1703 RoundOrderPrice(security, comboLegOrder.LimitPrice,
"LimitPrice",
1704 (roundedPrice) => comboLegOrder.LimitPrice = roundedPrice);
1717 foreach (var (legOrder, legSecurity) in orders)
1719 var legIncrement = legSecurity.PriceVariationModel.GetMinimumPriceVariation(
1721 if (legIncrement > 0 && (increment == 0 || legIncrement < increment))
1723 increment = legIncrement;
1727 RoundOrderPrice(groupOrderManager.LimitPrice, increment,
"LimitPrice",
1728 (roundedPrice) => groupOrderManager.LimitPrice = roundedPrice);
1736 private void RoundOrderPrice(
Security security, decimal price,
string priceType, Action<decimal> setPrice)
1739 RoundOrderPrice(price, increment, priceType, setPrice);
1742 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1743 private void RoundOrderPrice(decimal price, decimal increment,
string priceType, Action<decimal> setPrice)
1747 var roundedPrice = Math.Round(price / increment) * increment;
1748 setPrice(roundedPrice);
1749 SendWarningOnPriceChange(priceType, roundedPrice, price);
1753 private Order TryGetOrder(
int orderId)
1755 _completeOrders.TryGetValue(orderId, out var order);
1759 private void InvalidateOrders(List<Order> orders,
string message)
1761 for (var i = 0; i < orders.Count; i++)
1763 var orderInGroup = orders[i];
1764 if (!orderInGroup.Status.IsClosed())
1772 private void SendWarningOnPriceChange(
string priceType, decimal priceRound, decimal priceOriginal)
1774 if (!priceOriginal.Equals(priceRound))
1777 $
"Warning: To meet brokerage precision requirements, order {priceType.ToStringInvariant()} was rounded to {priceRound.ToStringInvariant()} from {priceOriginal.ToStringInvariant()}"
1782 private string GetShortableErrorMessage(Symbol symbol, decimal quantity)
1785 return $
"Order exceeds shortable quantity {shortableQuantity} for Symbol {symbol} requested {quantity})";