Java tutorial
/** * MIT License * * Copyright (c) 2015 Rob Terpilowski * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.sumzerotrading.broker.ib; import com.ib.client.CommissionReport; import com.ib.client.Contract; import com.ib.client.ContractDetails; import com.ib.client.EClientSocket; import com.ib.client.Execution; import com.ib.client.ExecutionFilter; import com.ib.client.Order; import com.ib.client.OrderState; import com.sumzerotrading.broker.BrokerError; import com.sumzerotrading.broker.BrokerErrorListener; import com.sumzerotrading.broker.IBroker; import com.sumzerotrading.broker.Position; import com.sumzerotrading.broker.order.OrderEvent; import com.sumzerotrading.broker.order.OrderEventListener; import com.sumzerotrading.broker.order.OrderStatus; import com.sumzerotrading.broker.order.TradeOrder; import com.sumzerotrading.data.ComboTicker; import com.sumzerotrading.data.InstrumentType; import com.sumzerotrading.data.SumZeroException; import com.sumzerotrading.data.Ticker; import com.sumzerotrading.ib.BaseIBConnectionDelegate; import com.sumzerotrading.ib.ContractBuilderFactory; import com.sumzerotrading.ib.ContractWrapper; import com.sumzerotrading.ib.IBConnectionInterface; import com.sumzerotrading.ib.IBSocket; import com.sumzerotrading.ib.IbUtils; import com.sumzerotrading.time.TimeUpdatedListener; import com.sumzerotrading.util.QuoteUtil; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.apache.commons.collections4.map.PassiveExpiringMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Supported Order types are: Market, Stop and Limit Supported order parameters * are parent/child, OCA, Good-after-time, good-till-date. Supported * Time-in-force: DAY, Good-till-canceled, Good-till-time, Immediate-or-cancel * * @author Rob Terpilowski */ public class InteractiveBrokersBroker extends BaseIBConnectionDelegate implements IBroker { protected static int contractRequestId = 1; protected static int executionRequestId = 1; protected static Logger logger = LoggerFactory.getLogger(InteractiveBrokersBroker.class); protected EClientSocket ibConnection; protected IBSocket ibSocket; protected IBConnectionInterface callbackInterface; protected Set<TradeOrder> currencyOrderList = new HashSet<>(); protected BlockingQueue<Integer> nextIdQueue = new LinkedBlockingQueue<>(); protected BlockingQueue<ZonedDateTime> brokerTimeQueue = new LinkedBlockingQueue<>(); protected BlockingQueue<BrokerError> brokerErrorQueue = new LinkedBlockingQueue<>(); protected BlockingQueue<OrderEvent> orderEventQueue = new LinkedBlockingQueue<>(); protected BlockingQueue<ContractDetails> contractDetailsQueue = new LinkedBlockingDeque<>(); protected int nextOrderId = -1; protected SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd HH:mm:ss"); protected DateTimeFormatter zonedDateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss"); protected Map<String, TradeOrder> orderMap = new HashMap<>(); protected Map<String, TradeOrder> completedOrderMap = new HashMap<>(); protected List<OrderEventListener> orderEventListeners = new ArrayList<>(); protected IBOrderEventProcessor orderProcessor; protected Set<String> filledOrderSet = new HashSet<>(); protected Timer currencyOrderTimer; protected Object lock = new Object(); protected Semaphore semaphore = new Semaphore(1); protected Semaphore tradeFileSemaphore = new Semaphore(1); protected boolean started = false; protected String directory; protected Map<String, OrderEvent> orderEventMap; protected CountDownLatch getPositionsCountdownLatch = null; protected List<Position> positionsList = new ArrayList<>(); /** * Used by Unit tests */ protected InteractiveBrokersBroker() { } public InteractiveBrokersBroker(IBSocket ibSocket) { this.ibSocket = ibSocket; try { loadOrderMaps(); } catch (Exception ex) { throw new SumZeroException(ex); } orderEventMap = new PassiveExpiringMap<>(30, TimeUnit.SECONDS); callbackInterface = ibSocket.getConnection(); callbackInterface.addIbConnectionDelegate(this); ibConnection = ibSocket.getClientSocket(); orderProcessor = new IBOrderEventProcessor(orderEventQueue, this); currencyOrderTimer = new Timer(true); currencyOrderTimer.schedule(getCurrencyOrderMonitor(), 0, 1000 * 60); } @Override public void addTimeUpdateListener(TimeUpdatedListener listener) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void removeTimeUpdateListener(TimeUpdatedListener listener) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public boolean isConnected() { return ibSocket.isConnected(); } @Override public void connect() { if (!isConnected()) { ibSocket.connect(); } if (!started) { orderProcessor.startProcessor(); started = true; } } @Override public void disconnect() { if (started) { orderProcessor.stopProcessor(); started = false; } } @Override public void execDetails(int orderId, Contract contract, Execution execution) { logger.info( "Execution details: orderId: " + orderId + " contract: " + contract + " Execution: " + execution); //not currently implemented } @Override public void nextValidId(int orderId) { try { nextIdQueue.put(orderId); } catch (Exception ex) { ex.printStackTrace(); } } @Override public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) { logger.info("OpenOrder: " + orderId + " Contract: " + contract + " Order: " + order + " OrderState: " + orderState); } public void timeReceived(ZonedDateTime time) { try { brokerTimeQueue.put(time); } catch (Exception ex) { ex.printStackTrace(); } } @Override public void orderStatus(int orderId, String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld) { logger.info("OrderStatus(): orderId: " + orderId + " Status: " + status + " filled: " + filled + " remaining: " + remaining + " avgFillPrice: " + avgFillPrice + " permId: " + permId + " parentId: " + parentId + " lastFillePrice: " + lastFillPrice + " clientId: " + clientId + " whyHeld: " + whyHeld); TradeOrder order = orderMap.get(Integer.toString(orderId)); if (order == null) { logger.error("Open Order with ID: " + orderId + " not found"); return; } order.setFilledSize(filled); order.setFilledPrice(avgFillPrice); try { OrderEvent event = OrderManagmentUtil.createOrderEvent(order, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, getZoneDateTime()); //Check if this order status has been seen in the last minute OrderEvent cachedEvent = orderEventMap.get(order.getOrderId()); if (cachedEvent != null && event.equals(cachedEvent)) { orderEventMap.put(order.getOrderId(), event); logger.info("Duplicate order status received....skipping"); return; } if (event.getOrderStatus().getStatus() == OrderStatus.Status.FILLED || event.getOrderStatus().getStatus() == OrderStatus.Status.CANCELED) { order.setOrderFilledTime(getZoneDateTime()); completedOrderMap.put(order.getOrderId(), order); orderMap.remove(order.getOrderId()); } order.setCurrentStatus(event.getOrderStatus().getStatus()); orderEventQueue.put(event); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } try { saveOrderMaps(); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } @Override public void commissionReport(CommissionReport commissionReport) { logger.info("CommssionReport(): " + commissionReport); } public void addOrderEventListener(OrderEventListener listener) { synchronized (orderEventListeners) { orderEventListeners.add(listener); } } public void removeOrderEventListener(OrderEventListener listener) { synchronized (orderEventListeners) { orderEventListeners.remove(listener); } } public void error(Exception e) { logger.error("BrokerError: " + e.getMessage(), e); putOnErrorQueue(new BrokerError(e)); } public void error(String str) { logger.error("BrokerError: " + str); putOnErrorQueue(new BrokerError(str)); } public void error(int id, int errorCode, String errorMsg) { logger.error("BrokerError: ID:" + id + " errorCode:" + errorCode + " errorMessage: " + errorMsg); putOnErrorQueue(new BrokerError(id, errorCode, errorMsg)); } protected void putOnErrorQueue(BrokerError error) { try { // brokerErrorQueue.put(error); } catch (Exception ex) { //logger.error(ex, ex); ex.printStackTrace(); } } public void cancelOrder(String idString) { int id = Integer.parseInt(idString); ibConnection.cancelOrder(id); } public void cancelOrder(TradeOrder order) { int id = Integer.parseInt(order.getOrderId()); ibConnection.cancelOrder(id); } public ZonedDateTime getCurrentTime() { ibConnection.reqCurrentTime(); try { return brokerTimeQueue.poll(2, TimeUnit.SECONDS); } catch (InterruptedException ex) { logger.error(directory); System.out.println("Time Out waiting to get current time from broker, returning local current time"); return ZonedDateTime.now(); } } public String getFormattedDate(int hour, int minute, int second) { throw new UnsupportedOperationException("Not supported yet."); } @Override public String getFormattedDate(ZonedDateTime date) { return date.format(zonedDateFormatter); } @Override public synchronized String getNextOrderId() { if (nextOrderId == -1) { try { ibConnection.reqIds(1); nextOrderId = nextIdQueue.take(); return nextOrderId + ""; } catch (InterruptedException ex) { logger.error(ex.getMessage(), ex); return -1 + ""; } } return ++nextOrderId + ""; } public void placeOrder(TradeOrder order) { order.setOrderEntryTime(getZoneDateTime()); if (order.getTicker().getInstrumentType() == InstrumentType.FOREX) { //IDEALPro is closed from 14:00-14:15 PST, and does not //accept orders from 14:00-14:05, queue orders that are placed //during that time. if (isIdealProClosed()) { queueOrder(order); return; } } List<IbOrderAndContract> orders = buildOrderAndContract(order); orders.get(orders.size() - 1).getOrder().m_transmit = true; for (IbOrderAndContract ibOrder : orders) { ibConnection.placeOrder(ibOrder.getOrder().m_orderId, ibOrder.getContract(), ibOrder.getOrder()); } } public void aquireLock() { try { semaphore.acquire(); } catch (InterruptedException ex) { throw new IllegalStateException(ex); } } public void releaseLock() { semaphore.release(); } public ComboTicker buildComboTicker(Ticker ticker1, Ticker ticker2) { return buildComboTicker(ticker1, 1, ticker2, 1); } @Override public ComboTicker buildComboTicker(Ticker ticker1, int ratio1, Ticker ticker2, int ratio2) { ContractDetails details1 = getContractDetails(ticker1); ContractDetails details2 = getContractDetails(ticker2); ComboTicker combo = new ComboTicker(ticker1, ticker2); combo.setContractId1(details1.m_summary.m_conId); combo.setFirstTickerRatio(ratio1); combo.setContractId2(details2.m_summary.m_conId); combo.setSecondTickerRatio(ratio2); return combo; } public void contractDetailsReceived(ContractDetails details) { try { contractDetailsQueue.put(details); } catch (Exception ex) { ex.printStackTrace(); } } public ContractDetails getContractDetails(Ticker ticker) { final Contract contract = ContractBuilderFactory.getContractBuilder(ticker).buildContract(ticker); ibConnection.reqContractDetails(contractRequestId++, contract); try { return contractDetailsQueue.poll(2, TimeUnit.SECONDS); } catch (InterruptedException ex) { throw new IllegalStateException(ex); } } @Override public synchronized TradeOrder requestOrderStatus(String orderId) { ExecutionFilter filter = new ExecutionFilter(); ibConnection.reqExecutions(executionRequestId++, filter); throw new IllegalStateException("Not yet implemented"); } @Override public List<TradeOrder> getOpenOrders() { return new ArrayList<>(orderMap.values()); } @Override public List<Position> getAllPositions() { getPositionsCountdownLatch = new CountDownLatch(1); ibConnection.reqPositions(); try { getPositionsCountdownLatch.await(30, TimeUnit.SECONDS); } catch (InterruptedException ex) { throw new SumZeroException(ex); } return positionsList; } @Override public void position(String account, Contract contract, int pos, double avgCost) { //IbUtils. //positionsList.add(new Position(ticker, pos, avgCost)); logger.info("Position - Account: " + account + " Contract: " + new ContractWrapper(contract) + " size: " + pos + " avgCost: " + avgCost); } @Override public void positionEnd() { getPositionsCountdownLatch.countDown(); logger.info("Position END()"); } protected void saveOrderMaps() throws Exception { tradeFileSemaphore.acquire(); try { File file = new File(getDirName() + "orders.ser"); ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(file)); output.writeObject(completedOrderMap); output.writeObject(orderMap); output.flush(); output.close(); } finally { tradeFileSemaphore.release(); } } protected void loadOrderMaps() throws Exception { tradeFileSemaphore.acquire(); try { createDir(); File file = new File(getDirName() + "orders.ser"); if (file.exists()) { ObjectInputStream input = new ObjectInputStream(new FileInputStream(file)); completedOrderMap = (HashMap) input.readObject(); orderMap = (HashMap) input.readObject(); input.close(); } } finally { tradeFileSemaphore.release(); } } protected void createDir() { try { Files.createDirectories(Paths.get(getDirName())); } catch (IOException ex) { logger.error(ex.getMessage(), ex); } } protected String getDirName() { return System.getProperty("user.dir") + "/" + "ib-order-management/client-" + ibSocket.getClientId() + "/"; } protected void queueOrder(TradeOrder order) { currencyOrderList.add(order); } protected void processCurrencyOrders() { if (currencyOrderList.isEmpty()) { return; } if (!isIdealProClosed()) { synchronized (currencyOrderList) { for (TradeOrder order : currencyOrderList) { placeOrder(order); } currencyOrderList.clear(); } } } protected ZonedDateTime getZoneDateTime() { return ZonedDateTime.now(); } protected boolean isIdealProClosed() { return false; } protected TimerTask getCurrencyOrderMonitor() { return new TimerTask() { @Override public void run() { processCurrencyOrders(); } }; } protected void fireOrderEvent(OrderEvent event) { synchronized (orderEventListeners) { for (OrderEventListener listener : orderEventListeners) { listener.orderEvent(event); } } } protected List<IbOrderAndContract> buildOrderAndContract(TradeOrder order) { List<IbOrderAndContract> orderList = new ArrayList<>(); orderMap.put(order.getOrderId(), order); Contract contract = ContractBuilderFactory.getContractBuilder(order.getTicker()) .buildContract(order.getTicker()); Order ibOrder = new Order(); if (order.getType() == TradeOrder.Type.MARKET_ON_OPEN) { order.setDuration(TradeOrder.Duration.MARKET_ON_OPEN); } ibOrder.m_action = IbUtils.getAction(order.getTradeDirection()); ibOrder.m_orderType = IbUtils.getOrderType(order.getType()); ibOrder.m_tif = IbUtils.getTif(order.getDuration()); ibOrder.m_orderId = Integer.parseInt(order.getOrderId()); ibOrder.m_totalQuantity = order.getSize(); if (order.getGoodAfterTime() != null) { ibOrder.m_goodAfterTime = getFormattedDate(order.getGoodAfterTime()); } if (order.getParentOrderId() != null && order.getParentOrderId().length() > 0) { ibOrder.m_parentId = Integer.parseInt(order.getParentOrderId()); } if (order.getDuration() == TradeOrder.Duration.GOOD_UTNIL_TIME) { if (order.getGoodUntilTime() != null) { ibOrder.m_goodTillDate = getFormattedDate(order.getGoodUntilTime()); } else { throw new IllegalStateException( "Duration is good-until-time, but no time has been set in the Order"); } } if (order.getType() == TradeOrder.Type.LIMIT) { if (order.getLimitPrice() == null) { throw new IllegalStateException("Limit price not set for LMT order: " + order.getOrderId()); } double limitPrice = QuoteUtil.getBigDecimalValue(order.getTicker(), order.getLimitPrice()) .doubleValue(); ibOrder.m_lmtPrice = limitPrice; } else if (order.getType() == TradeOrder.Type.STOP) { if (order.getStopPrice() == null) { throw new IllegalStateException("Stop price not set for StopLoss order: " + order.getOrderId()); } double stopPrice = QuoteUtil.getBigDecimalValue(order.getTicker(), order.getStopPrice()).doubleValue(); ibOrder.m_auxPrice = stopPrice; } if (order.getOcaGroup() != null) { ibOrder.m_ocaGroup = order.getOcaGroup(); } if (order.getReference() != null) { ibOrder.m_orderRef = order.getReference(); } //If the order has already been submitted, have TWS transmit immediately, otherwise this order may be //attched to another order which needs to be submitted first. ibOrder.m_transmit = order.isSubmitted(); //the order has now been submitted. order.setSubmitted(true); orderList.add(new IbOrderAndContract(ibOrder, contract)); for (TradeOrder tradeOrder : order.getChildOrders()) { orderList.addAll(buildOrderAndContract(tradeOrder)); } if (order.isSubmitChildOrdersFirst()) { Collections.reverse(orderList); } return orderList; } public void addBrokerErrorListener(BrokerErrorListener listener) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } public void removeBrokerErrorListener(BrokerErrorListener listener) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } public void cancelAndReplaceOrder(String originalOrderId, TradeOrder newOrder) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } protected static class IbOrderAndContract { protected Order order; protected Contract contract; protected IbOrderAndContract(Order order, Contract contract) { this.order = order; this.contract = contract; } public Contract getContract() { return contract; } public Order getOrder() { return order; } } }