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.
To install from CRAN:
install.packages("rib")
To install the latest snapshot from GitHub, assuming devtools
or at least remotes
is already installed:
::install_github("lbilli/rib") remotes
The user interacts mainly with two classes, implemented as R6
objects: - IBClient
: responsible to establish a connection
and send requests to the server - IBWrap
: 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
<- R6::R6Class("IBWrapCustom",
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
<- IBWrapCustom$new()
wrap <- IBClient$new()
ic
# Connect to the server with clientId = 1
$connect(port=4002, clientId=1)
ic
# Check connection messages (optional)
$checkMsg(wrap)
ic
# Define contract
<- IBContract(symbol="GOOG", secType="STK", exchange="SMART", currency="USD")
contract
# Define order
<- IBOrder(action="BUY", totalQuantity=10, orderType="LMT", lmtPrice=100)
order
<- 1 # Should match whatever is returned by the server
orderId
# Send order
$placeOrder(orderId, contract, order)
ic
# Check inbound messages
$checkMsg(wrap)
ic
# Disconnect
$disconnect() ic
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:
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
<- IBWrapSimple$new()
wrap <- IBClient$new()
ic $connect(port=4002, clientId=1)
ic
# Send requests, e.g.:
<- IBContract(symbol="GOOG", secType="STK", exchange="SMART", currency="USD")
contract $reqContractDetails(11, contract)
ic
# more requests can go here...
# Parse responses
# Might need to be called several times to exhaust all messages
$checkMsg(wrap)
ic
# Find results in
$context
wrap
# Disconnect
$disconnect() ic
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
<- IBWrapSimple$new()
wrap <- IBClient$new()
ic $connect(port=4002, clientId=1)
ic
# Main loop
repeat {
# Application logic goes here
# e.g.: send request
$reqContractDetails(...)
ic
# Wait and process responses
$checkMsg(wrap)
ic
if(done)
break
# Might be convenient to have a little wait here
Sys.sleep(1)
}
# Disconnect
$disconnect() ic
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 in wrap
, which must be an instance of a
class derived from IBWrap
. If no response is available, it
blocks up to timeout
seconds. If
wrap
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.
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 extra
size
argument, which is meaningful only when
TickType ∈ {BID, ASK, LAST}
. In these cases, the official
IB API fires an extra tickSize()
event instead. -
historicalData()
is invoked only once per request,
presenting all the historical data as a single data.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.
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.
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 $conId <- 12345
contract$currency <- "USD" contract
In the case of Contract
and Order
, helper
functions are also provided to simplify the
assignment to a subset of the fields:
<- IBContract(symbol="GOOG", secType="STK", exchange="SMART", currency="USD")
contract
# Equivalent to
<- Contract
contract $symbol <- "GOOG"
contract$secType <- "STK"
contract$exchange <- "SMART"
contract$currency <- "USD" contract
and
<- IBOrder(action="BUY", totalQuantity=100, orderType="LMT", lmtPrice=110)
order
# Equivalent to
<- Order
order $action <- "BUY"
order$totalQuantity <- 100
order$orderType <- "LMT"
order$lmtPrice <- 110 order
To instantiate a Condition
, invoke
fCondition(type)
and fill in the appropriate fields:
<- fCondition("Price")
condition $conId <- 265598L
condition$exchange <- "SMART"
condition$is_more <- TRUE
condition$value <- 200 condition
TagValueList
are implemented as R named character
vectors. Wherever a TagValueList
is needed, something like
this can be used:
<- c(tag1="value1", tag2="value2")
tagvaluelist # or, in case of an empty list:
<- character() emptylist