有人可以为我提供一个 reqExecutions 的工作示例吗?我很难使用 ewrapper 和回调机制。在谷歌搜索工作示例后,我无法得到任何可以简单工作的东西。请注意,我不是程序员,这就是为什么我很难让我的头脑围绕着 ewrapper 和回调。
1 回答
免责声明
在回答这个问题之前,我觉得我应该强调IBrokers 文档开头给出的免责声明:
本软件不以任何方式附属、认可或批准盈透证券或其任何附属公司。它绝对没有任何保证,除非用户能够阅读和理解来源,否则不应在实际交易中使用它。
所以看起来这个包是由独立程序员设计和维护的,他们现在或将来可能会或可能不会与官方 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
ortwsconn
。您可以使用twsConnect()
. 它在内部是一个普通的 R 环境,归类为twsconn
. - 提取由 TWS 连接对象包装的套接字连接。他们在这里使用了不寻常的设计;他们已经
`[[`()
为类重载了函数twsconn
。如果您查看IBrokers:::`[[.twsconn`
,您会看到它只是返回twsconn$conn
环境条目。 - 将请求数据(正确编码)打包到套接字上。编码数据由 10 个 NUL 分隔的字段(NUL
writeBin()
由字段。(不幸的是,它要求调用者完全指定所有过滤器字段。)然后它立即返回而不等待响应。
将上述与完全实现的请求功能进行对比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
包提供的另一个功能。这是实际打开消息代码并调用适当的回调函数 on 的函数e_current_time
,传递它curMsg
以及代码特定的尾随字段,通过调用解码readBin()
(上面未显示;参见processMsg
代码)。 - 获取回调函数的返回值(回想一下,这是
msg[2]
消息代码之后的第二个字段)到currentTime
. - 如果消息代码确实是针对当前时间请求的,它会中断 while 循环。(如果不是,那么
processMsg()
实际上触发了一个默认处理程序,它没有做任何有用的事情,并且该currentTime
值将被忽略。)
- 当消息到达时,它将第一个字节抓取到
currentTime
围绕该值构建一个 POSIXct 对象。这里真正需要的只是将其归类为 POSIXt 和 POSIXct,因为 POSIXct 类型通过存储 Unix 纪元时间来表示日期/时间点来方便地实现。
因此,如您所见,reqCurrentTime()
它所做的远不止reqExecutions()
. 在当前的形式下,reqExecutions()
它不做任何事情来处理响应,所以它根本没有用处。
解决方案
由于我熟悉 IB API,因此我能够填补缺失的功能,如下所示。
作为旁注,我引用了可从https://www.interactivebrokers.com/en/index.php?f=5041获得的 C++ API 源代码;关于客户端如何与套接字交互,官方 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=()
直接从套接字缓冲区进行解码。
我还建议在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()
以列表的形式返回所有响应记录,其中每个组件是 、 和 组件的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