Пакет reqExecutions iBrokers

Сможет ли кто-нибудь предоставить мне рабочий пример reqExecutions? У меня проблемы с механизмом ewrapper и обратного вызова. После поиска в Google рабочих примеров я не смог найти ничего, что просто работало бы. Пожалуйста, обратите внимание, что я не программист, и поэтому мне трудно понять ewrapper и callback.


person aajkaltak    schedule 22.02.2016    source источник


Ответы (1)


Отказ от ответственности

Прежде чем ответить на этот вопрос, я считаю, что должен подчеркнуть отказ от ответственности, данный в самом начале Документация iBrokers:

Это программное обеспечение никоим образом не связано, не одобрено и не одобрено Interactive Brokers или любым из ее аффилированных лиц. Он поставляется без каких-либо гарантий и не должен использоваться в реальной торговле, если пользователь не может прочитать и понять источник.

Таким образом, похоже, что этот пакет разработан и поддерживается независимыми программистами, которые могут или не могут иметь хорошую связь с официальной разработкой 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(), он просто проверит, что он наследуется от twsConnection или twsconn. Вы можете создать такое соединение с 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. 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. Захватывает возвращаемое значение функции обратного вызова (напомню, что это msg[2], второе поле после кода сообщения) в currentTime.
    3. Если код сообщения действительно соответствует запросу текущего времени, цикл while прерывается. (Если бы это было не так, то processMsg() фактически активировал обработчик по умолчанию, который не делает ничего полезного, и значение currentTime было бы проигнорировано.)
  5. Создает объект POSIXct вокруг значения currentTime. Все, что действительно требуется здесь, это классифицировать его как POSIXt и POSIXct, поскольку тип POSIXct удобно реализован путем хранения времени эпохи Unix для представления точки даты/времени.

Итак, как видите, reqCurrentTime() делает гораздо больше, чем reqExecutions(). В своей текущей форме reqExecutions() ничего не делает для обработки ответа, так что это совсем не полезно.

Решение

Поскольку я знаком с IB API, мне удалось восполнить недостающую функциональность, которую я представляю ниже.

В качестве примечания я сослался на исходный код C++ API, который доступен по адресу https://www.interactivebrokers.com/en/index.php?f=5041; официальный источник 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=() для декодирования прямо из буфера сокета.

Я также рекомендую ознакомиться с официальной документацией по API по адресу https://www.interactivebrokers.com/en/software/api/api.htm.

Во-первых, обратите внимание, что IBrokers предоставляет такие функции, как twsContract() и twsOrder(), позволяющие создавать объекты данных, специфичные для TWS. Там нет функции twsExecution(), поэтому я написал свою собственную, следуя тому же стилю построения объекта, включая присоединение класса twsExecution S3. Я также написал функцию 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
person bgoldst    schedule 29.02.2016
comment
Спасибо за подробный ответ. Я проведу казнь сегодня или завтра и вскоре после этого назначу тебе награду. - person aajkaltak; 01.03.2016