/* =========================================================== * TradeManager : a application to trade strategies for the Java(tm) platform * =========================================================== * * (C) Copyright 2011-2011, by Simon Allen and Contributors. * * Project Info: org.trade * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Oracle, Inc. * in the United States and other countries.] * * (C) Copyright 2011-2011, by Simon Allen and Contributors. * * Original Author: Simon Allen; * Contributor(s): -; * * Changes * ------- * */ package org.trade.broker; import java.io.IOException; import java.math.BigDecimal; import java.text.ParseException; import java.time.ZonedDateTime; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.trade.broker.client.Broker; import org.trade.broker.client.ClientSocket; import org.trade.broker.client.ClientWrapper; import org.trade.broker.client.OrderState; import org.trade.core.factory.ClassFactory; import org.trade.core.properties.ConfigProperties; import org.trade.core.util.CoreUtils; import org.trade.core.util.TradingCalendar; import org.trade.core.valuetype.Money; import org.trade.dictionary.valuetype.BarSize; import org.trade.dictionary.valuetype.ChartDays; import org.trade.dictionary.valuetype.OrderStatus; import org.trade.persistent.PersistentModel; import org.trade.persistent.dao.Contract; import org.trade.persistent.dao.TradeOrder; import org.trade.persistent.dao.TradeOrderfill; import org.trade.persistent.dao.Tradestrategy; import org.trade.strategy.data.CandleSeries; import com.ib.client.ContractDetails; /** */ public class BackTestBrokerModel extends AbstractBrokerModel implements ClientWrapper { /** * */ private static final long serialVersionUID = 3191422640254347940L; private final static Logger _log = LoggerFactory.getLogger(BackTestBrokerModel.class); // Use getId as key private static final ConcurrentHashMap<Integer, Tradestrategy> m_historyDataRequests = new ConcurrentHashMap<Integer, Tradestrategy>(); private static final ConcurrentHashMap<Integer, Contract> m_realTimeBarsRequests = new ConcurrentHashMap<Integer, Contract>(); private static final ConcurrentHashMap<Integer, Contract> m_contractRequests = new ConcurrentHashMap<Integer, Contract>(); private PersistentModel m_tradePersistentModel = null; private ClientSocket m_client = null; private static final int SCALE = 5; private static final int minOrderId = 100000; private AtomicInteger orderKey = null; private static Integer backfillDateFormat = 2; private static String backfillWhatToShow; private static Integer backfillOffsetDays = 0; private static Integer backfillUseRTH = 1; static { try { backfillWhatToShow = ConfigProperties.getPropAsString("trade.backfill.whatToShow"); backfillUseRTH = ConfigProperties.getPropAsInt("trade.backfill.useRTH"); } catch (Exception ex) { throw new IllegalArgumentException("Error initializing BrokerModel Msg: " + ex.getMessage()); } } public BackTestBrokerModel() { try { m_client = new ClientSocket(this); m_tradePersistentModel = (PersistentModel) ClassFactory .getServiceForInterface(PersistentModel._persistentModel, this); int maxKey = m_tradePersistentModel.findTradeOrderByMaxKey(); if (maxKey < 100000) { maxKey = 100000; } orderKey = new AtomicInteger(maxKey + 1); } catch (Exception ex) { throw new IllegalArgumentException("Error initializing BrokerModel Msg: " + ex.getMessage()); } } /** * Method getHistoricalData. * * @return ConcurrentHashMap<Integer,Tradestrategy> * @see org.trade.broker.BrokerModel#getHistoricalData() */ public ConcurrentHashMap<Integer, Tradestrategy> getHistoricalData() { return m_historyDataRequests; } /** * Method isConnected. * * @return boolean * @see org.trade.broker.BrokerModel#isConnected() */ public boolean isConnected() { return false; } /** * Method onConnect. * * @param host * String * @param port * Integer * @param clientId * Integer * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onConnect(String, Integer, Integer) */ public void onConnect(String host, Integer port, Integer clientId) { } /** * Method connectionClosed. * * @see com.ib.client.AnyWrapper#connectionClosed() */ public void connectionClosed() { onCancelAllRealtimeData(); this.fireConnectionClosed(true); error(0, 1101, "Error Connection was closed! "); } /** * Method disconnect. * * @throws BrokerModelException * @see org.trade.broker.BrokerModel#disconnect() */ public void onDisconnect() { if (isConnected()) { onCancelAllRealtimeData(); } this.fireConnectionClosed(false); } /** * Method getBackTestBroker. * * @param idTradestrategy * Integer * @see org.trade.broker.BrokerModel#getBackTestBroker(Integer) */ public Broker getBackTestBroker(Integer idTradestrategy) { return m_client.getBackTestBroker(idTradestrategy); } /** * Method getNextRequestId. * * @return Integer * @see org.trade.broker.BrokerModel#getNextRequestId() */ public Integer getNextRequestId() { return new Integer(orderKey.incrementAndGet()); } /** * Method nextValidId. * * @param orderId * int * @see com.ib.client.EWrapper#nextValidId(int) */ public void nextValidId(int orderId) { try { int maxKey = m_tradePersistentModel.findTradeOrderByMaxKey(); if (maxKey < minOrderId) { maxKey = minOrderId; } if (maxKey < orderId) { orderKey = new AtomicInteger(orderId); } else { orderKey = new AtomicInteger(maxKey + 1); } this.fireConnectionOpened(); } catch (Exception ex) { error(orderId, 3210, ex.getMessage()); } } /** * Method onSubscribeAccountUpdates. * * @param subscribe * boolean * @param account * Account * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onSubscribeAccountUpdates(boolean, * account) */ public void onSubscribeAccountUpdates(boolean subscribe, String accountNumber) throws BrokerModelException { } /** * Method onCancelAccountUpdates. * * @param accountNumber * String * @see org.trade.broker.BrokerModel#onCancelAccountUpdates(String) */ public void onCancelAccountUpdates(String accountNumber) { } /** * Method onReqFinancialAccount. * * @see org.trade.broker.onReqFinancialAccount() */ public void onReqFinancialAccount() { } /** * Method onReqReplaceFinancialAccount. * * @param xml * String * @param faDataType * int * * @see org.trade.broker.onReqReplaceFinancialAccount() */ public void onReqReplaceFinancialAccount(int faDataType, String xml) throws BrokerModelException { } /** * Method onReqManagedAccount. * * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onReqManagedAccount() */ public void onReqManagedAccount() throws BrokerModelException { } /** * Method onReqAllOpenOrders. * * @see org.trade.broker.BrokerModel#onReqAllOpenOrders() */ public void onReqAllOpenOrders() { // request list of all open orders // m_client.reqAllOpenOrders(); } /** * Method onReqOpenOrders. * * @see org.trade.broker.BrokerModel#onReqOpenOrders() */ public void onReqOpenOrders() { // request list of all open orders // m_client.reqOpenOrders(); } /** * Method onReqExecutions. * * @param tradestrategy * Tradestrategy * * @param addOrders * boolean * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onReqExecutions(Tradestrategy) */ public void onReqExecutions(Tradestrategy tradestrategy, boolean addOrders) throws BrokerModelException { } /** * Method onReqAllExecutions. * * @param mktOpenDate * ZonedDateTime * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onReqAllExecutions(Date) */ public void onReqAllExecutions(ZonedDateTime mktOpenDate) throws BrokerModelException { } /** * Method onReqRealTimeBars. * * @param contract * Contract * @param mktData * boolean * @throws BrokerModelException */ public void onReqRealTimeBars(Contract contract, boolean mktData) throws BrokerModelException { } /** * Method onReqMarketData. * * @param contract * Contract * @param genericTicklist * String * @param snapshot * boolean * @throws BrokerModelException */ public void onReqMarketData(Contract contract, String genericTicklist, boolean snapshot) throws BrokerModelException { } /** * Method onBrokerData. * * @param tradestrategy * Tradestrategy * @param ZonedDateTime * startDate * @param ZonedDateTime * endDate * @param Integer * barSize * @param Integer * chartDays * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onBrokerData(Contract , String , String * ) */ public void onBrokerData(final Tradestrategy tradestrategy, final ZonedDateTime endDate) throws BrokerModelException { try { if (this.isHistoricalDataRunning(tradestrategy)) { throw new BrokerModelException(tradestrategy.getId(), 3010, "Data request is already in progress for: " + tradestrategy.getContract().getSymbol() + " Please wait or cancel."); } m_historyDataRequests.put(tradestrategy.getId(), tradestrategy); if (this.isBrokerDataOnly()) { ZonedDateTime endDay = TradingCalendar .getDateAtTime(TradingCalendar.addTradingDays(endDate, backfillOffsetDays), endDate); String endDateTime = TradingCalendar.getFormattedDate(endDay, "yyyyMMdd HH:mm:ss"); m_contractRequests.put(tradestrategy.getContract().getId(), tradestrategy.getContract()); _log.debug("onBrokerData ReqId: " + tradestrategy.getId() + " Symbol: " + tradestrategy.getContract().getSymbol() + " end Time: " + endDateTime + " Period length: " + tradestrategy.getChartDays() + " Bar size: " + tradestrategy.getBarSize() + " WhatToShow: " + backfillWhatToShow + " Regular Trading Hrs: " + backfillUseRTH + " Date format: " + backfillDateFormat); m_client.reqHistoricalData(tradestrategy.getId(), tradestrategy, endDateTime, ChartDays.newInstance(tradestrategy.getChartDays()).getDisplayName(), BarSize.newInstance(tradestrategy.getBarSize()).getDisplayName(), backfillWhatToShow, backfillUseRTH, backfillDateFormat); } else { m_client.reqHistoricalData(tradestrategy.getId(), tradestrategy, null, ChartDays.newInstance(tradestrategy.getChartDays()).getDisplayName(), BarSize.newInstance(tradestrategy.getBarSize()).getDisplayName(), backfillWhatToShow, backfillUseRTH, backfillDateFormat); } } catch (Throwable ex) { throw new BrokerModelException(tradestrategy.getId(), 3020, "Error broker data Symbol: " + tradestrategy.getContract().getSymbol() + " Msg: " + ex.getMessage()); } } /** * Method isHistoricalDataRunning. * * @param contract * Contract * @return boolean * @see org.trade.broker.BrokerModel#isHistoricalDataRunning(Contract) */ public boolean isHistoricalDataRunning(Contract contract) { for (Tradestrategy item : m_historyDataRequests.values()) { if (contract.equals(item.getContract())) { return true; } } return false; } /** * Method isHistoricalDataRunning. * * @param tradestrategy * Tradestrategy * @return boolean */ public boolean isHistoricalDataRunning(Tradestrategy tradestrategy) { if (m_historyDataRequests.containsKey(tradestrategy.getId())) { return true; } return false; } /** * Method isRealtimeBarsRunning. * * @param contract * Contract * @return boolean * @see org.trade.broker.BrokerModel#isRealtimeBarsRunning(Contract) */ public boolean isRealtimeBarsRunning(Contract contract) { if (m_realTimeBarsRequests.containsKey(contract.getId())) { return true; } return false; } /** * Method isRealtimeBarsRunning. * * @param tradestrategy * Tradestrategy * @return boolean */ public boolean isRealtimeBarsRunning(Tradestrategy tradestrategy) { if (m_realTimeBarsRequests.containsKey(tradestrategy.getContract().getId())) { Contract contract = m_realTimeBarsRequests.get(tradestrategy.getContract().getId()); for (Tradestrategy item : contract.getTradestrategies()) { if (item.equals(tradestrategy)) { return true; } } } return false; } /** * Method isMarketDataRunning. * * @param contract * Contract * @return boolean * @see org.trade.broker.BrokerModel#isRealtimeBarsRunning(Contract) */ public boolean isMarketDataRunning(Contract contract) { return false; } /** * Method isMarketDataRunning. * * @param tradestrategy * Tradestrategy * @return boolean */ public boolean isMarketDataRunning(Tradestrategy tradestrategy) { return false; } /** * Method isAccountUpdatesRunning. * * @param accountNumber * String * @return boolean * @see org.trade.broker.BrokerModel#isAccountUpdatesRunning(String) */ public boolean isAccountUpdatesRunning(String accountNumber) { return false; } /** * Method onCancelAllRealtimeData. * * @see org.trade.broker.BrokerModel#onCancelAllRealtimeData() */ public void onCancelAllRealtimeData() { m_historyDataRequests.clear(); m_realTimeBarsRequests.clear(); } /** * Method onContractDetails. * * @param contract * Contract * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onContractDetails(Contract) */ public void onContractDetails(final Contract contract) throws BrokerModelException { /* * This will use the Yahoo API to get the data. */ m_contractRequests.put(contract.getId(), contract); } /** * Method onCancelContractDetails. * * @param contract * Contract * @see org.trade.broker.BrokerModel#onCancelContractDetails(Contract) */ public void onCancelContractDetails(Contract contract) { } /** * Method onCancelBrokerData. * * @param tradestrategy * Tradestrategy */ public void onCancelBrokerData(Tradestrategy tradestrategy) { if (m_historyDataRequests.containsKey(tradestrategy.getId())) { tradestrategy.getContract().removeTradestrategy(tradestrategy); synchronized (m_historyDataRequests) { m_historyDataRequests.remove(tradestrategy.getId()); m_historyDataRequests.notify(); } } m_client.removeBackTestBroker(tradestrategy.getId()); } /** * Method onCancelBrokerData. * * @param contract * Contract * @see org.trade.broker.BrokerModel#onCancelRealtimeBars(Contract) */ public void onCancelBrokerData(Contract contract) { for (Tradestrategy tradestrategy : m_historyDataRequests.values()) { if (contract.equals(tradestrategy.getContract())) { contract.removeTradestrategy(tradestrategy); m_client.removeBackTestBroker(tradestrategy.getId()); synchronized (m_historyDataRequests) { m_historyDataRequests.remove(tradestrategy.getId()); m_historyDataRequests.notify(); } } } } /** * Method onCancelRealtimeBars. * * @param contract * Contract * @see org.trade.broker.BrokerModel#onCancelRealtimeBars(Contract) */ public void onCancelRealtimeBars(Contract contract) { if (m_realTimeBarsRequests.containsKey(contract.getId())) { synchronized (m_realTimeBarsRequests) { m_realTimeBarsRequests.remove(contract.getId()); } } } /** * Method onCancelRealtimeBars. * * @param tradestrategy * Tradestrategy */ public void onCancelRealtimeBars(Tradestrategy tradestrategy) { if (m_realTimeBarsRequests.containsKey(tradestrategy.getContract().getId())) { Contract contract = m_realTimeBarsRequests.get(tradestrategy.getContract().getId()); for (Tradestrategy item : contract.getTradestrategies()) { if (item.equals(tradestrategy)) { contract.removeTradestrategy(tradestrategy); break; } } if (contract.getTradestrategies().isEmpty()) onCancelRealtimeBars(contract); } } /** * Method onCancelMarketData. * * @param contract * Contract * @see org.trade.broker.BrokerModel#onCancelRealtimeBars(Contract) */ public void onCancelMarketData(Contract contract) { } /** * Method onCancelMarketData. * * @param tradestrategy * Tradestrategy */ public void onCancelMarketData(Tradestrategy tradestrategy) { } /** * Method onPlaceOrd * * @param contract * Contract * @param tradeOrder * TradeOrder * @return TradeOrder * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onPlaceOrder(Contract, TradeOrder) */ public TradeOrder onPlaceOrder(final Contract contract, final TradeOrder tradeOrder) throws BrokerModelException { try { synchronized (tradeOrder) { if (null == tradeOrder.getOrderKey()) { tradeOrder.setOrderKey(getNextRequestId()); } if (null == tradeOrder.getClientId()) { tradeOrder.setClientId(999); } TradeOrder transientInstance = m_tradePersistentModel.persistTradeOrder(tradeOrder); // Debug logging _log.debug("Order Placed Key: " + transientInstance.getOrderKey()); TWSBrokerModel.logContract(TWSBrokerModel.getIBContract(contract)); TWSBrokerModel.logTradeOrder(TWSBrokerModel.getIBOrder(transientInstance)); return transientInstance; } } catch (Exception ex) { throw new BrokerModelException(tradeOrder.getOrderKey(), 3030, "Could not save or place TradeOrder: " + tradeOrder.getOrderKey() + " Msg: " + ex.getMessage()); } } /** * Method onCancelOrder. * * @param tradeOrder * TradeOrder * @throws BrokerModelException * @see org.trade.broker.BrokerModel#onCancelOrder(TradeOrder) */ public void onCancelOrder(TradeOrder tradeOrder) throws BrokerModelException { try { OrderState orderState = new OrderState(); orderState.m_status = OrderStatus.CANCELLED; openOrder(tradeOrder.getOrderKey(), null, tradeOrder, orderState); } catch (Exception ex) { throw new BrokerModelException(tradeOrder.getOrderKey(), 3040, "Could not CancelOrder: " + ex.getMessage()); } } /** * Method execDetails. * * When orders are filled the the exceDetails is fired followed by * openOrder() and orderStatus() the order methods fire twice. openOrder * gives us the commission amount on the second fire and order status from * both. Apart from that I have no idea why they fire twice. I assume its to * do with the margin and account updates. * * @param reqId * int * @param contractIB * com.ib.client.Contract * @param execution * Execution * @see http://www.interactivebrokers.com/php/apiUsersGuide/apiguide.htm */ public void execDetails(int reqId, Contract contractIB, TradeOrderfill execution) { try { BackTestBrokerModel.logExecution(execution); TradeOrder transientInstance = m_tradePersistentModel .findTradeOrderByKey(execution.getTradeOrder().getOrderKey()); if (null == transientInstance) { error(execution.getTradeOrder().getOrderKey(), 3170, "Warning Order not found for Order Key: " + execution.getTradeOrder().getOrderKey() + " make sure Client ID: " + 0 + " is not the master in TWS. On execDetails update."); return; } /* * We already have this order fill. */ if (transientInstance.existTradeOrderfill(execution.getExecId())) return; TradeOrderfill tradeOrderfill = new TradeOrderfill(); BackTestBrokerModel.populateTradeOrderfill(execution, tradeOrderfill); tradeOrderfill.setTradeOrder(transientInstance); transientInstance.addTradeOrderfill(tradeOrderfill); transientInstance.setAverageFilledPrice(tradeOrderfill.getAveragePrice()); transientInstance.setFilledQuantity(tradeOrderfill.getCumulativeQuantity()); transientInstance.setFilledDate(tradeOrderfill.getTime()); boolean isFilled = transientInstance.getIsFilled(); TradeOrder updatedOrder = m_tradePersistentModel.persistTradeOrderfill(transientInstance); // Let the controller know an order was filled if (updatedOrder.getIsFilled() && !isFilled) this.fireTradeOrderFilled(updatedOrder); } catch (Exception ex) { error(reqId, 3160, "Errors saving execution: " + ex.getMessage()); } } /** * Method execDetailsEnd. * * @param reqId * int */ public void execDetailsEnd(int reqId) { } /** * Method openOrder. * * This method is called to feed in open orders. * * @param orderId * int * @param contractIB * com.ib.client.Contract * @param order * com.ib.client.Order * @param orderState * OrderState * @see http://www.interactivebrokers.com/php/apiUsersGuide/apiguide.htm */ public void openOrder(int orderId, final Contract contract, final TradeOrder tradeOrder, final OrderState orderState) { try { TradeOrder transientInstance = m_tradePersistentModel.findTradeOrderByKey(tradeOrder.getOrderKey()); if (null == transientInstance) { error(orderId, 3170, "Warning Order not found for Order Key: " + orderId + " make sure Client ID: " + 0 + " is not the master in TWS. On openOrder update."); return; } /* * Check to see if anything has changed as this method gets fired * twice on order fills. */ if (BackTestBrokerModel.updateTradeOrder(tradeOrder, orderState, transientInstance)) { if (OrderStatus.FILLED.equals(transientInstance.getStatus())) { _log.debug("Order Key: " + transientInstance.getOrderKey() + " filled."); BackTestBrokerModel.logOrderState(orderState); BackTestBrokerModel.logTradeOrder(tradeOrder); TradeOrder updatedOrder = m_tradePersistentModel.persistTradeOrder(transientInstance); if (updatedOrder.hasTradePosition() && !updatedOrder.getTradePosition().isOpen()) { // Let the controller know a position was closed this.firePositionClosed(updatedOrder.getTradePosition()); } } else { _log.debug("Order key: " + transientInstance.getOrderKey() + " state changed. Status:" + orderState.m_status); BackTestBrokerModel.logOrderState(orderState); BackTestBrokerModel.logTradeOrder(tradeOrder); TradeOrder updatedOrder = m_tradePersistentModel.persistTradeOrder(transientInstance); if (OrderStatus.CANCELLED.equals(updatedOrder.getStatus())) { // Let the controller know a position was closed this.fireTradeOrderCancelled(updatedOrder); } } } } catch (Exception ex) { error(orderId, 3180, "Errors updating open order: " + ex.getMessage()); } } public void openOrderEnd() { } /** * Method orderStatus. * * This method is called whenever the status of an order changes. It is also * fired after reconnecting to TWS if the client has any open orders. * * @param orderId * int * @param status * String * @param filled * int * @param remaining * int * @param avgFillPrice * double * @param permId * int * @param parentId * int * @param lastFillPrice * double * @param clientId * int * @param whyHeld * String * * @see http://www.interactivebrokers.com/php/apiUsersGuide/apiguide.htm */ public void orderStatus(int orderId, String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld) { try { TradeOrder transientInstance = m_tradePersistentModel.findTradeOrderByKey(new Integer(orderId)); if (null == transientInstance) { error(orderId, 3170, "Warning Order not found for Order Key: " + orderId + " make sure Client ID: " + 0 + " is not the master in TWS. On orderStatus update."); return; } /* * Check to see if anything has changed as this method gets fired * twice on order fills. */ boolean changed = false; if (CoreUtils.nullSafeComparator(transientInstance.getStatus(), status.toUpperCase()) != 0) { transientInstance.setStatus(status.toUpperCase()); changed = true; } if (CoreUtils.nullSafeComparator(transientInstance.getWhyHeld(), whyHeld) != 0) { transientInstance.setWhyHeld(whyHeld); changed = true; } /* * If filled qty is greater than current filled qty set the new * value. */ if (CoreUtils.nullSafeComparator(new Integer(filled), transientInstance.getFilledQuantity()) == 1) { if (filled > 0) { transientInstance.setAverageFilledPrice(new BigDecimal(avgFillPrice)); transientInstance.setFilledQuantity(filled); changed = true; } } if (changed) { transientInstance.setLastUpdateDate(TradingCalendar.getDateTimeNowMarketTimeZone()); transientInstance.setStatus(status.toUpperCase()); transientInstance.setWhyHeld(whyHeld); _log.debug("Order Status changed. Status: " + status); TWSBrokerModel.logOrderStatus(orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld); boolean isFilled = transientInstance.getIsFilled(); TradeOrder updatedOrder = m_tradePersistentModel.persistTradeOrder(transientInstance); if (OrderStatus.CANCELLED.equals(updatedOrder.getStatus())) { // Let the controller know a position was closed this.fireTradeOrderCancelled(updatedOrder); } else { this.fireTradeOrderStatusChanged(updatedOrder); // Let the controller know an order was filled if (updatedOrder.getIsFilled() && !isFilled) this.fireTradeOrderFilled(updatedOrder); } } } catch (Exception ex) { error(orderId, 3100, "Errors updating open order status: " + ex.getMessage()); } } /** * Method error. * * @param e * Exception */ public void error(Exception ex) { _log.error("BrokerModel error msg: " + ex.getMessage()); // this.fireBrokerError(new BrokerManagerModelException(e)); } /** * Method error. * * @param msg * String */ public void error(String msg) { _log.error("BrokerModel error str: " + msg); // this.fireBrokerError(new BrokerManagerModelException(msg)); } /** * * 0 - 999 are IB TWS error codes for Orders or data 1000 - 1999 are IB TWS * System error 2000 - 2999 are IB TWS Warning 4000 - 4999 are application * warnings 5000 - 5999 are application information * * * @param id * int * @param code * int * @param msg * String * @see org.trade.broker.BrokerModel#error(int, int, String) */ public void error(int id, int code, String msg) { String symbol = "N/A"; BrokerModelException brokerModelException = null; if (m_contractRequests.containsKey(id)) { symbol = m_contractRequests.get(id).getSymbol(); synchronized (m_contractRequests) { m_contractRequests.remove(id); } } if (m_historyDataRequests.containsKey(id)) { symbol = m_historyDataRequests.get(id).getContract().getSymbol(); synchronized (m_historyDataRequests) { m_historyDataRequests.remove(id); m_historyDataRequests.notify(); } } if (m_realTimeBarsRequests.containsKey(id)) { symbol = m_realTimeBarsRequests.get(id).getSymbol(); } /* * Error code 162 (Historical data request pacing violation)and 366 (No * historical data query found for ticker id) are error code for no * market or historical data found. * * Error code 202, Order cancelled 201, Order rejected * * Error code 321 Error validating request:-'jd' : cause - FA data * operations ignored for non FA customers. * * Error code 502, Couldn't connect to TWS. Confirm that API is enabled * in TWS via the Configure>API menu command. */ String errorMsg = "Req/Order Id: " + id + " Code: " + code + " symbol: " + symbol + " Msg: " + msg; if (((code > 1999) && (code < 3000)) || ((code >= 200) && (code < 299)) || (code == 366) || (code == 162) || (code == 321) || (code == 3170)) { if (((code > 1999) && (code < 3000))) { _log.info(errorMsg); brokerModelException = new BrokerModelException(3, code, errorMsg); } else if (code == 202 || code == 201 || code == 3170) { _log.warn(errorMsg); brokerModelException = new BrokerModelException(2, code, errorMsg); } else if (code == 321) { _log.info(errorMsg); return; } else { _log.warn(errorMsg); brokerModelException = new BrokerModelException(2, code, errorMsg); } } else { if (m_realTimeBarsRequests.containsKey(id)) { synchronized (m_realTimeBarsRequests) { m_realTimeBarsRequests.remove(id); } } _log.error(errorMsg); brokerModelException = new BrokerModelException(1, code, errorMsg); } this.fireBrokerError(brokerModelException); } /** * Method contractDetails. * * @param reqId * int * @param contractDetails * ContractDetails * @see com.ib.client.EWrapper#contractDetails(int, ContractDetails) */ public void contractDetails(int reqId, Contract contractDetails) { try { if (m_contractRequests.containsKey(reqId)) { Contract contract = m_contractRequests.get(reqId); BackTestBrokerModel.logContract(contractDetails); if (BackTestBrokerModel.populateContract(contractDetails, contract)) { m_tradePersistentModel.persistContract(contract); synchronized (m_contractRequests) { m_contractRequests.remove(reqId); } } else { error(reqId, 3220, "Contract details not found for reqId: " + reqId + " Symbol: " + contractDetails.getSymbol()); } } } catch (Exception ex) { error(reqId, 3230, ex.getMessage()); } } /** * Method contractDetailsEnd. * * @param reqId * int */ public void contractDetailsEnd(int reqId) { if (m_contractRequests.containsKey(reqId)) { synchronized (m_contractRequests) { m_contractRequests.remove(reqId); } } } /** * Method historicalData. * * @param reqId * int * @param dateString * String * @param open * double * @param high * double * @param low * double * @param close * double * @param volume * int * @param tradeCount * int * @param vwap * double * @param hasGaps * boolean */ public void historicalData(int reqId, String dateString, double open, double high, double low, double close, int volume, int tradeCount, double vwap, boolean hasGaps) { try { volume = volume * 100; /* * Check to see if the trading day is today and this strategy is * selected to trade and that the market is open */ if (m_historyDataRequests.containsKey(reqId)) { Tradestrategy tradestrategy = m_historyDataRequests.get(reqId); if (dateString.contains("finished-")) { /* * The last one has arrived the reqId is the * tradeStrategyId. Remove this from the processing vector. */ CandleSeries candleSeries = tradestrategy.getStrategyData().getBaseCandleSeries(); m_tradePersistentModel.persistCandleSeries(candleSeries); _log.debug("HistoricalData complete Req Id: " + reqId + " Symbol: " + tradestrategy.getContract().getSymbol() + " Tradingday: " + tradestrategy.getTradingday().getOpen() + " candles to saved: " + candleSeries.getItemCount() + " Contract Tradestrategies size:: " + tradestrategy.getContract().getTradestrategies().size()); /* * Check to see if the trading day is today and this * strategy is selected to trade and that the market is open */ if (tradestrategy.getTrade() && !this.isBrokerDataOnly()) { this.fireHistoricalDataComplete(tradestrategy); } else { synchronized (m_historyDataRequests) { m_historyDataRequests.remove(reqId); m_historyDataRequests.notify(); } } } else { ZonedDateTime date = null; /* * There is a bug in the TWS interface format for dates * should always be milli sec but when 1 day is selected as * the period the dates come through as yyyyMMdd. */ if (dateString.length() == 8) { date = TradingCalendar.getZonedDateTimeFromDateString(dateString, "yyyyMMdd", TradingCalendar.MKT_TIMEZONE); } else { date = TradingCalendar.getZonedDateTimeFromMilli((Long.parseLong(dateString) * 1000)); } /* * For daily bars set the time to the open time. */ if (tradestrategy.getBarSize() > 3600) { date = TradingCalendar.getDateAtTime(date, tradestrategy.getTradingday().getOpen()); } if (tradestrategy.getTradingday().getClose().isAfter(date)) { if (backfillUseRTH == 1 && !TradingCalendar.isMarketHours(tradestrategy.getTradingday().getOpen(), tradestrategy.getTradingday().getClose(), date)) return; BigDecimal price = (new BigDecimal(close)).setScale(SCALE, BigDecimal.ROUND_HALF_EVEN); tradestrategy.getStrategyData().getBaseCandleSeries().getContract().setLastAskPrice(price); tradestrategy.getStrategyData().getBaseCandleSeries().getContract().setLastBidPrice(price); tradestrategy.getStrategyData().getBaseCandleSeries().getContract().setLastPrice(price); tradestrategy.getStrategyData().buildCandle(date, open, high, low, close, volume, vwap, tradeCount, 1, null); } } } else { _log.error("HistoricalData request not found for Req Id: " + reqId + " Date: " + dateString); } } catch (Exception ex) { error(reqId, 3260, ex.getMessage()); } } /** * Method realtimeBar. * * @param reqId * int * @param time * long * @param open * double * @param high * double * @param low * double * @param close * double * @param volume * long * @param vwap * double * @param tradeCount * int */ public synchronized void realtimeBar(int reqId, long time, double open, double high, double low, double close, long volume, double vwap, int tradeCount) { } /** * Method validateBrokerData. * * @param tradestrategy * Tradestrategy * * @return boolean * @throws BrokerModelException */ public boolean validateBrokerData(Tradestrategy tradestrategy) throws BrokerModelException { boolean valid = true; String errorMsg = "Symbol: " + tradestrategy.getContract().getSymbol() + " Bar Size/Chart Days combination was not valid for Yahoo API, these values have been updated." + "\n" + "Please validate and save." + "\n" + "Note Chart Days/BarSize combinations for Yahoo: " + "\n" + "Chart Hist = 1 D, Bar Size >= 1min" + "\n" + "Chart Hist > 1 D to 1 M, Bar Size >= 5min" + "\n" + "Chart Hist > 1 M to 3 M, Bar Size = 1 day"; if (tradestrategy.getBarSize() < 60) { tradestrategy.setBarSize(60); valid = false; } else if ((tradestrategy.getChartDays() > 1 && tradestrategy.getChartDays() < 7) && tradestrategy.getBarSize() < 300) { tradestrategy.setBarSize(300); valid = false; } else if (tradestrategy.getChartDays() > 30 && (tradestrategy.getBarSize() <= 3600)) { tradestrategy.setBarSize(1); valid = false; } if ((tradestrategy.getBarSize() < 300) && tradestrategy.getChartDays() > 1) { tradestrategy.setChartDays(1); valid = false; } else if (tradestrategy.getBarSize() <= 3600 && tradestrategy.getChartDays() > 30) { tradestrategy.setChartDays(7); valid = false; } if (!valid) { tradestrategy.setDirty(true); throw new BrokerModelException(1, 3901, errorMsg); } return true; } /** * Method logOrderState. * * @param orderState * OrderState */ public static void logOrderState(OrderState orderState) { _log.debug("Status: " + orderState.m_status + " Comms Amt: " + orderState.m_commission + " Comms Currency: " + orderState.m_commissionCurrency + " Warning txt: " + orderState.m_warningText + " Init Margin: " + orderState.m_initMargin + " Maint Margin: " + orderState.m_maintMargin + " Min Comms: " + orderState.m_minCommission + " Max Comms: " + orderState.m_maxCommission); } /** * Method updateTradeOrder. * * @param ibOrder * com.ib.client.Order * @param ibOrderState * OrderState * @param order * TradeOrder * @return boolean * @throws ParseException */ public static boolean updateTradeOrder(TradeOrder clientOrder, OrderState clientOrderState, TradeOrder order) throws ParseException { boolean changed = false; if (CoreUtils.nullSafeComparator(order.getOrderKey(), clientOrder.getOrderKey()) == 0) { if (CoreUtils.nullSafeComparator(order.getStatus(), clientOrderState.m_status.toUpperCase()) != 0) { order.setStatus(clientOrderState.m_status.toUpperCase()); changed = true; } if (CoreUtils.nullSafeComparator(order.getWarningMessage(), clientOrderState.m_warningText) != 0) { order.setWarningMessage(clientOrderState.m_warningText); changed = true; } Money comms = new Money(clientOrderState.m_commission); if (CoreUtils.nullSafeComparator(comms, new Money(Double.MAX_VALUE)) != 0 && CoreUtils.nullSafeComparator(order.getCommission(), comms.getBigDecimalValue()) != 0) { order.setCommission(comms.getBigDecimalValue()); changed = true; } if (CoreUtils.nullSafeComparator(order.getClientId(), clientOrder.getClientId()) != 0) { order.setClientId(clientOrder.getClientId()); changed = true; } if (CoreUtils.nullSafeComparator(order.getAction(), clientOrder.getAction()) != 0) { order.setAction(clientOrder.getAction()); changed = true; } if (CoreUtils.nullSafeComparator(order.getQuantity(), clientOrder.getQuantity()) != 0) { order.setQuantity(clientOrder.getQuantity()); changed = true; } if (CoreUtils.nullSafeComparator(order.getOrderType(), clientOrder.getOrderType()) != 0) { order.setOrderType(clientOrder.getOrderType()); changed = true; } if (CoreUtils.nullSafeComparator(new Money(clientOrder.getLimitPrice()), new Money(Double.MAX_VALUE)) != 0 && CoreUtils.nullSafeComparator(order.getLimitPrice(), clientOrder.getLimitPrice()) != 0) { order.setLimitPrice(clientOrder.getLimitPrice()); changed = true; } if (CoreUtils.nullSafeComparator(new Money(clientOrder.getAuxPrice()), new Money(Double.MAX_VALUE)) != 0 && CoreUtils.nullSafeComparator(order.getAuxPrice(), clientOrder.getAuxPrice()) != 0) { order.setAuxPrice(clientOrder.getAuxPrice()); changed = true; } if (CoreUtils.nullSafeComparator(order.getTimeInForce(), clientOrder.getTimeInForce()) != 0) { order.setTimeInForce(clientOrder.getTimeInForce()); changed = true; } if (CoreUtils.nullSafeComparator(order.getOcaGroupName(), clientOrder.getOcaGroupName()) != 0) { order.setOcaGroupName(clientOrder.getOcaGroupName()); changed = true; } if (CoreUtils.nullSafeComparator(order.getOcaType(), clientOrder.getOcaType()) != 0) { order.setOcaType(clientOrder.getOcaType()); changed = true; } if (CoreUtils.nullSafeComparator(order.getOrderReference(), clientOrder.getOrderReference()) != 0) { order.setOrderReference(clientOrder.getOrderReference()); changed = true; } if (CoreUtils.nullSafeComparator(order.getPermId(), clientOrder.getPermId()) != 0) { order.setPermId(clientOrder.getPermId()); changed = true; } if (CoreUtils.nullSafeComparator(order.getParentId(), clientOrder.getParentId()) != 0) { order.setParentId(clientOrder.getParentId()); changed = true; } if (CoreUtils.nullSafeComparator(order.getTransmit(), clientOrder.getTransmit()) != 0) { order.setTransmit(clientOrder.getTransmit()); changed = true; } if (CoreUtils.nullSafeComparator(order.getDisplayQuantity(), clientOrder.getDisplayQuantity()) != 0) { order.setDisplayQuantity(clientOrder.getDisplayQuantity()); changed = true; } if (CoreUtils.nullSafeComparator(order.getTriggerMethod(), clientOrder.getTriggerMethod()) != 0) { order.setTriggerMethod(clientOrder.getTriggerMethod()); changed = true; } if (CoreUtils.nullSafeComparator(order.getHidden(), clientOrder.getHidden()) != 0) { order.setHidden(clientOrder.getHidden()); changed = true; } if (null != clientOrder.getGoodAfterTime()) { if (CoreUtils.nullSafeComparator(order.getGoodAfterTime(), clientOrder.getGoodAfterTime()) != 0) { order.setGoodAfterTime(clientOrder.getGoodAfterTime()); changed = true; } } if (null != clientOrder.getGoodTillTime()) { if (CoreUtils.nullSafeComparator(order.getGoodTillTime(), clientOrder.getGoodTillTime()) != 0) { order.setGoodTillTime(clientOrder.getGoodTillTime()); changed = true; } } if (CoreUtils.nullSafeComparator(order.getOverrideConstraints(), clientOrder.getOverrideConstraints()) != 0) { order.setOverrideConstraints(clientOrder.getOverrideConstraints()); changed = true; } if (CoreUtils.nullSafeComparator(order.getAllOrNothing(), clientOrder.getAllOrNothing()) != 0) { order.setAllOrNothing(clientOrder.getAllOrNothing()); changed = true; } if (changed) order.setLastUpdateDate(TradingCalendar.getDateTimeNowMarketTimeZone()); } return changed; } /** * Method populateContract. * * @param contractDetails * com.ib.client.ContractDetails * @param transientContract * Contract * @throws ParseException */ public static boolean populateContract(Contract contractDetails, Contract transientContract) throws ParseException { boolean changed = false; if (CoreUtils.nullSafeComparator(transientContract.getSymbol(), contractDetails.getSymbol()) == 0) { if (CoreUtils.nullSafeComparator(transientContract.getLocalSymbol(), contractDetails.getLocalSymbol()) != 0) { transientContract.setLocalSymbol(contractDetails.getLocalSymbol()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getIdContractIB(), contractDetails.getIdContractIB()) != 0) { transientContract.setIdContractIB(contractDetails.getIdContractIB()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getPrimaryExchange(), contractDetails.getPrimaryExchange()) != 0) { transientContract.setPrimaryExchange(contractDetails.getPrimaryExchange()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getExchange(), contractDetails.getExchange()) != 0) { transientContract.setExchange(contractDetails.getExchange()); changed = true; } if (null != contractDetails.getExpiry()) { if (CoreUtils.nullSafeComparator(transientContract.getExpiry(), contractDetails.getExpiry()) != 0) { transientContract.setExpiry(contractDetails.getExpiry()); changed = true; } } if (CoreUtils.nullSafeComparator(transientContract.getSecIdType(), contractDetails.getSecIdType()) != 0) { transientContract.setSecIdType(contractDetails.getSecIdType()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getLongName(), contractDetails.getLongName()) != 0) { transientContract.setLongName(contractDetails.getLongName()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getCurrency(), contractDetails.getCurrency()) != 0) { transientContract.setCurrency(contractDetails.getCurrency()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getCategory(), contractDetails.getCategory()) != 0) { transientContract.setCategory(contractDetails.getCategory()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getIndustry(), contractDetails.getIndustry()) != 0) { transientContract.setIndustry(contractDetails.getIndustry()); changed = true; } Money minTick = new Money(contractDetails.getMinTick()); if (CoreUtils.nullSafeComparator(minTick, new Money(Double.MAX_VALUE)) != 0 && CoreUtils .nullSafeComparator(transientContract.getMinTick(), minTick.getBigDecimalValue()) != 0) { transientContract.setMinTick(minTick.getBigDecimalValue()); changed = true; } Money priceMagnifier = new Money(contractDetails.getPriceMagnifier()); if (CoreUtils.nullSafeComparator(priceMagnifier, new Money(Double.MAX_VALUE)) != 0 && CoreUtils.nullSafeComparator(transientContract.getPriceMagnifier(), priceMagnifier.getBigDecimalValue()) != 0) { transientContract.setPriceMagnifier(priceMagnifier.getBigDecimalValue()); changed = true; } Money multiplier = new Money(contractDetails.getPriceMultiplier()); if (CoreUtils.nullSafeComparator(multiplier, new Money(Double.MAX_VALUE)) != 0 && CoreUtils .nullSafeComparator(transientContract.getPriceMultiplier(), multiplier.getBigDecimalValue()) != 0) { transientContract.setPriceMultiplier(multiplier.getBigDecimalValue()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getSubCategory(), contractDetails.getSubCategory()) != 0) { transientContract.setSubCategory(contractDetails.getSubCategory()); changed = true; } if (CoreUtils.nullSafeComparator(transientContract.getTradingClass(), contractDetails.getTradingClass()) != 0) { transientContract.setTradingClass(contractDetails.getTradingClass()); changed = true; } } return changed; } /** * Method populateTradeOrderfill. * * @param execution * com.ib.client.Execution * @param tradeOrderfill * TradeOrderfill * @throws ParseException * @throws IOException */ public static void populateTradeOrderfill(TradeOrderfill execution, TradeOrderfill tradeOrderfill) throws ParseException, IOException { tradeOrderfill.setTime(execution.getTime()); tradeOrderfill.setExchange(execution.getExchange()); tradeOrderfill.setSide(execution.getSide()); tradeOrderfill.setQuantity(execution.getQuantity()); tradeOrderfill.setPrice(execution.getPrice()); tradeOrderfill.setAccountNumber(execution.getAccountNumber()); tradeOrderfill.setAveragePrice(execution.getAveragePrice()); tradeOrderfill.setCumulativeQuantity(execution.getCumulativeQuantity()); tradeOrderfill.setExecId(execution.getExecId()); } /** * Method logOrderStatus. * * @param orderId * int * @param status * String * @param filled * int * @param remaining * int * @param avgFillPrice * double * @param permId * int * @param parentId * int * @param lastFillPrice * double * @param clientId * int * @param whyHeld * String */ public static void logOrderStatus(int orderId, String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld) { _log.debug("orderId: " + orderId + " status: " + status + " filled: " + filled + " remaining: " + remaining + " avgFillPrice: " + avgFillPrice + " permId: " + permId + " parentId: " + parentId + " lastFillPrice: " + lastFillPrice + " clientId: " + clientId + " whyHeld: " + whyHeld); } /** * Method logTradeOrder. * * @param order * TradeOrder */ public static void logTradeOrder(TradeOrder order) { _log.debug("OrderKey: " + +order.getOrderKey() + " ClientId: " + order.getClientId() + " PermId: " + order.getPermId() + " Action: " + order.getAction() + " TotalQuantity: " + order.getQuantity() + " OrderType: " + order.getOrderType() + " LmtPrice: " + order.getLimitPrice() + " AuxPrice: " + order.getAuxPrice() + " Tif: " + order.getTimeInForce() + " OcaGroup: " + order.getOcaGroupName() + " OcaType: " + order.getOcaType() + " OrderRef: " + order.getOrderReference() + " Transmit: " + order.getTransmit() + " DisplaySize: " + order.getDisplayQuantity() + " TriggerMethod: " + order.getTriggerMethod() + " Hidden: " + order.getHidden() + " ParentId: " + order.getParentId() + " GoodAfterTime: " + order.getGoodAfterTime() + " GoodTillDate: " + order.getGoodTillTime() + " OverridePercentageConstraints: " + order.getOverrideConstraints() + " AllOrNone: " + order.getAllOrNothing()); } /** * Method logContract. * * @param contect * com.ib.client.Contract */ public static void logContract(Contract contract) { _log.debug("Symbol: " + contract.getSymbol() + " Sec Type: " + contract.getSecType() + " Exchange: " + contract.getExchange() + " Con Id: " + contract.getIdContractIB() + " Currency: " + contract.getCurrency() + " SecIdType: " + contract.getSecIdType() + " Primary Exch: " + contract.getPrimaryExchange() + " Local Symbol: " + contract.getLocalSymbol() + " Multiplier: " + contract.getPriceMultiplier() + " Expiry: " + contract.getExpiry() + " Category: " + contract.getCategory() + " Industry: " + contract.getIndustry() + " LongName: " + contract.getLongName()); } /** * Method logExecution. * * @param execution * com.ib.client.Execution */ public static void logExecution(TradeOrderfill execution) { _log.debug("execDetails OrderId: " + execution.getTradeOrder().getIdTradeOrder() + " Exchange: " + execution.getExchange() + " Side: " + execution.getSide() + " ExecId: " + execution.getExecId() + " Time: " + execution.getTime() + " Qty: " + execution.getQuantity() + " AveragePrice: " + execution.getAveragePrice() + " Price: " + execution.getPrice() + " CumulativeQuantity: " + execution.getCumulativeQuantity()); } }