基于IB(Interactive Brokers)盈透证券的股票及期货行情获取讲解

       盈透证券,作为老牌帝国主义券商,能够提供强到变态的交易软件TWS,多到变态的全球股票、期货等产品覆盖,以及低到变态的交易费用。如果做全球股票或期货交易,能够对接盈透证券相关接口还是不错的选择,但是这里忍不住要吐槽一下盈透的接口命名,一点都不优雅,感觉有点渣, 有种乱糟糟的感觉。
       下面文字摘自网络:“目前IB的最低开户金额为10000美元,如果你觉得这个最低开户金额有点高(对于美股新手来说这个起始额度稍微有些偏高),而且你并不是频繁交易的投资者(习惯于买入股票并持有较长时间的,那么IB盈透可能也不是特别适合你,因为IB盈透有每月最低佣金为10美金(相当于账户维护费,不交易的话也要收取10美金每月;新开户的话前三个月是免收的)的规定另外IB盈透对美股实时行情收取每月10美金的费用(当然,你可以选择不订阅美股实时行情,而且如果你每月佣金超过30美金,则实时行情费是免收的),那么你可以考虑其他没有最低开户金额要求、同时也没有账户维护成本的美股券商,比如Firstrade(第一理财)”。具体是不是这样收费,本人没有去验证。
       下面还是主要说说盈透的api用法。首先需要明确的是,使用IB API连接的不是IB的交易服务器,而是运行在自己机器的TWS(带图形界面的交易平台软件)或者IB Gateway(只提供简单日志的交易路由,由于没有复杂的图形界面,能够提供比TWS更高的交易性能,适合对接程序化交易)。这一点和前面几篇文章讲的tap或ctp接口区别很大。另一个大区别是,IB API里回调函数(负责向用户的程序推送数据)的工作线程需要用户自行创建和管理,虽提供了极大的灵活性,但是开发者一上来几乎不知道怎么去写这个线程,需要反复研究IB提供的demo才行。相比较,国内的tap和cpt接口回调线程都是sdk内部实现,用户不需要额外创建及管理,用户只需重写相应回调方法即可,开发上较为简单。
       IB API的主要类如下:
       1. EWrapper:类似CTP中的SPI类,提供回调函数。
       2. EClientSocket:类似CTP中的API类,提供主动函数。
       3. EReaderSignal:一个信号量,回调函数管理线程需要监听该信号量,当socket收到数据后该信号量会被触发。
       4. EReader:当上面的信号量被触发后,EReader中的processMsgs()处理函数将会被调用,进而调用EWrapper中的回调函数,用户通过重写相应的回调函数,就可以完成行情数据接收及报单。具体调用逻辑可以参考盈透提供的源码。
       下面为java版的示例代码,实际盈透还提供了c#,c++,python等版本的接口,但还是推荐使用java。
public class IBEWrapper implements EWrapper {

    private EReaderSignal readerSignal_;
    private EClientSocket clientSocket_;
    private int baseOrderId_ = -1;
    private final long logPeriod = 30*1000;
    private long tickPriceTimestamp = System.currentTimeMillis();
    private long tickSizeTimestamp = System.currentTimeMillis();
    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private final BlockingQueue marketDataQueue_ = new LinkedBlockingDeque();
    private static Logger LOGGER = LoggerFactory.getLogger(IBEWrapper.class);

    public IBEWrapper() {
        readerSignal_ = new EJavaSignal();
        clientSocket_ = new EClientSocket(this, readerSignal_);
    }

    public EClientSocket getClientSocket() {
        return clientSocket_;
    }

    public EReaderSignal getReaderSignal() {
        return readerSignal_;
    }

    public int getNextOrderId() {
        return ++baseOrderId_;
    }

    public BlockingQueue getMarketDataQueue() {
        return marketDataQueue_;
    }

    // 建立连接-----------------------------------------------------------------------------------------------/
    // 接收服务器的当前系统时间
    @Override
    public void currentTime(long time) {
        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
        Date date = new Date(time);
        LOGGER.info("currentTime...{} convert to {}", time, sf.format(date));
    }

    // 处理请求过程中发生异常
    @Override
    public void error(Exception e) {
        LOGGER.info("处理请求过程中发生异常..." + e.getMessage());
    }

    // TWS给client发送错误信息
    @Override
    public void error(String str) {
        LOGGER.info("TWS给client发送错误信息..." + str);
    }

    // 通讯出现错误或TWS给client发送消息
    @Override
    public void error(int id, int errorCode, String errorMsg) {
        LOGGER.info("通讯出现错误或TWS给client发送消息...error id: {}, code: {}, msg: {}", id, errorCode, errorMsg);
    }

    // TWS关闭socket连接, 或当TWS被关闭时调用
    @Override
    public void connectionClosed() {
        LOGGER.info("TWS关闭socket连接, 或TWS已被关闭");
    }

    @Override
    public void connectAck() {
        if (clientSocket_.isAsyncEConnect()) {
            LOGGER.info("Acknowledging connection");
            clientSocket_.startAPI();
        }
    }

    // 市场数据-----------------------------------------------------------------------------------------------/
    // 当市场数据改变时调用该方法, 没有延迟的立即更新价格
    @Override
    public void tickPrice(int tickerId, int field, double price, int canAutoExecute) {
        String fieldDesc = TickType.getField(field);
        MarketDataBean marketData = ConfigSingleton.getInstance().getMarketDataMap().get(tickerId);
        marketData.setTimeStamp(dateFormat.format(System.currentTimeMillis()));
        MarketDataBean marketDataCopy = null;
        switch (field) {
            case 1:
                // 买价 bidPrice
                marketData.setBidPrice1(price);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 2:
                // 卖价 askPrice
                marketData.setAskPrice1(price);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 4:
                // 最新价 lastPrice
                marketData.setLastPrice(price);
                BigDecimal lastPrice = new BigDecimal(Double.toString(price));
                BigDecimal preSettlePrice = new BigDecimal(Double.toString(marketData.getPreSettlePrice()));
                marketData.setChangeValue(lastPrice.subtract(preSettlePrice).doubleValue());
                if (marketData.getPreSettlePrice() > 0.000001) {
                    BigDecimal changeValue = new BigDecimal(Double.toString(100.0 * marketData.getChangeValue()));
                    marketData.setChangeRate(changeValue.divide(preSettlePrice, 6, BigDecimal.ROUND_HALF_UP).doubleValue());
                } else {
                    marketData.setChangeRate(0.0);
                }
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 6:
                // 最高价 high
                marketData.setHighPrice(price);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 7:
                // 最低价 low
                marketData.setLowPrice(price);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 9:
                // 收盘价 close
                marketData.setPreClosePrice(price);
                marketData.setPreSettlePrice(price);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 14:
                // 开盘价 open
                marketData.setOpenPrice(price);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            default:
                marketDataCopy = new MarketDataBean(marketData);
                LOGGER.error("tick price. ticker Id: {}, field: {}, desc: {}, price: {}, canAutoExecute: {}", tickerId, field, fieldDesc, price, canAutoExecute);
                break;
        }

        try {
            marketDataQueue_.put(marketDataCopy);
        } catch (InterruptedException e) {
            LOGGER.error("添加价格信息出错, 异常信息: " + e.getMessage());
        }

        if (System.currentTimeMillis() - tickPriceTimestamp > logPeriod) {
            tickPriceTimestamp = System.currentTimeMillis();
            LOGGER.info("tick price. ticker Id: {}, field: {}, desc: {}, price: {}, canAutoExecute: {}", tickerId, field, fieldDesc, price, canAutoExecute);
        }
    }

    // 当市场数据变动时调用该方法, 尺寸立即更新, 没有延迟
    @Override
    public void tickSize(int tickerId, int field, int size) {
        String fieldDesc = TickType.getField(field);
        MarketDataBean marketData = ConfigSingleton.getInstance().getMarketDataMap().get(tickerId);
        marketData.setTimeStamp(dateFormat.format(System.currentTimeMillis()));
        MarketDataBean marketDataCopy = null;
        switch (field) {
            case 0:
                // 买量 bidSize
                marketData.setBidQty1(size);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 3:
                // 卖量 askSize
                marketData.setAskQty1(size);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 5:
                // 最后价尺寸 lastSize
                marketDataCopy = new MarketDataBean(marketData);
                break;
            case 8:
                // 交易量 volume
                marketData.setTotalQty(size);
                marketDataCopy = new MarketDataBean(marketData);
                break;
            default:
                marketDataCopy = new MarketDataBean(marketData);
                LOGGER.error("tick size. ticker Id: {}, field: {}, desc: {}, size: {}", tickerId, field, fieldDesc, size);
                break;
        }

        try {
            marketDataQueue_.put(marketDataCopy);
        } catch (InterruptedException e) {
            LOGGER.error("添加寸头信息出错, 异常信息: " + e.getMessage());
        }

        if (System.currentTimeMillis() - tickSizeTimestamp > logPeriod) {
            tickSizeTimestamp = System.currentTimeMillis();
            LOGGER.debug("tick size. ticker Id: {}, field: {}, desc: {}, size: {}", tickerId, field, fieldDesc, size);
        }
    }

    // 当市场数据改变时调用该方法, 值被立即更新, 没有延迟
    @Override
    public void tickGeneric(int tickerId, int tickType, double value) {
        String typeDesc = TickType.getField(tickType);
        LOGGER.debug("tick generic. ticker Id: {}, type: {}, desc: {}, value: {}", tickerId, tickType, typeDesc, value);
    }

    // 当市场数据改变时调用该方法, 值被立即更新, 没有延迟
    @Override
    public void tickString(int tickerId, int tickType, String value) {
        String typeDesc = TickType.getField(tickType);
        LOGGER.debug("tick string. ticker Id: {}, type: {}, desc: {}, value: {}", tickerId, tickType, typeDesc, value);
    }

    @Override
    public void tickEFP(int tickerId, int tickType, double basisPoints,
                        String formattedBasisPoints, double impliedFuture, int holdDays,
                        String futureLastTradeDate, double dividendImpact,
                        double dividendsToLastTradeDate) {
        System.out.println("TickEFP. " + tickerId + ", Type: " + tickType + ", BasisPoints: " + basisPoints + ", FormattedBasisPoints: " +
                formattedBasisPoints + ", ImpliedFuture: " + impliedFuture + ", HoldDays: " + holdDays + ", FutureLastTradeDate: " + futureLastTradeDate +
                ", DividendImpact: " + dividendImpact + ", DividendsToLastTradeDate: " + dividendsToLastTradeDate);
    }

    // 当期权市场或期权底层变化时调用该方法
    @Override
    public void tickOptionComputation(int tickerId, int field,
                                      double impliedVol, double delta, double optPrice,
                                      double pvDividend, double gamma, double vega, double theta,
                                      double undPrice) {
        System.out.println("TickOptionComputation. TickerId: " + tickerId + ", field: " + field + ", ImpliedVolatility: " + impliedVol + ", Delta: " + delta
                + ", OptionPrice: " + optPrice + ", pvDividend: " + pvDividend + ", Gamma: " + gamma + ", Vega: " + vega + ", Theta: " + theta + ", UnderlyingPrice: " + undPrice);
    }

    @Override
    public void tickSnapshotEnd(int reqId) {
        System.out.println("TickSnapshotEnd: " + reqId);
    }

    @Override
    public void marketDataType(int reqId, int marketDataType) {
        System.out.println("MarketDataType. [" + reqId + "], Type: [" + marketDataType + "]\n");
    }

    // 定单---------------------------------------------------------------------------------------------------/
    @Override
    public void orderStatus(int orderId, String status, double filled,
                            double remaining, double avgFillPrice, int permId, int parentId,
                            double lastFillPrice, int clientId, String whyHeld) {
        LOGGER.info("OrderStatus. Id: " + orderId + ", Status: " + status + ", Filled" + filled + ", Remaining: " + remaining
                + ", AvgFillPrice: " + avgFillPrice + ", PermId: " + permId + ", ParentId: " + parentId + ", LastFillPrice: " + lastFillPrice +
                ", ClientId: " + clientId + ", WhyHeld: " + whyHeld);

        switch (status) {
            case "PendingSubmit":
                // 已经报单, 但还未接到来自定单目的地接收的确认信息
                break;
            case "PreSubmitted":
                // 模拟定单类型已经被IB系统接收, 但还未被选中
                break;
            case "Submitted":
                // 定单已经被定单目的地接受, 并处于工作中
                break;
            case "Filled":
                // 定单已被全部执行
                break;
            case "Cancelled":
                // 定单的剩余部分已被IB系统确认取消了, 这也会在IB或目的地拒绝你的定单时发生
                break;
            case "Inactive":
                // 表示定单已被系统接收(模拟定单)或交易所接收(本地定单), 但由于系统/交易所或其它原因, 目前定单处于非工作状态
                break;
            case "PendingCancel":
                // 已经发送了取消定单的请求, 但还未收到来自定单目的地的取消确认
                break;
            default:
                break;
        }
    }

    @Override
    public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) {
        System.out.println("OpenOrder. ID: " + orderId + ", " + contract.symbol() + ", " + contract.secType() + " @ " + contract.exchange() + ": " +
                order.action() + ", " + order.orderType() + " " + order.totalQuantity() + ", " + orderState.status());
    }

    @Override
    public void openOrderEnd() {
        System.out.println("OpenOrderEnd");
    }

    @Override
    public void nextValidId(int orderId) {
        LOGGER.info("Next Valid Id: {}", orderId);
        baseOrderId_ = orderId;
    }

    // 账户和投资组合------------------------------------------------------------------------------------------/
    @Override
    public void updateAccountValue(String key, String value, String currency, String accountName) {
        System.out.println("UpdateAccountValue. Key: " + key + ", Value: " + value + ", Currency: " + currency + ", AccountName: " + accountName);
    }

    @Override
    public void updatePortfolio(Contract contract, double position,
                                double marketPrice, double marketValue, double averageCost,
                                double unrealizedPNL, double realizedPNL, String accountName) {
        System.out.println("UpdatePortfolio. " + contract.symbol() + ", " + contract.secType() + " @ " + contract.exchange()
                + ": Position: " + position + ", MarketPrice: " + marketPrice + ", MarketValue: " + marketValue + ", AverageCost: " + averageCost
                + ", UnrealisedPNL: " + unrealizedPNL + ", RealisedPNL: " + realizedPNL + ", AccountName: " + accountName);
    }

    @Override
    public void updateAccountTime(String timeStamp) {
        System.out.println("UpdateAccountTime. Time: " + timeStamp + "\n");
    }

    @Override
    public void accountDownloadEnd(String accountName) {
        System.out.println("Account download finished: " + accountName + "\n");
    }

    // 合约详细-----------------------------------------------------------------------------------------------/
    @Override
    public void contractDetails(int reqId, ContractDetails contractDetails) {
        System.out.println("ContractDetails. ReqId: [" + reqId + "] - [" + contractDetails.contract().symbol() + "], [" + contractDetails.contract().secType() + "], ConId: [" + contractDetails.contract().conid() + "] @ [" + contractDetails.contract().exchange() + "]");
    }

    @Override
    public void bondContractDetails(int reqId, ContractDetails contractDetails) {
        System.out.println("bondContractDetails");
    }

    @Override
    public void contractDetailsEnd(int reqId) {
        System.out.println("ContractDetailsEnd. " + reqId + "\n");
    }

    // 执行---------------------------------------------------------------------------------------------------/
    @Override
    public void execDetails(int reqId, Contract contract, Execution execution) {
        System.out.println("ExecDetails. " + reqId + " - [" + contract.symbol() + "], [" + contract.secType() + "], [" + contract.currency() + "], [" + execution.execId() + "], [" + execution.orderId() + "], [" + execution.shares() + "]");
    }

    @Override
    public void execDetailsEnd(int reqId) {
        System.out.println("ExecDetailsEnd. " + reqId + "\n");
    }

    @Override
    public void commissionReport(CommissionReport commissionReport) {
        System.out.println("CommissionReport. [" + commissionReport.m_execId + "] - [" + commissionReport.m_commission + "] [" + commissionReport.m_currency + "] RPNL [" + commissionReport.m_realizedPNL + "]");
    }

    // 市场深度-----------------------------------------------------------------------------------------------/
    @Override
    public void updateMktDepth(int tickerId, int position, int operation, int side, double price, int size) {
        System.out.println("UpdateMarketDepth. " + tickerId + " - Position: " + position + ", Operation: " + operation + ", Side: " + side + ", Price: " + price + ", Size: " + size + "");
    }

    @Override
    public void updateMktDepthL2(int tickerId, int position, String marketMaker, int operation, int side, double price, int size) {
        System.out.println("updateMktDepthL2");
    }

    // 新闻公告-----------------------------------------------------------------------------------------------/
    @Override
    public void updateNewsBulletin(int msgId, int msgType, String message, String origExchange) {
        System.out.println("News Bulletins. " + msgId + " - Type: " + msgType + ", Message: " + message + ", Exchange of Origin: " + origExchange + "\n");
    }

    // 金融顾问-----------------------------------------------------------------------------------------------/
    @Override
    public void managedAccounts(String accountsList) {
        System.out.println("Account list: " + accountsList);
    }

    @Override
    public void receiveFA(int faDataType, String xml) {
        System.out.println("Receing FA: " + faDataType + " - " + xml);
    }

    @Override
    public void accountSummary(int reqId, String account, String tag, String value, String currency) {
        System.out.println("Acct Summary. ReqId: " + reqId + ", Acct: " + account + ", Tag: " + tag + ", Value: " + value + ", Currency: " + currency);
    }

    @Override
    public void accountSummaryEnd(int reqId) {
        System.out.println("AccountSummaryEnd. Req Id: " + reqId + "\n");
    }

    @Override
    public void position(String account, Contract contract, double pos, double avgCost) {
        System.out.println("Position. " + account + " - Symbol: " + contract.symbol() + ", SecType: " + contract.secType() + ", Currency: " + contract.currency() + ", Position: " + pos + ", Avg cost: " + avgCost);
    }

    @Override
    public void positionEnd() {
        System.out.println("PositionEnd \n");
    }

    // 历史数据-----------------------------------------------------------------------------------------------/
    @Override
    public void historicalData(int reqId, String date, double open,
                               double high, double low, double close, int volume, int count,
                               double WAP, boolean hasGaps) {
        System.out.println("HistoricalData. " + reqId + " - Date: " + date + ", Open: " + open + ", High: " + high + ", Low: " + low + ", Close: " + close + ", Volume: " + volume + ", Count: " + count + ", WAP: " + WAP + ", HasGaps: " + hasGaps);
    }

    // 市场扫描仪---------------------------------------------------------------------------------------------/
    @Override
    public void scannerParameters(String xml) {
        System.out.println("ScannerParameters. " + xml + "\n");
    }

    @Override
    public void scannerData(int reqId, int rank,
                            ContractDetails contractDetails, String distance, String benchmark,
                            String projection, String legsStr) {
        System.out.println("ScannerData. " + reqId + " - Rank: " + rank + ", Symbol: " + contractDetails.contract().symbol() + ", SecType: " + contractDetails.contract().secType() + ", Currency: " + contractDetails.contract().currency()
                + ", Distance: " + distance + ", Benchmark: " + benchmark + ", Projection: " + projection + ", Legs String: " + legsStr);
    }

    @Override
    public void scannerDataEnd(int reqId) {
        System.out.println("ScannerDataEnd. " + reqId);
    }

    // 实时柱-------------------------------------------------------------------------------------------------/
    @Override
    public void realtimeBar(int reqId, long time, double open, double high,
                            double low, double close, long volume, double wap, int count) {
        System.out.println("RealTimeBars. " + reqId + " - Time: " + time + ", Open: " + open + ", High: " + high + ", Low: " + low + ", Close: " + close + ", Volume: " + volume + ", Count: " + count + ", WAP: " + wap);
    }

    // 基本面数据----------------------------------------------------------------------------------------------/
    @Override
    public void fundamentalData(int reqId, String data) {
        System.out.println("FundamentalData. ReqId: [" + reqId + "] - Data: [" + data + "]");
    }

    // 其他---------------------------------------------------------------------------------------------------/
    @Override
    public void deltaNeutralValidation(int reqId, DeltaNeutralContract underComp) {
        System.out.println("deltaNeutralValidation");
    }

    @Override
    public void verifyMessageAPI(String apiData) {
        System.out.println("verifyMessageAPI");
    }

    @Override
    public void verifyCompleted(boolean isSuccessful, String errorText) {
        System.out.println("verifyCompleted");
    }

    @Override
    public void verifyAndAuthMessageAPI(String apiData, String xyzChallange) {
        System.out.println("verifyAndAuthMessageAPI");
    }

    @Override
    public void verifyAndAuthCompleted(boolean isSuccessful, String errorText) {
        System.out.println("verifyAndAuthCompleted");
    }

    @Override
    public void displayGroupList(int reqId, String groups) {
        System.out.println("Display Group List. ReqId: " + reqId + ", Groups: " + groups + "\n");
    }

    @Override
    public void displayGroupUpdated(int reqId, String contractInfo) {
        System.out.println("Display Group Updated. ReqId: " + reqId + ", Contract info: " + contractInfo + "\n");
    }

    @Override
    public void positionMulti(int reqId, String account, String modelCode,
                              Contract contract, double pos, double avgCost) {
        System.out.println("Position Multi. Request: " + reqId + ", Account: " + account + ", ModelCode: " + modelCode + ", Symbol: " + contract.symbol() + ", SecType: " + contract.secType() + ", Currency: " + contract.currency() + ", Position: " + pos + ", Avg cost: " + avgCost + "\n");
    }

    @Override
    public void positionMultiEnd(int reqId) {
        System.out.println("Position Multi End. Request: " + reqId + "\n");
    }

    @Override
    public void accountUpdateMulti(int reqId, String account, String modelCode,
                                   String key, String value, String currency) {
        System.out.println("Account Update Multi. Request: " + reqId + ", Account: " + account + ", ModelCode: " + modelCode + ", Key: " + key + ", Value: " + value + ", Currency: " + currency + "\n");
    }

    @Override
    public void accountUpdateMultiEnd(int reqId) {
        System.out.println("Account Update Multi End. Request: " + reqId + "\n");
    }

    @Override
    public void securityDefinitionOptionalParameter(int reqId, String exchange,
                                                    int underlyingConId, String tradingClass, String multiplier,
                                                    Set expirations, Set strikes) {
        System.out.println("Security Definition Optional Parameter. Request: " + reqId + ", Trading Class: " + tradingClass + ", Multiplier: " + multiplier + " \n");
    }

    @Override
    public void securityDefinitionOptionalParameterEnd(int reqId) {

    }

    @Override
    public void softDollarTiers(int reqId, SoftDollarTier[] tiers) {
        for (SoftDollarTier tier : tiers) {
            System.out.print("tier: " + tier + ", ");
        }

        System.out.println();
    }

}
public class IBServer {

    private final EClientSocket client_;
    private final EReaderSignal signal_;
    private Jedis jedisClient_;
    private NetServer netServer_;
    private final IBEWrapper wrapper_ = new IBEWrapper();
    private final ExecutorService executorService_ = Executors.newFixedThreadPool(2);
    private final ScheduledExecutorService subscribeScheduler_ = new ScheduledThreadPoolExecutor(1);
    private static Logger LOGGER = LoggerFactory.getLogger(IBServer.class);

    public IBServer(String host, int port, int clientId) {
        client_ = wrapper_.getClientSocket();
        signal_ = wrapper_.getReaderSignal();
        client_.eConnect(host, port, clientId);
        redisConnect();
        startupCallbackThread();
    }

    public void setNetServer(NetServer netServer) {
        this.netServer_ = netServer;
    }

    // 连接redis服务器
    private void redisConnect() {
        String host = ConfigSingleton.getInstance().getParamsBean().getRedisHost();
        int port = ConfigSingleton.getInstance().getParamsBean().getRedisPort();
        jedisClient_ = new Jedis(host, port, 5000);
        String authResult = jedisClient_.auth(ConfigSingleton.getInstance().getParamsBean().getRedisPassword());
        if (!authResult.equals("OK")) {
            LOGGER.info("redis已连接服务器, 但验证失败: " + authResult);
            jedisClient_.close();
            System.exit(1);
        } else {
            LOGGER.info("redis已连接服务器, 并验证成功: " + authResult);
        }
    }

    // 行情数据发送线程
    public void startupMarketDataDispatcher() {
        executorService_.execute(new Runnable() {
            @Override
            public void run() {
                for (;;) {
                    try {
                        MarketDataBean marketData = wrapper_.getMarketDataQueue().take();
                        MarketDataWrapper dataWrapper = new MarketDataWrapper();
                        dataWrapper.CMDID = 2000;
                        dataWrapper.DATA = marketData;
                        String message = JSON.toJSONString(dataWrapper);

                        // 分发至长连接客户端
                        netServer_.dispatcher(message);

                        // 分发至redis服务器
                        try {
                            jedisClient_.publish(marketData.getProductName(), message);
                        } catch (JedisConnectionException e) {
                            LOGGER.error("redis message: {} {}", marketData.getProductName(), message);
                            LOGGER.error("redis发布命令异常, 开始重连, 异常信息: " + e.getMessage());
                            jedisClient_.close();
                            redisConnect();
                        }
                    } catch(Exception e) {
                        LOGGER.error("行情数据发送出错, 异常信息: " + e.getMessage());
                    }
                }
            }
        });
    }

    // 启动回调线程
    private void startupCallbackThread() {
        final EReader reader = new EReader(client_, signal_);
        reader.start();
        executorService_.execute(new Runnable() {
            @Override
            public void run() {
                while (client_.isConnected()) {
                    signal_.waitForSignal();
                    try {
                        reader.processMsgs();
                    } catch (Exception e) {
                        LOGGER.error("Exception: " + e.getMessage());
                    }
                }
            }
        });
    }

    // 订阅股票及期货市场行情
    public void subscribeMarketData(int delaySeconds) {
        subscribeScheduler_.schedule(new Runnable() {
            @Override
            public void run() {
                List contracts = ConfigSingleton.getInstance().getContracts();
                for (ContractBean bean : contracts) {
                    Contract contract = new Contract();
                    contract.symbol(bean.getSymbol());// 品种
                    contract.secType(bean.getSecType());// 证券类型
                    contract.currency(bean.getCurrency());// 币种
                    contract.exchange(bean.getExchange());// 交易所
                    if (bean.getSecType().equals(Constants.FUTURE)) {
                        contract.lastTradeDateOrContractMonth(bean.getContractMonth());// 合约
                    }

                    client_.reqMktData(bean.getTickerId(), contract, ""/*"100,101,105,106,165,233,236,258"*/, false, null);
                    LOGGER.info("订阅合约, tickerId: {}, symbol: {}{}, secType: {}, exchange: {}",
                            bean.getTickerId(), bean.getSymbol(), bean.getContractMonth(), bean.getSecType(), bean.getExchange());
                }
            }
        }, delaySeconds, TimeUnit.SECONDS);
    }

    // 释放资源
    public void release() {
        executorService_.shutdown();
        subscribeScheduler_.shutdown();
        jedisClient_.close();
    }
	
}
       上述代码中,startupCallbackThread()就是创建回调线程的方法,和行情接收有关的回调函数tickPrice()及tickSize()接收数据后,会将数据立即保存进队列,相当于一个生产者。回调函数里面尽可能不要做复杂业务逻辑处理,防止阻塞线程。startupMarketDataDispatcher()主要是行情数据分发,即从上面队列中取出数据,分发至不同客服端,相当于一个消费者。本文示例中,主要面向长连接客户端及redis服务器分发数据,如何高效的分发数据,可以参考前面文章。subscribeMarketData()方法主要实现了股票及期货市场行情订阅功能,主要调用盈透reqMktData()接口,还是很简单的。
       总体上,盈透接口逻辑关系理清楚了,开发就简单很多,下面是盈透开发相关文档链接:
       https://www.interactivebrokers.com.hk/cn/software/api/api.htm
       http://interactivebrokers.github.io/tws-api/index.html#gsc.tab=0
       由于盈透接口版本众多,上面的文档还是要结合自己的开发环境来看。



你可能感兴趣的:(程序化交易,盈透证券,期货,股票)