虽然Class List很多,但是和开发者主要打交道的就三个类,EWrapper,EClientSocket,EReaderSignal。IB的代码交互方式,是一种类似于请求/映射的结构,其背后采用了一种异步方式,用户发送了请求消息至TWS或者IB Gate,然后经过处理后提交给IB的服务器或者直接提交到交易所进行处理,然后等待从IB服务器或者交易所返回信息,再推送至客户端,客户端则通过回调函数的形式,交给对应的代码进行数据处理。
这也是为什么IB的开发文档里,反复提及的延迟问题,也是为什么IB反复强调用户提交的每一个指令,应该有一个独一无二的标识符。因为这种结构决定了返回的消息不会按顺序返回,用户则需要通过ID辨识获得的数据是什么时候提交的,提交的是什么内容。
首先你需要TWS客户端:地址:https://www.interactivebrokers.com.hk/en/index.php?f=16042
而API则在这个地址:https://www.interactivebrokers.com.hk/en/index.php?f=1325
API References: http://interactivebrokers.github.io/tws-api/index.html
如果还有什么不懂,你应该看看我的上一个教程TWS开发准备工作。其他的Q&A,你可以在我的博客下面留言,有空的时候我会回复你的。
话不多说,我们直接上第一个程序。我的程序都采用了C#,而Java/CPP/Python等程序的编写原则上是一样的。所以你可以很容易重构自己的代码,也可以参考IB给的示例编写。目前你需要与TWS交互的一共4个类,分别是:EWrapper, EClientSocket, EReaderSignal, EReader。
EWrapper,在前面已经说了,主要是用来处理各种消息的回调函数集合,所有像TWS/IB Gateway发送的消息请求,全部会使用EWrapper进行处理,所以开发属于自己的分析程序,其中一个重点就是处理EWrapper获得的各类回调数据。
EClientSocket,封装的是TCP通信协议,与TWS或IB Gateway所有的通信协议都已经被封装在了EClientSocket里。IB默认使用这个类来进行数据通信,但是如果你需要使用EClientSocketSSL,进行加密通信。
EReaderSignal,是用来获取从TWS返回的消息的,而EReader,则是用来处理消息队列的,根据IB文档描述,用于接受来自TWS的消息,并且处理相关信号的工作应该使用一个单独的后台线程进行处理。
所以,我们首先对EWrapper的抽象方法进行实例化:
class IBWrapper : EWrapper
{
...
}
根据提示,我们会被要求实例化一大波函数,但是一开始我们需要关心,并且需要实现的只有这么几个:
/// 这个函数负责当客户端与TWS建立连接时使用,作用类似于消息握手
public void connectAck()
{
if (clientSocket.AsyncEConnect)
clientSocket.startApi();
}
/// 这个函数负责当客户端与TWS关闭连接时使用,现在我们只打印closed消息即可
public void connectionClosed()
{
Console.WriteLine("Connection to TWS closed");
}
/// IB API一共需要用户实现三种不同的错误信息,分别来自.NET自身的错误,来自IB API自身的异常抛出,以及来自TWS反馈的消息
/// 这个error属于IB自身异常抛出的回调处理函数
public void error(Exception e)
{
Console.WriteLine("API Error: thrown exception: {0}", e);
}
/// 这个error属于.net自身的错误,比如访问越界什么的
public void error(string str)
{
Console.WriteLine("Error: message: {0}", str);
}
/// 这个error则是TWS反馈消息的API,errorCode为-1的时候不代表着错误,而其他>0的
public void error(int id, int errorCode, string errorMsg)
{
Console.WriteLine("TWS Error: id:{0} code:{1} message:{2}", id, errorCode, errorMsg);
}
/// 这个API是用来接受返回的账户信息的,在建立连接的第一步时会调用这个函数
public void managedAccounts(string accountsList)
{
Console.WriteLine("Account list: " + accountsList);
}
/// nextValidId,根据IB描述的意思,在每一次任务下达后,都会生成一个可用的新的任务Id,因为IB的消息机制是异步的,每一个任务都用唯一的ID进行标识,并且处理,比如说你需要对不同订单下达“买入”、“卖出”命令,但是这两个命令结果返回的时间先后可能不一致,为了避免混淆,应该给这些不同的命令不同的ID
public void nextValidId(int orderId)
{
Console.WriteLine("Next Valid Id: " + orderId);
nextOrderId = orderId;
}
然后你需要准备三个变量,分别记录新的可用ID,和TWS连接的Socket,以及用于消息处理的EReaderSignal。
/// 可用的订单ID
private int nextOrderId;
/// TWS连接Socket,非SSL版
private EClientSocket clientSocket;
/// 消息处理类,这个不需要做太多干预
private EReaderSignal signal;
/// 以及对外的访问接口
public int NextOrderId {
get { return nextOrderId; }
set { nextOrderId = value; }
}
/// 以及对外的访问接口
public EClientSocket ClientSocket {
get { return clientSocket; }
set { clientSocket = value; }
}
/// 以及对外的访问接口
public EReaderSignal Signal
{
get { return signal; }
}
当你完成这些工作后,再创造一个构造函数,用于初始化EWrapper。
public IBWrapper()
{
signal = new EReaderMonitorSignal();
clientSocket = new EClientSocket(this, signal);
}
我们需要准备的前期工作基本完毕,现在回到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) { }
// 啥事也不干,就睡10s
Thread.Sleep(10000);
// 与TWS关闭连接
Console.WriteLine("Disconnecting from TWS...");
clientSocket.eDisconnect();
// 类似于断点,我们可以查看到代码的输出
Console.ReadKey();
}
成功以后得到的输出就是这样的,这些信息表示我们获取的信息和数据都是正常的,连接到交易所的通信也是正常的。
在下一章中,我们将在这个代码的基础上,创建一个简易的订单。