ReactIb is a C# library that wraps the Interactive Brokers TWS API in a strongly typed, asynchronous and reactive interface. I built it for automated trading, where the raw API makes reliable state management hard.
Stack: C#, async/await, Reactive Extensions (Rx), LINQ · Source: github.com/tekr/ReactIb
The problem
The TWS API is weakly typed and callback-based. Matching a response to the request that caused it needs hand-written synchronisation and shared state, and some responses carry no request identifier at all, so they can’t be matched reliably. Detecting when a request has completed, or failed, is similarly indirect.
That’s workable for a display-oriented application like TWS itself, where a person reads the results and error messages and decides what to do. It’s a real problem for automated trading, where the system must update its state deterministically, handle errors robustly and stay maintainable. A synchronous API would be easier to build against, but blocking calls bring their own costs.
The approach
C#’s async/await lets asynchronous code read like synchronous code, and Reactive Extensions (Rx) model streaming data as event streams that can be composed and queried with LINQ. ReactIb connects TWS to both: request/response calls become awaitable tasks, streaming data becomes observable sequences, and everything is strongly typed.
Examples
Fetching lists from TWS becomes a single awaited call:
// Connect to TWS on localhost at default port (7496)
ITwsApi twsApi = new TwsApi();
// Get IEnumerable of open orders
var openOrders = await twsApi.GetOpenOrdersAsync();
// Get IEnumerable of executions
var executions = await twsApi.GetExecutionsAsync();
// Contract object for historical query
var audUsdContract = new Contract
{
Currency = "USD",
Exchange = "IDEALPRO",
Symbol = "AUD",
SecType = "CASH"
};
// Fetch the last day's worth of hourly bars
var historicalBars = await twsApi.GetHistoricalBarsAsync(
audUsdContract,
DateTime.Today.AddDays(-1),
DateTime.Today,
BarSize.OneHour,
HistoricalBarType.Midpoint);
Every call to TWS is asynchronous, and ReactIb reconnects automatically if the connection drops (see Error handling below). Combined with LINQ, this gives compact queries over live account data, such as mark-to-market P&L per position, or average execution price and quantity by side for each security:
// Get mark-to-market PnL for each open position
var pnlByPosition = await Task.WhenAll(
(await twsApi.GetPositionsAsync()).Select(async p =>
{
// Get contract for primary exchange (if any) for pricing
var contract = (await twsApi.GetContractDetailsAsync(p.Contract))
.Select(r => r.ContractDetails.Summary)
.First(cd => cd.PrimaryExch == null || cd.Exchange == cd.PrimaryExch);
// Get snapshot from primary exchange and calc PnL using mid price (avg. of bid & ask)
return new
{
p.Contract,
Pnl = ((await twsApi.GetTaqSnapshotAsync(contract))
.Where(t => t.TickType == TickType.BidPrice || t.TickType == TickType.AskPrice)
.Average(t => t.Value)
* decimal.Parse(p.Contract.Multiplier ?? "1") - p.AverageCost) * p.Position
};
}));
// Get average executed price & qty by direction by security
var execByDirBySecurity = (await twsApi.GetExecutionsAsync())
// Last execution for each order has the latest avg price/cumulative qty
.GroupBy(e => e.OrderId)
.Select(g => g.Last())
.GroupBy(e => e.Contract.ConId)
.Select(g => new
{
g.First().Contract,
TotalExecBySide = g.GroupBy(e => e.Execution.Side).ToDictionary(g2 => g2.Key, g2 =>
{
// Get avg price and total qty for executions in this direction/security
var notional = g2.Sum(e => e.Execution.AvgPrice * e.Execution.CumQty);
var quantity = g2.Sum(e => e.Execution.CumQty);
return new { AveragePrice = notional / quantity, Quantity = quantity };
})
});
Streaming data, such as order status and market data, is exposed as observable sequences. The second example is a simple signal: it sends a buy order after three consecutive rising five-second bars.
// Display orders as they complete
twsApi.OrderStatus
.Where(os => os.Status == OrderStatus.Filled)
.Subscribe(os => Console.WriteLine($"Order {os.OrderId} has completed."));
// Send buy order on 3 consecutive 5-second up bars
(await twsApi.SubscribeRealtimeBarsAsync(audUsdContract, RealtimeBarType.Midpoint))
.Scan(0, (c, b) => b.Close > b.Open ? c + 1 : 0)
.Where(c => c == 3)
.Subscribe(async _ =>
{
var order = twsApi.CreateOrder();
order.TotalQuantity = 100000;
order.Action = "BUY";
order.OrderType = "MKT";
order.Tif = "DAY";
await twsApi.SendOrderAsync(order.OrderId, audUsdContract, order);
});
Subscriptions follow Rx conventions: disposing of the returned observable cancels the market data subscription on the server. More examples are in the Program class of the ConsoleTestApp project.
Error handling
TWS reports errors on a separate error channel, often with only a request ID to say which request they belong to. ReactIb routes them back to their source:
- Requests (orders, executions, positions, contract details, snapshots, historical data) each get their own request ID. An error carrying that ID faults the awaiting task with a
TwsException, which includes the TWS error code and whether the failure may be transient. Responses that TWS reports as errors but that really mean “no results”, such as a query that returned no data, complete the request with an empty list instead. - Transient failures, such as a dropped connection, are retried automatically, up to five attempts, before the task faults.
- Streaming subscriptions and orders report errors on an
Errorobservable, tagged with their request ID. Callers can mark an error as handled there; anything unhandled is logged by severity, with order rejections for insufficient margin logged as fatal. - Connection loss between TWS and IB pauses new requests until connectivity returns, and ReactIb reconnects and re-initialises automatically.
Building
ReactIb depends on the official TWS API, which can’t be redistributed, so it isn’t in the repo. Install the latest stable TWS API, then copy CSharpAPI.dll into ReactIb’s lib folder before building.
It was built against the TWS API as of early 2017. IB has generally kept the API backwards compatible, but I haven’t tested it against current versions.

