ch.algotrader.simulation.Simulator.java Source code

Java tutorial

Introduction

Here is the source code for ch.algotrader.simulation.Simulator.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.simulation;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.collections15.CollectionUtils;
import org.apache.commons.collections15.MultiMap;
import org.apache.commons.collections15.multimap.MultiHashMap;
import org.apache.commons.lang.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import ch.algotrader.accounting.PositionTracker;
import ch.algotrader.entity.Position;
import ch.algotrader.entity.Transaction;
import ch.algotrader.entity.marketData.MarketDataEventVO;
import ch.algotrader.entity.security.Security;
import ch.algotrader.entity.security.SecurityFamily;
import ch.algotrader.entity.strategy.CashBalance;
import ch.algotrader.entity.strategy.PortfolioValue;
import ch.algotrader.entity.strategy.Strategy;
import ch.algotrader.entity.trade.Fill;
import ch.algotrader.entity.trade.LimitOrderI;
import ch.algotrader.entity.trade.Order;
import ch.algotrader.entity.trade.OrderStatus;
import ch.algotrader.enumeration.Currency;
import ch.algotrader.enumeration.Side;
import ch.algotrader.enumeration.Status;
import ch.algotrader.enumeration.TransactionType;
import ch.algotrader.esper.EngineManager;
import ch.algotrader.event.dispatch.EventDispatcher;
import ch.algotrader.event.dispatch.EventRecipient;
import ch.algotrader.service.MarketDataCacheService;
import ch.algotrader.util.RoundUtil;
import ch.algotrader.util.collection.Pair;
import ch.algotrader.vo.CurrencyAmountVO;
import ch.algotrader.vo.TradePerformanceVO;

/**
 * Utility that can be used by strategies during in-memory simulations. It
 * provides similar functionality to the standard position, cash balance and
 * transaction management but is caclulated in memory and not persisted to the
 * database for performance reasons.
 *
 * @author <a href="mailto:aflury@algotrader.ch">Andy Flury</a>
 */
public class Simulator {

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

    private final Map<Pair<String, Currency>, CashBalance> cashBalances;
    private final Map<Pair<String, Security>, Position> positionsByStrategyAndSecurity;
    private final MultiMap<String, Position> positionsByStrategy;
    private final MultiMap<Security, Position> positionsBySecurity;

    private final MarketDataCacheService marketDataCacheService;
    private final PositionTracker positionTracker;
    private final EventDispatcher eventDispatcher;
    private final EngineManager engineManager;

    public Simulator(final MarketDataCacheService marketDataCacheService, final PositionTracker positionTracker,
            final EventDispatcher eventDispatcher, final EngineManager engineManager) {

        Validate.notNull(marketDataCacheService, "MarketDataCacheService is null");
        Validate.notNull(positionTracker, "PositionTracker is null");
        Validate.notNull(eventDispatcher, "EventDispatcher is null");
        Validate.notNull(engineManager, "EngineManager is null");

        this.marketDataCacheService = marketDataCacheService;
        this.positionTracker = positionTracker;
        this.eventDispatcher = eventDispatcher;
        this.engineManager = engineManager;

        this.cashBalances = new HashMap<>();
        this.positionsByStrategyAndSecurity = new HashMap<>();
        this.positionsByStrategy = new MultiHashMap<>();
        this.positionsBySecurity = new MultiHashMap<>();
    }

    protected MarketDataCacheService getMarketDataCacheService() {
        return this.marketDataCacheService;
    }

    public void clear() {
        this.positionsByStrategyAndSecurity.clear();
        this.positionsByStrategy.clear();
        this.positionsBySecurity.clear();
        this.cashBalances.clear();
    }

    public void createCashBalance(final String strategyName, final Currency currency, final BigDecimal amount) {

        if (findCashBalanceByStrategyAndCurrency(strategyName, currency) != null) {
            throw new IllegalStateException("cashBalance already exists");
        }

        Strategy strategy = Strategy.Factory.newInstance();
        strategy.setName(strategyName);

        CashBalance cashBalance = CashBalance.Factory.newInstance(currency, amount, strategy);

        createCashBalance(cashBalance);

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("created cashBalance: {}", cashBalance);
        }
    }

    public void sendOrder(final Order order) {

        Strategy strategy = order.getStrategy();
        Validate.notNull(strategy, "missing strategy for order " + order);
        Security security = order.getSecurity();
        Validate.notNull(security, "missing security for order " + order);

        final Date now = getCurrentTime();
        if (order.getDateTime() == null) {
            order.setDateTime(now);
        }

        if (order.getExchange() == null) {
            order.setExchange(security.getSecurityFamily().getExchange());
        }

        // propagate order
        this.eventDispatcher.sendEvent(strategy.getName(), order.convertToVO());

        // get the price
        BigDecimal price;
        if (order instanceof LimitOrderI) {

            // limit orders are executed at their limit price
            price = ((LimitOrderI) order).getLimit();
        } else {
            throw new UnsupportedOperationException("only LimitOrders allowed at this time");
        }

        // create one fill per order
        Fill fill = new Fill();
        fill.setDateTime(order.getDateTime());
        fill.setSide(order.getSide());
        fill.setQuantity(order.getQuantity());
        fill.setPrice(price);
        fill.setOrder(order);

        // propagate order
        this.eventDispatcher.sendEvent(strategy.getName(), fill);

        // create and OrderStatus
        OrderStatus orderStatus = OrderStatus.Factory.newInstance();
        orderStatus.setIntId(order.getIntId());
        orderStatus.setDateTime(getCurrentTime());
        orderStatus.setStatus(Status.EXECUTED);
        orderStatus.setFilledQuantity(order.getQuantity());
        orderStatus.setRemainingQuantity(0);
        orderStatus.setOrder(order);
        orderStatus.setAvgPrice(price);

        // propagate order status
        this.eventDispatcher.sendEvent(strategy.getName(), orderStatus.convertToVO());

        // create the transaction
        createTransaction(fill);
    }

    protected Transaction createTransaction(final Fill fill) {

        Order order = fill.getOrder();
        Security security = order.getSecurity();
        Strategy strategy = order.getStrategy();

        SecurityFamily securityFamily = security.getSecurityFamily();
        TransactionType transactionType = Side.BUY.equals(fill.getSide()) ? TransactionType.BUY
                : TransactionType.SELL;
        long quantity = Side.BUY.equals(fill.getSide()) ? fill.getQuantity() : -fill.getQuantity();

        BigDecimal executionCommission = getExecutionCommission(order, fill);
        BigDecimal clearingCommission = getClearingCommission(order, fill);

        Transaction transaction = Transaction.Factory.newInstance();
        transaction.setUuid(UUID.randomUUID().toString());
        transaction.setDateTime(fill.getDateTime());
        transaction.setExtId(fill.getExtId());
        transaction.setQuantity(quantity);
        transaction.setPrice(fill.getPrice());
        transaction.setType(transactionType);
        transaction.setSecurity(security);
        transaction.setStrategy(strategy);
        transaction.setCurrency(securityFamily.getCurrency());
        transaction.setExecutionCommission(executionCommission);
        transaction.setClearingCommission(clearingCommission);
        transaction.setAccount(order.getAccount());

        persistTransaction(transaction);

        return transaction;
    }

    protected BigDecimal getClearingCommission(final Order order, final Fill fill) {

        double clearingCommissionPerContract = order.getSecurity().getSecurityFamily()
                .getClearingCommission(order.getAccount().getBroker()).doubleValue();
        return RoundUtil.getBigDecimal(Math.abs(clearingCommissionPerContract * Math.abs(fill.getQuantity())));
    }

    protected BigDecimal getExecutionCommission(final Order order, final Fill fill) {

        double executionCommissionPerContract = order.getSecurity().getSecurityFamily()
                .getExecutionCommission(order.getAccount().getBroker()).doubleValue();
        return RoundUtil.getBigDecimal(Math.abs(executionCommissionPerContract * Math.abs(fill.getQuantity())));
    }

    /**
     * @copy ch.algotrader.service.TransactionServiceImpl.handlePersistTransaction(Transaction)
     */
    protected Position persistTransaction(final Transaction transaction) {

        TradePerformanceVO tradePerformance = null;

        Strategy strategy = transaction.getStrategy();
        Position position = findPositionByStrategyAndSecurity(strategy.getName(), transaction.getSecurity());
        if (position == null) {

            position = this.positionTracker.processFirstTransaction(transaction);

            // associate strategy
            position.setStrategy(strategy);

            createPosition(position);

            // associate reverse-relations (after position has received an id)
            transaction.setPosition(position);

        } else {

            // process the transaction (adjust quantity, cost and realizedPL)
            tradePerformance = this.positionTracker.processTransaction(position, transaction);

            // associate the position
            transaction.setPosition(position);
        }

        this.eventDispatcher.sendEvent(strategy.getName(), position.convertToVO());

        // add the amount to the corresponding cashBalance
        processTransaction(transaction);

        // propagate Transaction
        this.eventDispatcher.sendEvent(strategy.getName(), transaction.convertToVO());

        // propagate tradePerformance
        if (tradePerformance != null && tradePerformance.getProfit() != 0.0) {

            // propagate the TradePerformance event
            this.eventDispatcher.broadcast(tradePerformance, EventRecipient.SERVER_ENGINE_ONLY);
        }

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("executed transaction: {}", transaction);
        }
        return position;
    }

    /**
     * @copy ch.algotrader.service.CashBalanceServiceImpl.handleProcessTransaction(Transaction)
     */
    private void processTransaction(final Transaction transaction) {

        // process all currenyAmounts
        for (CurrencyAmountVO currencyAmount : transaction.getAttributions()) {
            processAmount(transaction.getStrategy(), currencyAmount);
        }
    }

    /**
     * @copy ch.algotrader.service.CashBalanceServiceImpl.handleProcessAmount(String, CurrencyAmountVO)
     */
    private void processAmount(final Strategy strategy, final CurrencyAmountVO currencyAmount) {

        CashBalance cashBalance = findCashBalanceByStrategyAndCurrency(strategy.getName(),
                currencyAmount.getCurrency());

        // create the cashBalance, if it does not exist yet
        if (cashBalance == null) {

            cashBalance = CashBalance.Factory.newInstance();

            // associate currency, amount and strategy
            cashBalance.setCurrency(currencyAmount.getCurrency());
            cashBalance.setAmount(currencyAmount.getAmount());
            cashBalance.setStrategy(strategy);

            createCashBalance(cashBalance);

        } else {

            cashBalance.setAmount(cashBalance.getAmount().add(currencyAmount.getAmount()));
        }
    }

    protected void createPosition(final Position position) {

        String name = position.getStrategy().getName();
        Security security = position.getSecurity();

        this.positionsByStrategyAndSecurity.put(new Pair<>(name, security), position);
        this.positionsByStrategy.put(name, position);
        this.positionsBySecurity.put(security, position);
    }

    private void createCashBalance(final CashBalance cashBalance) {
        this.cashBalances.put(new Pair<>(cashBalance.getStrategy().getName(), cashBalance.getCurrency()),
                cashBalance);
    }

    public Collection<Position> findAllPositions() {
        return this.positionsByStrategy.values();
    }

    public Collection<Position> findOpenPositions() {
        return CollectionUtils.select(this.positionsByStrategy.values(), entity -> entity.getQuantity() != 0);
    }

    public Position findPositionByStrategyAndSecurity(final String strategyName, final Security security) {
        return this.positionsByStrategyAndSecurity.get(new Pair<>(strategyName, security));
    }

    public Collection<Position> findPositionsByStrategy(final String strategyName) {
        return this.positionsByStrategy.get(strategyName);
    }

    public Collection<Position> findPositionsBySecurity(final Security security) {
        return this.positionsBySecurity.get(security);
    }

    public CashBalance findCashBalanceByStrategyAndCurrency(final String strategyName, final Currency currency) {
        return this.cashBalances.get(new Pair<>(strategyName, currency));
    }

    /**
     * {@link ch.algotrader.service.PortfolioService#getPortfolioValue()}
     */
    public PortfolioValue getPortfolioValue() {

        BigDecimal cashBalance = RoundUtil.getBigDecimal(getCashBalanceDoubleInternal(this.cashBalances.values()));
        BigDecimal marketValue = RoundUtil
                .getBigDecimal(getMarketValueDoubleInternal(this.positionsByStrategyAndSecurity.values()));

        PortfolioValue portfolioValue = PortfolioValue.Factory.newInstance();

        portfolioValue.setDateTime(getCurrentTime());
        portfolioValue.setCashBalance(cashBalance);
        portfolioValue.setMarketValue(marketValue); // might be null if there was no last tick for a particular security
        portfolioValue.setNetLiqValue(marketValue != null ? cashBalance.add(marketValue) : null); // add here to prevent another lookup

        return portfolioValue;
    }

    protected Date getCurrentTime() {
        return this.engineManager.getCurrentEPTime();
    }

    protected PositionTracker getPositionTracker() {
        return this.positionTracker;
    }

    /**
     * @copy ch.algotrader.service.PortfolioServiceImpl.getMarketValueDoubleInternal(Collection<Position>)
     */
    private double getMarketValueDoubleInternal(final Collection<Position> openPositions) {

        // sum of all positions
        double amount = 0.0;
        for (Position openPosition : openPositions) {
            final MarketDataEventVO marketDataEvent = this.marketDataCacheService
                    .getCurrentMarketDataEvent(openPosition.getSecurity().getId());
            amount += openPosition.getMarketValue(marketDataEvent).doubleValue();
        }
        return amount;
    }

    /**
     * @copy ch.algotrader.service.PortfolioServiceImpl.getCashBalanceDoubleInternal(Collection<CashBalance>, List<Position>)
     */
    private double getCashBalanceDoubleInternal(final Collection<CashBalance> cashBalances) {

        // sum of all cashBalances
        double amount = 0.0;
        for (CashBalance cashBalance : cashBalances) {
            amount += cashBalance.getAmount().doubleValue();
        }

        return amount;
    }
}