Возможно ли, чтобы кто-нибудь предоставил мне рабочий пример reqExecutions? Мне сложно работать с механизмом ewrapper и callback. После поиска google для рабочих примеров, я не мог понять, что будет просто работать. Пожалуйста, обратите внимание, что я не программист, и поэтому я с трудом собираю свою голову, обернутую вокруг ewrapper и callback.reqExecutions IBrokers package
ответ
Отказ
Прежде чем ответить на этот вопрос, я чувствую, что я должен подчеркнуть, отказ от ответственности, приведенное в самом начале 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)
}
Подводя итог вышесказанному,:
- Подтверждает данный 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). - Итерирует на сокете, ожидая входящих данных сокета.
- При поступлении сообщения, она захватывает первые байты в
curMsg
, который представляет собой код, обозначающий тип сообщения и вызовыprocessMsg()
на него. Это еще одна функция, предоставляемая пакетомIBrokers
. Это функция, которая фактически включает код сообщения и вызывает соответствующую функцию обратного вызова по адресуe_current_time
, передавая ееcurMsg
, а также связанные с кодами конечные поля, декодированные по вызовуreadBin()
(не показано выше, см.processMsg
для кода) , - Получает возвращаемое значение функции обратного вызова (напомним, что это
msg[2]
, второе поле после кода сообщения) вcurrentTime
. - Если код сообщения действительно был для текущего запроса времени, он прерывает цикл while. (Если это не так, то
processMsg()
на самом деле срабатывает обработчик по умолчанию, который не делает ничего полезного, а значениеcurrentTime
будет игнорироваться.)
- При поступлении сообщения, она захватывает первые байты в
- Создает объект 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
Спасибо за подробный ответ. Я буду исполнять казнь сегодня или завтра и награду за награду вскоре после – aajkaltak