在我们进一步深入IB的工作机理前,需要回到Wrapper中error函数,如果善用error函数提供的错误信息,将能帮助我们快速定位到错误,并且修正我们的代码。
关于IB错误Code,具体的编码,请查考网站 https://interactivebrokers.github.io/tws-api/message_codes.html。
这里,我重点说一下error函数中,带有id函数,这是我们继续开发客户端算法非常重要的基础。
public void error(int id, int errorCode, string errorMsg)
{
Console.WriteLine("TWS Error: id:{0} code:{1} message:{2}", id, errorCode, errorMsg);
}
它包含3个返回参数,整数型的id,整数型的errorCode,以及string型的errorMsg。这里的id是我们提交到TWS的任务id,也就是OrderId,当它为-1时,这个函数得到的信息,会有点类似一些Debug里面的Info级别消息,也就是告诉你当前TWS的工作状态,你的客户端与TWS连接情况,TWS与IB自身,交易所的连接情况等基础信息。
所以,上一篇文章里,我们啥也没干,在Console里一下子打出了一堆信息,就是这样的原因。
而errorCode,就是比较有用的错误信息了,它是不同的预定义的编码,具体编码信息,你可以从上面的连接里对照着找,比如这个截图里出现的2104,在IB自己的官网定义就是:
code | TWS message | Additional notes |
---|---|---|
2104 | Market data farm connection is OK | A notification that connection to the market data server is ok. This is a notification and not a true error condition, and is expected on first establishing connection. |
至于message,就是返回的消息咯。
另一个带Exception的error,指的是你调用IB API时产生的错误。比如你试图发送一个订单,但是你并未建立起跟TWS的连接,这个时候就会出现TCP连接错误的问题,而这个错误,就会通过
public void error(Exception e)
{
Console.WriteLine("API Error: thrown exception: {0}", e);
}
这个回调函数告诉你。因此,你可以使用调用栈信息,找出错误的代码位置,或者直接打印异常信息本身。
在IB API的架构设计,所有向IB Server提交的命令,都是通过EClient封装好的API来提交。啥玩意是EClient?
EclientSocket继承了Eclient和EClientMsgSink,这个类自身所担负的主要工作就是创建和维护与TWS通信的TCP信道。而任务命名请求,则全部交由EClient负责。
因此,对于TWS来说,你提交任务的基本方式,大体上是这样的:
clientSocket.reqXXXXX(id, ...);
因此,你能做什么,不能做什么,基本上就看Eclient是否封装了对应的API,以及API里需要的参数是什么样子的,所以具体的你可以看看API手册上是怎么写的。
那么明白了这一点,我们可以进入到下一个内容,向TWS提交合约信息。
所谓合约,如果从金融专业出发,指的是合同,或者更侠义的说是商品交割合同,也就是期货、股指等商品的交易合同。
IB把绝大部分的交易请求行为定义为合约(Contract),这意味着用户要提交具体的数据请求时,需要像填表一样,把相关任务的信息填入到Contract中,因此,不同类型的任务,需要填写的关键字是不一样的。
IB将很多操作,比如查股价,查期货合约等任务,都视为一个合约任务,不知道这种定义更偏向金融系统的人还是更偏向IT系统的,如果用我们IT的行话说,那一个Contract其实就是一个Request。
你要是还不明白这个是什么意思,那么容许我引入一下网络开发常提到的HTTP协议。在HTTP中,我们通过GET/POST命名,并附带各种参数,以此告知远程服务器对应的任务形式。
Contract也是类似的,只不过是用类进行表示的。所以要做什么事,能做什么事,两件函数相同但功能不同的任务如何区分,思路也基本一样,就是依靠提交的Contract类的不同参数定义来区分。
这其实也可以理解,因为IB涵盖的交易产品太多太广泛,必然会出现很多重复的地方,所以一个交易产品,需要多个不同的字段去缩小它的定义。
举例来说,当你告知IB,帮我查找“苹果”。如果不告知它具体的其他信息,它可能会以为你要查找苹果现货合约,苹果股票期权合约,或者苹果股票合约;它会完全confused。
所以要怎么去查找具体的商品合约信息呢?
IB给出的建议是让我们直接在TWS中找具体的信息(通过双击,或者在Contract Info -> Description):
然后我们可以得到这样的信息:
这个框里的东西,告知了我们对应的合约信息,包含交易所代码,合约代码,货币单位等重要的信息。所以如果你需要TWS告诉你苹果公司的股票合约信息,而不是期权合约信息,甚至不是苹果现货合约信息,那么你需要通过这个方法,找到苹果股票的合约信息。
还记得上一篇文章《#IB TWS编程手记——01.TWS建立基础连接》中怎样建立与TWS的连接么?
我们接下来要做的,就是在Main函数里先增加这么一个指令:
static void Main()
{
/// 创建Wrapper,并且获取创建好的clientSocket以及readerSignal
IBWrapper wrapper = new IBWrapper();
EClientSocket clientSocket = wrapper.ClientSocket;
EReaderSignal readerSignal = wrapper.Signal;
// 连接至服务器,IP地址,端口号,本客户端ID,默认为0
// 关于0有什么用,我们在后面的文章里再详细说明
clientSocket.eConnect("127.0.0.1", 7497, 0);
// 创建一个reader,用于处理消息事件
var reader = new EReader(clientSocket, readerSignal);
reader.Start();
// 创建后台线程,监控来自TWS的消息
new Thread(() => {
while (clientSocket.IsConnected()) {
readerSignal.waitForSignal();
reader.processMsgs();
}
}) { IsBackground = true }.Start();
// 这一步会一直循环,直到我们获得了可用的OrderID,>0 时表示着此时开始,可以向TWS发送命名了。
while (wrapper.NextOrderId <= 0) { }
// 新增加的指令
clientSocket.reqMatchingSymbols(wrapper.NextOrderId, "IB");
// 然后啥事也不干,就睡10s
Thread.Sleep(10000);
// 关闭连接
Console.WriteLine("Disconnecting from TWS...");
clientSocket.eDisconnect();
// 类似于断点,我们可以查看到代码的输出
Console.ReadKey();
}
我们注意下这个新增的代码:
clientSocket.reqMatchingSymbols(wrapper.NextOrderId, "IB");
这个指令的意思,是让TWS帮我们查找合约中,合约代码为“IB”的交易产品信息。还记得我们的Wrapper中,只实现了屈指可数的几个函数吗?
现在回到Wrapper的实现类,我们需要把回调函数“symbolSamples”实现它的方法:
public void symbolSamples(int reqId, ContractDescription[] contractDescriptions)
{
string derivSecTypes;
Console.WriteLine("Symbol Samples. Request Id: {0}", reqId);
foreach (var contractDescription in contractDescriptions)
{
derivSecTypes = "";
foreach (var derivSecType in contractDescription.DerivativeSecTypes)
{
derivSecTypes += derivSecType;
derivSecTypes += " ";
}
Console.WriteLine("Contract: conId - {0}, symbol - {1}, secType - {2}, primExchange - {3}, currency - {4}, derivativeSecTypes - {5}",
contractDescription.Contract.ConId, contractDescription.Contract.Symbol, contractDescription.Contract.SecType,
contractDescription.Contract.PrimaryExch, contractDescription.Contract.Currency, derivSecTypes);
}
}
然后你再重新执行一遍代码,看看有什么效果?然后再去跟TWS的信息做一下对比?