package org.marketcetera.core.position.impl; import java.math.BigDecimal; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.lang.Validate; import org.marketcetera.core.position.MarketDataSupport; import org.marketcetera.core.position.PositionMetrics; import org.marketcetera.core.position.PositionRow; import org.marketcetera.core.position.Trade; import org.marketcetera.core.position.MarketDataSupport.InstrumentMarketDataEvent; import org.marketcetera.core.position.MarketDataSupport.InstrumentMarketDataListener; import org.marketcetera.core.position.MarketDataSupport.InstrumentMarketDataListenerBase; import org.marketcetera.trade.Future; import org.marketcetera.trade.Option; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.misc.NamedThreadFactory; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; /* $License$ */ /** * Responsible for updating a PositionRow when trade or market data events occur. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: PositionRowUpdater.java 16604 2013-06-26 14:49:42Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: PositionRowUpdater.java 16604 2013-06-26 14:49:42Z colin $") public final class PositionRowUpdater { private final ListEventListener<Trade<?>> mListChangeListener; private EventList<Trade<?>> mTrades; private final PositionRowImpl mPositionRow; private final MarketDataSupport mMarketDataSupport; private final InstrumentMarketDataListener mSymbolChangeListener; private static final ExecutorService sMarketDataUpdateExecutor = Executors .newSingleThreadExecutor(new NamedThreadFactory("PositionRowUpdater")); //$NON-NLS-1$ private final AtomicBoolean mTickPending = new AtomicBoolean(); private final AtomicBoolean mClosingPricePending = new AtomicBoolean(); private final AtomicBoolean mMultiplierPending = new AtomicBoolean(); private volatile PositionMetricsCalculator mCalculator; private volatile BigDecimal mClosePrice; private volatile BigDecimal mMultiplier; private volatile BigDecimal mLastTradePrice; /** * Returns the PositionRow being managed by this class. * * @return the dynamically updated position */ public PositionRow getPosition() { return mPositionRow; } /** * Constructor. * * {@link #dispose()} must be called when the instance is no longer in use. * * @param positionRow * the position to update * @param trades * event list of trades that make up the position, may be null * @param marketData * the market data provider */ public PositionRowUpdater(PositionRowImpl positionRow, EventList<Trade<?>> trades, MarketDataSupport marketData) { Validate.noNullElements(new Object[] { positionRow, marketData }); mPositionRow = positionRow; mMarketDataSupport = marketData; mListChangeListener = new ListEventListener<Trade<?>>() { @Override public void listChanged(ListEvent<Trade<?>> listChanges) { PositionRowUpdater.this.listChanged(listChanges); } }; mSymbolChangeListener = new InstrumentMarketDataListenerBase() { @Override public void symbolTraded(InstrumentMarketDataEvent event) { tick(event.getNewAmount()); } @Override public void closePriceChanged(InstrumentMarketDataEvent event) { PositionRowUpdater.this.closePriceChanged(event.getNewAmount()); } @Override public void optionMultiplierChanged(InstrumentMarketDataEvent event) { PositionRowUpdater.this.instrumentMultiplierChanged(event.getNewAmount()); } @Override public void futureMultiplierChanged(InstrumentMarketDataEvent event) { PositionRowUpdater.this.instrumentMultiplierChanged(event.getNewAmount()); } }; mMarketDataSupport.addInstrumentMarketDataListener( mPositionRow.getInstrument(), mSymbolChangeListener); if (trades != null) { connect(trades); } else { mPositionRow.setPositionMetrics(recalculate()); } } /** * Connects this class to a list of trades. This can only be called on instances that were * created with a null trades lists. And it can only be called once. * * @param trades * the dynamically updated list of trades * @throws IllegalStateException * if this object is already connected to list of trades */ public void connect(EventList<Trade<?>> trades) { if (mTrades != null) { throw new IllegalStateException(); } mTrades = trades; mTrades.addListEventListener(mListChangeListener); mPositionRow.setPositionMetrics(recalculate()); } /** * Releases the resources held by this object. After dispose has been called, this object should * no longer be used. */ public void dispose() { if (mTrades != null) { mTrades.removeListEventListener(mListChangeListener); } mMarketDataSupport.removeInstrumentMarketDataListener(mPositionRow .getInstrument(), mSymbolChangeListener); } private void tick(BigDecimal tick) { mLastTradePrice = tick; /* * Since this modifies the position and will need a lock, the update * happens in a separate thread. mTickPending is used to avoid queuing * multiple updates since the runnable always uses the latest value. */ if (mTickPending.compareAndSet(false, true)) { sMarketDataUpdateExecutor.execute(new Runnable() { @Override public void run() { mTickPending.set(false); if (mCalculator != null) { mPositionRow.setPositionMetrics(mCalculator .tick(mLastTradePrice)); } } }); } } private void closePriceChanged(BigDecimal newPrice) { BigDecimal oldPrice = mClosePrice; /* * Since change close price requires a full recalculation, only do it if * necessary. */ if (oldPrice == null && newPrice == null) { return; } else if (oldPrice != null && newPrice != null && oldPrice.compareTo(newPrice) == 0) { return; } mClosePrice = newPrice; /* * Since this modifies the position and will need a lock, the update * happens in a separate thread. mClosingPricePending is used to avoid * queuing multiple updates since the runnable always uses the latest * value. */ if (mClosingPricePending.compareAndSet(false, true)) { sMarketDataUpdateExecutor.execute(new Runnable() { @Override public void run() { mClosingPricePending.set(false); mPositionRow.setPositionMetrics(recalculate()); } }); } } private void instrumentMultiplierChanged(BigDecimal multiplier) { BigDecimal oldMultiplier = mMultiplier; /* * Only process if necessary. */ if (oldMultiplier == null && multiplier == null) { return; } else if (oldMultiplier != null && multiplier != null && oldMultiplier.compareTo(multiplier) == 0) { return; } mMultiplier = multiplier; /* * Since this modifies the position and will need a lock, the update * happens in a separate thread. mMultiplierPending is used to avoid * queuing multiple updates since the runnable always uses the latest * value. */ if (mMultiplierPending.compareAndSet(false, true)) { sMarketDataUpdateExecutor.execute(new Runnable() { @Override public void run() { mMultiplierPending.set(false); mPositionRow.setPositionMetrics(recalculate()); } }); } } private void listChanged(ListEvent<Trade<?>> listChanges) { assert listChanges.getSourceList() == mTrades; while (listChanges.next()) { final int changeIndex = listChanges.getIndex(); final int changeType = listChanges.getType(); if (changeType == ListEvent.INSERT && mTrades.size() == changeIndex + 1) { Trade<?> trade = mTrades.get(changeIndex); mPositionRow.setPositionMetrics(mCalculator.trade(trade)); } else { mPositionRow.setPositionMetrics(recalculate()); } } } private PositionMetrics recalculate() { PositionMetricsCalculatorImpl calculator = new PositionMetricsCalculatorImpl( mPositionRow.getPositionMetrics().getIncomingPosition(), mClosePrice); // TODO: instrument specific functionality should be abstracted if (mPositionRow.getInstrument() instanceof Option) { mCalculator = new MultiplierCalculator(calculator, mMultiplier); } else if (mPositionRow.getInstrument() instanceof Future) { mCalculator = new MultiplierCalculator(calculator, mMultiplier); }else { mCalculator = calculator; } PositionMetrics metrics = mCalculator.tick(mLastTradePrice); if (mTrades != null) { for (Trade<?> trade : mTrades) { metrics = mCalculator.trade(trade); } } return metrics; } }