package org.marketcetera.core.position.impl;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.LinkedList;
import org.apache.commons.lang.Validate;
import org.marketcetera.core.position.PositionMetrics;
import org.marketcetera.core.position.Trade;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* An implementation of {@link PositionMetricsCalculator}.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: PositionMetricsCalculatorImpl.java 16154 2012-07-14 16:34:05Z colin $
* @since 1.5.0
*/
@ClassVersion("$Id: PositionMetricsCalculatorImpl.java 16154 2012-07-14 16:34:05Z colin $")
public final class PositionMetricsCalculatorImpl implements PositionMetricsCalculator {
private final BigDecimal mIncomingPosition;
private final CostElement mPositionCost = new CostElement();
private final CostElement tradingCost = new CostElement();
private final CostElement mUnrealizedCost = new CostElement();
private final LinkedList<PositionElement> mPositionElements = new LinkedList<PositionElement>();
private final boolean mClosingPriceAvailable;
private BigDecimal mLastTradePrice;
private BigDecimal mPosition;
private BigDecimal mRealizedPL = BigDecimal.ZERO;
/**
* Constructor.
*
* @param incomingPosition
* the incoming position that will be used to calculate position PL
* @param closingPrice
* the closing price that will be used to calculate position PL
* @throws IllegalArgumentException if incomingPosition is null
*/
public PositionMetricsCalculatorImpl(final BigDecimal incomingPosition, BigDecimal closingPrice) {
Validate.notNull(incomingPosition);
mIncomingPosition = incomingPosition;
mPosition = incomingPosition;
mClosingPriceAvailable = closingPrice != null;
if (mClosingPriceAvailable) {
mPositionCost.add(mPosition, closingPrice);
mUnrealizedCost.add(mPosition, closingPrice);
mPositionElements.add(new PositionElement(mPosition, closingPrice));
}
}
@Override
public synchronized PositionMetrics tick(final BigDecimal tradePrice) {
mLastTradePrice = tradePrice;
return createPositionMetrics();
}
@Override
public synchronized PositionMetrics trade(final Trade<?> trade) {
processTrade(trade.getQuantity(), trade.getPrice());
return createPositionMetrics();
}
/**
* Processes a trade, closing existing positions and creating new ones as necessary.
*
* @param quantity
* the quantity of the trade, positive for a buy and negative for a sell
* @param price
* the price of the trade
*/
private void processTrade(final BigDecimal quantity, final BigDecimal price) {
mPosition = mPosition.add(quantity);
// only bother with PNL if the closing price is available
if (mClosingPriceAvailable) {
tradingCost.add(quantity, price);
// determine the sides, +1 for long and -1 for short
int holdingSide = mUnrealizedCost.signum();
int tradingSide = quantity.signum();
BigDecimal remaining = quantity;
// if sides are different
if (tradingSide * holdingSide == -1) {
// close positions
while (!mPositionElements.isEmpty()) {
// get the oldest open position
PositionElement toClose = mPositionElements.peek();
// add the remaining trade quantity
BigDecimal leftover = toClose.quantity.add(remaining);
int leftoverSide = leftover.signum();
// if there is leftover on the open position
if (leftoverSide == holdingSide) {
// the trade only partially closed this position
processClose(remaining, toClose.price, price);
toClose.quantity = leftover;
// trade has been completely processed
return;
} else {
// the trade completely closed this position
processClose(toClose.quantity.negate(), toClose.price, price);
mPositionElements.remove();
remaining = leftover;
// if leftover is zero
if (leftoverSide == 0) {
// trade has been completely processed
return;
}
}
}
}
// if non-zero remaining quantity
if (remaining.signum() != 0) {
// create new position
mPositionElements.add(new PositionElement(remaining, price));
mUnrealizedCost.add(remaining, price);
}
}
}
/**
* Processes a position close, updating realized P&L and the unrealized cost
*
* @param quantity
* the quantity being closed, negative when closing a long position and positive when
* closing a short position
* @param openPrice
* the price at which the position was opened
* @param closePrice
* the price at which the position is closing
*/
private void processClose(final BigDecimal quantity, final BigDecimal openPrice,
final BigDecimal closePrice) {
// subtract closePrice from openPrice since quantity has opposite sign
// more readable may be:
// quantity.negate().multiply(closePrice.subtract(openPrice))
mRealizedPL = mRealizedPL.add(quantity.multiply(openPrice.subtract(closePrice)));
mUnrealizedCost.add(quantity, openPrice);
}
private PositionMetrics createPositionMetrics() {
BigDecimal unrealizedPL = null;
BigDecimal realizedPL = null;
BigDecimal tradingPL = null;
BigDecimal positionPL = null;
BigDecimal totalPL = null;
if (mClosingPriceAvailable) {
realizedPL = mRealizedPL;
if (mLastTradePrice != null) {
positionPL = mPositionCost.getPL(mLastTradePrice);
unrealizedPL = mUnrealizedCost.getPL(mLastTradePrice);
tradingPL = tradingCost.getPL(mLastTradePrice);
totalPL = realizedPL.add(unrealizedPL);
}
}
PositionMetricsImpl positionMetrics = new PositionMetricsImpl(mIncomingPosition,
mPosition, positionPL, tradingPL, realizedPL, unrealizedPL, totalPL);
// Theoretically, both ways of calculating total PL should give the same results
assert !mClosingPriceAvailable || mLastTradePrice == null
|| totalPL.compareTo(positionPL.add(tradingPL)) == 0 : positionMetrics;
if (SLF4JLoggerProxy.isDebugEnabled(this)) {
if (mLastTradePrice != null && totalPL.compareTo(positionPL.add(tradingPL)) != 0) {
SLF4JLoggerProxy.debug(this, MessageFormat.format(
"There is a discrepancy in the total PL.\n{0}", positionMetrics)); //$NON-NLS-1$
}
}
return positionMetrics;
}
private static class CostElement {
private BigDecimal quantity = BigDecimal.ZERO;
private BigDecimal cost = BigDecimal.ZERO;
public void add(BigDecimal quantity, BigDecimal price) {
this.quantity = this.quantity.add(quantity);
cost = cost.add(quantity.multiply(price));
}
public BigDecimal getPL(BigDecimal lastTradePrice) {
return quantity.multiply(lastTradePrice).subtract(cost);
}
public int signum() {
return quantity.signum();
}
}
private class PositionElement {
public BigDecimal quantity;
public BigDecimal price;
public PositionElement(BigDecimal quantity, BigDecimal price) {
this.quantity = quantity;
this.price = price;
}
}
}