#IB TWS编程手记——02.提交你的第一个Hello World!

关于IB的错误码

在我们进一步深入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里一下子打出了一堆信息,就是这样的原因。

#IB TWS编程手记——02.提交你的第一个Hello World!_第1张图片

而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?

#IB TWS编程手记——02.提交你的第一个Hello World!_第2张图片

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):

#IB TWS编程手记——02.提交你的第一个Hello World!_第3张图片

然后我们可以得到这样的信息:

#IB TWS编程手记——02.提交你的第一个Hello World!_第4张图片

这个框里的东西,告知了我们对应的合约信息,包含交易所代码,合约代码,货币单位等重要的信息。所以如果你需要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的信息做一下对比?

你可能感兴趣的:(自动量化交易技术)