package jtrade.trader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import jtrade.Symbol; import jtrade.marketfeed.Bar; import jtrade.marketfeed.BarListener; import jtrade.marketfeed.IBMarketFeed; import jtrade.marketfeed.MarketFeed; import jtrade.marketfeed.MarketListener; import jtrade.marketfeed.Tick; import jtrade.marketfeed.TickListener; import jtrade.util.Configurable; import jtrade.util.Util; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MarkerFactory; public class DummyTrader implements Trader, MarketListener, BarListener, TickListener { private static final Logger logger = LoggerFactory.getLogger(DummyTrader.class); private static final Logger blotter = LoggerFactory.getLogger("blotter"); public final Configurable<Integer> EXECUTION_DELAY_MILLIS = new Configurable<Integer>("EXECUTION_DELAY_MILLIS", 500); public final Configurable<String> BASE_CURRENCY = new Configurable<String>("BASE_CURRENCY", "EUR"); public final Configurable<Double> INITIAL_CAPITAL = new Configurable<Double>("INITIAL_CAPITAL", 10000.0); public final Configurable<Double> EXCHANGE_RATE_USD = new Configurable<Double>("EXCHANGE_RATE_USD", 1.0); public final Configurable<Double> EXCHANGE_RATE_SEK = new Configurable<Double>("EXCHANGE_RATE_SEK", 1.0); public final Configurable<Double> EXCHANGE_RATE_EUR = new Configurable<Double>("EXCHANGE_RATE_EUR", 1.0); public final Configurable<Double> EXCHANGE_RATE_GBP = new Configurable<Double>("EXCHANGE_RATE_GBP", 1.0); public final Configurable<Double> COMMISSION_MIN = new Configurable<Double>("COMMISSION_MIN", 0.0); public final Configurable<Double> COMMISSION_MAX = new Configurable<Double>("COMMISSION_MIN", 0.0); public final Configurable<Double> COMMISSION_PER_SHARE = new Configurable<Double>("COMMISSION_MIN", 0.0); public final Configurable<Double> COMMISSION_RATE = new Configurable<Double>("COMMISSION_MIN", 0.0); public final Configurable<Boolean> VERBOSE = new Configurable<Boolean>("VERBOSE", false); protected MarketFeed marketFeed; protected List<OrderListener> orderListeners; protected boolean connected; protected int nextValidOrderId; protected Map<Integer, OpenOrder> openOrdersById; protected DateTime lastProcessOrder; protected Portfolio portfolio; protected PerformanceTracker performanceTracker; protected Commission commission; public DummyTrader(MarketFeed marketFeed) { this.marketFeed = marketFeed; orderListeners = new ArrayList<OrderListener>(); openOrdersById = new ConcurrentHashMap<Integer, OpenOrder>(); portfolio = new Portfolio(marketFeed); portfolio.setBaseCurrency(BASE_CURRENCY.get()); portfolio.setCash(BASE_CURRENCY.get(), INITIAL_CAPITAL.get()); portfolio.setExchangeRate("USD", EXCHANGE_RATE_USD.get()); portfolio.setExchangeRate("SEK", EXCHANGE_RATE_SEK.get()); portfolio.setExchangeRate("EUR", EXCHANGE_RATE_EUR.get()); portfolio.setExchangeRate("GBP", EXCHANGE_RATE_GBP.get()); performanceTracker = new PerformanceTracker(marketFeed); commission = new Commission(0.0, 0.0, 0.0, 0.0, 0.0); if (marketFeed instanceof IBMarketFeed) { Configurable.configure(VERBOSE, true); } } @Override public Portfolio getPortfolio() { return portfolio; } @Override public PerformanceTracker getPerformanceTracker() { return performanceTracker; } @Override public void connect() { if (VERBOSE.get()) { logger.info("Connected"); } connected = true; marketFeed.addMarketListener(this); try { marketFeed.addBarListener(this); } catch (UnsupportedOperationException e) { marketFeed.addTickListener(this); } } @Override public void disconnect() { if (VERBOSE.get()) { logger.info("Disconnected"); } connected = false; try { marketFeed.removeBarListener(this); } catch (UnsupportedOperationException e) { marketFeed.removeTickListener(this); } } @Override public boolean isConnected() { return connected; } public void reset() { logger.info("Reset"); orderListeners.clear(); openOrdersById.clear(); nextValidOrderId = 0; } @Override public void addOrderListener(OrderListener listener) { orderListeners.add(listener); } @Override public void removeOrderListener(OrderListener listener) { orderListeners.remove(listener); } @Override public List<OpenOrder> getOpenOrders() { return new ArrayList<OpenOrder>(openOrdersById.values()); } @Override public List<OpenOrder> getOpenOrders(Symbol symbol, OrderType orderType) { List<OpenOrder> openOrders = new ArrayList<OpenOrder>(openOrdersById.size()); for (OpenOrder o : openOrdersById.values()) { if ((symbol == null || symbol.equals(o.getSymbol())) && (orderType == null || orderType.equals(o.getType()))) { openOrders.add(o); } } return openOrders; } @Override public OpenOrder getOpenOrder(Symbol symbol, OrderType orderType) { if (symbol == null) { throw new IllegalArgumentException("Symbol cannot be null"); } if (orderType == null) { throw new IllegalArgumentException("OrderType cannot be null"); } for (OpenOrder o : openOrdersById.values()) { if (symbol.equals(o.getSymbol()) && orderType.equals(o.getType())) { return o; } } return null; } @Override public void cancelOrder(Symbol symbol, OrderType orderType) { List<OpenOrder> openOrders = getOpenOrders(symbol, orderType); if (openOrders.isEmpty()) { if (VERBOSE.get()) { logger.info("Cannot cancel order, no open order found for {} {}", new Object[] { symbol, orderType }); } return; } for (OpenOrder o : openOrders) { cancelOrder(o); } } @Override public void cancelOrder(OpenOrder openOrder) { openOrder.setCancelled(); if (VERBOSE.get()) { logger.info("Cancelled order {}", openOrder); } for (OrderListener listener : orderListeners) { try { listener.onOrderCancelled(openOrder); } catch (Throwable t) { logger.error(t.getMessage(), t); } } } @Override public void cancelOrders() { for (OpenOrder o : openOrdersById.values()) { cancelOrder(o); } } @Override public OpenOrder placeOrder(Symbol symbol, int quantity, String reference) { return placeOrder(symbol, OrderType.MARKET, quantity, -1, -1, reference); } @Override public OpenOrder placeOrder(Symbol symbol, int quantity, double price, String reference) { return placeOrder(symbol, OrderType.LIMIT, quantity, price, -1, reference); } @Override public OpenOrder placeOrder(Symbol symbol, OrderType type, int quantity, double price, double stopPercent, String reference) { if (quantity == 0) { throw new IllegalArgumentException(String.format("Invalid quantity %s", quantity)); } if ((OrderType.LIMIT.equals(type) || OrderType.STOP_LIMIT.equals(type) || OrderType.TRAIL_LIMIT.equals(type)) && price <= 0) { throw new IllegalArgumentException(String.format("Invalid limit order price %s", price)); } if ((OrderType.STOP_MARKET.equals(type) || OrderType.STOP_LIMIT.equals(type) || OrderType.TRAIL_MARKET.equals(type) || OrderType.TRAIL_LIMIT.equals(type)) && stopPercent <= 0) { throw new IllegalArgumentException(String.format("Invalid stop order with stop percent %s", stopPercent)); } Tick lastTick = marketFeed.getLastTick(symbol); if (lastTick == null) { throw new IllegalStateException(String.format("Could not find price tick for symbol %s while placing order.", symbol)); } OpenOrder openOrder = getOpenOrder(symbol, type); if (openOrder != null && !openOrder.isFilled() && !openOrder.isCancelled()) { return openOrder;//throw new IllegalStateException("OpenOrder for same symbol and type already exists: " + openOrder); } double stopPrice = -1; double trailingStopOffset = -1; if (OrderType.STOP_MARKET.equals(type) || OrderType.STOP_LIMIT.equals(type) || OrderType.TRAIL_MARKET.equals(type) || OrderType.TRAIL_LIMIT.equals(type)) { trailingStopOffset = Util.round(lastTick.getMidPrice() * stopPercent, 10.0); if (quantity < 0) { stopPrice = lastTick.getMidPrice() - trailingStopOffset; } else { stopPrice = lastTick.getMidPrice() + trailingStopOffset; } stopPrice = Util.round(stopPrice, 2.0); } int orderId = ++nextValidOrderId; openOrder = new OpenOrder(orderId, symbol, type, quantity, price, stopPrice, trailingStopOffset, lastTick.getDateTime(), reference); openOrdersById.put(orderId, openOrder); if (VERBOSE.get()) { logger.info("Placed {}", openOrder); } for (OrderListener listener : orderListeners) { try { listener.onOrderPlaced(openOrder); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } return openOrder; } protected void processOpenOrders(DateTime dt) { if (lastProcessOrder != null && !dt.isAfter(lastProcessOrder)) { return; } if (openOrdersById.isEmpty()) { return; } for (Iterator<OpenOrder> openOrders = openOrdersById.values().iterator(); openOrders.hasNext();) { OpenOrder openOrder = openOrders.next(); if (!openOrder.isOpen()) { openOrders.remove(); continue; } if (dt.getMillis() - openOrder.getOrderDate().getMillis() < EXECUTION_DELAY_MILLIS.get()) { continue; } Symbol symbol = openOrder.getSymbol(); Tick lastTick = marketFeed.getLastTick(symbol); if (lastTick == null) { logger.debug("Cannot process order {}, no tick for symbol at %s", openOrder, dt); continue; } if (dt.getMillisOfDay() != 0 && (dt.getMillis() - lastTick.getDateTime().getMillis()) > 5 * 60 * 1000) { logger.debug("Cannot process order {}, {}, stale market data", openOrder, lastTick); continue; } if (lastTick.getAsk() <= 0 || lastTick.getBid() <= 0) { logger.debug("Cannot process order {}, {} is not valid", openOrder, lastTick); continue; } boolean fill = false; switch (openOrder.getType()) { case MARKET: fill = true; break; case LIMIT: fill = ((openOrder.isBuy() && lastTick.getAsk() <= openOrder.getPrice()) || (openOrder.isSell() && lastTick.getBid() >= openOrder.getPrice())); break; case STOP_MARKET: fill = ((openOrder.isBuy() && lastTick.getMidPrice() >= openOrder.getStopPrice()) || (openOrder.isSell() && lastTick.getMidPrice() <= openOrder .getStopPrice())); break; case STOP_LIMIT: fill = ((openOrder.isBuy() && lastTick.getMidPrice() >= openOrder.getStopPrice()) || (openOrder.isSell() && lastTick.getMidPrice() <= openOrder .getStopPrice())); fill &= ((openOrder.isBuy() && lastTick.getAsk() <= openOrder.getPrice()) || (openOrder.isSell() && lastTick.getBid() >= openOrder.getPrice())); break; case TRAIL_MARKET: openOrder.updateMinMaxPrice(lastTick.getMidPrice()); fill = ((openOrder.isBuy() && lastTick.getMidPrice() >= openOrder.getMinPrice() + openOrder.getTrailStopOffset()) || (openOrder.isSell() && lastTick .getMidPrice() <= openOrder.getMaxPrice() - openOrder.getTrailStopOffset())); break; case TRAIL_LIMIT: openOrder.updateMinMaxPrice(lastTick.getMidPrice()); fill = ((openOrder.isBuy() && lastTick.getMidPrice() >= openOrder.getMinPrice() + openOrder.getTrailStopOffset()) || (openOrder.isSell() && lastTick .getMidPrice() <= openOrder.getMaxPrice() - openOrder.getTrailStopOffset())); fill &= ((openOrder.isBuy() && lastTick.getAsk() <= openOrder.getPrice()) || (openOrder.isSell() && lastTick.getBid() >= openOrder.getPrice())); break; } // System.out.println(openOrder); // System.out.println(lastTick.getMidPrice()); // System.out.println(fill); if (fill) { double fillPrice = openOrder.isBuy() ? lastTick.getAsk() : lastTick.getBid(); int fillQuantity = 0; if (openOrder.isBuy()) { fillQuantity = Math.min(lastTick.getAskSize(), openOrder.getQuantity()); } else { fillQuantity = Math.max(-lastTick.getBidSize(), openOrder.getQuantity()); } if (fillQuantity == 0) { fillQuantity = openOrder.getQuantity(); } openOrder.update(fillQuantity, fillPrice, dt); if (VERBOSE.get()) { logExecution(openOrder, fillQuantity); } if (openOrder.isFilled()) { DateTime fillDate = openOrder.getFillDate(); double commissionAmount = commission.calculate(openOrder.getQuantityFilled(), openOrder.getAvgFillPrice()); openOrder.setCommission(commissionAmount); Position position = portfolio.getPosition(symbol); double[] values = position.update(fillDate, fillQuantity, fillPrice, commissionAmount); if (values[0] != 0.0) { portfolio.addCash(symbol.getCurrency(), values[3]); performanceTracker.updateTrades(fillDate, symbol, -(int)values[0], values[1], values[2], values[3]); } if (VERBOSE.get()) { logger.info("Filled {}", openOrder); logTrade(openOrder, position.getQuantity(), values[0], values[3], values[4]); } for (OrderListener listener : orderListeners) { try { listener.onOrderFilled(openOrder, position); } catch (Throwable t) { logger.error(t.getMessage(), t); } } } } } lastProcessOrder = dt; } protected void logExecution(OpenOrder openOrder, int quantity) { Object[] params = new Object[] { openOrder.getFillDate(), "EXEC", openOrder.getAction(), openOrder.getType(), quantity, openOrder.getSymbol(), openOrder.getSymbol().getCurrency(), Util.round(openOrder.getLastFillPrice(), 4), "", "", "", "", "", openOrder.getReference() != null ? openOrder.getReference() : "", "DU000000" }; blotter.info(MarkerFactory.getMarker("EXECUTION"), "{},{},{},{},{},{},{},{},{},{},{},{},{},{}", params); } protected void logTrade(OpenOrder openOrder, int position, double costBasis, double realized, double unrealized) { Object[] params = new Object[] { openOrder.getFillDate(), "TRADE", openOrder.getAction(), openOrder.getType(), openOrder.getQuantityFilled(), openOrder.getSymbol(), openOrder.getSymbol().getCurrency(), Util.round(openOrder.getAvgFillPrice(), 4), position, Util.round(costBasis, 4), Util.round(realized, 4), Util.round(unrealized, 4), Util.round(openOrder.getCommission(), 4), openOrder.getReference() != null ? openOrder.getReference() : "", "DU000000" }; blotter.info(MarkerFactory.getMarker("TRADE"), "{},{},{},{},{},{},{},{},{},{},{},{},{},{}", params); } protected void processPerformanceTracker(DateTime dateTime) { performanceTracker.updatePortfolio(dateTime, portfolio); } @Override public void onDay(DateTime dateTime) { } @Override public void onHour(DateTime dateTime) { processPerformanceTracker(dateTime); } @Override public void onMinute(DateTime dateTime) { } @Override public void onBar(Bar bar) { processOpenOrders(bar.getDateTime()); } @Override public void onTick(Tick tick) { processOpenOrders(tick.getDateTime()); } @Override public Position setPosition(Symbol symbol, int quantity) { // TODO Auto-generated method stub return null; } @Override public Position setPosition(Symbol symbol, int quantity, ExecutionMethod executionMethod) { // TODO Auto-generated method stub return null; } @Override public void cancelExecution(Symbol symbol) { // TODO Auto-generated method stub } @Override public void cancelExecutions() { // TODO Auto-generated method stub } @Override public Commission getCommission() { return commission; } @Override public void setCommission(Commission commission) { this.commission = commission; } }