ch.algotrader.service.algo.VWAPOrderService.java Source code

Java tutorial

Introduction

Here is the source code for ch.algotrader.service.algo.VWAPOrderService.java

Source

/***********************************************************************************
 * 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.algo;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

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.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import ch.algotrader.entity.exchange.Exchange;
import ch.algotrader.entity.marketData.Bar;
import ch.algotrader.entity.security.Security;
import ch.algotrader.entity.security.SecurityFamily;
import ch.algotrader.entity.trade.Fill;
import ch.algotrader.entity.trade.MarketOrder;
import ch.algotrader.entity.trade.OrderStatus;
import ch.algotrader.entity.trade.OrderStatusVO;
import ch.algotrader.entity.trade.OrderValidationException;
import ch.algotrader.entity.trade.algo.AlgoOrder;
import ch.algotrader.entity.trade.algo.VWAPOrder;
import ch.algotrader.enumeration.Duration;
import ch.algotrader.enumeration.MarketDataEventType;
import ch.algotrader.enumeration.Status;
import ch.algotrader.enumeration.TimePeriod;
import ch.algotrader.service.CalendarService;
import ch.algotrader.service.HistoricalDataService;
import ch.algotrader.service.OrderExecutionService;
import ch.algotrader.service.SimpleOrderService;
import ch.algotrader.util.DateTimeLegacy;
import ch.algotrader.util.RoundUtil;

/**
 * @author <a href="mailto:aflury@algotrader.ch">Andy Flury</a>
 */
public class VWAPOrderService extends AbstractAlgoOrderExecService<VWAPOrder, VWAPOrderStateVO>
        implements ApplicationContextAware {

    private static final double MAX_PARTICIPATION = 0.5;
    private static final DecimalFormat twoDigitFormat = new DecimalFormat("#,##0.00");

    private static final Logger LOGGER = LogManager.getLogger(VWAPOrderService.class);

    private final OrderExecutionService orderExecutionService;
    private final CalendarService calendarService;
    private final SimpleOrderService simpleOrderService;

    private ApplicationContext applicationContext;

    public VWAPOrderService(final OrderExecutionService orderExecutionService,
            final CalendarService calendarService, final SimpleOrderService simpleOrderService) {

        super(orderExecutionService, simpleOrderService);

        Validate.notNull(orderExecutionService, "OrderExecutionService is null");
        Validate.notNull(calendarService, "CalendarService is null");
        Validate.notNull(simpleOrderService, "SimpleOrderService is null");

        this.calendarService = calendarService;
        this.simpleOrderService = simpleOrderService;
        this.orderExecutionService = orderExecutionService;
    }

    @Override
    public Class<? extends AlgoOrder> getAlgoOrderType() {
        return VWAPOrder.class;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    protected VWAPOrderStateVO handleValidateOrder(final VWAPOrder algoOrder) throws OrderValidationException {

        return createAlgoOrderState(algoOrder, new Date());
    }

    VWAPOrderStateVO createAlgoOrderState(final VWAPOrder algoOrder, final Date dateTime)
            throws OrderValidationException {

        Validate.notNull(algoOrder, "vwapOrder missing");

        Security security = algoOrder.getSecurity();
        SecurityFamily family = security.getSecurityFamily();
        Exchange exchange = family.getExchange();

        HistoricalDataService historicalDataService = this.applicationContext.getBean(HistoricalDataService.class);

        List<Bar> bars = historicalDataService.getHistoricalBars(security.getId(), //
                DateUtils.truncate(new Date(), Calendar.DATE), //
                algoOrder.getLookbackPeriod(), //
                TimePeriod.DAY, //
                algoOrder.getBucketSize(), //
                MarketDataEventType.TRADES, //
                Collections.emptyMap());

        TreeMap<LocalTime, Long> buckets = new TreeMap<>();
        Set<LocalDate> tradingDays = new HashSet<>();
        for (Bar bar : bars) {
            int vol = bar.getVol();
            LocalTime time = DateTimeLegacy.toLocalTime(bar.getDateTime());
            tradingDays.add(DateTimeLegacy.toLocalDate(bar.getDateTime()));
            if (buckets.containsKey(time)) {
                buckets.put(time, buckets.get(time) + vol);
            } else {
                buckets.put(time, (long) vol);
            }
        }

        // verify start and end time
        if (algoOrder.getStartTime() == null) {
            if (this.calendarService.isOpen(exchange.getId())) {
                algoOrder.setStartTime(dateTime);
            } else {
                Date nextOpenTime = this.calendarService.getNextOpenTime(exchange.getId());
                algoOrder.setStartTime(nextOpenTime);
            }
        }

        Date closeTime = this.calendarService.getNextCloseTime(exchange.getId());
        if (algoOrder.getEndTime() == null) {
            algoOrder.setEndTime(closeTime);
        }

        if (algoOrder.getStartTime().compareTo(dateTime) < 0) {
            throw new OrderValidationException("startTime needs to be in the future " + algoOrder);
        } else if (algoOrder.getEndTime().compareTo(dateTime) <= 0) {
            throw new OrderValidationException("endTime needs to be in the future " + algoOrder);
        } else if (algoOrder.getEndTime().compareTo(closeTime) > 0) {
            throw new OrderValidationException("endTime needs to be before next market closeTime for " + algoOrder);
        } else if (algoOrder.getEndTime().compareTo(algoOrder.getStartTime()) <= 0) {
            throw new OrderValidationException("endTime needs to be after startTime for " + algoOrder);
        }

        int historicalVolume = 0;
        LocalTime startTime = DateTimeLegacy.toLocalTime(algoOrder.getStartTime());
        LocalTime endTime = DateTimeLegacy.toLocalTime(algoOrder.getEndTime());
        LocalTime firstBucketStart = buckets.floorKey(startTime);
        LocalTime lastBucketStart = buckets.floorKey(endTime);

        SortedMap<LocalTime, Long> subBuckets = buckets.subMap(firstBucketStart, true, lastBucketStart, true);
        for (Map.Entry<LocalTime, Long> bucket : subBuckets.entrySet()) {

            long vol = bucket.getValue() / tradingDays.size();
            bucket.setValue(vol);

            if (bucket.getKey().equals(firstBucketStart)) {
                LocalTime firstBucketEnd = firstBucketStart.plus(algoOrder.getBucketSize().getValue(),
                        ChronoUnit.MILLIS);
                double fraction = (double) ChronoUnit.MILLIS.between(startTime, firstBucketEnd)
                        / algoOrder.getBucketSize().getValue();
                historicalVolume += vol * fraction;
            } else if (bucket.getKey().equals(lastBucketStart)) {
                double fraction = (double) ChronoUnit.MILLIS.between(lastBucketStart, endTime)
                        / algoOrder.getBucketSize().getValue();
                historicalVolume += vol * fraction;
            } else {
                historicalVolume += vol;
            }
        }

        double participation = algoOrder.getQuantity() / (double) historicalVolume;

        if (participation > MAX_PARTICIPATION) {
            throw new OrderValidationException("participation rate " + twoDigitFormat.format(participation * 100.0)
                    + "% is above 50% of historical market volume for " + algoOrder);
        }

        if (LOGGER.isInfoEnabled()) {
            LOGGER.debug("participation of {} is {}%", algoOrder.getDescription(),
                    twoDigitFormat.format(participation * 100.0));
        }

        return new VWAPOrderStateVO(participation, buckets);
    }

    @Override
    public void handleSendOrder(final VWAPOrder algoOrder, final VWAPOrderStateVO algoOrderState) {

        sendNextOrder(algoOrder, algoOrderState, new Date());
    }

    @Override
    protected void handleModifyOrder(final VWAPOrder algoOrder, final VWAPOrderStateVO algoOrderState) {
    }

    @Override
    protected void handleCancelOrder(final VWAPOrder algoOrder, final VWAPOrderStateVO algoOrderState) {
    }

    public void sendNextOrder(VWAPOrder algoOrder, Date dateTime) {

        Optional<VWAPOrderStateVO> optional = getAlgoOrderState(algoOrder);
        if (optional.isPresent()) {
            VWAPOrderStateVO orderState = optional.get();
            sendNextOrder(algoOrder, orderState, dateTime);
        }
    }

    void sendNextOrder(VWAPOrder algoOrder, VWAPOrderStateVO orderState, Date dateTime) {

        OrderStatusVO orderStatus = this.orderExecutionService.getStatusByIntId(algoOrder.getIntId());

        long bucketVolume = orderState.getBucketVolume(DateTimeLegacy.toLocalTime(dateTime));
        long targetVolume = Math.round(orderState.getParticipation() * bucketVolume);
        int avgIntervalLenth = (int) ((algoOrder.getMaxInterval() + algoOrder.getMinInterval()) / 2.0 * 1000.0);
        int intervalsPerBucket = (int) (algoOrder.getBucketSize().getValue() / avgIntervalLenth);
        double randomFactor = (1 - algoOrder.getQtyRandomFactor()
                + 2 * algoOrder.getQtyRandomFactor() * Math.random());
        long quantity = Math.round(randomFactor * targetVolume / intervalsPerBucket);
        quantity = Math.max(1, quantity);
        quantity = Math.min(orderStatus.getRemainingQuantity(), quantity);

        // create the limit order
        MarketOrder order = MarketOrder.Factory.newInstance();
        order.setSecurity(algoOrder.getSecurity());
        order.setStrategy(algoOrder.getStrategy());
        order.setSide(algoOrder.getSide());
        order.setQuantity(quantity);
        order.setAccount(algoOrder.getAccount());

        // associate the childOrder with the parentOrder
        order.setParentOrder(algoOrder);

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("next childOrder for {},quantity={},bucketVolume={},targetVolume={}",
                    algoOrder.getDescription(), quantity, bucketVolume, targetVolume);
        }

        this.simpleOrderService.sendOrder(order);
    }

    @Override
    public void handleChildFill(VWAPOrder algoOrder, VWAPOrderStateVO orderState, Fill fill) {

        orderState.storeFill(fill);
    }

    @Override
    public void handleOrderStatus(VWAPOrder algoOrder, VWAPOrderStateVO algoOrderState, OrderStatus orderStatus) {

        if (!EnumSet.of(Status.EXECUTED, Status.CANCELED).contains(orderStatus.getStatus())) {
            return;
        }

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(algoOrder.getDescription() + "," + getResults(algoOrder, algoOrderState, new Date()));
        }
    }

    Map<String, Object> getResults(VWAPOrder algoOrder, VWAPOrderStateVO algoOrderState, Date dateTime) {

        SecurityFamily family = algoOrder.getSecurity().getSecurityFamily();
        int minutes = (int) (dateTime.getTime() - algoOrder.getStartTime().getTime()) / 60000;

        HistoricalDataService historicalDataService = this.applicationContext.getBean(HistoricalDataService.class);

        List<Bar> bars = historicalDataService.getHistoricalBars(algoOrder.getSecurity().getId(), //
                new Date(), //
                minutes * 60, //
                TimePeriod.SEC, //
                Duration.MIN_1, //
                MarketDataEventType.TRADES, //
                Collections.emptyMap());

        double benchmarkMarketValue = 0.0;
        long benchmarkVolume = 0;
        for (Bar bar : bars) {
            benchmarkMarketValue += bar.getVwap().doubleValue() * bar.getVol();
            benchmarkVolume += bar.getVol();
        }
        BigDecimal benchmarkPrice = RoundUtil.getBigDecimal(benchmarkMarketValue / benchmarkVolume,
                family.getScale());

        double marketValue = 0.0;
        long volume = 0;
        for (Fill fill : algoOrderState.getFills()) {
            marketValue += fill.getPrice().doubleValue() * fill.getQuantity();
            volume += fill.getQuantity();
        }
        BigDecimal price = RoundUtil.getBigDecimal(marketValue / volume, family.getScale());

        Map<String, Object> results = new HashMap<>();
        results.put("price", price);
        results.put("benchmarkPrice", benchmarkPrice);
        results.put("duration(mins)", minutes);
        return results;
    }

}