reqExecutions IBrokers包

aaj*_*tak 4 r ibrokers interactive-brokers

有人可以为我提供reqExecutions的工作示例吗?我在使用ewrapper和回调机制时遇到了困难.在搜索谷歌的工作示例后,我无法掌握任何简单的工作.请注意,我不是程序员,这就是为什么很难让我的头脑缠绕在ewrapper和回调上.

bgo*_*dst 13

Disclaimer

Before answering this question, I feel I should emphasize the disclaimer given at the very start of the IBrokers documentation:

This software is in no way affiliated, endorsed, or approved by Interactive Brokers or any of its affiliates. It comes with absolutely no warranty and should not be used in actual trading unless the user can read and understand the source.

So it looks like this package is designed and maintained by independent programmers who may or may not have a good tie-in with official IB API development now or in the future.

Further to the above, I looked at a lot of the package source, and it's fairly incomplete, vis-à-vis the actual IB API source. In fact, you've stumbled upon one of the strands of incompleteness; in the IBrokers Reference Card it says:

Executions

返回twsExecution对象中的执行详细信息.此方法目前仅作为请求实现,没有内置机制来管理响应数据,除了它被丢弃.

(注意:IBrokersRef()如果您已配置,可以访问参考卡options()$pdfviewer.如果没有,您可以手动打开PDF;运行system.file('doc/IBrokersREFCARD.pdf',package='IBrokers')以获取其位置.)

所以,最重要的是,小心这个包; 你不应该依赖它进行真正的交易,除非你真的知道你在做什么.

功能

看一下实际的reqExecutions()包装功能,我们有:

function (twsconn, reqId = "0", ExecutionFilter)
{
    if (!is.twsConnection(twsconn))
        stop("invalid 'twsConnection' object")
    con <- twsconn[[1]]
    VERSION <- "3"
    outgoing <- c(.twsOutgoingMSG$REQ_EXECUTIONS, VERSION, as.character(reqId),
        ExecutionFilter$clientId, ExecutionFilter$acctCode, ExecutionFilter$time,
        ExecutionFilter$symbol, ExecutionFilter$secType, ExecutionFilter$exchange,
        ExecutionFilter$side)
    writeBin(outgoing, con)
}
Run Code Online (Sandbox Code Playgroud)

总结一下,它:

  1. 验证给定的TWS连接对象是否属于正确的S3类型.如果你研究一下is.twsConnection(),它只是检查它是继承自twsConnection还是twsconn.您可以创建这样的连接twsConnect().这是一个内部的普通R环境,被归类为twsconn.
  2. 提取由TWS连接对象包装的套接字连接.他们在这里使用不寻常的设计; 他们`[[`()twsconn班级重载了这个功能.如果您查看IBrokers:::`[[.twsconn`,您会看到它只返回twsconn$conn环境条目.
  3. Packs the request data (properly encoded) onto the socket. The encoded data consists of 10 NUL-separated fields (the NULs are added by writeBin()): a request type enumeration, a request version, a request identifier (which could be used to differentiate multiple simultaneous requests of the same type), and then 7 filter fields. (Unfortunately, it requires the caller to fully specify all filter fields.) It then returns immediately without waiting for a response.

Contrast the above with a fully-implemented request function, reqCurrentTime():

function (twsconn)
{
    .reqCurrentTime(twsconn)
    con <- twsconn[[1]]
    e_current_time <- eWrapper()
    e_current_time$currentTime <- function(curMsg, msg, timestamp,
        file, ...) {
        msg[2]
    }
    while (isConnected(twsconn)) {
        socketSelect(list(con), FALSE, NULL)
        curMsg <- readBin(con, character(), 1)
        currentTime <- processMsg(curMsg, con, eWrapper = e_current_time,
            twsconn = twsconn, timestamp = NULL, file = "")
        if (curMsg == .twsIncomingMSG$CURRENT_TIME)
            break
    }
    structure(as.numeric(currentTime), class = c("POSIXt", "POSIXct"))
}
Run Code Online (Sandbox Code Playgroud)

This:

  1. Delegates to a package-private function .reqCurrentTime() to send the actual request, similar to what reqExecutions() does. I won't include it here, but you can view its body with IBrokers:::.reqCurrentTime.
  2. Generates a list of callback functions using eWrapper() which it strangely names e_current_time.
  3. Overrides the default handler for the response, which will arrive via a call to currentTime() on the callback list object. The handler simply returns the second field, msg[2], which represents the server's idea of the current time, encoded as a Unix epoch value (seconds since 1970-01-01).
  4. Iterates on the socket, waiting for incoming socket data.
    1. When a message arrives, it grabs the first byte into curMsg, which is a code representing the message type, and calls processMsg() on it. This is another function provided by the IBrokers package. This is the function that actually switches on the message code and calls the appropriate callback function on e_current_time, passing it curMsg as well as the code-specific trailing fields, decoded via a call to readBin() (not shown above; see processMsg for the code).
    2. Grabs the return value of the callback function (recall this is msg[2], the second field after the message code) into currentTime.
    3. If the message code was indeed for the current time request, it breaks the while loop. (If it wasn't, then processMsg() actually triggered a default handler which doesn't do anything useful, and the currentTime value would be ignored.)
  5. Builds a POSIXct object around the currentTime value. All that's really required here is classing it as POSIXt and POSIXct, since the POSIXct type is conveniently implemented by storing the Unix epoch time to represent a date/time point.

So, as you can see, reqCurrentTime() is doing a lot more than reqExecutions(). In its current form, reqExecutions() doesn't do anything to process the response, so it's not really useful at all.

Solution

Since I'm familiar with the IB API, I was able to fill in the missing functionality, which I present below.

As a side note, I referenced the C++ API source code which is available from https://www.interactivebrokers.com/en/index.php?f=5041; the official API source can be taken as authoritative with respect to how the client needs to interact with the socket. For example, you can view the EClient::reqExecutions() function in /TWS API/source/CppClient/client/EClient.cpp to see how it encodes request fields onto the socket, and similarly you can view the EDecoder::processExecutionDataMsg() function in the /TWS API/source/CppClient/client/EDecoder.cpp file to see how it decodes the response from the server. Basically, it uses some C preprocessor macros (ENCODE_FIELD() and DECODE_FIELD()) which expand to a call to some C++ template functions for encoding (template<class T> void EClient::EncodeField(std::ostream& os, T value)) and C++ overloaded functions for decoding (bool EDecoder::DecodeField(bool/int/long/double/std::string& doubleValue, const char*& ptr, const char* endPtr)) which ultimately use the C++ streaming operators to stream primitive fields to the socket std::ostream后跟一个NUL用于编码,并调用atoi(),atof()std::string::operator=()从插座缓冲解码的权利.

我还建议您访问https://www.interactivebrokers.com/en/software/api/api.htm查看官方API文档.

首先,注意IBrokers提供类似twsContract()twsOrder()允许构造特定于TWS的数据对象的函数.没有twsExecution()功能,所以我编写了自己的功能,遵循相同的对象构造风格,包括附加twsExecutionS3类.我还写了一个print.twsExecution()函数,所以twsExecution对象的打印方式与其他对象相同,这基本上就意味着str(unclass(x)).

Secondly, I wrote my own replacement for reqExecutions() called reqExecutions2() which encodes the request data onto the socket as per reqExecutions(), and then overrides the response handler and iterates on the socket waiting for response messages, similar to reqCurrentTime(). I was a little bit more verbose with the code, as I tried to follow the C++ algorithm as closely as possible, including its request version checks on response handling to conditionally take certain fields off the socket. I also included monitoring for error messages from the server, so that the while loop automatically breaks and the function returns on errors. reqExecutions2() returns all response records as a list, where each component is a nested list of reqId, contract, and execution components. This follows the execDetails() callback design in the official API; see https://www.interactivebrokers.com/en/software/api/api.htm (C++ -> Class EWrapper Functions -> Executions -> execDetails()).

Lastly, for convenience I wrote a wrapper around reqExecutions2() called reqExecutionsFrame(), which converts the list of records into a single data.frame.

So, without further ado, here's the code:

library(IBrokers);

## constructor for an execution object
twsExecution <- function(
    execId=NA_character_,
    time=NA_character_,
    acctNumber=NA_character_,
    exchange=NA_character_,
    side=NA_character_,
    shares=NA_integer_,
    price=NA_real_,
    permId=NA_integer_,
    clientId=NA_integer_, ## no long type in R, but decoding process squeezes longs through ints, so may as well use int
    orderId=NA_integer_, ## no long type in R, but decoding process squeezes longs through ints, so may as well use int
    liquidation=NA_integer_,
    cumQty=NA_integer_,
    avgPrice=NA_real_,
    orderRef=NA_character_,
    evRule=NA_character_,
    evMultiplier=NA_real_
) {
    structure(
        list(
            execId=execId,
            time=time,
            acctNumber=acctNumber,
            exchange=exchange,
            side=side,
            shares=shares,
            price=price,
            permId=permId,
            clientId=clientId,
            orderId=orderId,
            liquidation=liquidation,
            cumQty=cumQty,
            avgPrice=avgPrice,
            orderRef=orderRef,
            evRule=evRule,
            evMultiplier=evMultiplier
        ),
        class='twsExecution'
    );
}; ## end twsExecution()
print.twsExecution <- function(x,...) str(unclass(x));

## replacement for reqExecutions()
reqExecutions2 <- function(twscon,reqId=0L,filter=list()) {

    ## validate the connection object
    if (!is.twsConnection(twscon)) stop('invalid twsConnection object.');
    if (!isConnected(twscon)) stop('peer has gone away. check your IB connection',call.=F);

    ## shallow validation of args
    if (!is.integer(reqId) || length(reqId) != 1L) stop('reqId must be a scalar integer.');
    if (!is.list(filter) || (is.null(names(filter)) && length(filter) > 0L)) stop('filter must be a named list.');

    ## send encoded request
    socketcon <- twscon[[1]]; ## extract socket connection from TWS connection object
    VERSION <- '3';
    prepareField <- function(x) if (is.null(x) || length(x) != 1L || is.na(x)) '' else as.character(x); ## empty string is accepted as unspecified
    outgoing <- c(
        .twsOutgoingMSG$REQ_EXECUTIONS,
        VERSION,
        prepareField(reqId), ## will receive this in the response along with data
        prepareField(filter$clientId), ## any client id; if invalid, will get zero results
        prepareField(filter$acctCode), ## must be a valid account code; seems to be ignored completely if invalid
        prepareField(filter$time), ## yyyymmdd HH:MM:SS
        prepareField(filter$symbol), ## must be a valid contract symbol, case-insensitive
        prepareField(filter$secType), ## STK|OPT|FUT|IND|FOP|CASH|BAG|NEWS
        prepareField(filter$exchange), ## must be a valid exchange name, case-insensitive; seems to be ignored completely if invalid
        prepareField(filter$side) ## buy|sell
    );
    writeBin(outgoing,socketcon); ## automatically appends a NUL after each vector element

    ## set handler method
    ## note: don't need to explicitly handle execDetailsEnd(); it provides no new data, and the below while-loop will check for and break on it
    ew <- eWrapper();
    ew$execDetails <- function(curMsg,msg,timestamp,file,...) {

        ## reqId and most contract and execution fields are returned in a character vector in msg
        ## build a return value by mapping the fields to their corresponding parameters of twsContract() and twsExecution()
        n <- (function() { n <- 0L; function() n <<- n+1L; })();
        version <- as.integer(msg[n()]);
        reqId <- if (version >= 7L) as.integer(msg[n()]) else -1L;
        orderId <- as.integer(msg[n()]); ## not sure why this is out-of-order with the remaining execution fields
        ## contract fields
        conId <- as.integer(msg[n()]);
        symbol <- msg[n()];
        secType <- msg[n()];
        lastTradeDateOrContractMonth <- msg[n()];
        strike <- as.double(msg[n()]);
        right <- msg[n()];
        multiplier <- ''; ##multiplier <- if (version >= 9L) msg[n()] else ''; ----- missing?
        exch <- msg[n()];
        primaryExchange <- ''; ## not returned
        currency <- msg[n()];
        localSymbol <- msg[n()];
        tradingClass <- if (version >= 10L) msg[n()] else '';
        includeExpired <- F; ## not returned
        secIdType <- ''; ## not returned
        secId <- ''; ## not returned
        comboLegsDescrip <- ''; ## not returned
        comboLegs <- ''; ## not returned
        underComp <- 0L; ## not returned
        ## execution fields
        execId <- msg[n()];
        time <- msg[n()];
        acctNumber <- msg[n()];
        exchange <- msg[n()];
        side <- msg[n()];
        shares <- as.integer(msg[n()]);
        price <- as.double(msg[n()]);
        permId <- as.integer(msg[n()]);
        clientId <- as.integer(msg[n()]);
        ## (orderId already assigned)
        liquidation <- as.integer(msg[n()]);
        cumQty <- if (version >= 6L) as.integer(msg[n()]) else 0L;
        avgPrice <- if (version >= 6L) as.double(msg[n()]) else 0;
        orderRef <- if (version >= 8L) msg[n()] else '';
        evRule <- if (version >= 9L) msg[n()] else '';
        evMultiplier <- if (version >= 9L) as.double(msg[n()]) else 0;

        ## build the list to return
        ## note: the twsContract() and twsExecution() functions provided with the IBrokers package as of 0.9-12 do not take all of the above fields; we'll pass what they take
        list(
            reqId=reqId,
            contract=twsContract(
                conId=conId,
                symbol=symbol,
                sectype=secType,
                exch=exch,
                primary=primaryExchange,
                expiry=lastTradeDateOrContractMonth,
                strike=strike,
                currency=currency,
                right=right,
                local=localSymbol,
                multiplier=multiplier,
                combo_legs_desc=comboLegsDescrip,
                comboleg=comboLegs,
                include_expired=includeExpired,
                secIdType=secIdType,
                secId=secId
            ),
            execution=twsExecution(
                execId=execId,
                time=time,
                acctNumber=acctNumber,
                exchange=exchange,
                side=side,
                shares=shares,
                price=price,
                permId=permId,
                clientId=clientId,
                orderId=orderId,
                liquidation=liquidation,
                cumQty=cumQty,
                avgPrice=avgPrice,
                orderRef=orderRef,
                evRule=evRule,
                evMultiplier=evMultiplier
            )
        );

    }; ## end execDetails()

    ## hack errorMessage() so we can differentiate between true errors and info messages; not the best design on the part of IB to conflate these
    body(ew$errorMessage)[[length(body(ew$errorMessage))+1L]] <- substitute(msg);

    ## iterate until we get the expected responses off the socket
    execList <- list();
    while (isConnected(twscon)) {
        socketSelect(list(socketcon),F,NULL);
        curMsg <- readBin(socketcon,character(),1L);
        res <- processMsg(curMsg,socketcon,eWrapper=ew,twsconn=twscon,timestamp=NULL,file='');
        ## check for error
        if (curMsg == .twsIncomingMSG$ERR_MSG) {
            ## note: the actual message was already catted inside processMsg() -> ew$errorMessage(); just abort if true error
            code <- as.integer(res[3L]);
            if (!code%in%c( ## blacklist info messages
                0   , ## "Warning: Approaching max rate of 50 messages per second (%d)"
                2103, ## "A market data farm is disconnected."
                2104, ## "A market data farm is connected."
                2105, ## "A historical data farm is disconnected."
                2106, ## "A historical data farm is connected."
                2107, ## "A historical data farm connection has become inactive but should be available upon demand."
                2108, ## "A market data farm connection has become inactive but should be available upon demand."
                2119  ## "Market data farm is connecting:%s" -- undocumented
            )) stop(paste0('request error ',code));
        }; ## end if
        ## check for data
        if (curMsg == .twsIncomingMSG$EXECUTION_DATA)
            execList[[length(execList)+1L]] <- res;
        ## check for completion
        if (curMsg == .twsIncomingMSG$EXECUTION_DATA_END) break;
    }; ## end while

    execList;

}; ## end reqExecutions2()

reqExecutionsFrame <- function(...) {
    res <- reqExecutions2(...);
    do.call(rbind,lapply(res,function(e) do.call(data.frame,c(list(reqId=e$reqId),e$contract,e$execution,stringsAsFactors=F))));
}; ## end reqExecutionsFrame()
Run Code Online (Sandbox Code Playgroud)

Here's a demo on my paper trading account:

## create the TWS connection, selecting an arbitrary client id
twscon <- twsConnect(0L);

twscon; ## this is how it displays by default
## <twsConnection,0 @ 20160229 07:36:33 EST, nextId=4268>
(function(x) c(typeof(x),mode(x),class(x)))(twscon); ## show type info
## [1] "environment" "environment" "twsconn"     "environment"
ls(twscon); ## list the entries in the environment
## [1] "clientId" "conn" "connected" "connected.at" "nextValidId" "port" "server.version"
twscon$conn; ## actual socket connection across which I/O travels between the client and server
##        description              class               mode               text
## "->localhost:7496"         "sockconn"               "ab"           "binary"
##             opened           can read          can write
##           "opened"              "yes"              "yes"

## demo the current time request
## note some info messages are always written onto the socket by the server after we create a connection; the while loop simply passes through them before getting to the current time response
reqCurrentTime(twscon);
## TWS Message: 2 -1 2104 Market data farm connection is OK:cashfarm
## TWS Message: 2 -1 2104 Market data farm connection is OK:usfarm
## TWS Message: 2 -1 2106 HMDS data farm connection is OK:cashhmds
## TWS Message: 2 -1 2106 HMDS data farm connection is OK:ushmds
## [1] "2016-02-29 07:40:10 EST"

## demo the executions request code; shows some random executions I did earlier today (in a paper trading account, of course!)
reqExecutionsFrame(twscon);
##   reqId    conId symbol sectype     exch primary expiry strike currency right   local multiplier combo_legs_desc comboleg include_expired secIdType secId                  execId               time acctNumber exchange side shares   price    permId clientId    orderId liquidation cumQty avgPrice orderRef evRule evMultiplier
## 1     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d38c6c.01.01 20160229  02:58:06   XXXXXXXX IDEALPRO  SLD 100000 1.35305 195295721        0 2147483647           0 100000  1.35305     <NA>   <NA>           NA
## 2     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d38c6f.01.01 20160229  02:58:15   XXXXXXXX IDEALPRO  BOT  25000 1.35310 195295723        0 2147483647           0  25000  1.35310     <NA>   <NA>           NA
## 3     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d38c76.01.01 20160229  02:58:42   XXXXXXXX IDEALPRO  BOT  75000 1.35330 195295723        0 2147483647           0 100000  1.35325     <NA>   <NA>           NA
## 4     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d39e0b.01.01 20160229  05:48:50   XXXXXXXX IDEALPRO  BOT 100000 1.35710 195295940        0 2147483647           0 100000  1.35710     <NA>   <NA>           NA
## 5     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d39e16.01.01 20160229  05:49:14   XXXXXXXX IDEALPRO  SLD 100000 1.35720 195295942        0 2147483647           0 100000  1.35720     <NA>   <NA>           NA

## demo some filtering
reqExecutionsFrame(twscon,filter=twsExecutionFilter(side='buy'));
##   reqId    conId symbol sectype     exch primary expiry strike currency right   local multiplier combo_legs_desc comboleg include_expired secIdType secId                  execId               time acctNumber exchange side shares  price    permId clientId    orderId liquidation cumQty avgPrice orderRef evRule evMultiplier
## 1     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d38c6f.01.01 20160229  02:58:15   XXXXXXXX IDEALPRO  BOT  25000 1.3531 195295723        0 2147483647           0  25000  1.35310     <NA>   <NA>           NA
## 2     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d38c76.01.01 20160229  02:58:42   XXXXXXXX IDEALPRO  BOT  75000 1.3533 195295723        0 2147483647           0 100000  1.35325     <NA>   <NA>           NA
## 3     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d39e0b.01.01 20160229  05:48:50   XXXXXXXX IDEALPRO  BOT 100000 1.3571 195295940        0 2147483647           0 100000  1.35710     <NA>   <NA>           NA
reqExecutionsFrame(twscon,filter=twsExecutionFilter(side='buy',time='20160229 04:00:00'));
##   reqId    conId symbol sectype     exch primary expiry strike currency right   local multiplier combo_legs_desc comboleg include_expired secIdType secId                  execId               time acctNumber exchange side shares  price    permId clientId    orderId liquidation cumQty avgPrice orderRef evRule evMultiplier
## 1     0 15016062    USD    CASH IDEALPRO                     0      CAD       USD.CAD                                               FALSE                 0001f4e8.56d39e0b.01.01 20160229  05:48:50   XXXXXXXX IDEALPRO  BOT 100000 1.3571 195295940        0 2147483647           0 100000   1.3571     <NA>   <NA>           NA

## demo error handling
reqExecutionsFrame(twscon,filter=twsExecutionFilter(side='invalid'));
## TWS Message: 2 0 321 Error validating request:-'gf' : cause - Invalid side
## Error in reqExecutions2(...) : request error 321
Run Code Online (Sandbox Code Playgroud)