package org.marketcetera.core.position.impl; import java.math.BigDecimal; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.marketcetera.core.position.PositionMetrics; import org.marketcetera.core.position.Trade; /* $License$ */ /** * Basic implementation of {@link PositionMetricsCalculator} that recomputes each value from scratch * every time. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: BasicCalculator.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ public class BasicCalculator implements PositionMetricsCalculator { private List<Trade<?>> mTrades = new ArrayList<Trade<?>>(); private BigDecimal mTick; private final BigDecimal mIncomingPosition; private final BigDecimal mClosingPrice; public BasicCalculator(BigDecimal incomingPosition, BigDecimal closingPrice) { mIncomingPosition = incomingPosition; mClosingPrice = closingPrice; } public PositionMetrics tick(String current) { return tick(new BigDecimal(current)); } @Override public PositionMetrics tick(BigDecimal current) { this.mTick = current; return createPositionMetrics(); } @Override public PositionMetrics trade(Trade<?> trade) { mTrades.add(trade); return createPositionMetrics(); } private PositionMetrics createPositionMetrics() { BigDecimal positionPL = getPositionPL(); BigDecimal tradingPL = getTradingPL(); BigDecimal totalPL = null; if (mTick != null) { totalPL = positionPL.add(tradingPL); } return new PositionMetricsImpl(mIncomingPosition, getPosition(), positionPL, tradingPL, getRealizedPL(), getUnrealizedPL(), totalPL); } private BigDecimal getPosition() { BigDecimal position = mIncomingPosition; for (Trade<?> trade : mTrades) { position = position.add(trade.getQuantity()); } return position; } private BigDecimal getPositionPL() { if (mTick == null) { return null; } return mIncomingPosition.multiply(mTick.subtract(mClosingPrice)); } private BigDecimal getTradingPL() { if (mTick == null) { return null; } BigDecimal trading = BigDecimal.ZERO; for (Trade<?> trade : mTrades) { BigDecimal single = mTick.subtract(trade.getPrice()).multiply(trade.getQuantity()); trading = trading.add(single); } return trading; } private BigDecimal getRealizedPL() { BigDecimal total = BigDecimal.ZERO; Queue<PositionElement> longs = new LinkedList<PositionElement>(); Queue<PositionElement> shorts = new LinkedList<PositionElement>(); adjustIncomingPosition(longs, shorts); for (Trade<?> trade : mTrades) { total = total.add(processTrade(longs, shorts, trade)); } return total; } /** * Helper method that updates two arrays of long and short position elements based on a trade. */ private BigDecimal processTrade(Queue<PositionElement> longs, Queue<PositionElement> shorts, Trade<?> trade) { BigDecimal total = BigDecimal.ZERO; Queue<PositionElement> source, dest; BigDecimal remaining; BigDecimal quantity = trade.getQuantity(); BigDecimal price = trade.getPrice(); if (quantity.signum() == 1) { // buy remaining = quantity; source = shorts; dest = longs; } else { // sell remaining = quantity.negate(); source = longs; dest = shorts; } while (remaining.signum() == 1 && !source.isEmpty()) { PositionElement element = source.peek(); int compare = element.quantity.compareTo(remaining); BigDecimal priceDifference = price.subtract(element.price); if (source == shorts) { // negate the price difference for closing short positions // since realized gains happen when price has decreased priceDifference = priceDifference.negate(); } if (compare == 0) { // position element is closed total = total.add(priceDifference.multiply(remaining)); source.remove(); remaining = BigDecimal.ZERO; } else if (compare > 0) { total = total.add(priceDifference.multiply(remaining)); element.quantity = element.quantity.subtract(remaining); remaining = BigDecimal.ZERO; } else if (compare < 0) { total = total.add(priceDifference.multiply(element.quantity)); source.remove(); remaining = remaining.subtract(element.quantity); } } if (remaining.signum() == 1) { dest.add(new PositionElement(remaining, price)); } return total; } private BigDecimal getUnrealizedPL() { if (mTick == null) { return null; } Queue<PositionElement> longs = new LinkedList<PositionElement>(); Queue<PositionElement> shorts = new LinkedList<PositionElement>(); adjustIncomingPosition(longs, shorts); for (Trade<?> trade : mTrades) { processTrade(longs, shorts, trade); } BigDecimal total = BigDecimal.ZERO; for (PositionElement element : longs) { total = total.add(mTick.subtract(element.price).multiply(element.quantity)); } for (PositionElement element : shorts) { total = total.subtract(mTick.subtract(element.price).multiply(element.quantity)); } return total; } private void adjustIncomingPosition(Queue<PositionElement> longs, Queue<PositionElement> shorts) { if (mIncomingPosition.signum() == 1) { longs.add(new PositionElement(mIncomingPosition, mClosingPrice)); } else if (mIncomingPosition.signum() == -1) { shorts.add(new PositionElement(mIncomingPosition.negate(), mClosingPrice)); } } private class PositionElement { public BigDecimal quantity; public BigDecimal price; public PositionElement(BigDecimal quantity, BigDecimal price) { this.quantity = quantity; this.price = price; } } }