@@ -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