近来发现一些朋友用IB的API编写自己的系统, 故开此贴提供一个交流的地方. 本人在IB API的开发上有少许经验, 在时间许可的情况下愿意和大家分享和交流. 首先从如何用API来发送Bracket Order开始. 在自动交易系统里, Bracket Order基本是必须的. 下面的psedo code包含了全部过程. 由于我是使用的IB API的Java版本, 下面的代码不可避免的也是Java风格的. //create orders Order entryOrder = createAOrder(...); Order targetOrder = createAOrder(...); Order stopOrder = createAOrder(...); //attach to prime order which is entry order targetOrder.setParentID(entryOrder.getID()); stopOrder.setParentID(entryOrder.getID()); //set oca group string String oca = a unique identifier (current time can be used); targetOrder.setOcaGroup(oca); stopOrder.setOcaGroup(oca); //set transmit property entryOrder .setTransmit(false); targetOrder.setTransmit(false); stopOrder.setTransmit(true); //then send them out broker.placeOrder(entryOrder ); broker.placeOrder(targetOrder); broker.placeOrder(stopOrder);
IB API的设计还是比较简单易用的. 客户在使用时只需要实现接口EWrapper所定义的函数就可以了.这些函数涵盖了客户端和IB服务器间进行发送和接收信息的各种行为,而具体的socket通讯是由类EClientSocket来完成的, 因此在实现接口EWrapper的类里应该有一个EClientSocket成员, 然后将发送信息的任务交给成员EClientSocket, 但是接收信息的部分完全是客户端的任务了. 下面是一个例子: public class EWrapperAdapter implements EWrapper{ EClientSocket m_client = new EClientSocket(this); //实现发送信息的例子 public void onPlaceOrder() { //将发送信息的任务交给成员EClientSocket m_client.placeOrder(m_id, m_contract, m_order); } //实现接收消息的例子 public void tickPrice(int tickerId, int field, double price, int canAutoExecute) { // 接收信息的部分完全是客户端的任务 print("id=" + tickerId + " " + TickType.getField(field) + "=" + price + " " + ((canAutoExecute != 0) ? " canAutoExecute": " noAutoExecute")); } //其他方法雷同} 这样定义了的EWrapperAdapter能够用来实现我们所需要的操作, 比如 EWrapperAdapter wrapper = new EWrapperAdapter(); int id = ... Contract contract = ... Order order = ... m_client.onPlaceOrder(id, contract, order);网上所能看到的IB API的大多数例子是采用这种集中在一个类中实现EWrapper的方法. 这种方式的优点是简单, 易于上手, 但是对于复杂的情况这种方法就不太灵活. 比如我只用IB来交易, 从一个data source来取得实时数据,而从另外一个data source来取得历史数据. 为了实现这种情况, 我们只需要分析一下EWrapper, 发现它是把这三部分功能放在一起的: Broker 功能: 发送,修改order等 RealTimeFeed 实时数据功能: 接受实时streaming数据 BackfillFeed 历史数据功能: 回补历史数据上面对应整体的EWrapper有一个整体客户端EClientSocket, 与此类似, 我们需要定义BrokerClient, RealTimeFeedClient和BackfillFeedClient来实现Broker, RealtimeFeed和BackfillFeed的所需要的功能. 这样设计的好处是客户可以根据需要在不同的broker间和数据提供商间切换, 当然对每一个broker和数据提供商我们需要编写相应的client类, 即BrokerClient, RealTimeFeedClient和BackfillFeedClient.
今天我们只讨论客户端和服务器间的市场数据的交流. 上面提到这个部分可以分解成为2部分: RealTimeFeed 实时数据功能: 接受实时streaming数据 BackfillFeed 历史数据功能: 回补历史数据事实上RealTimeFeed和BackfillFeed有很多共性, 设计的时候可以把它们作为一个更一般的类QuoteFeed的子类: QuoteFeed 处理市场数据: RealTimeFeed (extends QuoteFeed) 实时数据功能 BackfillFeed (extends QuoteFeed) 历史数据功能相应的RealTimeClient和BackfillClient被设计成更一般类QuoteClient的子类. 用户只要在适当的地方设定具体的QuoteFeed把柄, 并能够得到这些把柄就可以了. 比如可以设计一个类QuoteFeedManager如下: class QuoteFeedManager { void setRealTimeFeed(RealTimeFeed feed); RealTimeFeed getRealTimeFeed(); void setBackfillFeed(BackfillFeed feed); BackfillFeed getBackfillFeed();}在下面的例子里IB用来作为实时数据源: RealTmeClient ibClient= new IBRealTimeClient(); RealTimeFeed ibRealTimeFeed = new RealTimeFeed(ibClient); QuoteFeedManager.setRealTimeFeed(ibRealTimeFeed);如前一帖所说, 如果利用其他的数据源只需把相应的client类(包括RealTimeClient和BackfillClient)写好就可以了, 这样就把数据源和整体的框架部分完全分开了, 是用户可以很容易利用其他数据源的API. 待有时间将讨论如何设计把从服务器接收到的信息变成事件传播到相应的listeners, 以及以IB API为例如何具体编写client类.
我们在上面所用到的分解的目的是为了代更加模块化, 把不相关的东西分开. 应用同样的道理, 我们可以把所涉及到的事件也进行分析分解. 当从服务器收到一个事件时, 客户端应该做出相应的动作. public class IBRealTimeFeedClient extends ... implements ..{ .... //实现接收消息的例子 public void tickPrice(int tickerId, int field, double price, int canAutoExecute) { // 接收信息的部分完全是客户端的任务 print("id=" + tickerId + " " + TickType.getField(field) + "=" + price + " " + ((canAutoExecute != 0) ? " canAutoExecute": " noAutoExecute"));}} 这里客户端收到服务器的quote事件以后就马上处理了. 这种把客户的事件处理直接放在IBRealTimeClient里是把不同的部分给混在一起了, 对复杂的系统很不适用, 因为IBRealTimeClient应该主要是负责接收streaming quote事件而不是来处理事件的, 它应该和处理事件的部分是分开的. 为此我们设计一个类QuoteMonitor, 用来监视IBRealTimeClient所收到的quote事件: IBRealTimeClient一经收到quote事件, QuoteMonitor马上把这个事件播发出去. public class IBRealTimeFeedClient extends ... implements ...{ .... //实现接收消息的例子 public void tickPrice(int tickerId, int field, double price, int canAutoExecute) { QuoteMonitor monitor = feed.getQuoteMonitor(tickerId); QuoteRecord newQuote = monitor.getStreamingQuote(); switch (field) { case IBHelper.BID: // bid newQuote.setBidPrice((float) price); monitor.sendQuoteEvent(QuoteFieldConstants.BID_PRICE); break; case IBHelper.ASK: // ask newQuote.setAskPrice((float) price); monitor.sendQuoteEvent(QuoteFieldConstants.ASK_PRICE); break;.........} 对每一个产品Equity, 如果需要从服务器去数据, 那么相对应地就有一个QuoteMonitor. public class QuoteMonitor extends ... { private QuoteRecord streamingQuote; QuoteEventListener[] listenerList; public QuoteRecord getStreamingQuote(); public void addQuoteEventListener(QuoteEventListener listener); public void sendQuoteEvent(final int quoteField) { QuoteEvent event = new QuoteEvent(quoteField, this.streamingQuote); for each QuoteEventListener listener listener.quoteChanged(event); }} 接口QuoteEventListener是客户端用来处理quote事件的, interface QuoteEventListener { void quoteChanged(QuoteEvent event);} 因为QuoteMonitor是用来监视实时quote时间的, 所以把它放在RealTimeFeed里: class RealTimeFeed extends ...{ Hashtable<Integer, QuoteMonitor> idToQuoteMonitorTable; public QuoteMonitor getQuoteMonitor(int idx) { return idToQuoteMonitorTable.get(idx); }} 有了上面的基本建设, 我们现在就可以把事件的处理和IB完全分开了. 比如, 我们想利用新得到的quote来更新chart, 那么就可以这么写: class Chart .... implements QuoteEventListener { public void draw(QuoteRecord quote, ...); public void quoteChanged(QuoteEvent event){ QuoteRecord quote = event.getQuoteRecord(); draw(quote); }} 然后在程序中需要将生成的Chart注册到QuoteMonitor里: Chart chart = new Chart(...); QuoteMonitor monitor = RealTimeFeed.getQuoteMonitor(...); monitor.addQuoteEventListener(chart); 这样IBRealTimeClient收到新的quote以后, QuoteMonitor就传播给Chart了, 进而更新图形.
本来这个贴是为了交流IB API使用的经验, 但是当我开始写的时候, 我发现我对IB API的了解很陌生了. 我当时只是实现了IBBroekrClient, IBRealTimeClient and IBBackfillClient几个类, 并且具体的事件处理全都代理给了QuoteMonitor和OrderMonitor. 同时我也并没有利用API里的所有功能, 只停留在取futures数据和发送order上. 所以我对讨论IB API的具体用法感到力不从心, 而把自己在系统开发上心得写出来, 希望对有些朋友有用.
通过利用QuoteMonitor来监听从IB服务器来的quote事件并将去发布出去, 类IBStreamingClient的两个最主要的函数可以写成: Code: class IBStreamingClient implements ... { .... public void tickPrice(int tickerId, int field, double price, int canAutoExecute) { QuoteMonitor monitor = feed.getQuoteMonitor(tickerId); QuoteRecord newQuote = monitor.getStreamingQuote(); switch (field) { case IBHelper.BID: // bid newQuote.setBidPrice((float) price); monitor.sendQuoteEvent(QuoteFieldConstants.BID_PRICE); break; case IBHelper.ASK: // ask newQuote.setAskPrice((float) price); monitor.sendQuoteEvent(QuoteFieldConstants.ASK_PRICE); break; case IBHelper.LAST: // last newQuote.setLast((float) price); monitor.sendQuoteEvent(QuoteFieldConstants.LAST); // newQuote.setLastSize(0); // W.logFine(this, price); break; ... default: return; } } public void tickSize(int tickerId, int field, int size) { QuoteMonitor monitor = feed.getQuoteMonitor(tickerId); QuoteRecord newQuote = monitor.getStreamingQuote(); switch (field) { case IBHelper.BID_SIZE: // bid newQuote.setBidSize(size); monitor.sendQuoteEvent(QuoteFieldConstants.BID_SIZE); break; case IBHelper.ASK_SIZE: // ask newQuote.setAskSize(size); monitor.sendQuoteEvent(QuoteFieldConstants.ASK_SIZE); break; case IBHelper.LAST_SIZE: // last monitor.sendQuoteEvent(QuoteFieldConstants.LAST_SIZE); break; case IBHelper.VOLUME: // volume int previousSize = monitor.getPreviousSize(); monitor.setPreviousSize(size); int volume = size - previousSize; if (volume <= 0) return; TimeZone timeZone = DateTimeUtils.DefaultTimeZone; newQuote.setDate(DateTimeUtils.getYMD(timeZone)); newQuote.setTime(DateTimeUtils.getHMS(timeZone)); newQuote.setLastSize(volume); monitor.sendQuoteEvent(QuoteFieldConstants.VOLUME); break; default: return; } } } 需要说明的是: IB并不只是在一个新交易发生的时刻发送LAST和LAST_SIZE事件, IB还会重复发送这两个事件, 所以事件LAST和LAST_SIZE并不是说明当下有一个新交易tick, 也有可能是把前一个交易的LAST和LAST_SIZE重复发送一遍. 在上面的代码里, 我们利用事件VOLUME来判断当下的事件是不是对应一个新的交易发生, 即如果volume有变化, 说明新的交易发生了, 在这种情况下生成一个新的tick quote并发送出去.
1. 在定义bracket order时, 给tareget order 和 stop order先定一个价格 2. 成交后再根据某种规则比如成交价来修改tareget order 和 stop order的价格
非常好的帖子。 从技术的角度看,当启动与tws的连接后,就有一个单独的子线程监控和接收服务器端传来的数据。 至于接手后的数据处理,应该和具体需求相关,发送event事件固然是一个很好的,清晰的处理方式, 但是直接进行callback操作也未尝不可,理论上来说,直接的callback应该比event处理方式更快些。