lbilli / rib

An R implementation of Interactive Brokers API
GNU General Public License v3.0
34 stars 7 forks source link

Live updating dataframe #9

Open KKel414 opened 4 years ago

KKel414 commented 4 years ago

Hello, I have the following code that I am using to stream data, and hopefully create a live updating plot in R.

Can you please guide me on how I can export the wrap$context$historicalfield into a R dataframe that is steadily updated as the streamed data comes through?

library(rib)

tickers <- c("AAPL", "GOOG")

wrap <- IBWrapSimple$new()
ic   <- IBClient$new(wrap)

ic$connect(port=7497, clientId=5)

for(i in 1:length(tickers))
  ic$reqHistoricalData(i, IBContract(tickers[i]), endDateTime = "", durationStr = "1 M", barSizeSetting = "1 day", useRTH = "1", 
                       whatToShow = "TRADES", keepUpToDate = TRUE, formatDate = "1")

repeat
  ic$checkMsg()
lbilli commented 4 years ago

Here's a very quick idea on how to keep historical dataframes up to date. For semplicity, I'll store them in a list indexed the same way as tickers to make it trivial to map ticker -> reqId -> data.frame.

library(rib)

# derive a new wrapper with customized historicalData() and historicalDataUpdate()
IBWrapTest <- R6::R6Class("IBWrapTest",

  inherit= IBWrapSimple,

  public= list(

    initialize= function() 
                  # list holding the data.frames
                  self$context$dflist <- list(), 

    historicalData= function(reqId, bar) 
                      # store initial history
                      self$context$dflist[[reqId]] <- bar,

    historicalDataUpdate= function(reqId, bar) {

                            df <- self$context$dflist[[reqId]]
                            n <- nrow(df)

                            if(bar$time == df$time[n])
                              # update the last bar
                              self$context$dflist[[reqId]][n, ] <- bar

                            else   
                              # bar has changed, append a new one
                              self$context$dflist[[reqId]] <- rbind(df, bar)
                          }    
  )
)

wrap <- IBWrapTest$new()
ic   <- IBClient$new(wrap)

ic$connect(port=7497, clientId=5)

tickers <- c("AAPL", "GOOG")

for(i in 1:length(tickers))
  ic$reqHistoricalData(i, IBContract(tickers[i]), "", "1 D", "5 mins", "TRADES", TRUE, 1, TRUE)

repeat {
  # keep history up-to-date
  ic$checkMsg()

  # do something with it
  wrap$context$dflist[[1]]  # AAPL
  wrap$context$dflist[[2]]  # GOOG
}
KKel414 commented 4 years ago

@lbilli many thanks - I see the logic, and makes sense to me.

I ran into the following error - any further guidance you can provide me?

Error in wrap$context$dflist[[1]] : subscript out of bounds

lbilli commented 4 years ago

It's difficult to say from here. But as dflist is initialized as an empty list, you'd get that error if you try to access its elements, e.g. dflist[[1]] before any data is received and historicalData() invoked.

KKel414 commented 3 years ago

Many thanks again @lbilli - data download and updating works like a charm.

One further question I may ask is how do you place orders in the rib framework assuming we use the above constantly updating dataframe code?

I did the following, namely inserted the calls to orderId1 and orderId2 and then the # Send order section in the loop, but it comes out with a Error: 1 103 Duplicate order id issue.

What I ultimately want to do is pull in the wrap$context$dflist[[1]] and wrap$context$dflist[[2]] into a separate function I have written, and to place trades if the ratio of the prices exceeds a certain level. There would be two trades - one long and one short. So I am confused how orderId would work.

wrap <- IBWrapTest$new()
ic   <- IBClient$new(wrap)

ic$connect(port=7497, clientId=5)

tickers <- c("AAPL", "GOOG")

# Define contract
contract1 <- IBContract("GOOG")
contract2 <- IBContract("AAPL")

# Define order
order1 <- IBOrder("BUY", 10, "LMT", 1000)

orderId1 <- 1    # Should match whatever is returned by the server

order2 <- IBOrder("SELL", 10, "LMT", 1000)

orderId2 <- 1    # Should match whatever is returned by the server

for(i in 1:length(tickers))
  ic$reqHistoricalData(i, IBContract(tickers[i]), "", "1 D", "5 mins", "TRADES", TRUE, 1, TRUE)

repeat {
  # keep history up-to-date
  ic$checkMsg()

  # do something with it
  wrap$context$dflist[[1]]  # AAPL
  wrap$context$dflist[[2]]  # GOOG

  # Send order
  ic$placeOrder(orderId1, contract1, order1)
  ic$placeOrder(orderId2, contract2, order2)

  # Check messages
  ic$checkMsg()

}
lbilli commented 3 years ago

As per IB API documentation, each new Order needs a unique orderId that is greater than any previous Order. The orderId can be reused to modify existing orders.

Upon connection, the server always responds with nextValidId() which contains the first orderId that can be used. If using a variation of IBWrapSimple/IBWrapTest, that number is stored in wrap$context$nextId. So, the logic to send orders should be something like:

wrap <- IBWrapTest$new()
ic   <- IBClient$new(wrap)

ic$connect(port=7497, clientId=5)

# wait to make sure you receive all initial messages
Sys.sleep(1)
ic$checkMsg()

orderId <- wrap$context$nextId

[...]

# send first order
ic$placeOrder(orderId, contract1, order1)
orderId <- orderId + 1

[...]

# send another order
ic$placeOrder(orderId, contract2, order2)
orderId <- orderId + 1

[...]