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;
}
}
}