Сможет ли кто-нибудь предоставить мне рабочий пример reqExecutions? У меня проблемы с механизмом ewrapper и обратного вызова. После поиска в Google рабочих примеров я не смог найти ничего, что просто работало бы. Пожалуйста, обратите внимание, что я не программист, и поэтому мне трудно понять ewrapper и callback.
Пакет reqExecutions iBrokers
Ответы (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)
}
Подводя итог вышесказанному, это:
- Проверяет, что данный объект подключения TWS имеет правильный тип S3. Если вы посмотрите на
is.twsConnection()
, он просто проверит, что он наследуется отtwsConnection
илиtwsconn
. Вы можете создать такое соединение сtwsConnect()
. Это простая внутренняя среда R, классифицированная какtwsconn
. - Извлекает соединение сокета, которое обернуто объектом соединения TWS. Здесь используется необычный дизайн; они перегрузили функцию
`[[`()
для классаtwsconn
. Если вы посмотрите наIBrokers:::`[[.twsconn`
, вы увидите, что он просто возвращает запись средыtwsconn$conn
. - Упаковывает данные запроса (правильно закодированные) в сокет. Закодированные данные состоят из 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"))
}
Этот:
- Делегаты частной функции пакета
.reqCurrentTime()
для отправки фактического запроса, аналогично тому, что делаетreqExecutions()
. Я не буду включать его сюда, но вы можете просмотреть его тело с помощьюIBrokers:::.reqCurrentTime
. - Генерирует список функций обратного вызова, используя
eWrapper()
, который он странно называетe_current_time
. - Переопределяет обработчик по умолчанию для ответа, который будет получен через вызов
currentTime()
в объекте списка обратных вызовов. Обработчик просто возвращает второе поле,msg[2]
, которое представляет представление сервера о текущем времени, закодированное как значение эпохи Unix (секунды с 1970-01-01). - Iterates on the socket, waiting for incoming socket data.
- When a message arrives, it grabs the first byte into
curMsg
, which is a code representing the message type, and callsprocessMsg()
on it. This is another function provided by theIBrokers
package. This is the function that actually switches on the message code and calls the appropriate callback function one_current_time
, passing itcurMsg
as well as the code-specific trailing fields, decoded via a call toreadBin()
(not shown above; seeprocessMsg
for the code). - Захватывает возвращаемое значение функции обратного вызова (напомню, что это
msg[2]
, второе поле после кода сообщения) вcurrentTime
. - Если код сообщения действительно соответствует запросу текущего времени, цикл while прерывается. (Если бы это было не так, то
processMsg()
фактически активировал обработчик по умолчанию, который не делает ничего полезного, и значениеcurrentTime
было бы проигнорировано.)
- When a message arrives, it grabs the first byte into
- Создает объект 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