Package com.quantcomponents.ib

Source Code of com.quantcomponents.ib.IBAdapter$PositionClient$PositionPriceUpdater

/*******************************************************************************
* Copyright (c) 2013 Luigi Sgro. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Luigi Sgro - initial API and implementation
******************************************************************************/
package com.quantcomponents.ib;

import java.net.ConnectException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ib.client.Contract;
import com.ib.client.ContractDetails;
import com.ib.client.EWrapperMsgGenerator;
import com.ib.client.Execution;
import com.ib.client.ExecutionFilter;
import com.ib.client.Order;
import com.ib.client.TickType;
import com.quantcomponents.algo.IExecutionService;
import com.quantcomponents.algo.IOrder;
import com.quantcomponents.algo.IOrderStatusListener;
import com.quantcomponents.algo.IPosition;
import com.quantcomponents.algo.IPositionListener;
import com.quantcomponents.algo.ITrade;
import com.quantcomponents.algo.PositionBean;
import com.quantcomponents.algo.TradeBean;
import com.quantcomponents.core.exceptions.NoDataReturnedException;
import com.quantcomponents.core.exceptions.PacingViolationException;
import com.quantcomponents.core.exceptions.RequestFailedException;
import com.quantcomponents.core.model.BarSize;
import com.quantcomponents.core.model.DataType;
import com.quantcomponents.core.model.IContract;
import com.quantcomponents.core.model.ITaskMonitor;
import com.quantcomponents.core.model.SecurityType;
import com.quantcomponents.core.model.TimePeriod;
import com.quantcomponents.core.model.beans.ContractBean;
import com.quantcomponents.core.model.beans.ImmutableContractBean;
import com.quantcomponents.marketdata.IOHLCPoint;
import com.quantcomponents.marketdata.IRealTimeMarketDataProvider;
import com.quantcomponents.marketdata.ITickPoint;
import com.quantcomponents.marketdata.OHLCPoint;
import com.quantcomponents.marketdata.TickPoint;

public class IBAdapter implements IRealTimeMarketDataProvider, IExecutionService {
  private static final Logger logger = Logger.getLogger(IBAdapter.class.getName());
 
  public static final String[] MKT_DATA_LINES = { "Less than 499","500 - 749","750 - 999","more than 999" };
 
  public static final long DEFAULT_MIN_ELAPSED_BETWEEN_HIST_DATA_REQ_BATCHES_MS = 10000;
  public static final long HIST_DATA_REQUEST_SLEEP_QUANTUM = 500;
  public static final long CONNECTION_SLEEP_QUANTUM = 100;
  public static final long HANDLE_REQUEST_SLEEP_QUANTUM = 100;
  public static final int DEFAULT_NUM_OF_REQS_IN_HIST_DATA_REQ_BATCHES = 5;
  public static final int DEFAULT_MIN_HIST_DATA_PERIOD_MS = 60000;
  public static final int DEFAULT_MAX_BARS_PER_REQUEST = 1999;
  public static final int DEFAULT_MAX_WAIT_TO_CONNECT_MS = 10000;
  public static final int DEFAULT_MAX_WAIT_RESPONSE_MS = 30000;
 
  public static final String DEFAULT_HOST = "";
  public static final Integer DEFAULT_PORT = 7496;
  public static final Integer DEFAULT_CLIENT_ID = 0;
  public static final Integer DEFAULT_START_REQ_ID = 0;
  public static final Integer DEFAULT_MKT_DATA_LINES_INDEX = 0;

  private static final Pattern HIST_DATA_LAST_UPDATE_MESSAGE = Pattern.compile("finished-\\d{8}\\s+\\d\\d:\\d\\d:\\d\\d-(\\d{8}\\s+\\d\\d:\\d\\d:\\d\\d)");
 
  // a separate thread for clients to process asynchronously IB events
  // due to IB client single thread architecture, a call to IB from an event handler blocks and times-out
  // single thread to preserve sequence of events
  private final ExecutorService threadPool = Executors.newSingleThreadExecutor();
 
  private final String host;
  private final int port;
  private final int clientId;
  private final int noMktDataLinesKey;
  private final String accountId;
 
  private long minElapsedBetweenHistDataReqBatches = DEFAULT_MIN_ELAPSED_BETWEEN_HIST_DATA_REQ_BATCHES_MS;
  private int numOfReqsInHistDataReqBatches = DEFAULT_NUM_OF_REQS_IN_HIST_DATA_REQ_BATCHES;
  private int minHistDataPeriodMs = DEFAULT_MIN_HIST_DATA_PERIOD_MS;
  private int maxBarPerRequest = DEFAULT_MAX_BARS_PER_REQUEST;
  private int maxWaitToConnectMs = DEFAULT_MAX_WAIT_TO_CONNECT_MS;
  private int maxWaitResponseMs = DEFAULT_MAX_WAIT_RESPONSE_MS;
 
  private volatile IBConnection connectionInstance;
  private final AtomicInteger requestSequence;
  private final IBConstantTranslator constantTranslator;
 
  private Map<String, Set<IRealTimeDataListener>> realTimeDataListeners = new ConcurrentHashMap<String, Set<IRealTimeDataListener>>();
  private Map<String, Integer> realTimeDataRequestIds = new ConcurrentHashMap<String, Integer>();
 
  private Map<String, Set<ITickListener>> tickListeners = new ConcurrentHashMap<String, Set<ITickListener>>();
  private Map<String, Integer> tickRequestIds = new ConcurrentHashMap<String, Integer>();
 
  private Set<IOrderStatusListener> orderStatusListeners = new CopyOnWriteArraySet<IOrderStatusListener>();
  private Set<IPositionListener> positionListeners = new CopyOnWriteArraySet<IPositionListener>();
 
  private Map<Integer, IOrder> sentOrders = new ConcurrentHashMap<Integer, IOrder>();
 
  private volatile int nextOrderId = -1;
 
  private OrderStatusClient orderStatusClient;
  private PositionClient positionClient;
 
  private class OrderStatusClient extends IBClient {
    @Override
    public void orderStatus(int orderId, final String status, final int filled, final int remaining, final double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld) {
      final String orderIdStr = Integer.toString(orderId);
      if ("Submitted".equals(status) || "PreSubmitted".equals(status)) {
        threadPool.execute(new Runnable() {
          @Override
          public void run() {
            for (IOrderStatusListener listener : orderStatusListeners) {
              try {
                listener.onOrderSubmitted(orderIdStr, true);
              } catch (Throwable t) {
                logger.log(Level.SEVERE, "Exception during order status dispatch", t);
              }
            }
          }});
      } else  if ("Filled".equals(status)) {
        threadPool.execute(new Runnable() {
          @Override
          public void run() {
            for (IOrderStatusListener listener : orderStatusListeners) {
              try {
                listener.onOrderFilled(orderIdStr, filled, remaining == 0 ? true : false , avgFillPrice);
              } catch (Throwable t) {
                logger.log(Level.SEVERE, "Exception during order status dispatch", t);
              }
            }
          }});
      } else if ("Cancelled".equals(status)) {
        threadPool.execute(new Runnable() {
          @Override
          public void run() {
            for (IOrderStatusListener listener : orderStatusListeners) {
              try {
                listener.onOrderCancelled(orderIdStr);
              } catch (Throwable t) {
                logger.log(Level.SEVERE, "Exception during order status dispatch", t);
              }
            }
          }});
      } else {
        threadPool.execute(new Runnable() {
          @Override
          public void run() {
            for (IOrderStatusListener listener : orderStatusListeners) {
              try {
                listener.onOrderStatus(orderIdStr, status);
              } catch (Throwable t) {
                logger.log(Level.SEVERE, "Exception during order status dispatch", t);
              }
            }
          }});
      }
    }
   
    @Override
    protected void doSendRequest() {
      throw new UnsupportedOperationException();
    }
  }

  private class PositionClient extends IBClient {
   
    private class PositionPriceUpdater {
      PositionBean position;
      ITickListener tickListener;
    }
    final Map<IContract, PositionPriceUpdater> positionUpdaters = new ConcurrentHashMap<IContract, PositionPriceUpdater>();
   
    @Override
    public void updatePortfolio(Contract iBContract, int positionAmt, double marketPrice, double marketValue, double averageCost, double unrealizedPNL, double realizedPNL, String accountName) {
      if (accountName.equals(accountId)) {
        final IContract contract = ContractBean.copyOf(new IBContract(iBContract, constantTranslator));
        final PositionBean position = new PositionBean(new Date(), positionAmt, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL);
       
        if (!positionUpdaters.containsKey(contract)) { // setup price listener for that contract: since IB sends too few updates, we update automatically based on price changes
          PositionPriceUpdater positionPriceUpdater = new PositionPriceUpdater();
          positionPriceUpdater.position = position;
          final ITickListener tickListener = new ITickListener() {
            @Override
            public void onTick(ITickPoint tick) {
              if (tick.getDataType() == DataType.TRADES || tick.getDataType() == DataType.MIDPOINT && SecurityType.CASH==contract.getSecurityType()) {
                PositionPriceUpdater pu = positionUpdaters.get(contract);
                if (pu != null) {
                  synchronized (pu) { // update position with current price
                    pu.position.setMarketPrice(tick.getValue());
                    pu.position.setMarketValue(tick.getValue() * pu.position.getSignedAmount());
                    pu.position.setUnrealizedPnl((tick.getValue() - pu.position.getAveragePrice()) * pu.position.getSignedAmount());
                  }
                  deliverPosition(contract, pu.position);
                }
              }
            }};
          positionPriceUpdater.tickListener = tickListener;
          positionUpdaters.put(contract, positionPriceUpdater);
          threadPool.execute(new Runnable() {
            @Override
            public void run() {
              try {
                startTicks(contract, tickListener);
              } catch (Throwable e) {
                logger.log(Level.SEVERE, "Exception while setting up price listener for position update", e);
              }
            }
          });
        } else {
          PositionPriceUpdater pu = positionUpdaters.get(contract);
          if (pu != null) {
            synchronized(pu) { // renew position with original values from IB
              pu.position = position;
            }
          }
        }
       
        deliverPosition(contract, position);
      }
    }
   
    public void stopPositionPriceUpdates() throws ConnectException {
      for (Map.Entry<IContract, PositionPriceUpdater> entry : positionUpdaters.entrySet()) {
        stopTicks(entry.getKey(), entry.getValue().tickListener);
      }
      positionUpdaters.clear();
    }
   
    private void deliverPosition(final IContract contract, final IPosition position) {
      threadPool.execute(new Runnable() {
        @Override
        public void run() {
          for (IPositionListener listener : positionListeners) {
            try {
              listener.onPositionUpdate(contract, PositionBean.copyOf(position));
            } catch (Throwable t) {
              logger.log(Level.SEVERE, "Exception during position update dispatch", t);
            }
          }
        }});
    }
   
    @Override
    protected void doSendRequest() {
      throw new UnsupportedOperationException();
    }
  };

  public IBAdapter(String host, int port, int clientId, int firstRequestNo, int noMktDataLinesKey, String accountId) {
    this.host = host;
    this.port = port;
    this.clientId = clientId;
    this.noMktDataLinesKey = noMktDataLinesKey;
    this.accountId = accountId;
    requestSequence = new AtomicInteger(firstRequestNo);
    constantTranslator = new IBConstantTranslator();
  }

  public IBConstantTranslator getConstantTranslator() {
    return constantTranslator;
  }

  private void connect(ITaskMonitor taskMonitor) throws ConnectException {
    if (connectionInstance == null || !connectionInstance.isConnected()) {
      Thread connectionThread = new Thread(new Runnable() {
        @Override
        public void run() {
          if (connectionInstance == null) {
            connectionInstance = new IBConnection(host, port, clientId);
          }
        }});
      connectionThread.start();
      long waited = 0;
      while ((taskMonitor == null || !taskMonitor.isCancelled()) && (connectionInstance == null || !connectionInstance.isConnected()) && waited < maxWaitToConnectMs) {
        try {
          Thread.sleep(CONNECTION_SLEEP_QUANTUM);
        } catch (InterruptedException e) {
          logger.log(Level.WARNING, "Exception while waiting for connection to IB", e);
        }
        waited += CONNECTION_SLEEP_QUANTUM;
      }
      if (taskMonitor != null && taskMonitor.isCancelled()) {
        throw new ConnectException("Connection cancelled by user after " + waited + "ms");
      }
      if (connectionInstance == null || !connectionInstance.isConnected()) {
        throw new ConnectException("Connection failed after " + waited + "ms");
      }
    }
    if (taskMonitor != null) {
      taskMonitor.done();
    }
  }

  private synchronized IBConnection getConnection() throws ConnectException {
    connect(null);
    return connectionInstance;
  }

  public synchronized boolean isConnected() {
    if (connectionInstance != null && connectionInstance.isConnected())
      return true;
    else
      return false;
  }

  public synchronized void disconnect() {
    if (connectionInstance != null)
      connectionInstance.close();
  }

  @Override
  public DataType[] availableDataTypes() {
    return new DataType[] {
      DataType.TRADES,
      DataType.BID_ASK,
      DataType.MIDPOINT,
      DataType.BID,
      DataType.ASK,
      DataType.VOLUME};
  }

  @Override
  public BarSize[] availableBarSizes() {
    return new BarSize[] {
      BarSize.FIVE_MINS,
      BarSize.ONE_SEC,
      BarSize.FIVE_SECS,
      BarSize.TEN_SECS,
      BarSize.FIFTEEN_SECS,
      BarSize.THIRTY_SECS,
      BarSize.ONE_MIN,
      BarSize.TWO_MINS,
      BarSize.THREE_MINS,
      BarSize.TEN_MINS,
      BarSize.FIFTEEN_MINS,
      BarSize.TWENTY_MINS,
      BarSize.THIRTY_MINS,
      BarSize.ONE_HOUR,
      BarSize.FOUR_HOURS,
      BarSize.ONE_DAY};
  }

  @Override
  public Deque<ITrade> getTrades() throws ConnectException, RequestFailedException {
    SimpleDateFormat executionFilterDateFormat = new SimpleDateFormat("yyyyMMdd-00:00:00");
    final ExecutionFilter filter = new ExecutionFilter();
    filter.m_clientId = clientId;
    filter.m_time = executionFilterDateFormat.format(new Date());
    final Deque<ITrade> trades = new LinkedList<ITrade>();
   
    IBClient tradesClient = new IBClient() {
      @Override
      public void doSendRequest() {
        getSender().reqExecutions(getPendingReqId(), filter);
      }
     
      @Override
      public void execDetails(int reqId, Contract iBContract, Execution execution) {
        if (reqId == getPendingReqId()) {
          IOrder order = sentOrders.get(execution.m_orderId);
          trades.add(TradeBean.copyOf(new IBTradeInfo(execution, order)));
        }
      }
     
      @Override
      public void execDetailsEnd(int reqId) {
        if (reqId == getPendingReqId()) {
          setRequestComplete();
        }
      }
    };
   
    IBConnection connection = getConnection();
   
    connection.addClient(tradesClient);

    tradesClient.sendRequest(requestSequence.getAndIncrement());

    try {
      handleRequest(tradesClient);
    } finally {
      connection.removeClient(tradesClient);
   

    return trades;
  }

  @Override
  public List<IContract> searchContracts(IContract criteria, ITaskMonitor taskMonitor) throws ConnectException, RequestFailedException {
    final Contract iBCriteria = IBContract.toIBContract(criteria, constantTranslator);
    final List<IContract> result = new ArrayList<IContract>();
   
    if (taskMonitor != null) {
      taskMonitor.beginTask(1);
    }

    IBClient contractDetailsClient = new IBClient() {
      @Override
      public void doSendRequest() {
        getSender().reqContractDetails(getPendingReqId(), iBCriteria);
      }

      @Override
      public void contractDetails(int reqId, ContractDetails details) {
        if (reqId == getPendingReqId()) {
          IBContract iBContract = new IBContract(details.m_summary, constantTranslator);
          iBContract.setDetails(details);
          ImmutableContractBean bean = new ImmutableContractBean(iBContract);
          result.add(bean);
        }
      }

      @Override
      public void contractDetailsEnd(int reqId) {
        if (reqId == getPendingReqId())
          setRequestComplete();
      }
    };

    IBConnection connection = getConnection();
   
    connection.addClient(contractDetailsClient);

    contractDetailsClient.sendRequest(requestSequence.getAndIncrement());

    try {
      handleRequest(contractDetailsClient);
    } finally {
      connection.removeClient(contractDetailsClient);
    }

    if (taskMonitor != null) {
      taskMonitor.done();
    }

    return result;
  }
 
  private int maxYearsOfHistoricalData() throws RequestFailedException {
    switch (noMktDataLinesKey) {
    case 0:
      return 1; // Less than 499
    case 1:
      return 2; // 500 - 749
    case 2:
      return 3; // 750 - 999
    case 3:
      return 4; // more than 999
    default:
      throw new RequestFailedException("Illegal number of market data lines");
    }
  }
 
  private long maxRequestPeriodPerBarSize(BarSize barSize) {
    if (barSize.compareTo(BarSize.ONE_SEC) <= 0) {
      return 1000L * 60L * 30L; // 30 min
    } else if (barSize.compareTo(BarSize.FIVE_SECS) < 0) {
      return 1000L * 60L * 60L * 2L; // 2 hours
    } else if (barSize.compareTo(BarSize.FIFTEEN_SECS) < 0) {
      return 1000L * 60L * 60L * 4L; // 4 hours
    } else if (barSize.compareTo(BarSize.THIRTY_SECS) <= 0) {
      return 1000L * 60L * 60L * 24L; // 1 day
    } else if (barSize.compareTo(BarSize.ONE_MIN) <= 0) {
      return 1000L * 60L * 60L * 24L * 2L; // 2 days
    } else if (barSize.compareTo(BarSize.FIFTEEN_MINS) <= 0) {
      return 1000L * 60L * 60L * 24L * 7L; // 1 week
    } else if (barSize.compareTo(BarSize.ONE_HOUR) <= 0) {
      return 1000L * 60L * 60L * 24L * 30L; // 1 month
    } else
      return 1000L * 60L * 60L * 24L * 30L * 12L * 4L; // default: 4 years
  }

  public List<IOHLCPoint> historicalBars(IContract contract, Date startDateTime, Date endDateTime,
      BarSize barSize, DataType dataType, boolean includeAfterHours, ITaskMonitor taskMonitor) throws ConnectException, RequestFailedException {

    SimpleDateFormat timestampDateFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss");

    LinkedList<List<IOHLCPoint>> tmpResults = new LinkedList<List<IOHLCPoint>>();
   
    // find max request period based on IB limitations
   
    long remainingPeriod = endDateTime.getTime() - startDateTime.getTime();
   
    // check number of years
    long numberOfYears = remainingPeriod / 360L / 24L / 60L / 60L / 1000L;
    int maxYears = maxYearsOfHistoricalData();
    if (numberOfYears > maxYears) {
      throw new RequestFailedException("Current number of market data lines only allows for " + maxYears + " of historical data");
    }
    // break request in smaller batches to comply with IB limitations
    long maxPeriodForOneRequest = remainingPeriod;
    long barsPerInterval = maxPeriodForOneRequest / barSize.getDurationInMs();
    if (barsPerInterval > maxBarPerRequest) {
      maxPeriodForOneRequest = barSize.getDurationInMs() * maxBarPerRequest;
    }
    maxPeriodForOneRequest = Math.min(maxPeriodForOneRequest, maxRequestPeriodPerBarSize(barSize));
   
    Date nextEndDateTime = endDateTime;
   
    // comply with IB timing restrictions
    long lastBatchTime = 0L;
    int requestNo = 0;
   
    int approxCallNo = (int) (remainingPeriod / maxPeriodForOneRequest);
    if (taskMonitor != null) {
      taskMonitor.beginTask(approxCallNo * 10); // retrieving then adding partial results: IB calls count 7 - saving results count 3
    }

    logger.log(Level.INFO, "Start downloading: " + new Date()); // TODO: remove
    boolean forceSleep = false;
    while (remainingPeriod > 0) {
     
      if (taskMonitor != null && taskMonitor.isCancelled()) {
        throw new RequestFailedException("Request cancelled by user");
      }
     
      if (requestNo % numOfReqsInHistDataReqBatches == 0 || forceSleep) {
        long elapsedSinceLastBatch = System.currentTimeMillis() - lastBatchTime;
        logger.log(Level.INFO, "Now: " + timestampDateFormat.format(new Date()) + "; Elapsed: " + elapsedSinceLastBatch); // TODO: remove
        if (elapsedSinceLastBatch < minElapsedBetweenHistDataReqBatches) {
          try {
            logger.log(Level.INFO, "Sleeping: " + (minElapsedBetweenHistDataReqBatches - elapsedSinceLastBatch) + "ms"); // TODO: remove
            for (long partElapsed = 0L; partElapsed < minElapsedBetweenHistDataReqBatches - elapsedSinceLastBatch; partElapsed += HIST_DATA_REQUEST_SLEEP_QUANTUM) {
              Thread.sleep(HIST_DATA_REQUEST_SLEEP_QUANTUM);
              if (taskMonitor != null && taskMonitor.isCancelled()) {
                throw new RequestFailedException("Request cancelled by user");
              }
            }
          } catch (InterruptedException e) {
            throw new RequestFailedException("Exception while sleeping through request batches", e);
          }
        }
        lastBatchTime = System.currentTimeMillis();
        forceSleep = false;
      }
      long currentRequestPeriod;
      if (remainingPeriod > maxPeriodForOneRequest) {
        currentRequestPeriod = maxPeriodForOneRequest;
      } else if (remainingPeriod < minHistDataPeriodMs) {
        currentRequestPeriod = minHistDataPeriodMs;
      } else {
        currentRequestPeriod = remainingPeriod;
      }
      TimePeriod requestPeriod = TimePeriod.findApproxPeriod(currentRequestPeriod);
      List<IOHLCPoint> partialResult = null;

      logger.log(Level.INFO, "Request #" + requestNo + ". Data until: " + nextEndDateTime); // TODO: remove
      try {
       
        partialResult = doGetHistoricalData(contract, nextEndDateTime, requestPeriod, barSize, dataType, includeAfterHours);
       
      } catch (PacingViolationException e) {
        logger.log(Level.WARNING, "Pacing violation exception while retrieving historical data from IB. Force sleep", e);
        forceSleep = true;
        continue;
      } catch (NoDataReturnedException e) {
        logger.log(Level.INFO, "Historical data request from IB returned no data. Continue with next request", e);
        partialResult = null;
      }
      if (partialResult == null || nextEndDateTime.equals(partialResult.get(0).getIndex())) { // empty or duplicate result
        logger.log(Level.INFO, "Empty or duplicate result: " + nextEndDateTime); // TODO: remove
        nextEndDateTime = TimePeriod.subtractPeriodFromDate(nextEndDateTime, requestPeriod);
      } else {
        tmpResults.addFirst(partialResult);
        nextEndDateTime = partialResult.get(0).getIndex();
      }
      remainingPeriod = nextEndDateTime.getTime() - startDateTime.getTime();
      requestNo++;
     
      if (taskMonitor != null) {
        taskMonitor.worked(7);
      }
    }

    logger.log(Level.INFO, "All data downloaded. Create time series: " + new Date()); // TODO: remove
    List<IOHLCPoint> result = new ArrayList<IOHLCPoint>();

    IOHLCPoint lastBarAdded = null;
    Date dateOfLastBarAdded = new Date(0L);
    for (List<IOHLCPoint> partialResult : tmpResults) {

      if (taskMonitor != null && taskMonitor.isCancelled()) {
        throw new RequestFailedException("Request cancelled by user");
      }
     
      for (IOHLCPoint bar : partialResult) {
        Date endOfLastBarAdded = new Date(dateOfLastBarAdded.getTime() + barSize.getDurationInMs());
        if (!bar.getIndex().before(startDateTime)) {
          if (!bar.getIndex().before(endOfLastBarAdded)) {
            result.add(bar);
            lastBarAdded = bar;
            dateOfLastBarAdded = bar.getIndex();           
          } else if (bar.getIndex().after(dateOfLastBarAdded) && bar.getIndex().before(endOfLastBarAdded)) { // consolidate partial bars (e.g. ES 15.00-15.15 + 15.30-14.00)
            result.set(result.size() - 1, OHLCPoint.merge(lastBarAdded, bar));
          }
        }
        if (!bar.getIndex().before(startDateTime) && bar.getIndex().after(dateOfLastBarAdded)) {
          result.add(bar);
          dateOfLastBarAdded = bar.getIndex();
        }
      }
     
      if (taskMonitor != null) {
        taskMonitor.worked(3);
      }
    }
   
    if (taskMonitor != null) {
      taskMonitor.done();
    }
   
    // TODO remove
    logger.log(Level.INFO, "Now the time is: " + new Date());
    if (!result.isEmpty()) {
      logger.log(Level.INFO, "Beginning of historical data: " + result.get(0).getIndex());
      logger.log(Level.INFO, "End of historical data: " + result.get(result.size() - 1).getIndex());
    }

    return result;
  }

  private List<IOHLCPoint> doGetHistoricalData(IContract contract, final Date endDateTime, final TimePeriod period,
      final BarSize barSize, DataType dataType, final boolean includeAfterHours) throws ConnectException, RequestFailedException {
    final Contract iBContract = IBContract.toIBContract(contract, constantTranslator);
    final String iBDurationUnit = constantTranslator.getCode(period.getUnitOfTime());
    final String iBBarSize = constantTranslator.getCode(barSize);
    final String iBDataType = dataType == DataType.BID_ASK ? "MIDPOINT" : constantTranslator.getCode(dataType); // BID_ASK does not return OHLC bars: open = WAVG(BID), close = WAVG(ASK)
    final List<IOHLCPoint> result = new ArrayList<IOHLCPoint>();
    final SimpleDateFormat timestampDateFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss");

    IBClient historicalDataClient = new IBClient() {
      private DateFormat sendDateFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
      private DateFormat receiveDateFormat = new SimpleDateFormat("yyyyMMdd");

      @Override
      public void doSendRequest() {
        sendDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        String endTimeRepr = sendDateFormat.format(endDateTime);
        logger.log(Level.INFO, "Historical Data Request: " + endTimeRepr + "; " + period.getAmount() + " " + iBDurationUnit + "; " + iBBarSize + "; " + iBDataType);
        getSender().reqHistoricalData(getPendingReqId(), iBContract, endTimeRepr, period.getAmount() + " " + iBDurationUnit, iBBarSize, iBDataType,
            includeAfterHours ? 0 : 1, 2);
      }

      @Override
      public void historicalData(int reqId, String dateRepr, double open, double high, double low, double close, int volume, int count, double WAP,
          boolean hasGaps) {
        if (reqId == getPendingReqId()) {
          if (dateRepr.startsWith("finished-")) {
            Matcher matcher = HIST_DATA_LAST_UPDATE_MESSAGE.matcher(dateRepr);
            if (matcher.matches()) {
              String timestampRepr = matcher.group(1);
              try {
                Date timestamp = timestampDateFormat.parse(timestampRepr);
                ((OHLCPoint)result.get(result.size() - 1)).setLastUpdate(timestamp);
              } catch (ParseException e) {
                e.printStackTrace();
              }
            }
            setRequestComplete();
          } else {
            Date date;
            if (dateRepr.length() == 8) { // alternate format sent by IB when barSize = 1 DAY: yyyyMMdd
              try {
                date = receiveDateFormat.parse(dateRepr);
              } catch (ParseException e) {
                logger.log(Level.SEVERE, "Unparseable date in historical data: " + dateRepr, e);
                e.printStackTrace();
                return;
              }
            } else {
              Long dateInSecs = Long.valueOf(dateRepr);
              date = new Date(dateInSecs * 1000);
            }
            OHLCPoint data = new OHLCPoint(barSize, date, open, high, low, close, (long)volume, WAP, count);
            result.add(data);
          }
        }
      }
    };

    IBConnection connection = getConnection();

    connection.addClient(historicalDataClient);

    historicalDataClient.sendRequest(requestSequence.getAndIncrement());

    try {
      handleRequest(historicalDataClient);
    } finally {
      connection.removeClient(historicalDataClient);
    }

    return result;
  }
 
  private String buildRealTimeSeriesRequestCode(IContract contract, BarSize barSize, DataType dataType, boolean includeAfterHours) {
    SimpleDateFormat eightDigitDateFormat = new SimpleDateFormat("yyyyMMdd");
    StringBuilder buffer = new StringBuilder();
    buffer.append(contract.getSymbol()).append("|");
    buffer.append(contract.getCurrency().getCurrencyCode()).append("|");
    buffer.append(contract.getSecurityType().name()).append("|");
    if (contract.getExpiryDate() != null) {
      buffer.append(eightDigitDateFormat.format(contract.getExpiryDate())).append("|");
    }
    buffer.append(barSize.name()).append("|");
    buffer.append(dataType.name()).append("|");
    buffer.append(Boolean.toString(includeAfterHours));
    return buffer.toString();
  }

  @Override
  public void startRealTimeBars(IContract contract, final BarSize barSize, DataType dataType, final boolean includeAfterHours, IRealTimeDataListener listener)
      throws ConnectException, RequestFailedException {
   
    final int barSizeConst = 5;
    final Contract iBContract = IBContract.toIBContract(contract, constantTranslator);
    final String iBDataType = dataType == DataType.BID_ASK ? "MIDPOINT" : constantTranslator.getCode(dataType); // BID_ASK does not return OHLC bars: open = WAVG(BID), close = WAVG(ASK)
   
    final String realTimeSeriesRequestCode = buildRealTimeSeriesRequestCode(contract, barSize, dataType, includeAfterHours);
   
    Set<IRealTimeDataListener> setOfListeners = realTimeDataListeners.get(realTimeSeriesRequestCode);
   
    boolean newRequest = false;
    IBConnection connection = null;
    IBClient realTimeDataClient = null;
   
    if (setOfListeners == null || setOfListeners.isEmpty()) {
      newRequest = true;
      setOfListeners = new CopyOnWriteArraySet<IRealTimeDataListener>();
      realTimeDataListeners.put(realTimeSeriesRequestCode, setOfListeners);
     
      final Set<IRealTimeDataListener> currentListeners = setOfListeners; // duplicate as final variable to make it valid for use by the inner class
      realTimeDataClient = new IBClient() {
        @Override
        public void doSendRequest() {
          int requestId = getPendingReqId();
          realTimeDataRequestIds.put(realTimeSeriesRequestCode, requestId);
          getSender().reqRealTimeBars(getPendingReqId(), iBContract, barSizeConst, iBDataType, !includeAfterHours);
        }

        @Override
        public void realtimeBar(int reqId, long time, double open, double high, double low, double close, long volume, double wap, int count) {
          if (reqId == getPendingReqId()) {
            final OHLCPoint ohlc = new OHLCPoint(BarSize.FIVE_SECS, new Date((long)time * 1000L), open, high, low, close, volume, wap, count);
            threadPool.execute(new Runnable() {
              @Override
              public void run() {
                for (IRealTimeDataListener listener : currentListeners) {
                  try {
                    listener.onRealTimeBar(ohlc);
                  } catch (Throwable t) {
                    logger.log(Level.SEVERE, "Excetion while dispatching realtime bar", t);
                  }
                }
              }});
          }
        }
      };

      connection = getConnection();
      connection.addClient(realTimeDataClient);
    }
   
    setOfListeners.add(listener);
   
    if (newRequest) {
      realTimeDataClient.sendRequest(requestSequence.getAndIncrement());
    }
  }

  @Override
  public void stopRealTimeBars(IContract contract, BarSize barSize, DataType dataType, boolean includeAfterHours, IRealTimeDataListener listener) throws ConnectException {
    String realTimeSeriesRequestCode = buildRealTimeSeriesRequestCode(contract, barSize, dataType, includeAfterHours);
   
    Set<IRealTimeDataListener> setOfListeners = realTimeDataListeners.get(realTimeSeriesRequestCode);
   
    if (setOfListeners != null) {
      setOfListeners.remove(listener);
     
      if (setOfListeners.isEmpty()) {
        realTimeDataListeners.remove(setOfListeners);
        Integer requestId = realTimeDataRequestIds.remove(realTimeSeriesRequestCode);
        getConnection().getSender().cancelRealTimeBars(requestId);
      }
    }
  }
 
  private String buildTickRequestCode(IContract contract) {
    StringBuilder buffer = new StringBuilder();
    buffer.append(contract.getSymbol()).append("|");
    buffer.append(contract.getCurrency().getCurrencyCode()).append("|");
    buffer.append(contract.getSecurityType().name());
    return buffer.toString();
  }

  @Override
  public void startTicks(IContract contract, ITickListener listener) throws ConnectException, RequestFailedException {
    final Contract iBContract = IBContract.toIBContract(contract, constantTranslator);
   
    final String tickRequestCode = buildTickRequestCode(contract);
   
    Set<ITickListener> setOfListeners = tickListeners.get(tickRequestCode);
   
    boolean newRequest = false;
    IBConnection connection = null;
    IBClient tickClient = null;
   
    if (setOfListeners == null || setOfListeners.isEmpty()) {
      newRequest = true;
      setOfListeners = new CopyOnWriteArraySet<ITickListener>();
      tickListeners.put(tickRequestCode, setOfListeners);
     
      final Set<ITickListener> currentListeners = setOfListeners; // duplicate as final variable to make it valid for use by the inner class
      tickClient = new IBClient() {
        private Double lastTradePrice;
        private Double lastAskPrice;
        private Double lastBidPrice;
       
        @Override
        public void doSendRequest() {
          int requestId = getPendingReqId();
          tickRequestIds.put(tickRequestCode, requestId);
          getSender().reqMktData(getPendingReqId(), iBContract, "233", false);
        }

        @Override
        public void tickSize(int reqId, int field, int size) {
          if (reqId == getPendingReqId()) {
            Date timestamp = new Date();
            ITickPoint tick = null;
            switch (field) {
            case TickType.ASK_SIZE:
              if (lastAskPrice != null) {
                tick = new TickPoint(timestamp, DataType.ASK, lastAskPrice, size);
              }
              break;
            case TickType.BID_SIZE:
              if (lastBidPrice != null) {
                tick = new TickPoint(timestamp, DataType.BID, lastBidPrice, size);
              }
              break;
            case TickType.LAST_SIZE:
              if (lastTradePrice != null) {
                tick = new TickPoint(timestamp, DataType.TRADES, lastTradePrice, size);
              }
              break;
            case TickType.VOLUME:
              // not enough information to be useful
            }
            if (tick != null) {
              final ITickPoint constantTick = tick;
              threadPool.execute(new Runnable() {
                @Override
                public void run() {
                  for (ITickListener listener : currentListeners) {
                    try {
                      listener.onTick(constantTick);
                    } catch (Throwable t) {
                      logger.log(Level.SEVERE, "Excetion while dispatching size tick", t);
                    }
                  }
                }});
            }
          }
        }

        @Override
        public void tickPrice(int reqId, int field, double price, int canAutoExecute) {
          if (reqId == getPendingReqId()) {
            Date timestamp = new Date();
            ITickPoint tick = null;
            ITickPoint midTick = null;
            switch (field) {
            case TickType.ASK:
              lastAskPrice = price;
              tick = new TickPoint(timestamp, DataType.ASK, lastAskPrice, 0);
              midTick = new TickPoint(timestamp, DataType.MIDPOINT, lastBidPrice != null ? (lastBidPrice + lastAskPrice) / 2 : lastAskPrice, 0);
              break;
            case TickType.BID:
              lastBidPrice = price;
              tick = new TickPoint(timestamp, DataType.BID, lastBidPrice, 0);
              midTick = new TickPoint(timestamp, DataType.MIDPOINT, lastAskPrice != null ? (lastBidPrice + lastAskPrice) / 2 : lastBidPrice, 0);
              break;
            case TickType.LAST:
            case TickType.OPEN:
            case TickType.CLOSE:
              tick = new TickPoint(timestamp, DataType.TRADES, price, 0);
              lastTradePrice = price;
              break;
            }
            if (tick != null) {
              final ITickPoint constantTick = tick;
              final ITickPoint constantMidTick = midTick;
              threadPool.execute(new Runnable() {
                @Override
                public void run() {
                  for (ITickListener listener : currentListeners) {
                    listener.onTick(constantTick);
                    if (constantMidTick != null) {
                      try {
                        listener.onTick(constantMidTick);
                      } catch (Throwable t) {
                        logger.log(Level.SEVERE, "Excetion while dispatching size tick", t);
                      }
                    }
                  }
                }});
            }
          }
        }
       
        @Override
        public void tickString(int reqId, int tickType, String value) {
//          if (reqId == getPendingReqId()) {
//            switch (tickType) {
//            case TickType.RT_VOLUME: //value example: "1307.50;17;1337617022706;1271944;1300.6509583;false"
//              double price;
//              int size;
//              Date timestamp;
//              String[] tokens = value.split(";");
//              if (tokens.length >= 6) {
//                price = Double.valueOf(tokens[0]);
//                size = Integer.valueOf(tokens[1]);
//                timestamp = new Date(Long.valueOf(tokens[2]));
//              }
//            }
//          }
        }
       
        @Override
        public void tickGeneric(int tickerId, int tickType, double value) {
          logger.log(Level.WARNING, "Generic tick received from IB: " + EWrapperMsgGenerator.tickGeneric(tickerId, tickType, value).toString());
        }
      };

      connection = getConnection();
      connection.addClient(tickClient);
    }
   
    setOfListeners.add(listener);
   
    if (newRequest) {
      tickClient.sendRequest(requestSequence.getAndIncrement());
    }
  }

  @Override
  public void stopTicks(IContract contract, ITickListener listener) throws ConnectException {
    String tickRequestCode = buildTickRequestCode(contract);
   
    Set<ITickListener> setOfListeners = tickListeners.get(tickRequestCode);
   
    if (setOfListeners != null) {
      setOfListeners.remove(listener);
     
      if (setOfListeners.isEmpty()) {
        tickListeners.remove(setOfListeners);
        Integer requestId = tickRequestIds.remove(tickRequestCode);
        getConnection().getSender().cancelMktData(requestId);
      }
    }
  }

  public int nextOrderId() throws ConnectException, RequestFailedException {
    if (nextOrderId == -1) {
      IBClient orderIdClient = new IBClient() {
        @Override
        public void nextValidId(int orderId) {
          logger.log(Level.INFO, "Received order id: " + orderId);
          nextOrderId = orderId;
          setRequestComplete();
        }

        @Override
        protected void doSendRequest() {
          logger.log(Level.INFO, "Request order id");
          getSender().reqIds(1);
        }
      };
     
      IBConnection connection = getConnection();
      connection.addClient(orderIdClient);
      orderIdClient.sendRequest(requestSequence.getAndIncrement());
      try {
        handleRequest(orderIdClient);
      } finally {
        connection.removeClient(orderIdClient);
      }
    }
    return nextOrderId++;
  }

  private int sendOrder(final Order iBOrder, final Contract iBContract) throws ConnectException, RequestFailedException {
    final int id = nextOrderId();
    getConnection().getSender().placeOrder(id, iBContract, iBOrder);
    return id;
  }

  @Override
  public String sendOrder(IOrder order) throws ConnectException, RequestFailedException {
    Order iBOrder = IBOrder.toIBOrder(order, constantTranslator);
    Contract iBContract = IBContract.toIBContract(order.getContract(), constantTranslator);
    int id = sendOrder(iBOrder, iBContract);
    iBOrder.m_orderId = id;
    sentOrders.put(id, new IBOrder(order.getContract(), iBOrder, constantTranslator));
    logger.log(Level.INFO, "Sent order: " + id);
    return Integer.toString(id);
  }
 
  @Override
  public String[] sendBracketOrders(IOrder parent, IOrder[] children) throws ConnectException, RequestFailedException {
    Order iBParentOrder = IBOrder.toIBOrder(parent, constantTranslator);
    Contract iBParentContract = IBContract.toIBContract(parent.getContract(), constantTranslator);
   
    int parentId = sendOrder(iBParentOrder, iBParentContract);
   
    String[] ids = new String[1 + children.length];
    ids[0] = Integer.toString(parentId);
   
    for (int i = 0; i < children.length; i++) {
      Order iBChildOrder = IBOrder.toIBOrder(children[i], constantTranslator);
      iBChildOrder.m_parentId = parentId;
      Contract iBChildContract = IBContract.toIBContract(children[i].getContract(), constantTranslator);
      int childId = sendOrder(iBChildOrder, iBChildContract);
      ids[i + 1] = Integer.toString(childId);
    }
    return ids;
  }

  @Override
  public void addOrderStatusListener(IOrderStatusListener listener) throws ConnectException {
    orderStatusListeners.add(listener);
    if (orderStatusClient == null) {
      orderStatusClient = new OrderStatusClient();
      getConnection().addClient(orderStatusClient);
    }
  }

  @Override
  public void removeOrderStatusListener(IOrderStatusListener listener) throws ConnectException {
    orderStatusListeners.remove(listener);
    if (orderStatusListeners.isEmpty() && orderStatusClient != null) {
      getConnection().removeClient(orderStatusClient);
      orderStatusClient = null;
    }
  }

  @Override
  public void addPositionListener(IPositionListener listener) throws ConnectException {
    positionListeners.add(listener);
    if (positionClient == null) {
      positionClient = new PositionClient();
      IBConnection connection = getConnection();
      connection.addClient(positionClient);
      connection.getSender().reqAccountUpdates(true, accountId);
    }
  }

  @Override
  public void removePositionListener(IPositionListener listener) throws ConnectException {
    positionListeners.remove(listener);
    if (positionClient != null) {
      IBConnection connection = getConnection();
      connection.getSender().reqAccountUpdates(false, accountId);
      connection.removeClient(positionClient);
      positionClient.stopPositionPriceUpdates();
      positionClient = null;
    }
  }
 
  private boolean waitResponse(IBClient requestClient) {
    for (int wait = 0; wait < maxWaitResponseMs && requestClient.isRequestInProgress(); wait += HANDLE_REQUEST_SLEEP_QUANTUM) {
      try {
        Thread.sleep(HANDLE_REQUEST_SLEEP_QUANTUM);
      } catch (InterruptedException e) {
      }
    }
    return requestClient.isRequestInProgress() == false && requestClient.hasErrors() == false;
  }

  private void handleRequest(IBClient requestClient) throws RequestFailedException, PacingViolationException {
    if (!waitResponse(requestClient)) {
      if (requestClient.hasErrors()) {
        if (requestClient.getErrorMessage().contains("pacing violation")) {
          throw new PacingViolationException(requestClient.getErrorMessage(), requestClient.getException());
        } else if (requestClient.getErrorMessage().contains("query returned no data")) {
          throw new NoDataReturnedException(requestClient.getErrorMessage(), requestClient.getException());
        }
        throw new RequestFailedException(requestClient.getErrorMessage(), requestClient.getException());
      }
      throw new RequestFailedException();
    }
  }

  public long getMinElapsedBetweenHistDataReqBatches() {
    return minElapsedBetweenHistDataReqBatches;
  }

  public void setMinElapsedBetweenHistDataReqBatches(long minElapsedBetweenHistDataReqBatches) {
    this.minElapsedBetweenHistDataReqBatches = minElapsedBetweenHistDataReqBatches;
  }

  public int getNumOfReqsInHistDataReqBatches() {
    return numOfReqsInHistDataReqBatches;
  }

  public void setNumOfReqsInHistDataReqBatches(int numOfReqsInHistDataReqBatches) {
    this.numOfReqsInHistDataReqBatches = numOfReqsInHistDataReqBatches;
  }

  public int getMaxHistDataPeriodMs() {
    return minHistDataPeriodMs;
  }

  public void setMinHistDataPeriodMs(int minHistDataPeriodMs) {
    this.minHistDataPeriodMs = minHistDataPeriodMs;
  }

  public int getMaxBarPerRequest() {
    return maxBarPerRequest;
  }

  public void setMaxBarPerRequest(int maxBarPerRequest) {
    this.maxBarPerRequest = maxBarPerRequest;
  }

  public int getMaxWaitToConnectMs() {
    return maxWaitToConnectMs;
  }

  public void setMaxWaitToConnectMs(int maxWaitToConnectMs) {
    this.maxWaitToConnectMs = maxWaitToConnectMs;
  }

  public int getMaxWaitResponseMs() {
    return maxWaitResponseMs;
  }

  public void setMaxWaitResponseMs(int maxWaitResponseMs) {
    this.maxWaitResponseMs = maxWaitResponseMs;
  }
}
TOP

Related Classes of com.quantcomponents.ib.IBAdapter$PositionClient$PositionPriceUpdater

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.