【问题标题】:reqExecutions IBrokers packagereqExecutions IBrokers 包
【发布时间】:2016-02-22 17:14:20
【问题描述】:

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

【问题讨论】:

    标签: r interactive-brokers ibrokers


    【解决方案1】:

    免责声明

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

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

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

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

    处决

    twsExecution 对象中返回执行细节。此方法目前仅作为请求实现,除了丢弃响应数据外,没有内置机制来管理响应数据。

    (注意:如果您配置了options()$pdfviewer,则可以使用IBrokersRef() 访问参考卡。如果没有,您可以手动打开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(),它只会检查它是否继承自twsConnectiontwsconn。您可以使用twsConnect() 创建这样的连接。它在内部是一个普通的 R 环境,归类为 twsconn
    2. 提取由 TWS 连接对象包装的套接字连接。他们在这里使用了不寻常的设计;他们为twsconn 类重载了`[[`() 函数。如果您查看 IBrokers:::`[[.twsconn`,您会发现它只返回了 twsconn$conn 环境条目。
    3. 将请求数据(正确编码)打包到套接字上。编码数据由 10 个 NUL 分隔的字段组成(NUL 由 writeBin() 添加):请求类型枚举、请求版本、请求标识符(可用于区分同一类型的多个同时请求)和然后是 7 个过滤字段。 (不幸的是,它要求调用者完全指定所有过滤器字段。)然后它立即返回,而无需等待响应。

    将上述与完全实现的请求功能进行对比,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 包提供的另一个功能。这是实际打开消息代码并在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 源可以被视为权威。例如,您可以查看/TWS API/source/CppClient/client/EClient.cpp 中的EClient::reqExecutions() 函数以查看它如何将请求字段编码到套接字上,同样您可以查看/TWS API/source/CppClient/client/EDecoder.cpp 文件中的EDecoder::processExecutionDataMsg() 函数以查看它如何解码来自的响应服务器。基本上,它使用一些 C 预处理器宏(ENCODE_FIELD()DECODE_FIELD()),这些宏扩展为调用一些用于编码的 C++ 模板函数(template&lt;class T&gt; void EClient::EncodeField(std::ostream&amp; os, T value))和用于解码的 C++ 重载函数(bool EDecoder::DecodeField(bool/int/long/double/std::string&amp; doubleValue, const char*&amp; 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() 函数,所以我编写了自己的函数,遵循相同的对象构造风格,包括附加twsExecution S3 类。我还写了一个print.twsExecution() 函数,所以twsExecution 对象的打印方式与其他对象相同,这基本上意味着str(unclass(x))

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

    【讨论】:

    • 感谢您的详细回复。我将在今天或明天执行执行,并在不久之后奖励您
    猜你喜欢
    • 1970-01-01
    • 2017-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-08
    相关资源
    最近更新 更多