/// <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 = Convert.ToDecimal(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> /// 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> /// 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> /// 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 var tickerId = e.Id; var errorCode = e.Code; var errorMsg = e.Message; // rewrite these messages to be single lined errorMsg = errorMsg.Replace("\r\n", ". ").Replace("\r", ". ").Replace("\n", ". "); Log.Trace(string.Format("InteractiveBrokersBrokerage.HandleError(): Order: {0} ErrorCode: {1} - {2}", tickerId, errorCode, errorMsg)); // figure out the message type based on our code collections below var brokerageMessageType = BrokerageMessageType.Information; if (ErrorCodes.Contains(errorCode)) { brokerageMessageType = BrokerageMessageType.Error; } else if (WarningCodes.Contains(errorCode)) { brokerageMessageType = BrokerageMessageType.Warning; } // code 1100 is a connection failure, we'll wait a minute before exploding gracefully if (errorCode == 1100 && !_disconnected1100Fired) { _disconnected1100Fired = true; // begin the try wait logic TryWaitForReconnect(); } else if (errorCode == 1102) { // we've reconnected _disconnected1100Fired = false; OnMessage(BrokerageMessageEvent.Reconnected(errorMsg)); } else if (errorCode == 506) { Log.Trace("InteractiveBrokersBrokerage.HandleError(): Server Version: " + _client.ClientSocket.ServerVersion); } if (InvalidatingCodes.Contains(errorCode)) { Log.Trace(string.Format("InteractiveBrokersBrokerage.HandleError.InvalidateOrder(): Order: {0} ErrorCode: {1} - {2}", tickerId, errorCode, errorMsg)); // invalidate the order var order = _orderProvider.GetOrderByBrokerageId(tickerId); const int orderFee = 0; var orderEvent = new OrderEvent(order, DateTime.UtcNow, orderFee) { Status = OrderStatus.Invalid }; OnOrderEvent(orderEvent); } OnMessage(new BrokerageMessageEvent(brokerageMessageType, errorCode, errorMsg)); }
private void HandleTickSize(object sender, IB.TickSizeEventArgs e) { Symbol 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.Field) { case IBApi.TickType.BID_SIZE: tick.TickType = TickType.Quote; _lastBidPrices.TryGetValue(symbol, out tick.BidPrice); _lastBidSizes[symbol] = tick.Quantity; tick.Value = tick.BidPrice; tick.BidSize = tick.Quantity; break; case IBApi.TickType.ASK_SIZE: tick.TickType = TickType.Quote; _lastAskPrices.TryGetValue(symbol, out tick.AskPrice); _lastAskSizes[symbol] = tick.Quantity; tick.Value = tick.AskPrice; tick.AskSize = tick.Quantity; break; case IBApi.TickType.LAST_SIZE: 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 void HandleBrokerTime(object sender, IB.CurrentTimeUtcEventArgs e) { // keep track of clock drift _brokerTimeDiff = e.CurrentTimeUtc.Subtract(DateTime.UtcNow); }
/// <summary> /// Creates a holding object from te UpdatePortfolioEventArgs /// </summary> private Holding CreateHolding(IB.UpdatePortfolioEventArgs e) { var currencySymbol = Currencies.GetCurrencySymbol(e.Contract.Currency); return new Holding { Symbol = MapSymbol(e.Contract), Type = ConvertSecurityType(e.Contract.SecType), Quantity = e.Position, AveragePrice = Convert.ToDecimal(e.AverageCost), MarketPrice = Convert.ToDecimal(e.MarketPrice), ConversionRate = 1m, // this will be overwritten when GetAccountHoldings is called to ensure fresh values CurrencySymbol = currencySymbol }; }