ch.algotrader.service.ib.IBNativeHistoricalDataServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.algotrader.service.ib.IBNativeHistoricalDataServiceImpl.java

Source

/***********************************************************************************
 * AlgoTrader Enterprise Trading Framework
 *
 * Copyright (C) 2015 AlgoTrader GmbH - All rights reserved
 *
 * All information contained herein is, and remains the property of AlgoTrader GmbH.
 * The intellectual and technical concepts contained herein are proprietary to
 * AlgoTrader GmbH. Modification, translation, reverse engineering, decompilation,
 * disassembly or reproduction of this material is strictly forbidden unless prior
 * written permission is obtained from AlgoTrader GmbH
 *
 * Fur detailed terms and conditions consult the file LICENSE.txt or contact
 *
 * AlgoTrader GmbH
 * Aeschstrasse 6
 * 8834 Schindellegi
 ***********************************************************************************/
package ch.algotrader.service.ib;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.ib.client.Contract;
import com.ib.client.TagValue;

import ch.algotrader.adapter.IdGenerator;
import ch.algotrader.adapter.ib.IBPendingRequests;
import ch.algotrader.adapter.ib.IBSession;
import ch.algotrader.adapter.ib.IBUtil;
import ch.algotrader.concurrent.Promise;
import ch.algotrader.concurrent.PromiseImpl;
import ch.algotrader.config.IBConfig;
import ch.algotrader.dao.marketData.BarDao;
import ch.algotrader.dao.security.SecurityDao;
import ch.algotrader.entity.marketData.Bar;
import ch.algotrader.entity.marketData.Tick;
import ch.algotrader.entity.security.Security;
import ch.algotrader.enumeration.Broker;
import ch.algotrader.enumeration.Duration;
import ch.algotrader.enumeration.FeedType;
import ch.algotrader.enumeration.MarketDataEventType;
import ch.algotrader.enumeration.TimePeriod;
import ch.algotrader.service.ExternalServiceException;
import ch.algotrader.service.HistoricalDataService;
import ch.algotrader.service.HistoricalDataServiceImpl;
import ch.algotrader.service.ServiceException;
import ch.algotrader.util.DateTimeLegacy;

/**
 * See <a href="http://www.interactivebrokers.com/php/apiUsersGuide/apiguide/api/historical_data_limitations.htm">Historical Data Limitations</a> for further details.
 *
 * @author <a href="mailto:aflury@algotrader.ch">Andy Flury</a>
 */
public class IBNativeHistoricalDataServiceImpl extends HistoricalDataServiceImpl implements HistoricalDataService {

    private static final Logger LOGGER = LogManager.getLogger(IBNativeHistoricalDataServiceImpl.class);
    private static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyyMMdd  HH:mm:ss");

    // The following conditions can cause a pacing violation:
    // (1) Making identical historical data requests within 15 seconds;
    // (2) Making six or more historical data requests for the same Contract, Exchange and Tick Type within two seconds.
    // (3) Making more than 60 historical data requests in any ten-minute period.

    // Safest delay between consecutive invocations
    private static final int pacingMillis = 15 * 1000;

    private final AtomicLong lastTimeStamp;
    private final IBSession iBSession;
    private final IBConfig iBConfig;
    private final IBPendingRequests pendingRequests;
    private final IdGenerator requestIdGenerator;
    private final SecurityDao securityDao;

    public IBNativeHistoricalDataServiceImpl(final IBSession iBSession, final IBConfig iBConfig,
            final IBPendingRequests pendingRequests, final IdGenerator requestIdGenerator,
            final SecurityDao securityDao, final BarDao barDao) {

        super(barDao);

        Validate.notNull(iBSession, "IBSession is null");
        Validate.notNull(iBConfig, "IBConfig is null");
        Validate.notNull(pendingRequests, "IBPendingRequests is null");
        Validate.notNull(requestIdGenerator, "IdGenerator is null");
        Validate.notNull(securityDao, "SecurityDao is null");

        this.lastTimeStamp = new AtomicLong(0L);
        this.iBSession = iBSession;
        this.iBConfig = iBConfig;
        this.pendingRequests = pendingRequests;
        this.requestIdGenerator = requestIdGenerator;
        this.securityDao = securityDao;
    }

    @Override
    public synchronized List<Bar> getHistoricalBars(long securityId, Date endDate, int timePeriodLength,
            TimePeriod timePeriod, Duration barSize, MarketDataEventType marketDataEventType,
            Map<String, String> properties) {

        Validate.notNull(endDate, "End date is null");
        Validate.notNull(timePeriod, "Time period is null");
        Validate.notNull(barSize, "Bar size is null");
        Validate.notNull(marketDataEventType, "Bar type is null");

        Security security = this.securityDao.get(securityId);
        if (security == null) {
            throw new ServiceException("security was not found: " + securityId);
        }

        int scale = security.getSecurityFamily().getScale(Broker.IB.name());
        Contract contract = IBUtil.getContract(security);
        int requestId = (int) this.requestIdGenerator.generateId();
        String dateString = dateTimeFormat.format(DateTimeLegacy.toLocalDateTime(endDate));

        String durationString = timePeriodLength + " ";
        switch (timePeriod) {
        case SEC:
            durationString += "S";
            break;
        case DAY:
            durationString += "D";
            break;
        case WEEK:
            durationString += "W";
            break;
        case MONTH:
            durationString += "M";
            break;
        case YEAR:
            durationString += "Y";
            break;
        default:
            throw new ServiceException("timePeriod is not allowed " + timePeriod);
        }

        String[] barSizeName = barSize.name().split("_");

        String barSizeString = barSizeName[1] + " ";
        switch (barSizeName[0]) {
        case "SEC":
            barSizeString += "sec";
            break;
        case "MIN":
            barSizeString += "min";
            break;
        case "HOUR":
            barSizeString += "hour";
            break;
        case "DAY":
            barSizeString += "day";
            break;
        default:
            throw new ServiceException("barSize is not allowed " + barSize);
        }

        if (Integer.parseInt(barSizeName[1]) > 1) {
            barSizeString += "s";
        }

        String marketDataEventTypeString;
        switch (marketDataEventType) {
        case TRADES:
            marketDataEventTypeString = "TRADES";
            break;
        case MIDPOINT:
            marketDataEventTypeString = "MIDPOINT";
            break;
        case BID:
            marketDataEventTypeString = "BID";
            break;
        case ASK:
            marketDataEventTypeString = "ASK";
            break;
        case BID_ASK:
            marketDataEventTypeString = "BID_ASK";
            break;
        default:
            throw new ServiceException("unsupported marketDataEventType " + marketDataEventType);
        }

        // avoid pacing violations
        long waitMillis = (this.lastTimeStamp.get() + pacingMillis) - System.currentTimeMillis();
        if (waitMillis > 0) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("waiting {} seconds until next historical data request",
                        new BigDecimal(((double) waitMillis) / 1000).setScale(2, BigDecimal.ROUND_HALF_EVEN));
            }
            try {
                Thread.sleep(waitMillis);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
                throw new ServiceException(ex);
            }
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                    "Request historic data; request id = {}; security id = {}; conId = {}; date = {}; duration = {}; bar size = {}",
                    requestId, securityId, contract.m_conId, dateString, durationString, barSizeString);
        }

        PromiseImpl<List<Bar>> promise = new PromiseImpl<>(null);
        this.pendingRequests.addHistoricDataRequest(requestId, promise);
        this.iBSession.reqHistoricalData(requestId, contract, dateString, durationString, barSizeString,
                marketDataEventTypeString, this.iBConfig.useRTH() ? 1 : 0, 1, Collections.<TagValue>emptyList());
        List<Bar> bars = getBarsBlocking(promise);

        this.lastTimeStamp.set(System.currentTimeMillis());

        // set & update fields
        for (Bar bar : bars) {
            bar.setSecurity(security);
            bar.setFeedType(FeedType.IB.name());
            bar.getOpen().setScale(scale, RoundingMode.HALF_UP);
            bar.getHigh().setScale(scale, RoundingMode.HALF_UP);
            bar.getLow().setScale(scale, RoundingMode.HALF_UP);
            bar.getClose().setScale(scale, RoundingMode.HALF_UP);
            bar.getVwap().setScale(scale, RoundingMode.HALF_UP);
            bar.setBarSize(barSize);
        }
        return bars;
    }

    @Override
    public List<Tick> getHistoricalTicks(long securityId, Date endDate, int timePeriodLength, TimePeriod timePeriod,
            MarketDataEventType marketDataEventType, Map<String, String> properties) {

        throw new UnsupportedOperationException("historical ticks not supported for IB");
    }

    private List<Bar> getBarsBlocking(final Promise<List<Bar>> promise) {
        try {
            int requestTimeout = this.iBConfig.getRequestTimeout();
            return promise.get(requestTimeout, TimeUnit.SECONDS);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw new ServiceException(ex);
        } catch (TimeoutException ex) {
            throw new ExternalServiceException("Service request timeout");
        } catch (ExecutionException ex) {
            throw IBNativeSupport.rethrow(ex.getCause());
        }
    }

}