reqExecutions IBrokers 包
reqExecutions IBrokers package
有人可以为我提供一个 reqExecutions 的工作示例吗?我很难使用 ewrapper 和回调机制。在搜索 google 的工作示例后,我无法得到任何可以简单工作的东西。请注意,我不是程序员,这就是为什么我很难全神贯注于 ewrapper 和回调。
免责声明
在回答这个问题之前,我觉得我应该强调IBrokers documentation开头的免责声明:
This software is in no way affiliated, endorsed, or approved by Interactive Brokers or any of its affiliates. It comes with absolutely no warranty and should not be used in actual trading unless the user can read and understand the source.
所以看起来这个包是由独立程序员设计和维护的,他们现在或将来可能会或可能不会与官方 IB API 开发有很好的配合。
除此之外,我查看了很多包源代码,与实际的 IB API 源代码相比,它相当不完整。事实上,您偶然发现了不完整的部分之一;在 IBrokers 参考卡中它说:
Executions
Returns execution details in a twsExecution object. This method is currently only implemented as a request, with no built-in mechanism to manage response data apart from it being discarded.
(注意:如果您配置了 options()$pdfviewer
,您可以使用 IBrokersRef()
访问参考卡片。如果没有,您可以手动打开 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 环境,class编辑为 twsconn
。
- 提取TWS连接对象包裹的套接字连接。他们在这里使用了不寻常的设计;他们为
twsconn
class 重载了 `[[`()
函数。如果您查看 IBrokers:::`[[.twsconn`
,您会发现它只是 return 的 twsconn$conn
环境条目。
- 将请求数据(正确编码)打包到套接字上。编码后的数据由10个NUL分隔字段组成(NUL由
writeBin()
添加):请求类型枚举、请求版本、请求标识符(可用于区分同一类型的多个同时请求) ,然后是 7 个过滤字段。 (不幸的是,它要求调用者完全指定所有筛选字段。)然后它会立即 returns,无需等待响应。
将上面的内容与完全实现的请求函数进行对比,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()
到达。处理程序只是 return 第二个字段,msg[2]
,它代表服务器对当前时间的想法,编码为 Unix 纪元值(自 1970-01-01 以来的秒数)。
- 迭代套接字,等待传入的套接字数据。
- 当消息到达时,它将第一个字节抓取到
curMsg
中,这是一个表示消息类型的代码,并在其上调用processMsg()
。这是 IBrokers
包提供的另一个功能。这是实际打开消息代码并在 e_current_time
上调用适当回调函数的函数,传递它 curMsg
以及代码特定的尾随字段,通过调用 [=41= 解码](上面未显示;请参阅 processMsg
了解代码)。
- 抓取回调函数的return值(记得这是
msg[2]
,消息代码后的第二个字段)到currentTime
.
- 如果消息代码确实是针对当前时间请求的,它会中断 while 循环。 (如果不是,那么
processMsg()
实际上触发了一个没有做任何有用的默认处理程序,并且 currentTime
值将被忽略。)
- 围绕
currentTime
值构建一个 POSIXct 对象。这里真正需要的只是 class 将其作为 POSIXt 和 POSIXct,因为 POSIXct 类型通过存储 Unix 纪元时间来表示 date/time 点来方便地实现。
因此,如您所见,reqCurrentTime()
比 reqExecutions()
做得更多。在目前的形式中,reqExecutions()
不执行任何处理响应的操作,因此它根本没有用处。
解决方案
因为我熟悉 IB API,所以我能够填补缺失的功能,我将在下面展示。
作为旁注,我引用了 C++ API 源代码,可从 https://www.interactivebrokers.com/en/index.php?f=5041 获得;官方 API 来源可以被视为关于客户端需要如何与套接字交互的权威。例如,您可以查看 /TWS API/source/CppClient/client/EClient.cpp
中的 EClient::reqExecutions()
函数以了解它如何将请求字段编码到套接字上,同样您可以查看 /TWS API/source/CppClient/client/EDecoder.cpp
文件中的 EDecoder::processExecutionDataMsg()
函数查看它如何解码来自服务器的响应。基本上,它使用一些 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 class。我还写了一个 print.twsExecution()
函数,所以 twsExecution
对象将以与其他对象相同的方式打印,这基本上意味着 str(unclass(x))
.
其次,我编写了自己的 reqExecutions()
替代品 reqExecutions2()
,它根据 reqExecutions()
将请求数据编码到套接字上,然后覆盖响应处理程序并迭代等待响应消息的套接字,类似于reqCurrentTime()
。我对代码有点冗长,因为我试图尽可能地遵循 C++ 算法,包括它对响应处理的请求版本检查,以有条件地从套接字中取出某些字段。我还包括了对来自服务器的错误消息的监视,以便 while 循环自动中断并且函数 returns 出现错误。 reqExecutions2()
return 将所有响应记录作为列表,其中每个组件都是 reqId
、contract
和 execution
组件的嵌套列表。这里沿用了官方API中的execDetails()
回调设计;参见 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
有人可以为我提供一个 reqExecutions 的工作示例吗?我很难使用 ewrapper 和回调机制。在搜索 google 的工作示例后,我无法得到任何可以简单工作的东西。请注意,我不是程序员,这就是为什么我很难全神贯注于 ewrapper 和回调。
免责声明
在回答这个问题之前,我觉得我应该强调IBrokers documentation开头的免责声明:
This software is in no way affiliated, endorsed, or approved by Interactive Brokers or any of its affiliates. It comes with absolutely no warranty and should not be used in actual trading unless the user can read and understand the source.
所以看起来这个包是由独立程序员设计和维护的,他们现在或将来可能会或可能不会与官方 IB API 开发有很好的配合。
除此之外,我查看了很多包源代码,与实际的 IB API 源代码相比,它相当不完整。事实上,您偶然发现了不完整的部分之一;在 IBrokers 参考卡中它说:
Executions
Returns execution details in a twsExecution object. This method is currently only implemented as a request, with no built-in mechanism to manage response data apart from it being discarded.
(注意:如果您配置了 options()$pdfviewer
,您可以使用 IBrokersRef()
访问参考卡片。如果没有,您可以手动打开 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 环境,class编辑为twsconn
。 - 提取TWS连接对象包裹的套接字连接。他们在这里使用了不寻常的设计;他们为
twsconn
class 重载了`[[`()
函数。如果您查看IBrokers:::`[[.twsconn`
,您会发现它只是 return 的twsconn$conn
环境条目。 - 将请求数据(正确编码)打包到套接字上。编码后的数据由10个NUL分隔字段组成(NUL由
writeBin()
添加):请求类型枚举、请求版本、请求标识符(可用于区分同一类型的多个同时请求) ,然后是 7 个过滤字段。 (不幸的是,它要求调用者完全指定所有筛选字段。)然后它会立即 returns,无需等待响应。
将上面的内容与完全实现的请求函数进行对比,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()
到达。处理程序只是 return 第二个字段,msg[2]
,它代表服务器对当前时间的想法,编码为 Unix 纪元值(自 1970-01-01 以来的秒数)。 - 迭代套接字,等待传入的套接字数据。
- 当消息到达时,它将第一个字节抓取到
curMsg
中,这是一个表示消息类型的代码,并在其上调用processMsg()
。这是IBrokers
包提供的另一个功能。这是实际打开消息代码并在e_current_time
上调用适当回调函数的函数,传递它curMsg
以及代码特定的尾随字段,通过调用 [=41= 解码](上面未显示;请参阅processMsg
了解代码)。 - 抓取回调函数的return值(记得这是
msg[2]
,消息代码后的第二个字段)到currentTime
. - 如果消息代码确实是针对当前时间请求的,它会中断 while 循环。 (如果不是,那么
processMsg()
实际上触发了一个没有做任何有用的默认处理程序,并且currentTime
值将被忽略。)
- 当消息到达时,它将第一个字节抓取到
- 围绕
currentTime
值构建一个 POSIXct 对象。这里真正需要的只是 class 将其作为 POSIXt 和 POSIXct,因为 POSIXct 类型通过存储 Unix 纪元时间来表示 date/time 点来方便地实现。
因此,如您所见,reqCurrentTime()
比 reqExecutions()
做得更多。在目前的形式中,reqExecutions()
不执行任何处理响应的操作,因此它根本没有用处。
解决方案
因为我熟悉 IB API,所以我能够填补缺失的功能,我将在下面展示。
作为旁注,我引用了 C++ API 源代码,可从 https://www.interactivebrokers.com/en/index.php?f=5041 获得;官方 API 来源可以被视为关于客户端需要如何与套接字交互的权威。例如,您可以查看 /TWS API/source/CppClient/client/EClient.cpp
中的 EClient::reqExecutions()
函数以了解它如何将请求字段编码到套接字上,同样您可以查看 /TWS API/source/CppClient/client/EDecoder.cpp
文件中的 EDecoder::processExecutionDataMsg()
函数查看它如何解码来自服务器的响应。基本上,它使用一些 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 class。我还写了一个 print.twsExecution()
函数,所以 twsExecution
对象将以与其他对象相同的方式打印,这基本上意味着 str(unclass(x))
.
其次,我编写了自己的 reqExecutions()
替代品 reqExecutions2()
,它根据 reqExecutions()
将请求数据编码到套接字上,然后覆盖响应处理程序并迭代等待响应消息的套接字,类似于reqCurrentTime()
。我对代码有点冗长,因为我试图尽可能地遵循 C++ 算法,包括它对响应处理的请求版本检查,以有条件地从套接字中取出某些字段。我还包括了对来自服务器的错误消息的监视,以便 while 循环自动中断并且函数 returns 出现错误。 reqExecutions2()
return 将所有响应记录作为列表,其中每个组件都是 reqId
、contract
和 execution
组件的嵌套列表。这里沿用了官方API中的execDetails()
回调设计;参见 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