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

Java tutorial

Introduction

Here is the source code for ch.algotrader.service.ib.IBNativeReferenceDataServiceImpl.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.text.DecimalFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import org.apache.commons.lang.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ib.client.Contract;
import com.ib.client.ContractDetails;

import ch.algotrader.adapter.IdGenerator;
import ch.algotrader.adapter.ib.IBPendingRequests;
import ch.algotrader.adapter.ib.IBSession;
import ch.algotrader.concurrent.Promise;
import ch.algotrader.concurrent.PromiseImpl;
import ch.algotrader.config.CommonConfig;
import ch.algotrader.config.IBConfig;
import ch.algotrader.dao.security.FutureDao;
import ch.algotrader.dao.security.OptionDao;
import ch.algotrader.dao.security.SecurityFamilyDao;
import ch.algotrader.dao.security.StockDao;
import ch.algotrader.entity.security.Future;
import ch.algotrader.entity.security.FutureFamily;
import ch.algotrader.entity.security.Option;
import ch.algotrader.entity.security.OptionFamily;
import ch.algotrader.entity.security.SecurityFamily;
import ch.algotrader.entity.security.Stock;
import ch.algotrader.enumeration.Broker;
import ch.algotrader.enumeration.OptionType;
import ch.algotrader.future.FutureSymbol;
import ch.algotrader.option.OptionSymbol;
import ch.algotrader.service.ExternalServiceException;
import ch.algotrader.service.ReferenceDataService;
import ch.algotrader.service.ServiceException;
import ch.algotrader.util.DateTimeLegacy;
import ch.algotrader.util.RoundUtil;

/**
 * @author <a href="mailto:aflury@algotrader.ch">Andy Flury</a>
 */
@Transactional(propagation = Propagation.SUPPORTS)
public class IBNativeReferenceDataServiceImpl implements ReferenceDataService {

    private static final Logger LOGGER = LogManager.getLogger(IBNativeReferenceDataServiceImpl.class);
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final DateTimeFormatter YEAR_MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyyMM");
    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#######");

    private final CommonConfig commonConfig;

    private final IBSession iBSession;

    private final IBConfig ibConfig;

    private final IBPendingRequests pendingRequests;

    private final IdGenerator requestIdGenerator;

    private final OptionDao optionDao;

    private final FutureDao futureDao;

    private final SecurityFamilyDao securityFamilyDao;

    private final StockDao stockDao;

    public IBNativeReferenceDataServiceImpl(final CommonConfig commonConfig, final IBSession iBSession,
            final IBConfig ibConfig, final IBPendingRequests pendingRequests, final IdGenerator requestIdGenerator,
            final OptionDao optionDao, final FutureDao futureDao, final SecurityFamilyDao securityFamilyDao,
            final StockDao stockDao) {

        Validate.notNull(commonConfig, "CommonConfig is null");
        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(optionDao, "OptionDao is null");
        Validate.notNull(futureDao, "FutureDao is null");
        Validate.notNull(securityFamilyDao, "SecurityFamilyDao is null");
        Validate.notNull(stockDao, "StockDao is null");

        this.commonConfig = commonConfig;
        this.iBSession = iBSession;
        this.ibConfig = ibConfig;
        this.pendingRequests = pendingRequests;
        this.requestIdGenerator = requestIdGenerator;
        this.optionDao = optionDao;
        this.futureDao = futureDao;
        this.securityFamilyDao = securityFamilyDao;
        this.stockDao = stockDao;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void retrieve(long securityFamilyId) {

        SecurityFamily securityFamily = this.securityFamilyDao.get(securityFamilyId);

        int requestId = (int) this.requestIdGenerator.generateId();
        Contract contract = new Contract();

        contract.m_symbol = securityFamily.getSymbolRoot(Broker.IB.name());

        contract.m_currency = securityFamily.getCurrency().toString();

        contract.m_exchange = securityFamily.getExchange().getIbCode();

        contract.m_multiplier = DECIMAL_FORMAT.format(securityFamily.getContractSize(Broker.IB.name()));

        if (securityFamily instanceof OptionFamily) {
            contract.m_secType = "OPT";
        } else if (securityFamily instanceof FutureFamily) {
            contract.m_secType = "FUT";
        } else {
            throw new ServiceException("illegal securityFamily type");
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Request contract details; request id = {}; symbol = {}", requestId, contract.m_symbol);
        }

        PromiseImpl<List<ContractDetails>> promise = new PromiseImpl<>(null);
        this.pendingRequests.addContractDetailRequest(requestId, promise);
        this.iBSession.reqContractDetails(requestId, contract);

        Set<ContractDetails> contractDetailsSet = getContractDetailsBlocking(promise);
        if (securityFamily instanceof OptionFamily) {
            retrieveOptions((OptionFamily) securityFamily, contractDetailsSet);
        } else if (securityFamily instanceof FutureFamily) {
            retrieveFutures((FutureFamily) securityFamily, contractDetailsSet);
        } else {
            throw new ServiceException("illegal securityFamily type");
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void retrieveStocks(long securityFamilyId, String symbol) {

        Validate.notEmpty(symbol, "Symbol is empty");

        SecurityFamily securityFamily = this.securityFamilyDao.get(securityFamilyId);

        int requestId = (int) this.requestIdGenerator.generateId();
        Contract contract = new Contract();

        contract.m_symbol = symbol;

        contract.m_currency = securityFamily.getCurrency().toString();

        contract.m_exchange = securityFamily.getExchange().getIbCode();

        contract.m_secType = "STK";

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Request stock details; request id = {}; symbol = {}", requestId, contract.m_symbol);
        }

        PromiseImpl<List<ContractDetails>> promise = new PromiseImpl<>(null);
        this.pendingRequests.addContractDetailRequest(requestId, promise);
        this.iBSession.reqContractDetails(requestId, contract);

        Set<ContractDetails> contractDetailsSet = getContractDetailsBlocking(promise);
        retrieveStocks(securityFamily, contractDetailsSet);

    }

    private Set<ContractDetails> getContractDetailsBlocking(final Promise<List<ContractDetails>> promise) {
        try {
            int requestTimeout = this.ibConfig.getRequestTimeout();
            return new HashSet<>(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());
        }
    }

    private void retrieveOptions(OptionFamily securityFamily, Set<ContractDetails> contractDetailsSet) {

        // get all current options
        List<Option> allOptions = this.optionDao.findBySecurityFamily(securityFamily.getId());
        Map<String, Option> mapByConid = allOptions.stream().filter(e -> e.getConid() != null)
                .collect(Collectors.toMap(e -> e.getConid(), e -> e));
        Map<String, Option> mapBySymbol = allOptions.stream().collect(Collectors.toMap(e -> e.getSymbol(), e -> e));

        for (ContractDetails contractDetails : contractDetailsSet) {

            Contract contract = contractDetails.m_summary;
            String conid = String.valueOf(contract.m_conId);
            if (!mapByConid.containsKey(conid)) {

                OptionType type = "C".equals(contract.m_right) ? OptionType.CALL : OptionType.PUT;
                BigDecimal strike = RoundUtil.getBigDecimal(contract.m_strike,
                        securityFamily.getScale(Broker.IB.name()));
                LocalDate expirationDate = DATE_FORMAT.parse(contract.m_expiry, LocalDate::from);
                String symbol = OptionSymbol.getSymbol(securityFamily, expirationDate, type, strike,
                        this.commonConfig.getOptionSymbolPattern());
                if (!mapBySymbol.containsKey(symbol)) {
                    Option option = Option.Factory.newInstance();

                    final String isin = OptionSymbol.getIsin(securityFamily, expirationDate, type, strike);
                    String ric = OptionSymbol.getRic(securityFamily, expirationDate, type, strike);

                    option.setSymbol(symbol);
                    option.setIsin(isin);
                    option.setRic(ric);
                    option.setConid(conid);
                    option.setOptionType(type);
                    option.setStrike(strike);
                    option.setExpiration(DateTimeLegacy.toLocalDate(expirationDate));
                    option.setSecurityFamily(securityFamily);
                    option.setUnderlying(securityFamily.getUnderlying());

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Creating option based on IB definition: {} {} {} {}", contract.m_symbol,
                                contract.m_right, contract.m_expiry, contract.m_strike);
                    }
                    this.optionDao.save(option);
                }
            }
        }
    }

    private void retrieveFutures(FutureFamily securityFamily, Set<ContractDetails> contractDetailsSet) {

        // get all current futures
        List<Future> allFutures = this.futureDao.findBySecurityFamily(securityFamily.getId());
        Map<String, Future> mapByConid = allFutures.stream().filter(e -> e.getConid() != null)
                .collect(Collectors.toMap(e -> e.getConid(), e -> e));
        Map<String, Future> mapBySymbol = allFutures.stream().collect(Collectors.toMap(e -> e.getSymbol(), e -> e));

        for (ContractDetails contractDetails : contractDetailsSet) {

            Contract contract = contractDetails.m_summary;
            String conid = String.valueOf(contract.m_conId);
            if (!mapByConid.containsKey(conid)) {

                LocalDate expiration = DATE_FORMAT.parse(contract.m_expiry, LocalDate::from);
                String contractMonthString = contractDetails.m_contractMonth;
                LocalDate contractMonth = parseYearMonth(contractMonthString);

                String symbol = FutureSymbol.getSymbol(securityFamily, contractMonth,
                        this.commonConfig.getFutureSymbolPattern());
                if (!mapBySymbol.containsKey(symbol)) {
                    Future future = Future.Factory.newInstance();

                    final String isin = FutureSymbol.getIsin(securityFamily, contractMonth);
                    String ric = FutureSymbol.getRic(securityFamily, contractMonth);

                    future.setSymbol(symbol);
                    future.setIsin(isin);
                    future.setRic(ric);
                    future.setConid(conid);
                    future.setExpiration(DateTimeLegacy.toLocalDate(expiration));
                    future.setMonthYear(contractMonthString);
                    future.setSecurityFamily(securityFamily);
                    future.setUnderlying(securityFamily.getUnderlying());

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Creating future based on IB definition: {} {}", contract.m_symbol,
                                contractDetails.m_contractMonth);
                    }
                    this.futureDao.save(future);
                }
            }
        }
    }

    private void retrieveStocks(SecurityFamily securityFamily, Set<ContractDetails> contractDetailsSet) {

        // get all current stocks
        Map<String, Stock> existingStocks = new HashMap<>();

        for (Stock stock : this.stockDao.findStocksBySecurityFamily(securityFamily.getId())) {
            existingStocks.put(stock.getSymbol(), stock);
        }

        // contractDetailsList most likely only contains one entry
        Set<Stock> newStocks = new HashSet<>();
        for (ContractDetails contractDetails : contractDetailsSet) {

            Contract contract = contractDetails.m_summary;

            String symbol = contract.m_symbol;
            String conid = String.valueOf(contract.m_conId);
            String description = contractDetails.m_longName;

            // update stocks (conid) that already exist
            if (existingStocks.containsKey(symbol)) {

                Stock stock = existingStocks.get(symbol);
                stock.setConid(conid);

            } else {

                Stock stock = Stock.Factory.newInstance();
                stock.setSymbol(symbol);
                stock.setDescription(description);
                stock.setConid(conid);
                stock.setSecurityFamily(securityFamily);
                stock.setUnderlying(securityFamily.getUnderlying());

                newStocks.add(stock);
            }
        }

        this.stockDao.saveAll(newStocks);
    }

    private LocalDate parseYearMonth(final CharSequence s) {
        if (s == null || s.length() == 0) {
            return null;
        }
        try {
            TemporalAccessor parsed = YEAR_MONTH_FORMAT.parse(s);
            int year = parsed.get(ChronoField.YEAR);
            int month = parsed.get(ChronoField.MONTH_OF_YEAR);
            return LocalDate.of(year, month, 1);
        } catch (DateTimeParseException ex) {
            throw new ServiceException("Invalid year/month format: " + s);
        }
    }

}