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

Java tutorial

Introduction

Here is the source code for ch.algotrader.service.algo.SlicingOrderService.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.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.lang.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import ch.algotrader.entity.marketData.TickI;
import ch.algotrader.entity.marketData.TickVO;
import ch.algotrader.entity.security.Security;
import ch.algotrader.entity.security.SecurityFamily;
import ch.algotrader.entity.trade.Fill;
import ch.algotrader.entity.trade.LimitOrder;
import ch.algotrader.entity.trade.OrderStatus;
import ch.algotrader.entity.trade.OrderStatusVO;
import ch.algotrader.entity.trade.algo.AlgoOrder;
import ch.algotrader.entity.trade.algo.SlicingOrder;
import ch.algotrader.enumeration.Side;
import ch.algotrader.enumeration.Status;
import ch.algotrader.service.MarketDataCacheService;
import ch.algotrader.service.OrderExecutionService;
import ch.algotrader.service.SimpleOrderService;
import ch.algotrader.util.collection.Pair;

/**
 * @author <a href="mailto:aflury@algotrader.ch">Andy Flury</a>
 */
public class SlicingOrderService extends AbstractAlgoOrderExecService<SlicingOrder, SlicingOrderStateVO> {

    private static final Logger LOGGER = LogManager.getLogger(SlicingOrderService.class);
    private static final DecimalFormat twoDigitFormat = new DecimalFormat("#,##0.00");

    private final OrderExecutionService orderExecutionService;
    private final MarketDataCacheService marketDataCacheService;
    private final SimpleOrderService simpleOrderService;

    public SlicingOrderService(final OrderExecutionService orderExecutionService,
            final MarketDataCacheService marketDataCacheService, final SimpleOrderService simpleOrderService) {

        super(orderExecutionService, simpleOrderService);

        Validate.notNull(marketDataCacheService, "MarketDataCacheService is null");

        this.orderExecutionService = orderExecutionService;
        this.marketDataCacheService = marketDataCacheService;
        this.simpleOrderService = simpleOrderService;
    }

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

    @Override
    protected SlicingOrderStateVO handleValidateOrder(final SlicingOrder algoOrder) {
        return new SlicingOrderStateVO();
    }

    @Override
    public void handleSendOrder(final SlicingOrder algoOrder, final SlicingOrderStateVO slicingOrderState) {

        sendNextOrder(algoOrder, slicingOrderState);
    }

    @Override
    protected void handleModifyOrder(final SlicingOrder algoOrder, final SlicingOrderStateVO algoOrderState) {
        throw new UnsupportedOperationException();
    }

    @Override
    protected void handleCancelOrder(final SlicingOrder order, final SlicingOrderStateVO algoOrderState) {
    }

    public void increaseOffsetTicks(SlicingOrder slicingOrder) {

        Validate.notNull(slicingOrder, "slicingOrder missing");

        Optional<SlicingOrderStateVO> optional = getAlgoOrderState(slicingOrder);
        if (optional.isPresent()) {
            SlicingOrderStateVO orderState = optional.get();

            synchronized (orderState) {
                orderState.setCurrentOffsetTicks(orderState.getCurrentOffsetTicks() + 1);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("increaseOffsetTicks of {} to {}", slicingOrder.getDescription(),
                            orderState.getCurrentOffsetTicks());
                }
            }
        }
    }

    public void decreaseOffsetTicks(SlicingOrder slicingOrder) {

        Validate.notNull(slicingOrder, "slicingOrder missing");

        Optional<SlicingOrderStateVO> optional = getAlgoOrderState(slicingOrder);
        if (optional.isPresent()) {
            SlicingOrderStateVO orderState = optional.get();

            synchronized (orderState) {
                orderState.setCurrentOffsetTicks(Math.max(orderState.getCurrentOffsetTicks() - 1, 0));
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("decreaseOffsetTicks of {} to {}", slicingOrder.getDescription(),
                            orderState.getCurrentOffsetTicks());
                }
            }
        }
    }

    public void sendNextOrder(SlicingOrder slicingOrder) {

        Optional<SlicingOrderStateVO> optional = getAlgoOrderState(slicingOrder);
        if (optional.isPresent()) {
            sendNextOrder(slicingOrder, optional.get());
        }
    }

    private void sendNextOrder(SlicingOrder slicingOrder, SlicingOrderStateVO slicingOrderState) {

        Validate.notNull(slicingOrder, "slicingOrder missing");
        if (slicingOrderState == null) {
            return; // already done
        }

        Security security = slicingOrder.getSecurity();
        SecurityFamily family = security.getSecurityFamily();

        long remainingQuantity;
        OrderStatusVO orderStatus = this.orderExecutionService.getStatusByIntId(slicingOrder.getIntId());
        if (orderStatus != null) {
            remainingQuantity = orderStatus.getRemainingQuantity();
        } else {
            remainingQuantity = slicingOrder.getQuantity();
        }

        TickVO tick = (TickVO) this.marketDataCacheService.getCurrentMarketDataEvent(security.getId());
        if (tick == null) {
            throw new IllegalStateException("no market data subscription for " + security);
        }

        // limit (at least one tick above market but do not exceed the market)
        BigDecimal limit;
        long marketVolume;
        if (Side.BUY.equals(slicingOrder.getSide())) {

            marketVolume = tick.getVolAsk();
            limit = family.adjustPrice(null, tick.getAsk(), -slicingOrderState.getCurrentOffsetTicks());

            if (limit.compareTo(tick.getBid()) <= 0.0) {
                limit = family.adjustPrice(null, tick.getBid(), 1);
                slicingOrderState
                        .setCurrentOffsetTicks(family.getSpreadTicks(null, tick.getBid(), tick.getAsk()) - 1);
            }

            if (limit.compareTo(tick.getAsk()) > 0.0) {
                limit = tick.getAsk();
                slicingOrderState.setCurrentOffsetTicks(0);
            }

        } else {

            marketVolume = tick.getVolBid();
            limit = family.adjustPrice(null, tick.getBid(), slicingOrderState.getCurrentOffsetTicks());

            if (limit.compareTo(tick.getAsk()) >= 0.0) {
                limit = family.adjustPrice(null, tick.getAsk(), -1);
                slicingOrderState
                        .setCurrentOffsetTicks(family.getSpreadTicks(null, tick.getBid(), tick.getAsk()) - 1);
            }

            if (limit.compareTo(tick.getBid()) < 0.0) {
                limit = tick.getBid();
                slicingOrderState.setCurrentOffsetTicks(0);
            }
        }

        // ignore maxVolPct / maxQuantity if they are zero
        double maxVolPct = slicingOrder.getMaxVolPct() == 0.0 ? Double.MAX_VALUE : slicingOrder.getMaxVolPct();
        long maxQuantity = slicingOrder.getMaxQuantity() == 0 ? Long.MAX_VALUE : slicingOrder.getMaxQuantity();

        // evaluate the order minimum and maximum qty
        long orderMinQty = Math.max(Math.round(marketVolume * slicingOrder.getMinVolPct()),
                slicingOrder.getMinQuantity());
        long orderMaxQty = Math.min(Math.round(marketVolume * maxVolPct), maxQuantity);

        // orderMinQty cannot be greater than orderMaxQty
        if (orderMinQty > orderMaxQty) {
            orderMinQty = orderMaxQty;
        }

        // randomize the quantity between orderMinQty and orderMaxQty
        long quantity = Math.round(orderMinQty + Math.random() * (orderMaxQty - orderMinQty));

        // make sure that the remainingQty after the next slice is greater than minQuantity
        long remainingQuantityAfterSlice = remainingQuantity - quantity;
        if (slicingOrder.getMinQuantity() > 0 && slicingOrder.getMaxQuantity() > 0
                && remainingQuantityAfterSlice > 0 && remainingQuantityAfterSlice < slicingOrder.getMinQuantity()) {

            // if quantity is below half between minQuantity and maxQuantity
            if (quantity < (slicingOrder.getMinQuantity() + slicingOrder.getMaxQuantity()) / 2.0) {

                // take full remaining quantity but not more than orderMaxQty
                quantity = Math.min(remainingQuantity, orderMaxQty);
            } else {

                // make sure remaining after slice quantity is greater than minQuantity
                quantity = remainingQuantity - slicingOrder.getMinQuantity();
            }
        }

        // qty should be at least one
        quantity = Math.max(quantity, 1);

        // qty should be maximum remainingQuantity
        quantity = Math.min(quantity, remainingQuantity);

        // create the limit order
        LimitOrder order = LimitOrder.Factory.newInstance();
        order.setSecurity(security);
        order.setStrategy(slicingOrder.getStrategy());
        order.setSide(slicingOrder.getSide());
        order.setQuantity(quantity);
        order.setLimit(limit);
        order.setAccount(slicingOrder.getAccount());

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

        // store the current order and tick
        slicingOrderState.addPair(new Pair<>(order, tick));

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("next slice for {},currentOffsetTicks={},qty={},vol={},limit={},bid={},ask={}",
                    slicingOrder.getDescription(), slicingOrderState.getCurrentOffsetTicks(), order.getQuantity(),
                    (Side.BUY.equals(order.getSide()) ? tick.getVolAsk() : tick.getVolBid()), limit, tick.getBid(),
                    tick.getAsk());
        }

        this.simpleOrderService.sendOrder(order);
    }

    @Override
    public void handleChildFill(SlicingOrder algoOrder, SlicingOrderStateVO orderState, Fill fill) {
        orderState.storeFill(fill);
    }

    @Override
    public void handleOrderStatus(SlicingOrder algoOrder, SlicingOrderStateVO algoOrderState,
            OrderStatus orderStatus) {

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

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

    Map<String, Object> getResults(SlicingOrder slicingOrder, SlicingOrderStateVO algoOrderState) {

        List<Pair<LimitOrder, TickI>> pairs = algoOrderState.getPairs();
        List<Fill> fills = algoOrderState.getFills();
        String broker = slicingOrder.getAccount().getBroker();
        SecurityFamily securityFamily = slicingOrder.getSecurity().getSecurityFamily();
        int orderCount = pairs.size();

        Map<String, Object> results = new HashMap<>();
        if (fills.size() > 0) {

            long startTime = (pairs.get(0).getFirst()).getDateTime().getTime();
            Fill lastFill = fills.get(fills.size() - 1);
            long totalTime = lastFill.getDateTime().getTime() - startTime;

            // sort fills by intOrderId
            Map<String, List<Fill>> fillsByIntId = new HashMap<>();
            for (Fill fill : fills) {
                String intId = fill.getOrder().getIntId();
                List<Fill> fillsByIntOrderId = fillsByIntId.get(intId);
                if (fillsByIntOrderId == null) {
                    fillsByIntOrderId = new ArrayList<Fill>();
                    fillsByIntId.put(intId, fillsByIntOrderId);
                }
                fillsByIntOrderId.add(fill);
            }

            long totalTimeToFill = 0;
            int fillCount = 0;
            int filledOrderCount = 0;
            double sumOrderQtyToMarketVol = 0;
            double sumFilledQtyToMarketVol = 0;
            double sumFilledQtyToOrderQty = 0;
            double sumOffsetOrderToMarket = 0;
            double sumOffsetFillToOrder = 0;

            for (Pair<LimitOrder, TickI> pair : pairs) {

                TickI tick = pair.getSecond();
                LimitOrder order = pair.getFirst();

                double marketVol = order.getSide().equals(Side.BUY) ? tick.getVolAsk() : tick.getVolBid();
                double orderQty = order.getQuantity();

                BigDecimal marketPrice = order.getSide().equals(Side.BUY) ? tick.getAsk() : tick.getBid();
                BigDecimal orderPrice = order.getLimit();

                sumOrderQtyToMarketVol += order.getQuantity() / marketVol;
                filledOrderCount = fills.size() > 0 ? ++filledOrderCount : filledOrderCount;

                List<Fill> fillsByIntOrderId = fillsByIntId.get(order.getIntId());
                if (fillsByIntOrderId != null) {
                    for (Fill fill : fillsByIntOrderId) {

                        double filledQty = fill.getQuantity();
                        BigDecimal fillPrice = fill.getPrice();

                        fillCount++;

                        totalTimeToFill += fill.getDateTime().getTime() - order.getDateTime().getTime();

                        sumOrderQtyToMarketVol += orderQty / marketVol;
                        sumFilledQtyToOrderQty += filledQty / orderQty;
                        sumFilledQtyToMarketVol += filledQty / marketVol;

                        sumOffsetOrderToMarket += Math
                                .abs(securityFamily.getSpreadTicks(broker, orderPrice, marketPrice));
                        sumOffsetFillToOrder += Math
                                .abs(securityFamily.getSpreadTicks(broker, fillPrice, orderPrice));
                    }
                }
            }

            results.put("totalTime(msec)", totalTime);
            results.put("avgTimeToFill(msec)", (int) (totalTimeToFill / fillCount));
            results.put("orderCount", orderCount);
            results.put("filledOrderCount", filledOrderCount);
            results.put("filledOrder%", twoDigitFormat.format(filledOrderCount / orderCount * 100.0));
            results.put("avgOrderQtyToMarketVol%",
                    twoDigitFormat.format(sumOrderQtyToMarketVol / fillCount * 100.0));
            results.put("avgFilledQtyToOrderQty%",
                    twoDigitFormat.format(sumFilledQtyToOrderQty / fillCount * 100.0));
            results.put("avgFilledQtyToMarketVol%",
                    twoDigitFormat.format(sumFilledQtyToMarketVol / fillCount * 100.0));
            results.put("avgOffsetOrderToMarket", twoDigitFormat.format(sumOffsetOrderToMarket / fillCount));
            results.put("avgOffsetFillToOrder", twoDigitFormat.format(sumOffsetFillToOrder / fillCount));
        } else {
            results.put("orderCount", orderCount);
        }

        return results;
    }

}