An Implementation of 'Interactive Brokers' API.
rib
An R implementation of Interactive Brokers API
Originally inspired by IBrokers
, rib
is a native R client that implements Interactive Brokers API to communicate with TWS or IBGateway.
It aims to be feature complete, however it does not support legacy versions. Currently, only API versions v176+
are supported.
The package design mirrors the official C++/Java IB API, which is based on an asynchronous communication model over TCP.
Installation
To install from CRAN:
install.packages("rib")
To install the latest snapshot from GitHub, assuming devtools
or at least remotes
is already installed:
remotes::install_github("lbilli/rib")
Usage
The user interacts mainly with two classes, implemented as R6
objects:
IBClient
: responsible to establish a connection and send requests to the serverIBWrap
: base class holding the callbacks that are invoked when responses are processed. User customizations are derived from this class.
Other data structures, such as Contract
and Order
, are implemented as R lists, or nested lists, and mirror the respective classes in the official IB API.
A complete minimal working example is shown. For this code to work, an instance of IB TWS or IBGateway needs to be running on the local machine and listening on port 4002
. Note:demo or paper account recommended!! :smirk:
library(rib)
# Define a customized callbacks wrapper
IBWrapCustom <- R6::R6Class("IBWrapCustom",
class= FALSE,
cloneable= FALSE,
lock_class= TRUE,
inherit= IBWrap,
public= list(
# Customized methods go here
error= function(id, errorCode, errorString, advancedOrderRejectJson)
cat("Error:", id, errorCode, errorString, advancedOrderRejectJson, "\n"),
nextValidId= function(orderId)
cat("Next OrderId:", orderId, "\n"),
managedAccounts= function(accountsList)
cat("Managed Accounts:", accountsList, "\n")
# more method overrides can go here...
)
)
# Instantiate wrapper and client
wrap <- IBWrapCustom$new()
ic <- IBClient$new()
# Connect to the server with clientId = 1
ic$connect(port=4002, clientId=1)
# Check connection messages (optional)
ic$checkMsg(wrap)
# Define contract
contract <- IBContract(symbol="GOOG", secType="STK", exchange="SMART", currency="USD")
# Define order
order <- IBOrder(action="BUY", totalQuantity=10, orderType="LMT", lmtPrice=100)
orderId <- 1 # Should match whatever is returned by the server
# Send order
ic$placeOrder(orderId, contract, order)
# Check inbound messages
ic$checkMsg(wrap)
# Disconnect
ic$disconnect()
As R is single threaded, in general it is the user's responsibility to code some kind of event loop to periodically process incoming messages: i.e. there is no background Reader
monitoring the connection and processing the server responses.
Two possible approaches, with or without a loop, are described:
Straight Request-Response pattern. No loop.
This is the simplest case. It's not suitable for data subscriptions or whenever a stream of messages is expected. It follows the pattern:
- connect
- send requests
- process responses
- disconnect
library(rib)
# Instantiate wrapper, client and connect
wrap <- IBWrapSimple$new()
ic <- IBClient$new()
ic$connect(port=4002, clientId=1)
# Send requests, e.g.:
contract <- IBContract(symbol="GOOG", secType="STK", exchange="SMART", currency="USD")
ic$reqContractDetails(11, contract)
# more requests can go here...
# Parse responses
# Might need to be called several times to exhaust all messages
ic$checkMsg(wrap)
# Find results in
wrap$context
# Disconnect
ic$disconnect()
Request-Response loop
A more realistic application requires a loop around the previous example:
- connect
- repeat
- send requests
- process responses
- if done exit
- disconnect
library(rib)
# Instantiate wrapper, client and connect
wrap <- IBWrapSimple$new()
ic <- IBClient$new()
ic$connect(port=4002, clientId=1)
# Main loop
repeat {
# Application logic goes here
# e.g.: send request
ic$reqContractDetails(...)
# Wait and process responses
ic$checkMsg(wrap)
if(done)
break
# Might be convenient to have a little wait here
Sys.sleep(1)
}
# Disconnect
ic$disconnect()
Implementation Details
IBClient
This implements the IB EClient
class functionality. Among its methods:
new()
: create a new instance.connect(host, port, clientId, connectOptions)
: establish a connection.disconnect()
: terminate the connection.checkMsg(wrap, timeout)
: wait for responses and dispatch callbacks defined inwrap
, which must be an instance of a class derived fromIBWrap
. If no response is available, it blocks up totimeout
seconds. Ifwrap
is missing, messages are taken off the wire and discarded. Return the number of responses processed. Needs to be called regularly!- methods that send requests to the server. Refer to the official IB
EClient
class documentation for further details and method signatures.
IBWrap
Like the official IB EWrapper
class, this holds the callbacks that are dispatched when responses are processed. IBWrap
itself contains only stubs. Users need to derive from it and override the desired methods. The code above provides a template of the procedure.
For a more extensive example refer to the definition of IBWrapSimple
, mainly provided for illustrative purposes, which simply prints out the content of the responses or store it within IBWrapSimple$context
for later inspection.
For more details about callback definitions and signatures, refer to the official IB EWrapper
class documentation.
Notes
Callbacks are generally invoked with arguments and types matching the signatures as described in the official documentation. However, there are few exceptions:
tickPrice()
has an extrasize
argument, which is meaningful only whenTickType ∈ {BID, ASK, LAST}
. In these cases, the official IB API fires an extratickSize()
event instead.historicalData()
is invoked only once per request, presenting all the historical data as a singledata.frame
, whereas the official IB API invokes it row-by-row.scannerData()
is also invoked once per request and its arguments are in fact vectors rather than single values.
These modifications make it possible to establish the rule: one callback per server response.
Consequently, historicalDataEnd()
and scannerDataEnd()
are redundant and are not used in this package.
data.frame
are passed to several other callbacks, such as: softDollarTiers()
, familyCodes()
, mktDepthExchanges()
, smartComponents()
, newsProviders()
, histogramData()
, marketRule()
and the historicalTicks*()
family.
Missing Values
Occasionally, for numerical types, there is the need to represent the lack of a value.
IB API does not have a uniform solution across the board, but rather it adopts a variety of sentinel values. They can be either the plain 0
or the largest representable value of a given type such as 2147483647
and 9223372036854775807
for 32- and 64-bit integers respectively or 1.7976931348623157E308
for 64-bit floating point.
This package makes an effort to use R built-in NA
in all circumstances.
Data Structures
Other classes that mainly hold data are also replicated. They are implemented as R lists, possibly nested, with names, types and default values matching the IB API counterparts: e.g.Contract
, Order
, ComboLeg
, ExecutionFilter
, ScannerSubscription
and Condition
.
To use them, simply make a copy and override their elements:
contract <- Contract
contract$conId <- 12345
contract$currency <- "USD"
In the case of Contract
and Order
, helper functions are also provided to simplify the assignment to a subset of the fields:
contract <- IBContract(symbol="GOOG", secType="STK", exchange="SMART", currency="USD")
# Equivalent to
contract <- Contract
contract$symbol <- "GOOG"
contract$secType <- "STK"
contract$exchange <- "SMART"
contract$currency <- "USD"
and
order <- IBOrder(action="BUY", totalQuantity=100, orderType="LMT", lmtPrice=110)
# Equivalent to
order <- Order
order$action <- "BUY"
order$totalQuantity <- 100
order$orderType <- "LMT"
order$lmtPrice <- 110
To instantiate a Condition
, invoke fCondition(type)
and fill in the appropriate fields:
condition <- fCondition("Price")
condition$conId <- 265598L
condition$exchange <- "SMART"
condition$is_more <- TRUE
condition$value <- 200
TagValueList
are implemented as R named character vectors. Wherever a TagValueList
is needed, something like this can be used:
tagvaluelist <- c(tag1="value1", tag2="value2")
# or, in case of an empty list:
emptylist <- character()