/// <summary> /// Creates a new InteractiveBrokersBrokerage from the specified values /// </summary> /// <param name="account">The Interactive Brokers account name</param> /// <param name="host">host name or IP address of the machine where TWS is running. Leave blank to connect to the local host.</param> /// <param name="port">must match the port specified in TWS on the Configure>API>Socket Port field.</param> /// <param name="agentDescription">Used for Rule 80A describes the type of trader.</param> public InteractiveBrokersBrokerage(string account, string host, int port, IB.AgentDescription agentDescription = IB.AgentDescription.Individual) : base("Interactive Brokers Brokerage") { _account = account; _host = host; _port = port; _clientID = Interlocked.Increment(ref _nextClientID); _agentDescription = agentDescription; _client = new IB.IBClient(); }
/// <summary> /// Maps the IB Contract's symbol to a QC symbol /// </summary> private static Symbol MapSymbol(IB.Contract contract) { var securityType = ConvertSecurityType(contract.SecurityType); if (securityType == SecurityType.Forex) { // reformat for QC var symbol = contract.Symbol + contract.Currency; return new Symbol(SecurityIdentifier.GenerateForex(symbol, Market.FXCM), symbol); } if (securityType == SecurityType.Equity) { return new Symbol(SecurityIdentifier.GenerateEquity(contract.Symbol, Market.USA), contract.Symbol); } throw new NotImplementedException("The specified security type has not been implemented: " + securityType); }
/// <summary> /// Converts a QC order to an IB order /// </summary> private IB.Order ConvertOrder(Order order, IB.Contract contract, int ibOrderID) { var ibOrder = new IB.Order { ClientId = _clientID, OrderId = ibOrderID, Account = _account, Action = ConvertOrderDirection(order.Direction), TotalQuantity = Math.Abs(order.Quantity), OrderType = ConvertOrderType(order.Type), AllOrNone = false, Tif = IB.TimeInForce.GoodTillCancel, Transmit = true, Rule80A = _agentDescription }; if (order.Type == OrderType.MarketOnOpen) { ibOrder.Tif = IB.TimeInForce.MarketOnOpen; } var limitOrder = order as LimitOrder; var stopMarketOrder = order as StopMarketOrder; var stopLimitOrder = order as StopLimitOrder; if (limitOrder != null) { ibOrder.LimitPrice = RoundPrice(limitOrder.LimitPrice, GetMinTick(contract)); } else if (stopMarketOrder != null) { ibOrder.AuxPrice = RoundPrice(stopMarketOrder.StopPrice, GetMinTick(contract)); } else if (stopLimitOrder != null) { var minTick = GetMinTick(contract); ibOrder.LimitPrice = RoundPrice(stopLimitOrder.LimitPrice, minTick); ibOrder.AuxPrice = RoundPrice(stopLimitOrder.StopPrice, minTick); } // not yet supported //ibOrder.ParentId = //ibOrder.OcaGroup = return ibOrder; }
/// <summary> /// Handle order events from IB /// </summary> private void HandleOrderStatusUpdates(object sender, IB.OrderStatusEventArgs update) { try { var order = _orderProvider.GetOrderByBrokerageId(update.OrderId); if (order == null) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Unable to locate order with BrokerageID " + update.OrderId); return; } var status = ConvertOrderStatus(update.Status); if (order.Status == OrderStatus.Filled && update.Filled == 0 && update.Remaining == 0) { // we're done with this order, remove from our state int value; _orderFills.TryRemove(order.Id, out value); } var orderFee = 0m; int filledThisTime; lock (_orderFillsLock) { // lock since we're getting and updating in multiple operations var currentFilled = _orderFills.GetOrAdd(order.Id, 0); if (currentFilled == 0) { // apply order fees on the first fill event TODO: What about partial filled orders that get cancelled? var security = _securityProvider.GetSecurity(order.Symbol); orderFee = security.FeeModel.GetOrderFee(security, order); } filledThisTime = update.Filled - currentFilled; _orderFills.AddOrUpdate(order.Id, currentFilled, (sym, filled) => update.Filled); } if (status == OrderStatus.Invalid) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): ERROR -- " + update.OrderId); } // set status based on filled this time if (filledThisTime != 0) { status = update.Remaining != 0 ? OrderStatus.PartiallyFilled : OrderStatus.Filled; } // don't send empty fill events else if (status == OrderStatus.PartiallyFilled || status == OrderStatus.Filled) { Log.Trace("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Ignored zero fill event: OrderId: " + update.OrderId + " Remaining: " + update.Remaining); return; } // mark sells as negative quantities var fillQuantity = order.Direction == OrderDirection.Buy ? filledThisTime : -filledThisTime; order.PriceCurrency = _securityProvider.GetSecurity(order.Symbol).SymbolProperties.QuoteCurrency; var orderEvent = new OrderEvent(order, DateTime.UtcNow, orderFee, "Interactive Brokers Fill Event") { Status = status, FillPrice = update.LastFillPrice, FillQuantity = fillQuantity }; if (update.Remaining != 0) { orderEvent.Message += " - " + update.Remaining + " remaining"; } // if we're able to add to our fixed length, unique queue then send the event // otherwise it is a duplicate, so skip it if (_recentOrderEvents.Add(orderEvent.ToString() + update.Remaining)) { OnOrderEvent(orderEvent); } } catch(InvalidOperationException err) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Unable to resolve executions for BrokerageID: " + update.OrderId + " - " + err); } catch (Exception err) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): " + err); } }
/// <summary> /// Handles error messages from IB /// </summary> private void HandleError(object sender, IB.ErrorEventArgs e) { // https://www.interactivebrokers.com/en/software/api/apiguide/tables/api_message_codes.htm // rewrite these messages to be single lined e.ErrorMsg = e.ErrorMsg.Replace("\r\n", ". ").Replace("\r", ". ").Replace("\n", ". "); Log.Trace(string.Format("InteractiveBrokersBrokerage.HandleError(): Order: {0} ErrorCode: {1} - {2}", e.TickerId, e.ErrorCode, e.ErrorMsg)); // figure out the message type based on our code collections below var brokerageMessageType = BrokerageMessageType.Information; if (ErrorCodes.Contains((int) e.ErrorCode)) { brokerageMessageType = BrokerageMessageType.Error; } else if (WarningCodes.Contains((int) e.ErrorCode)) { brokerageMessageType = BrokerageMessageType.Warning; } // code 1100 is a connection failure, we'll wait a minute before exploding gracefully if ((int) e.ErrorCode == 1100 && !_disconnected1100Fired) { _disconnected1100Fired = true; // begin the try wait logic TryWaitForReconnect(); } else if ((int) e.ErrorCode == 1102) { // we've reconnected _disconnected1100Fired = false; OnMessage(BrokerageMessageEvent.Reconnected(e.ErrorMsg)); } if (InvalidatingCodes.Contains((int)e.ErrorCode)) { Log.Trace(string.Format("InteractiveBrokersBrokerage.HandleError.InvalidateOrder(): Order: {0} ErrorCode: {1} - {2}", e.TickerId, e.ErrorCode, e.ErrorMsg)); // invalidate the order var order = _orderProvider.GetOrderByBrokerageId(e.TickerId); const int orderFee = 0; var orderEvent = new OrderEvent(order, DateTime.UtcNow, orderFee) { Status = OrderStatus.Invalid }; OnOrderEvent(orderEvent); } OnMessage(new BrokerageMessageEvent(brokerageMessageType, (int) e.ErrorCode, e.ErrorMsg)); }
private decimal GetMinTick(IB.Contract contract) { IB.ContractDetails details; if (_contractDetails.TryGetValue(contract.Symbol, out details)) { return (decimal) details.MinTick; } details = GetContractDetails(contract); if (details == null) { // we were unable to find the contract details return 0; } return (decimal) details.MinTick; }
/// <summary> /// Gets the execution details matching the filter /// </summary> /// <returns>A list of executions matching the filter</returns> public List<IB.ExecDetailsEventArgs> GetExecutions(string symbol, IB.SecurityType? type, string exchange, DateTime? timeSince, IB.ActionSide? side) { var filter = new IB.ExecutionFilter { AcctCode = _account, ClientId = _clientID, Exchange = exchange, SecurityType = type ?? IB.SecurityType.Undefined, Symbol = symbol, Time = timeSince ?? DateTime.MinValue, Side = side ?? IB.ActionSide.Undefined }; var details = new List<IB.ExecDetailsEventArgs>(); using (var client = new IB.IBClient()) { client.Connect(_host, _port, IncrementClientID()); var manualResetEvent = new ManualResetEvent(false); int requestID = GetNextRequestID(); // define our event handlers EventHandler<IB.ExecutionDataEndEventArgs> clientOnExecutionDataEnd = (sender, args) => { if (args.RequestId == requestID) manualResetEvent.Set(); }; EventHandler<IB.ExecDetailsEventArgs> clientOnExecDetails = (sender, args) => { if (args.RequestId == requestID) details.Add(args); }; client.ExecDetails += clientOnExecDetails; client.ExecutionDataEnd += clientOnExecutionDataEnd; // no need to be fancy with request id since that's all this client does is 1 request client.RequestExecutions(requestID, filter); if (!manualResetEvent.WaitOne(5000)) { throw new TimeoutException("InteractiveBrokersBrokerage.GetExecutions(): Operation took longer than 1 second."); } // remove our event handlers client.ExecDetails -= clientOnExecDetails; client.ExecutionDataEnd -= clientOnExecutionDataEnd; } return details; }
/// <summary> /// Creates a new InteractiveBrokersBrokerage from the specified values /// </summary> /// <param name="orderProvider">An instance of IOrderProvider used to fetch Order objects by brokerage ID</param> /// <param name="securityProvider">The security provider used to give access to algorithm securities</param> /// <param name="account">The Interactive Brokers account name</param> /// <param name="host">host name or IP address of the machine where TWS is running. Leave blank to connect to the local host.</param> /// <param name="port">must match the port specified in TWS on the Configure>API>Socket Port field.</param> /// <param name="agentDescription">Used for Rule 80A describes the type of trader.</param> public InteractiveBrokersBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider, string account, string host, int port, IB.AgentDescription agentDescription = IB.AgentDescription.Individual) : base("Interactive Brokers Brokerage") { _orderProvider = orderProvider; _securityProvider = securityProvider; _account = account; _host = host; _port = port; _clientID = IncrementClientID(); _agentDescription = agentDescription; _client = new IB.IBClient(); // set up event handlers _client.UpdatePortfolio += HandlePortfolioUpdates; _client.OrderStatus += HandleOrderStatusUpdates; _client.UpdateAccountValue += HandleUpdateAccountValue; _client.Error += HandleError; _client.TickPrice += HandleTickPrice; _client.TickSize += HandleTickSize; _client.CurrentTime += HandleBrokerTime; // we need to wait until we receive the next valid id from the server _client.NextValidId += (sender, e) => { // only grab this id when we initialize, and we'll manually increment it here to avoid threading issues if (_nextValidID == 0) { _nextValidID = e.OrderId; _waitForNextValidID.Set(); } Log.Trace("InteractiveBrokersBrokerage.HandleNextValidID(): " + e.OrderId); }; }
/// <summary> /// Handle portfolio changed events from IB /// </summary> private void HandlePortfolioUpdates(object sender, IB.UpdatePortfolioEventArgs e) { _accountHoldingsResetEvent.Reset(); var holding = CreateHolding(e); _accountHoldings[holding.Symbol] = holding; OnPortfolioChanged(new SecurityEvent(holding.Symbol, e.Position, e.AverageCost)); }
/// <summary> /// Handle order events from IB /// </summary> private void HandleOrderStatusUpdates(object sender, IB.OrderStatusEventArgs update) { try { if (update.Status == IB.OrderStatus.PreSubmitted || update.Status == IB.OrderStatus.PendingSubmit) { return; } var status = ConvertOrderStatus(update.Status); if (status != OrderStatus.PartiallyFilled && status != OrderStatus.Filled && status != OrderStatus.Canceled && status != OrderStatus.Submitted && status != OrderStatus.Invalid) { Log.Trace("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Status: " + status); return; } if (status == OrderStatus.Invalid) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): ERROR -- " + update.OrderId); } var order = _orderProvider.GetOrderByBrokerageId(update.OrderId); if (order == null) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Unable to locate order with BrokerageID " + update.OrderId); return; } // mark sells as negative quantities var fillQuantity = order.Direction == OrderDirection.Buy ? update.Filled : -update.Filled; var orderEvent = new OrderEvent(order, "Interactive Brokers Fill Event") { Status = status, FillPrice = update.AverageFillPrice, FillQuantity = fillQuantity }; if (update.Remaining != 0) { orderEvent.Message += " - " + update.Remaining + " remaining"; } // if we're able to add to our fixed length, unique queue then send the event // otherwise it is a duplicate, so skip it if (_recentOrderEvents.Add(orderEvent.ToString() + update.Remaining)) { OnOrderEvent(orderEvent); } } catch(InvalidOperationException err) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Unable to resolve executions for BrokerageID: " + update.OrderId + " - " + err.Message); } catch (Exception err) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): " + err.Message); } }
void HandleBrokerTime(object sender, IB.CurrentTimeEventArgs e) { DateTime brokerTime = e.Time.ToLocalTime(); _brokerTimeDiff = brokerTime.Subtract(DateTime.Now); }
/// <summary> /// Maps the IB Contract's symbol to a QC symbol /// </summary> private static Symbol MapSymbol(IB.Contract contract) { if (contract.SecurityType == IB.SecurityType.Cash) { // reformat for QC return new Symbol(contract.Symbol + contract.Currency); } return new Symbol(contract.Symbol); }
void HandleTickPrice(object sender, IB.TickPriceEventArgs e) { var symbol = default(SymbolCacheKey); if (!_subscribedTickets.TryGetValue(e.TickerId, out symbol)) return; var tick = new Tick(); // in the event of a symbol change this will break since we'll be assigning the // new symbol to the permtick which won't be known by the algorithm tick.Symbol = new Symbol(symbol.Item2); tick.Time = GetBrokerTime(); if (symbol.Item1 == SecurityType.Forex) { // forex exchange hours are specified in UTC-05 tick.Time = tick.Time.ConvertTo(TimeZones.NewYork, TimeZones.EasternStandard); } tick.Value = e.Price; if (e.Price <= 0 && symbol.Item1 != SecurityType.Future && symbol.Item1 != SecurityType.Option) return; switch (e.TickType) { case IB.TickType.BidPrice: tick.TickType = TickType.Quote; tick.BidPrice = e.Price; _lastBidSizes.TryGetValue(symbol, out tick.Quantity); _lastBidPrices[symbol] = e.Price; break; case IB.TickType.AskPrice: tick.TickType = TickType.Quote; tick.AskPrice = e.Price; _lastAskSizes.TryGetValue(symbol, out tick.Quantity); _lastAskPrices[symbol] = e.Price; break; case IB.TickType.LastPrice: tick.TickType = TickType.Trade; tick.Value = e.Price; _lastPrices[symbol] = e.Price; break; case IB.TickType.HighPrice: case IB.TickType.LowPrice: case IB.TickType.ClosePrice: case IB.TickType.OpenPrice: default: return; } lock (_ticks) if (tick.IsValid()) _ticks.Add(tick); }
void HandleTickSize(object sender, IB.TickSizeEventArgs e) { var symbol = default(SymbolCacheKey); if (!_subscribedTickets.TryGetValue(e.TickerId, out symbol)) return; var tick = new Tick(); // in the event of a symbol change this will break since we'll be assigning the // new symbol to the permtick which won't be known by the algorithm tick.Symbol = new Symbol(symbol.Item2); tick.Quantity = AdjustQuantity(symbol.Item1, e.Size); tick.Time = GetBrokerTime(); if (tick.Quantity == 0) return; switch (e.TickType) { case IB.TickType.BidSize: tick.TickType = TickType.Quote; _lastBidPrices.TryGetValue(symbol, out tick.BidPrice); _lastBidSizes[symbol] = tick.Quantity; tick.Value = tick.BidPrice; break; case IB.TickType.AskSize: tick.TickType = TickType.Quote; _lastAskPrices.TryGetValue(symbol, out tick.AskPrice); _lastAskSizes[symbol] = tick.Quantity; tick.Value = tick.AskPrice; break; case IB.TickType.LastSize: tick.TickType = TickType.Trade; decimal lastPrice; _lastPrices.TryGetValue(symbol, out lastPrice); _lastVolumes[symbol] = tick.Quantity; tick.Value = lastPrice; break; default: return; } lock (_ticks) if (tick.IsValid()) _ticks.Add(tick); }
/// <summary> /// Handle order events from IB /// </summary> private void HandleOrderStatusUpdates(object sender, IB.OrderStatusEventArgs update) { try { if (update.Status == IB.OrderStatus.PreSubmitted || update.Status == IB.OrderStatus.PendingSubmit) { return; } var status = ConvertOrderStatus(update.Status); if (status != OrderStatus.PartiallyFilled && status != OrderStatus.Filled && status != OrderStatus.Canceled && status != OrderStatus.Submitted && status != OrderStatus.Invalid) { Log.Trace("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Status: " + status); return; } if (status == OrderStatus.Invalid) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): ERROR -- " + update.OrderId); } var order = _orderProvider.GetOrderByBrokerageId(update.OrderId); if (order == null) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Unable to locate order with BrokerageID " + update.OrderId); return; } int filledThisTime; lock (_orderFillsLock) { // lock since we're getting and updating in multiple operations var currentFilled = _orderFills.GetOrAdd(order.Symbol, 0); filledThisTime = update.Filled - currentFilled; _orderFills.AddOrUpdate(order.Symbol, currentFilled, (sym, filled) => update.Filled); } // don't send empty fill events if (filledThisTime == 0 && (status == OrderStatus.PartiallyFilled || status == OrderStatus.Filled)) { Log.Trace("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Ignored zero fill event: OrderId: " + update.OrderId + " Remaining: " + update.Remaining); return; } // mark sells as negative quantities var fillQuantity = order.Direction == OrderDirection.Buy ? filledThisTime : -filledThisTime; const int orderFee = 0; var orderEvent = new OrderEvent(order, DateTime.UtcNow, orderFee, "Interactive Brokers Fill Event") { Status = status, FillPrice = update.LastFillPrice, FillQuantity = fillQuantity }; if (update.Remaining != 0) { orderEvent.Message += " - " + update.Remaining + " remaining"; } // if we're able to add to our fixed length, unique queue then send the event // otherwise it is a duplicate, so skip it if (_recentOrderEvents.Add(orderEvent.ToString() + update.Remaining)) { OnOrderEvent(orderEvent); } } catch(InvalidOperationException err) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): Unable to resolve executions for BrokerageID: " + update.OrderId + " - " + err); } catch (Exception err) { Log.Error("InteractiveBrokersBrokerage.HandleOrderStatusUpdates(): " + err); } }
/// <summary> /// Maps the IB Contract's symbol to a QC symbol /// </summary> private Symbol MapSymbol(IB.Contract contract) { var securityType = ConvertSecurityType(contract.SecurityType); var ibSymbol = securityType == SecurityType.Forex ? contract.Symbol + contract.Currency : contract.Symbol; var market = securityType == SecurityType.Forex ? Market.FXCM : Market.USA; return _symbolMapper.GetLeanSymbol(ibSymbol, securityType, market); }
void HandleBrokerTime(object sender, IB.CurrentTimeEventArgs e) { // keep track of clock drift _brokerTimeDiff = e.Time.Subtract(DateTime.UtcNow); }
void HandleTickPrice(object sender, IB.TickPriceEventArgs e) { var symbol = default(SymbolCacheKey); if (!_subscribedTickets.TryGetValue(e.TickerId, out symbol)) return; var tick = new Tick(); tick.Symbol = symbol.Item2; tick.Time = GetBrokerTime(); tick.Value = e.Price; if (e.Price <= 0 && symbol.Item1 != SecurityType.Future && symbol.Item1 != SecurityType.Option) return; switch (e.TickType) { case IB.TickType.BidPrice: tick.TickType = TickType.Quote; tick.BidPrice = e.Price; _lastBidSizes.TryGetValue(symbol, out tick.Quantity); _lastBidPrices[symbol] = e.Price; break; case IB.TickType.AskPrice: tick.TickType = TickType.Quote; tick.AskPrice = e.Price; _lastAskSizes.TryGetValue(symbol, out tick.Quantity); _lastAskPrices[symbol] = e.Price; break; case IB.TickType.LastPrice: tick.TickType = TickType.Trade; tick.Value = e.Price; _lastPrices[symbol] = e.Price; break; case IB.TickType.HighPrice: case IB.TickType.LowPrice: case IB.TickType.ClosePrice: case IB.TickType.OpenPrice: default: return; } lock (_ticks) if (tick.IsValid()) _ticks.Add(tick); }
void HandleTickSize(object sender, IB.TickSizeEventArgs e) { var symbol = default(Symbol); if (!_subscribedTickets.TryGetValue(e.TickerId, out symbol)) return; var tick = new Tick(); // in the event of a symbol change this will break since we'll be assigning the // new symbol to the permtick which won't be known by the algorithm tick.Symbol = symbol; var securityType = symbol.ID.SecurityType; tick.Quantity = AdjustQuantity(securityType, e.Size); tick.Time = GetBrokerTime(); if (securityType == SecurityType.Forex) { // forex exchange hours are specified in UTC-05 tick.Time = tick.Time.ConvertTo(TimeZones.NewYork, TimeZones.EasternStandard); } if (tick.Quantity == 0) return; switch (e.TickType) { case IB.TickType.BidSize: tick.TickType = TickType.Quote; _lastBidPrices.TryGetValue(symbol, out tick.BidPrice); _lastBidSizes[symbol] = tick.Quantity; tick.Value = tick.BidPrice; tick.BidSize = tick.Quantity; break; case IB.TickType.AskSize: tick.TickType = TickType.Quote; _lastAskPrices.TryGetValue(symbol, out tick.AskPrice); _lastAskSizes[symbol] = tick.Quantity; tick.Value = tick.AskPrice; tick.AskSize = tick.Quantity; break; case IB.TickType.LastSize: tick.TickType = TickType.Trade; decimal lastPrice; _lastPrices.TryGetValue(symbol, out lastPrice); _lastVolumes[symbol] = tick.Quantity; tick.Value = lastPrice; break; default: return; } lock (_ticks) if (tick.IsValid()) _ticks.Add(tick); }
void HandleTickSize(object sender, IB.TickSizeEventArgs e) { var symbol = default(SymbolCacheKey); if (!_subscribedTickets.TryGetValue(e.TickerId, out symbol)) return; var tick = new Tick(); tick.Symbol = symbol.Item2; tick.Quantity = AdjustQuantity(symbol.Item1, e.Size); tick.Time = GetBrokerTime(); if (tick.Quantity == 0) return; switch (e.TickType) { case IB.TickType.BidSize: tick.TickType = TickType.Quote; _lastBidPrices.TryGetValue(symbol, out tick.BidPrice); _lastBidSizes[symbol] = tick.Quantity; tick.Value = tick.BidPrice; break; case IB.TickType.AskSize: tick.TickType = TickType.Quote; _lastAskPrices.TryGetValue(symbol, out tick.AskPrice); _lastAskSizes[symbol] = tick.Quantity; tick.Value = tick.AskPrice; break; case IB.TickType.LastSize: tick.TickType = TickType.Trade; decimal lastPrice; _lastPrices.TryGetValue(symbol, out lastPrice); _lastVolumes[symbol] = tick.Quantity; tick.Value = lastPrice; break; default: return; } lock (_ticks) if (tick.IsValid()) _ticks.Add(tick); }
private string GetPrimaryExchange(IB.Contract contract) { IB.ContractDetails details; if (_contractDetails.TryGetValue(contract.Symbol, out details)) { return details.Summary.PrimaryExchange; } details = GetContractDetails(contract); if (details == null) { // we were unable to find the contract details return null; } return details.Summary.PrimaryExchange; }
/// <summary> /// Maps the IB Contract's symbol to a QC symbol /// </summary> private static string MapSymbol(IB.Contract contract) { if (contract.SecurityType == IB.SecurityType.Cash) { // reformat for QC return contract.Symbol + contract.Currency; } return contract.Symbol; }
private IB.ContractDetails GetContractDetails(IB.Contract contract) { IB.ContractDetails details = null; var requestID = GetNextRequestID(); var manualResetEvent = new ManualResetEvent(false); // define our event handlers EventHandler<IB.ContractDetailsEventArgs> clientOnContractDetails = (sender, args) => { // ignore other requests if (args.RequestId != requestID) return; details = args.ContractDetails; _contractDetails.TryAdd(contract.Symbol, details); manualResetEvent.Set(); }; _client.ContractDetails += clientOnContractDetails; // make the request for data _client.RequestContractDetails(requestID, contract); // we'll wait a second, but it may not exist so just pass through manualResetEvent.WaitOne(1000); // be sure to remove our event handlers _client.ContractDetails -= clientOnContractDetails; return details; }
/// <summary> /// Maps OrderType enum /// </summary> private OrderType ConvertOrderType(IB.Order order) { switch (order.OrderType) { case IB.OrderType.Limit: return OrderType.Limit; case IB.OrderType.Stop: return OrderType.StopMarket; case IB.OrderType.StopLimit: return OrderType.StopLimit; case IB.OrderType.MarketOnClose: return OrderType.MarketOnClose; case IB.OrderType.Market: if (order.Tif == IB.TimeInForce.MarketOnOpen) { return OrderType.MarketOnOpen; } return OrderType.Market; default: throw new InvalidEnumArgumentException("order.OrderType", (int)order.OrderType, typeof(OrderType)); } }
/// <summary> /// Stores all the account values /// </summary> private void HandleUpdateAccountValue(object sender, IB.UpdateAccountValueEventArgs e) { //https://www.interactivebrokers.com/en/software/api/apiguide/activex/updateaccountvalue.htm try { _accountProperties[e.Currency + ":" + e.Key] = e.Value; // we want to capture if the user's cash changes so we can reflect it in the algorithm if (e.Key == AccountValueKeys.CashBalance && e.Currency != "BASE") { var cashBalance = decimal.Parse(e.Value, CultureInfo.InvariantCulture); _cashBalances.AddOrUpdate(e.Currency, cashBalance); OnAccountChanged(new AccountEvent(e.Currency, cashBalance)); } } catch (Exception err) { Log.Error("InteractiveBrokersBrokerage.HandleUpdateAccountValue(): " + err); } }
/// <summary> /// Maps IB's OrderStats enum /// </summary> private OrderStatus ConvertOrderStatus(IB.OrderStatus status) { switch (status) { case IB.OrderStatus.ApiPending: case IB.OrderStatus.PendingSubmit: case IB.OrderStatus.PreSubmitted: return OrderStatus.New; case IB.OrderStatus.ApiCancelled: case IB.OrderStatus.PendingCancel: case IB.OrderStatus.Canceled: return OrderStatus.Canceled; case IB.OrderStatus.Submitted: return OrderStatus.Submitted; case IB.OrderStatus.Filled: return OrderStatus.Filled; case IB.OrderStatus.PartiallyFilled: return OrderStatus.PartiallyFilled; case IB.OrderStatus.Error: return OrderStatus.Invalid; case IB.OrderStatus.Inactive: Log.Error("InteractiveBrokersBrokerage.ConvertOrderStatus(): Inactive order"); return OrderStatus.None; case IB.OrderStatus.None: return OrderStatus.None; // not sure how to map these guys default: throw new InvalidEnumArgumentException("status", (int)status, typeof(IB.OrderStatus)); } }
/// <summary> /// Handle portfolio changed events from IB /// </summary> private void HandlePortfolioUpdates(object sender, IB.UpdatePortfolioEventArgs e) { _accountHoldingsResetEvent.Reset(); var holding = CreateHolding(e); _accountHoldings[holding.Symbol.Value] = holding; }
/// <summary> /// Maps SecurityType enum /// </summary> private static SecurityType ConvertSecurityType(IB.SecurityType type) { switch (type) { case IB.SecurityType.Stock: return SecurityType.Equity; case IB.SecurityType.Option: return SecurityType.Option; case IB.SecurityType.Commodity: return SecurityType.Commodity; case IB.SecurityType.Cash: return SecurityType.Forex; case IB.SecurityType.Future: return SecurityType.Future; // we don't map these security types to anything specific yet, load them as custom data instead of throwing case IB.SecurityType.Index: case IB.SecurityType.FutureOption: case IB.SecurityType.Bag: case IB.SecurityType.Bond: case IB.SecurityType.Warrant: case IB.SecurityType.Bill: case IB.SecurityType.Undefined: return SecurityType.Base; default: throw new ArgumentOutOfRangeException("type"); } }
private Order ConvertOrder(IB.Order ibOrder, IB.Contract contract) { // this function is called by GetOpenOrders which is mainly used by the setup handler to // initialize algorithm state. So the only time we'll be executing this code is when the account // has orders sitting and waiting from before algo initialization... // because of this we can't get the time accurately Order order; var mappedSymbol = MapSymbol(contract); var orderType = ConvertOrderType(ibOrder); switch (orderType) { case OrderType.Market: order = new MarketOrder(mappedSymbol, ibOrder.TotalQuantity, new DateTime() // not sure how to get this data ); break; case OrderType.MarketOnOpen: order = new MarketOnOpenOrder(mappedSymbol, ibOrder.TotalQuantity, new DateTime()); break; case OrderType.MarketOnClose: order = new MarketOnCloseOrder(mappedSymbol, ibOrder.TotalQuantity, new DateTime() ); break; case OrderType.Limit: order = new LimitOrder(mappedSymbol, ibOrder.TotalQuantity, ibOrder.LimitPrice, new DateTime() ); break; case OrderType.StopMarket: order = new StopMarketOrder(mappedSymbol, ibOrder.TotalQuantity, ibOrder.AuxPrice, new DateTime() ); break; case OrderType.StopLimit: order = new StopLimitOrder(mappedSymbol, ibOrder.TotalQuantity, ibOrder.AuxPrice, ibOrder.LimitPrice, new DateTime() ); break; default: throw new InvalidEnumArgumentException("orderType", (int) orderType, typeof (OrderType)); } order.BrokerId.Add(ibOrder.OrderId.ToString()); return order; }
/// <summary> /// Creates a holding object from te UpdatePortfolioEventArgs /// </summary> private Holding CreateHolding(IB.UpdatePortfolioEventArgs e) { string currencySymbol; if (!Currencies.CurrencySymbols.TryGetValue(e.Contract.Currency, out currencySymbol)) { currencySymbol = "$"; } return new Holding { Symbol = MapSymbol(e.Contract), Type = ConvertSecurityType(e.Contract.SecurityType), Quantity = e.Position, AveragePrice = e.AverageCost, MarketPrice = e.MarketPrice, ConversionRate = 1m, // this will be overwritten when GetAccountHoldings is called to ensure fresh values CurrencySymbol = currencySymbol }; }