Skip to content

Commit 2f75eb6

Browse files
authored
Enable transactions concurrency (#191)
* Enable transactions concurrency * Fix sequential requests id when placing orders concurrently * Minor change
1 parent 6a085db commit 2f75eb6

File tree

1 file changed

+59
-40
lines changed

1 file changed

+59
-40
lines changed

QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersBrokerage.cs

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ public sealed class InteractiveBrokersBrokerage : Brokerage, IDataQueueHandler,
290290
/// </summary>
291291
public bool IsFinancialAdvisor => IsMasterAccount(_account);
292292

293+
/// <summary>
294+
/// Enables concurrent order requests processing
295+
/// </summary>
296+
public override bool ConcurrencyEnabled => true;
297+
293298
/// <summary>
294299
/// Returns true if the account is a financial advisor master account
295300
/// </summary>
@@ -714,6 +719,9 @@ public override List<Holding> GetAccountHoldings()
714719
}
715720
}
716721

722+
// Prevent holdings calculation every time we receive portfolio updates from IB
723+
_loadExistingHoldings = false;
724+
717725
return holdings;
718726
}
719727

@@ -1514,54 +1522,65 @@ private void IBPlaceOrder(Order order, bool needsNewId, string exchange = null)
15141522
}
15151523
}
15161524

1525+
CheckRateLimiting();
1526+
15171527
int ibOrderId;
1518-
if (needsNewId)
1528+
ManualResetEventSlim orderSubmittedEvent = null;
1529+
1530+
// Let's lock here so that getting request id and placing the order is atomic.
1531+
// If there are multiple threads placing orders at the same time, two threads could
1532+
// get ids but the one with the higher id could place the order first, making the other
1533+
// order request to fail, since IB will assume the previous request ID was already used.
1534+
lock (_nextValidIdLocker)
15191535
{
1520-
// the order ids are generated for us by the SecurityTransactionManaer
1521-
var id = GetNextId();
1522-
foreach (var newOrder in orders)
1536+
if (needsNewId)
15231537
{
1524-
newOrder.BrokerId.Add(id.ToStringInvariant());
1538+
// the order ids are generated for us by the SecurityTransactionManaer
1539+
var id = GetNextId();
1540+
foreach (var newOrder in orders)
1541+
{
1542+
newOrder.BrokerId.Add(id.ToStringInvariant());
1543+
}
1544+
ibOrderId = id;
1545+
}
1546+
else if (order.BrokerId.Any())
1547+
{
1548+
// this is *not* perfect code
1549+
ibOrderId = Parse.Int(order.BrokerId[0]);
1550+
}
1551+
else
1552+
{
1553+
throw new ArgumentException("Expected order with populated BrokerId for updating orders.");
15251554
}
1526-
ibOrderId = id;
1527-
}
1528-
else if (order.BrokerId.Any())
1529-
{
1530-
// this is *not* perfect code
1531-
ibOrderId = Parse.Int(order.BrokerId[0]);
1532-
}
1533-
else
1534-
{
1535-
throw new ArgumentException("Expected order with populated BrokerId for updating orders.");
1536-
}
1537-
1538-
Log.Trace($"InteractiveBrokersBrokerage.PlaceOrder(): Symbol: {order.Symbol.Value} Quantity: {order.Quantity}. Id: {order.Id}. BrokerId: {ibOrderId}");
15391555

1540-
_requestInformation[ibOrderId] = new RequestInformation
1541-
{
1542-
RequestId = ibOrderId,
1543-
RequestType = RequestType.PlaceOrder,
1544-
AssociatedSymbol = order.Symbol,
1545-
Message = $"[Id={ibOrderId}] IBPlaceOrder: {order.Symbol.Value} ({GetContractDescription(contract)} )"
1546-
};
1556+
Log.Trace($"InteractiveBrokersBrokerage.PlaceOrder(): Symbol: {order.Symbol.Value} Quantity: {order.Quantity}. Id: {order.Id}. BrokerId: {ibOrderId}");
15471557

1548-
CheckRateLimiting();
1558+
_requestInformation[ibOrderId] = new RequestInformation
1559+
{
1560+
RequestId = ibOrderId,
1561+
RequestType = RequestType.PlaceOrder,
1562+
AssociatedSymbol = order.Symbol,
1563+
Message = $"[Id={ibOrderId}] IBPlaceOrder: {order.Symbol.Value} ({GetContractDescription(contract)} )"
1564+
};
15491565

1550-
if (order.Type == OrderType.OptionExercise)
1551-
{
1552-
// IB API requires exerciseQuantity to be positive
1553-
_client.ClientSocket.exerciseOptions(ibOrderId, contract, 1, decimal.ToInt32(order.AbsoluteQuantity), _account, 0,
1554-
string.Empty, string.Empty, false);
1566+
if (order.Type == OrderType.OptionExercise)
1567+
{
1568+
// IB API requires exerciseQuantity to be positive
1569+
_client.ClientSocket.exerciseOptions(ibOrderId, contract, 1, decimal.ToInt32(order.AbsoluteQuantity), _account, 0,
1570+
string.Empty, string.Empty, false);
1571+
}
1572+
else
1573+
{
1574+
_pendingOrderResponse[ibOrderId] = orderSubmittedEvent = new ManualResetEventSlim(false);
1575+
var ibOrder = ConvertOrder(orders, contract, ibOrderId);
1576+
_client.ClientSocket.placeOrder(ibOrder.OrderId, contract, ibOrder);
1577+
}
15551578
}
1556-
else
1557-
{
1558-
ManualResetEventSlim eventSlim = _pendingOrderResponse[ibOrderId] = eventSlim = new ManualResetEventSlim(false);
1559-
1560-
var ibOrder = ConvertOrder(orders, contract, ibOrderId);
1561-
_client.ClientSocket.placeOrder(ibOrder.OrderId, contract, ibOrder);
15621579

1580+
if (order.Type != OrderType.OptionExercise)
1581+
{
15631582
var noSubmissionOrderTypes = _noSubmissionOrderTypes.Contains(order.Type);
1564-
if (!eventSlim.Wait(noSubmissionOrderTypes ? _noSubmissionOrdersResponseTimeout : _responseTimeout))
1583+
if (!orderSubmittedEvent.Wait(noSubmissionOrderTypes ? _noSubmissionOrdersResponseTimeout : _responseTimeout))
15651584
{
15661585
if (noSubmissionOrderTypes)
15671586
{
@@ -1575,7 +1594,7 @@ private void IBPlaceOrder(Order order, bool needsNewId, string exchange = null)
15751594

15761595
if (_pendingOrderResponse.TryRemove(ibOrderId, out var _))
15771596
{
1578-
eventSlim.DisposeSafely();
1597+
orderSubmittedEvent.DisposeSafely();
15791598

15801599
var orderEvents = orders.Where(order => order != null).Select(order => new OrderEvent(order, DateTime.UtcNow, OrderFee.Zero)
15811600
{
@@ -1592,7 +1611,7 @@ private void IBPlaceOrder(Order order, bool needsNewId, string exchange = null)
15921611
}
15931612
else
15941613
{
1595-
eventSlim.DisposeSafely();
1614+
orderSubmittedEvent.DisposeSafely();
15961615
}
15971616
}
15981617
}

0 commit comments

Comments
 (0)