Java tutorial
/*********************************************************************************** * AlgoTrader Enterprise Trading Framework * * Copyright (C) 2015 AlgoTrader GmbH - All rights reserved * * All information contained herein is, and remains the property of AlgoTrader GmbH. * The intellectual and technical concepts contained herein are proprietary to * AlgoTrader GmbH. Modification, translation, reverse engineering, decompilation, * disassembly or reproduction of this material is strictly forbidden unless prior * written permission is obtained from AlgoTrader GmbH * * Fur detailed terms and conditions consult the file LICENSE.txt or contact * * AlgoTrader GmbH * Aeschstrasse 6 * 8834 Schindellegi ***********************************************************************************/ package ch.algotrader.service.bb; import java.io.IOException; import java.math.BigDecimal; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.apache.commons.lang.Validate; import org.apache.commons.lang.time.DateUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.DisposableBean; import com.bloomberglp.blpapi.Element; import com.bloomberglp.blpapi.Event; import com.bloomberglp.blpapi.Message; import com.bloomberglp.blpapi.Request; import com.bloomberglp.blpapi.Service; import com.bloomberglp.blpapi.Session; import ch.algotrader.adapter.bb.BBAdapter; import ch.algotrader.adapter.bb.BBConstants; import ch.algotrader.adapter.bb.BBMessageHandler; import ch.algotrader.adapter.bb.BBSession; import ch.algotrader.dao.marketData.BarDao; import ch.algotrader.dao.security.SecurityDao; import ch.algotrader.entity.marketData.Bar; import ch.algotrader.entity.marketData.Tick; import ch.algotrader.entity.security.Security; import ch.algotrader.enumeration.MarketDataEventType; import ch.algotrader.enumeration.Broker; import ch.algotrader.enumeration.Duration; import ch.algotrader.enumeration.FeedType; import ch.algotrader.enumeration.InitializingServiceType; import ch.algotrader.enumeration.TimePeriod; import ch.algotrader.service.ExternalServiceException; import ch.algotrader.service.HistoricalDataService; import ch.algotrader.service.HistoricalDataServiceImpl; import ch.algotrader.service.InitializationPriority; import ch.algotrader.service.InitializingServiceI; import ch.algotrader.service.ServiceException; import ch.algotrader.util.DateTimeLegacy; import ch.algotrader.util.RoundUtil; /** * @author <a href="mailto:aflury@algotrader.ch">Andy Flury</a> */ @InitializationPriority(value = InitializingServiceType.BROKER_INTERFACE) public class BBHistoricalDataServiceImpl extends HistoricalDataServiceImpl implements HistoricalDataService, InitializingServiceI, DisposableBean { private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); private static final Logger LOGGER = LogManager.getLogger(BBHistoricalDataServiceImpl.class); private static BBSession session; private final BBAdapter bBAdapter; private final SecurityDao securityDao; public BBHistoricalDataServiceImpl(final BBAdapter bBAdapter, final SecurityDao securityDao, final BarDao barDao) { super(barDao); Validate.notNull(bBAdapter, "BBAdapter is null"); Validate.notNull(securityDao, "SecurityDao is null"); this.bBAdapter = bBAdapter; this.securityDao = securityDao; } /** * {@inheritDoc} */ @Override public void init() { try { session = this.bBAdapter.createReferenceDataSession(); session.startService(); } catch (IOException ex) { throw new ExternalServiceException(ex); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new ServiceException(ex); } } @Override public List<Bar> getHistoricalBars(long securityId, Date endDate, int timePeriodLength, TimePeriod timePeriod, final Duration barSize, MarketDataEventType marketDataEventType, Map<String, String> properties) { Validate.notNull(endDate, "End date is null"); Validate.notNull(timePeriod, "Time period is null"); Validate.notNull(barSize, "Bar size is null"); Validate.notNull(marketDataEventType, "Market data request type is null"); Security security = this.securityDao.get(securityId); if (security == null) { throw new ServiceException("security was not found " + securityId); } if (security.getBbgid() == null) { throw new ServiceException("security has no bbgid " + securityId); } String securityString = "/bbgid/" + security.getBbgid(); // send the request by using either IntrayBarRequest or HistoricalDataRequest try { if (barSize.getValue() < Duration.DAY_1.getValue()) { sendIntradayBarRequest(endDate, timePeriodLength, timePeriod, barSize, marketDataEventType, securityString, properties); } else { sendHistoricalDataRequest(endDate, timePeriodLength, timePeriod, barSize, marketDataEventType, securityString, properties); } } catch (IOException ex) { throw new ExternalServiceException(ex); } // instantiate the message handler BBHistoricalBarMessageHandler messageHandler = new BBHistoricalBarMessageHandler(security, barSize); // process responses boolean done = false; while (!done) { try { done = messageHandler.processEvent(session); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new ServiceException(ex); } } return messageHandler.getBarList(); } @Override public List<Tick> getHistoricalTicks(long securityId, Date endDate, int timePeriodLength, TimePeriod timePeriod, MarketDataEventType marketDataEventType, Map<String, String> properties) { Validate.notNull(endDate, "End date is null"); Validate.notNull(timePeriod, "Time period is null"); Validate.notNull(marketDataEventType, "Market data request type is null"); Security security = this.securityDao.get(securityId); if (security == null) { throw new ServiceException("security was not found " + securityId); } if (security.getBbgid() == null) { throw new ServiceException("security has no bbgid " + securityId); } String securityString = "/bbgid/" + security.getBbgid(); // send the request by using either IntrayBarRequest or HistoricalDataRequest try { sendIntradayTickRequest(endDate, timePeriodLength, timePeriod, marketDataEventType, securityString, properties); } catch (IOException ex) { throw new ExternalServiceException(ex); } // instantiate the message handler BBHistoricalTickMessageHandler messageHandler = new BBHistoricalTickMessageHandler(security); // process responses boolean done = false; while (!done) { try { done = messageHandler.processEvent(session); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new ServiceException(ex); } } return messageHandler.getTickList(); } private void sendIntradayBarRequest(Date endDate, int timePeriodLength, TimePeriod timePeriod, final Duration barSize, MarketDataEventType marketDataEventType, final String securityString, Map<String, String> properties) throws IOException { int barSizeInt = (int) (barSize.getValue() / 60000); String marketDataEventTypeString; switch (marketDataEventType) { case TRADES: marketDataEventTypeString = "TRADE"; break; case BID: marketDataEventTypeString = "BID"; break; case ASK: marketDataEventTypeString = "ASK"; break; case BEST_BID: marketDataEventTypeString = "BEST_BID"; break; case BEST_ASK: marketDataEventTypeString = "BEST_ASK"; break; default: throw new IllegalArgumentException("unsupported marketDataEventType " + marketDataEventType); } Date startDate = getStartDate(endDate, timePeriodLength, timePeriod); String startDateString = dateTimeFormat.format(DateTimeLegacy.toGMTDateTime(startDate)); String endDateString = dateTimeFormat.format(DateTimeLegacy.toGMTDateTime(endDate)); Service service = session.getService(); Request request = service.createRequest("IntradayBarRequest"); request.set("security", securityString); request.set("eventType", marketDataEventTypeString); request.set("interval", barSizeInt); request.set("startDateTime", startDateString); request.set("endDateTime", endDateString); for (Map.Entry<String, String> entry : properties.entrySet()) { request.set(entry.getKey(), entry.getValue()); } // send request session.sendRequest(request, null); } private void sendIntradayTickRequest(Date endDate, int timePeriodLength, TimePeriod timePeriod, MarketDataEventType marketDataEventType, final String securityString, Map<String, String> properties) throws IOException { String marketDataEventTypeString; switch (marketDataEventType) { case TRADES: marketDataEventTypeString = "TRADE"; break; case BID: marketDataEventTypeString = "BID"; break; case ASK: marketDataEventTypeString = "ASK"; break; case BEST_BID: marketDataEventTypeString = "BEST_BID"; break; case BEST_ASK: marketDataEventTypeString = "BEST_ASK"; break; default: throw new IllegalArgumentException("unsupported marketDataEventType " + marketDataEventType); } Date startDate = getStartDate(endDate, timePeriodLength, timePeriod); String startDateString = dateTimeFormat.format(DateTimeLegacy.toGMTDateTime(startDate)); String endDateString = dateTimeFormat.format(DateTimeLegacy.toGMTDateTime(endDate)); Service service = session.getService(); Request request = service.createRequest("IntradayTickRequest"); request.set("security", securityString); request.append("eventTypes", marketDataEventTypeString); request.set("startDateTime", startDateString); request.set("endDateTime", endDateString); for (Map.Entry<String, String> entry : properties.entrySet()) { request.set(entry.getKey(), entry.getValue()); } // send request session.sendRequest(request, null); } private void sendHistoricalDataRequest(Date endDate, int timePeriodLength, TimePeriod timePeriod, Duration barSize, MarketDataEventType marketDataEventType, String securityString, Map<String, String> properties) throws IOException { String barSizeString; switch (barSize) { case DAY_1: barSizeString = "DAILY"; break; case WEEK_1: barSizeString = "WEEKLY"; break; case MONTH_1: barSizeString = "MONTHLY"; break; case MONTH_3: barSizeString = "QUARTERLY"; break; case MONTH_6: barSizeString = "SEMI_ANNUALLY"; break; case YEAR_1: barSizeString = "YEARLY"; break; default: throw new IllegalArgumentException("unsupported barSize " + barSize); } if (!MarketDataEventType.TRADES.equals(marketDataEventType)) { throw new IllegalArgumentException("unsupported marketDataEventType " + marketDataEventType); } Date startDate = getStartDate(endDate, timePeriodLength, timePeriod); String startDateString = dateFormat.format(DateTimeLegacy.toGMTDate(startDate)); String endDateString = dateFormat.format(DateTimeLegacy.toGMTDate(endDate)); Service service = session.getService(); Request request = service.createRequest("HistoricalDataRequest"); // add security Element securities = request.getElement("securities"); securities.appendValue(securityString); // add fields Element fields = request.getElement("fields"); fields.appendValue("PX_LAST"); fields.appendValue("OPEN"); fields.appendValue("HIGH"); fields.appendValue("LOW"); fields.appendValue("VOLUME"); request.set("periodicitySelection", barSizeString); request.set("startDate", startDateString); request.set("endDate", endDateString); for (Map.Entry<String, String> entry : properties.entrySet()) { request.set(entry.getKey(), entry.getValue()); } // send request session.sendRequest(request, null); } private Date getStartDate(Date endDate, int timePeriodLength, TimePeriod timePeriod) { switch (timePeriod) { case DAY: return DateUtils.addDays(endDate, -timePeriodLength); case WEEK: return DateUtils.addWeeks(endDate, -timePeriodLength); case MONTH: return DateUtils.addMonths(endDate, -timePeriodLength); case YEAR: return DateUtils.addYears(endDate, -timePeriodLength); default: throw new IllegalArgumentException("timePeriod is not allowed " + timePeriod); } } @Override public void destroy() throws Exception { if (session != null && session.isRunning()) { session.stop(); } } private class BBHistoricalBarMessageHandler extends BBMessageHandler { private final Security security; private final Duration barSize; private final List<Bar> barList; public BBHistoricalBarMessageHandler(Security security, Duration barSize) { this.security = security; this.barSize = barSize; this.barList = new ArrayList<>(); } @Override protected void processResponseEvent(Event event, Session session) { for (Message msg : event) { if (msg.hasElement(BBConstants.RESPONSE_ERROR)) { Element errorInfo = msg.getElement(BBConstants.RESPONSE_ERROR); LOGGER.error("request failed {} ({})", errorInfo.getElementAsString(BBConstants.CATEGORY), errorInfo.getElementAsString(BBConstants.MESSAGE)); continue; } if (msg.messageType() == BBConstants.INTRADAY_BAR_RESPONSE) { processIntradayBarResponse(msg); } else if (msg.messageType() == BBConstants.HISTORICAL_DATA_RESPONSE) { processHistoricalDataResponse(msg); } else { throw new IllegalArgumentException("unknown reponse type: " + msg.messageType()); } } } private void processIntradayBarResponse(Message msg) { Element data = msg.getElement(BBConstants.BAR_DATA).getElement(BBConstants.BAR_TICK_DATA); int numBars = data.numValues(); for (int i = 0; i < numBars; ++i) { Element fields = data.getValueAsElement(i); Calendar calendar = fields.getElementAsDate(BBConstants.TIME).calendar(); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); Date time = calendar.getTime(); double open = fields.getElementAsFloat64(BBConstants.OPEN); double high = fields.getElementAsFloat64(BBConstants.HIGH); double low = fields.getElementAsFloat64(BBConstants.LOW); double close = fields.getElementAsFloat64(BBConstants.CLOSE); long volume = fields.getElementAsInt64(BBConstants.VOLUME); int scale = this.security.getSecurityFamily().getScale(Broker.BB.name()); Bar bar = Bar.Factory.newInstance(); bar.setDateTime(time); bar.setOpen(RoundUtil.getBigDecimal(open, scale)); bar.setHigh(RoundUtil.getBigDecimal(high, scale)); bar.setLow(RoundUtil.getBigDecimal(low, scale)); bar.setClose(RoundUtil.getBigDecimal(close, scale)); bar.setVol((int) volume); bar.setBarSize(this.barSize); bar.setFeedType(FeedType.BB.name()); bar.setSecurity(this.security); this.barList.add(bar); } } private void processHistoricalDataResponse(Message msg) { Element data = msg.getElement(BBConstants.SECURITY_DATA).getElement(BBConstants.FIELD_DATA); int numBars = data.numValues(); for (int i = 0; i < numBars; ++i) { Element bbBar = data.getValueAsElement(i); //ignore bars with not PX_LAST if (!bbBar.hasElement("PX_LAST")) { continue; } Calendar calendar = bbBar.getElementAsDate(BBConstants.DATE).calendar(); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); Date date = calendar.getTime(); double close = bbBar.getElementAsFloat64("PX_LAST"); // instruments might only have a PX_LAST double open = close; if (bbBar.hasElement("OPEN")) { open = bbBar.getElementAsFloat64("OPEN"); } double high = close; if (bbBar.hasElement("HIGH")) { high = bbBar.getElementAsFloat64("HIGH"); } double low = close; if (bbBar.hasElement("LOW")) { low = bbBar.getElementAsFloat64("LOW"); } long volume = 0; if (bbBar.hasElement("VOLUME")) { volume = bbBar.getElementAsInt64("VOLUME"); } int scale = this.security.getSecurityFamily().getScale(Broker.BB.name()); Bar bar = Bar.Factory.newInstance(); bar.setDateTime(date); bar.setOpen(RoundUtil.getBigDecimal(open, scale)); bar.setHigh(RoundUtil.getBigDecimal(high, scale)); bar.setLow(RoundUtil.getBigDecimal(low, scale)); bar.setClose(RoundUtil.getBigDecimal(close, scale)); bar.setVol((int) volume); bar.setBarSize(this.barSize); bar.setFeedType(FeedType.BB.name()); bar.setSecurity(this.security); this.barList.add(bar); } } public List<Bar> getBarList() { return this.barList; } } private class BBHistoricalTickMessageHandler extends BBMessageHandler { private final Security security; private final List<Tick> tickList; public BBHistoricalTickMessageHandler(Security security) { this.security = security; this.tickList = new ArrayList<>(); } @Override protected void processResponseEvent(Event event, Session session) { for (Message msg : event) { if (msg.hasElement(BBConstants.RESPONSE_ERROR)) { Element errorInfo = msg.getElement(BBConstants.RESPONSE_ERROR); LOGGER.error("request failed {} ({})", errorInfo.getElementAsString(BBConstants.CATEGORY), errorInfo.getElementAsString(BBConstants.MESSAGE)); continue; } if (msg.messageType() == BBConstants.INTRADAY_TICK_RESPONSE) { processIntradayTickResponse(msg); } else { throw new IllegalArgumentException("unknown reponse type: " + msg.messageType()); } } } private void processIntradayTickResponse(Message msg) { Element data = msg.getElement(BBConstants.TICK_DATA).getElement(BBConstants.TICK_TICK_DATA); int numBars = data.numValues(); for (int i = 0; i < numBars; ++i) { Element fields = data.getValueAsElement(i); Calendar calendar = fields.getElementAsDate(BBConstants.TIME).calendar(); calendar.setTimeZone(TimeZone.getTimeZone("UTC")); Date time = calendar.getTime(); String type = fields.getElementAsString(BBConstants.TYPE); double value = fields.getElementAsFloat64(BBConstants.VALUE); int size = fields.getElementAsInt32(BBConstants.SIZE); if (value == 0.0) { continue; } int scale = this.security.getSecurityFamily().getScale(Broker.BB.name()); BigDecimal valueBD = RoundUtil.getBigDecimal(value, scale); Tick tick = Tick.Factory.newInstance(); tick.setDateTime(time); switch (type) { case "TRADE": tick.setLast(valueBD); tick.setVol(size); tick.setLastDateTime(time); break; case "BID": tick.setBid(valueBD); tick.setVolBid(size); break; case "ASK": tick.setAsk(valueBD); tick.setVolAsk(size); break; default: throw new IllegalArgumentException("unknown type " + type); } tick.setVol(size); tick.setFeedType(FeedType.BB.name()); tick.setSecurity(this.security); this.tickList.add(tick); } } public List<Tick> getTickList() { return this.tickList; } } }