17 using System.Collections.Concurrent;
18 using System.Collections.Generic;
43 private bool _needsScan;
44 private DateTime _nextOptionAssignmentTime;
45 private readonly ConcurrentDictionary<int, Order> _pending;
46 private readonly
object _needsScanLock =
new object();
47 private readonly HashSet<Symbol> _pendingOptionAssignments =
new HashSet<Symbol>();
59 : this(algorithm,
"Backtesting Brokerage")
72 _pending =
new ConcurrentDictionary<int, Order>();
100 where kvp.Value.Holdings.AbsoluteQuantity > 0
101 select
new Holding(kvp.Value)).ToList();
127 lock (_needsScanLock)
130 SetPendingOrder(order);
133 AddBrokerageOrderId(order);
159 lock (_needsScanLock)
162 if (!_pending.TryGetValue(order.
Id, out pending))
169 SetPendingOrder(order);
172 AddBrokerageOrderId(order);
198 if (!order.TryGetGroupOrders(TryGetOrder, out var orders))
204 foreach (var orderInGroup
in orders)
206 lock (_needsScanLock)
208 if (!_pending.TryRemove(orderInGroup.Id, out var _))
216 AddBrokerageOrderId(orderInGroup);
234 ProcessAssignmentOrders();
236 lock (_needsScanLock)
244 var stillNeedsScan =
false;
247 foreach (var kvp
in _pending.OrderBySafe(x => x.Key))
249 var order = kvp.Value;
252 Log.
Error(
"BacktestingBrokerage.Scan(): Null pending order found: " + kvp.Key);
253 _pending.TryRemove(kvp.Key, out order);
257 if (order.Status.IsClosed())
260 _pending.TryRemove(order.Id, out var _);
267 stillNeedsScan =
true;
271 if (!order.TryGetGroupOrders(TryGetOrder, out var orders))
274 stillNeedsScan =
true;
280 Log.
Error($
"BacktestingBrokerage.Scan(): Unable to process orders: [{string.Join(",
", orders.Select(o => o.Id))}] The security no longer exists. UtcTime: {Algorithm.UtcTime}");
286 if (!TryOrderPreChecks(securities, out stillNeedsScan))
297 catch (Exception err)
300 RemoveOrders(orders,
OrderStatus.Invalid, err.Message);
303 Algorithm.
Error($
"Order Error: ids: [{string.Join(",
", orders.Select(o => o.Id))}], Error executing margin models: {err.Message}");
307 var fills =
new List<OrderEvent>();
312 var security = securities[order];
313 var model = security.FillModel;
318 if (order.Type ==
OrderType.OptionExercise)
320 var option = (
Option)security;
321 fills.AddRange(option.OptionExerciseModel.OptionExercise(option, order as
OptionExerciseOrder));
334 var fill = model.Fill(context);
335 if (fill.All(x => order.TimeInForce.IsFillValid(security, order, x)))
337 fills.AddRange(fill);
342 foreach (var fill
in fills)
349 if (fill.OrderFee.Value.Amount == 0m)
353 var legKVP = securities.Where(x => x.Key.Id == fill.OrderId).Single();
354 fill.OrderFee = legKVP.Value.FeeModel.GetOrderFee(
new OrderFeeParameters(legKVP.Value, legKVP.Key));
359 catch (Exception err)
362 Algorithm.
Error($
"Order Error: id: {order.Id}, Transaction model failed to fill for order type: {order.Type} with error: {err.Message}");
365 else if (order.Status ==
OrderStatus.CancelPending)
373 var message = securities.GetErrorMessage(hasSufficientBuyingPowerResult);
374 RemoveOrders(orders,
OrderStatus.Invalid, message);
380 if (fills.Count == 0)
385 List<OrderEvent> fillEvents =
new(orders.Count);
386 List<Tuple<Order, OrderEvent>> positionAssignments =
new(orders.Count);
387 foreach (var targetOrder
in orders)
389 var orderFills = fills.Where(f => f.OrderId == targetOrder.Id);
390 foreach (var fill
in orderFills)
393 if (targetOrder.Status != fill.Status || fill.FillQuantity != 0)
398 targetOrder.Status = fill.Status;
399 fillEvents.Add(fill);
402 if (fill.IsAssignment)
404 positionAssignments.Add(Tuple.Create(targetOrder, fill));
410 foreach (var assignment
in positionAssignments)
412 assignment.Item2.Message = assignment.Item1.Tag;
416 if (fills.All(x => x.Status.IsClosed()))
418 foreach (var o
in orders)
420 _pending.TryRemove(o.Id, out var _);
425 stillNeedsScan =
true;
431 _needsScan = stillNeedsScan || !_pending.IsEmpty;
438 private void OnOrderUpdated(
Order order)
455 private void ProcessAssignmentOrders()
459 _nextOptionAssignmentTime =
Algorithm.
UtcTime.RoundDown(Time.OneHour) + Time.OneHour;
462 .Where(security => security.Symbol.SecurityType.IsOption() && security.Holdings.IsShort)
463 .OrderBy(security => security.Symbol.ID.Symbol))
465 var option = (
Option)security;
467 if (result !=
null && result.Quantity != 0)
469 if (!_pendingOptionAssignments.Add(option.Symbol))
471 throw new InvalidOperationException($
"Duplicate option exercise order request for symbol {option.Symbol}. Please contact support");
486 for (
int i = 0; i < orderEvents.Count; i++)
488 _pendingOptionAssignments.Remove(orderEvents[i].
Symbol);
490 base.OnOrderEvents(orderEvents);
514 private void SetPendingOrder(
Order order)
516 _pending[order.
Id] = order;
527 foreach (var delisting
in delistings?.Values.OrderBy(x => !x.Symbol.SecurityType.IsOption()))
529 Log.
Debug($
"BacktestingBrokerage.ProcessDelistings(): Delisting {delisting.Type}: {delisting.Symbol.Value}, UtcTime: {Algorithm.UtcTime}, DelistingTime: {delisting.Time}");
538 if (security.Symbol.SecurityType.IsOption())
553 var universe = ukvp.Value;
554 if (universe.ContainsMember(security.Symbol))
557 if (userUniverse !=
null)
559 userUniverse.
Remove(security.Symbol);
572 foreach (var cancelledOrder
in cancelledOrders)
574 Log.
Trace(
"AlgorithmManager.Run(): " + cancelledOrder);
580 private void RemoveOrders(List<Order> orders,
OrderStatus orderStatus,
string message =
"")
582 var orderEvents =
new List<OrderEvent>(orders.Count);
583 for (var i = 0; i < orders.Count; i++)
585 var order = orders[i];
587 _pending.TryRemove(order.Id, out var _);
593 private bool TryOrderPreChecks(Dictionary<Order, Security> ordersSecurities, out
bool stillNeedsScan)
596 stillNeedsScan =
false;
598 var removedOrdersIds =
new HashSet<int>();
600 foreach (var kvp
in ordersSecurities)
603 var security = kvp.Value;
605 if (order.Type ==
OrderType.MarketOnOpen)
611 var currentBar = security.GetLastData();
612 var localOrderTime = order.Time.ConvertFromUtc(security.Exchange.TimeZone);
613 if (currentBar ==
null || localOrderTime >= currentBar.EndTime)
615 stillNeedsScan =
true;
622 if (order.TimeInForce.IsOrderExpired(security, order))
625 RemoveOrders(ordersSecurities.Select(kvp => kvp.Key).ToList(),
OrderStatus.Canceled,
"The order has expired.");
641 private Order TryGetOrder(
int orderId)
643 _pending.TryGetValue(orderId, out var order);
647 private static void AddBrokerageOrderId(
Order order)
649 var orderId = order.
Id.ToStringInvariant();
650 if (!order.
BrokerId.Contains(orderId))