6

有人可以为我提供一个 reqExecutions 的工作示例吗?我很难使用 ewrapper 和回调机制。在谷歌搜索工作示例后,我无法得到任何可以简单工作的东西。请注意,我不是程序员,这就是为什么我很难让我的头脑围绕着 ewrapper 和回调。

4

1 回答 1

17

免责声明

在回答这个问题之前,我觉得我应该强调IBrokers 文档开头给出的免责声明:

本软件不以任何方式附属、认可或批准盈透证券或其任何附属公司。它绝对没有任何保证,除非用户能够阅读和理解来源,否则不应在实际交易中使用它。

所以看起来这个包是由独立程序员设计和维护的,他们现在或将来可能会或可能不会与官方 IB API 开发有很好的联系。

除上述内容外,我查看了很多包源,与实际的 IB API 源相比,它相当不完整。事实上,你偶然发现了不完整的线索之一。在 IBrokers 参考卡上写着:

处决

返回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)
}

综上所述,它:

  1. 验证给定的 TWS 连接对象是正确的 S3 类型。如果您查看is.twsConnection(),它只是检查它是否继承自twsConnectionor twsconn。您可以使用twsConnect(). 它在内部是一个普通的 R 环境,归类为twsconn.
  2. 提取由 TWS 连接对象包装的套接字连接。他们在这里使用了不寻常的设计;他们已经`[[`()为类重载了函数twsconn。如果您查看IBrokers:::`[[.twsconn`,您会看到它只是返回twsconn$conn环境条目。
  3. 将请求数据(正确编码)打包到套接字上。编码数据由 10 个 NUL 分隔的字段(NULwriteBin()由字段。(不幸的是,它要求调用者完全指定所有过滤器字段。)然后它立即返回而不等待响应。

将上述与完全实现的请求功能进行对比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"))
}

这个:

  1. 委托一个包私有函数.reqCurrentTime()来发送实际请求,类似于做什么reqExecutions()。我不会在此处包含它,但您可以使用IBrokers:::.reqCurrentTime.
  2. 生成一个回调函数列表,使用eWrapper()它奇怪地命名e_current_time
  3. currentTime()覆盖响应的默认处理程序,该处理程序将通过对回调列表对象的调用到达。处理程序只返回第二个字段 ,msg[2]它表示服务器对当前时间的想法,编码为 Unix 纪元值(自 1970-01-01 以来的秒数)。
  4. 迭代套接字,等待传入的套接字数据。
    1. 当消息到达时,它将第一个字节抓取到curMsg中,这是一个表示消息类型的代码,并对其进行调用processMsg()。这是IBrokers包提供的另一个功能。这是实际打开消息代码并调用适当的回调函数 on 的函数e_current_time,传递它curMsg以及代码特定的尾随字段,通过调用解码readBin()(上面未显示;参见processMsg代码)。
    2. 获取回调函数的返回值(回想一下,这是msg[2]消息代码之后的第二个字段)到currentTime.
    3. 如果消息代码确实是针对当前时间请求的,它会中断 while 循环。(如果不是,那么processMsg()实际上触发了一个默认处理程序,它没有做任何有用的事情,并且该currentTime值将被忽略。)
  5. currentTime围绕该值构建一个 POSIXct 对象。这里真正需要的只是将其归类为 POSIXt 和 POSIXct,因为 POSIXct 类型通过存储 Unix 纪元时间来表示日期/时间点来方便地实现。

因此,如您所见,reqCurrentTime()它所做的远不止reqExecutions(). 在当前的形式下,reqExecutions()它不做任何事情来处理响应,所以它根本没有用处。

解决方案

由于我熟悉 IB API,因此我能够填补缺失的功能,如下所示。

作为旁注,我引用了可从https://www.interactivebrokers.com/en/index.php?f=5041获得的 C++ API 源代码;关于客户端如何与套接字交互,官方 API 源可以被视为权威。例如,您可以查看EClient::reqExecutions()函数/TWS API/source/CppClient/client/EClient.cpp以查看它如何将请求字段编码到套接字上,同样您可以查看文件中的EDecoder::processExecutionDataMsg()函数/TWS API/source/CppClient/client/EDecoder.cpp以查看它如何解码来自服务器的响应。基本上,它使用一些 C 预处理器宏 (ENCODE_FIELD()DECODE_FIELD()),这些宏扩展为调用一些用于编码 ( ) 的 C++ 模板函数template<class T> void EClient::EncodeField(std::ostream& os, T value)和用于解码 ( ) 的 C++ 重载函数bool EDecoder::DecodeField(bool/int/long/double/std::string& doubleValue, const char*& ptr, const char* endPtr),最终使用 C++ 流操作符将原始字段流式传输到套接字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)).

其次,我编写了自己的替换reqExecutions()调用reqExecutions2(),它根据 将请求数据编码到套接字上reqExecutions(),然后覆盖响应处理程序并在套接字上迭代等待响应消息,类似于reqCurrentTime(). 我对代码有点冗长,因为我试图尽可能地遵循 C++ 算法,包括它对响应处理的请求版本检查,以有条件地从套接字中取出某些字段。我还包括监视来自服务器的错误消息,以便 while 循环自动中断并且函数在错误时返回。reqExecutions2()以列表的形式返回所有响应记录,其中每个组件是 、 和 组件的reqId嵌套contract列表execution。这遵循execDetails()官方API中的回调设计;请参阅https://www.interactivebrokers.com/en/software/api/api.htm ( C++ -> Class EWrapper Functions -> Executions -> execDetails())。

最后,为方便起见,我编写了一个reqExecutions2()名为的包装器reqExecutionsFrame(),它将记录列表转换为单个 data.frame。

所以,事不宜迟,代码如下:

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()

这是我的模拟交易账户的演示:

## 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
于 2016-02-29T13:19:38.520 回答