2016-02-22 5 views
2

Возможно ли, чтобы кто-нибудь предоставил мне рабочий пример reqExecutions? Мне сложно работать с механизмом ewrapper и callback. После поиска google для рабочих примеров, я не мог понять, что будет просто работать. Пожалуйста, обратите внимание, что я не программист, и поэтому я с трудом собираю свою голову, обернутую вокруг ewrapper и callback.reqExecutions IBrokers package

ответ

11

Отказ

Прежде чем ответить на этот вопрос, я чувствую, что я должен подчеркнуть, отказ от ответственности, приведенное в самом начале IBrokers documentation:

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

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

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

Решение

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

В качестве побочного примечания я ссылался на исходный код 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++ для потоков примитивных полей для socket 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; (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 
+0

Спасибо за подробный ответ. Я буду исполнять казнь сегодня или завтра и награду за награду вскоре после – aajkaltak