package org.juxtapose.fxtradingsystem.aggregator; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import java.util.TreeSet; import org.juxtapose.fxtradingsystem.BigDecimals; import org.juxtapose.fxtradingsystem.constants.FXDataConstants; import org.juxtapose.fxtradingsystem.constants.FXProducerServiceConstants; import org.juxtapose.fxtradingsystem.marketdata.IMarketDataSubscriber; import org.juxtapose.fxtradingsystem.marketdata.MarketDataConstants; import org.juxtapose.fxtradingsystem.marketdata.MarketDataSource; import org.juxtapose.fxtradingsystem.marketdata.QPMessage; import org.juxtapose.fxtradingsystem.priceengine.PriceEngineDataConstants; import org.juxtapose.streamline.producer.ISTMEntryKey; import org.juxtapose.streamline.producer.STMEntryProducer; import org.juxtapose.streamline.producer.executor.Executable; import org.juxtapose.streamline.stm.DataTransaction; import org.juxtapose.streamline.stm.ISTM; import org.juxtapose.streamline.stm.STMTransaction; import org.juxtapose.streamline.tools.DataConstants; import org.juxtapose.streamline.util.ISTMEntry; import org.juxtapose.streamline.util.ISTMEntryRequestSubscriber; import org.juxtapose.streamline.util.PersistentArrayList; import org.juxtapose.streamline.util.Status; import org.juxtapose.streamline.util.data.DataTypeArrayList; import org.juxtapose.streamline.util.data.DataTypeBigDecimal; import org.juxtapose.streamline.util.data.DataTypeLong; public class LiquidityPoolProducer extends STMEntryProducer implements IMarketDataSubscriber, ISTMEntryRequestSubscriber{ final String source; final String ccy1; final String ccy2; final String period; ArrayList<ISTMEntryKey> keys = new ArrayList<ISTMEntryKey>(); public static PriceEntryCompare priceEntryComparatorBid = new PriceEntryCompare( true ); public static PriceEntryCompare priceEntryComparatorAsk = new PriceEntryCompare( false ); public LiquidityPoolProducer(ISTMEntryKey inKey, ISTM inSTM, String inSource, String inCcy1, String inCcy2, String inPeriod) { super(inKey, inSTM); source = inSource; ccy1 = inCcy1; ccy2 = inCcy2; period = inPeriod; } @Override protected void start() { if( source == null || ccy1 == null || ccy2 == null || period == null ) { stm.logError( "Missing required field in MarketDataProducer" ); return; } try { startSubscription(); }catch( Exception e ) { stm.logError( e.getMessage(), e ); } } public void startSubscription( ) throws Exception { if( source.equals( FXDataConstants.STATE_SOURCE_WILDCARD )) { requestSourceKey( FXDataConstants.STATE_SOURCE_REUTERS ); requestSourceKey( FXDataConstants.STATE_SOURCE_BLOOMBERG ); requestSourceKey( FXDataConstants.STATE_SOURCE_UBS ); requestSourceKey( FXDataConstants.STATE_SOURCE_GOLDMAN ); } else { QPMessage subMessage = new QPMessage( QPMessage.SUBSCRIBE, ccy1, ccy2, period); MarketDataSource.addSubscriber( this, subMessage, source ); } } private void requestSourceKey( String inSource ) { HashMap<String, String> query = new HashMap<String, String>(); query.put( MarketDataConstants.FIELD_TYPE, PriceEngineDataConstants.STATE_TYPE_LIQUIDITY ); query.put( MarketDataConstants.FIELD_CCY1, ccy1 ); query.put( MarketDataConstants.FIELD_CCY2, ccy2 ); query.put( MarketDataConstants.FIELD_PERIOD, FXDataConstants.STATE_PERIOD_SP ); query.put( FXDataConstants.FIELD_SOURCE, inSource ); query.put( FXDataConstants.FIELD_INSTRUMENT, FXDataConstants.STATE_INSTRUMENT_SPOT ); stm.getDataKey( FXProducerServiceConstants.AGGREGATOR, this, inSource, query); } @Override public void marketDataUpdated(final QPMessage inMessage, int inHash) { stm.execute( new Executable( inHash ) { @Override public void run() { parseQuote( inMessage ); } }, getPriority() ); } private final void parseQuote( final QPMessage inQuote ) { stm.commit( new DataTransaction(entryKey, this, true ) { @Override public void execute() { createMessageFromQuote( inQuote, this ); Long timeStamp = System.nanoTime(); putValue( MarketDataConstants.FIELD_TIMESTAMP, new DataTypeLong( timeStamp )); if( getStatus() != Status.OK ) setStatus( Status.OK ); } }); } private final void createMessageFromQuote( final QPMessage inMessage, DataTransaction inTransaction ) { PersistentArrayList<DataTypeArrayList> bidSide = createSide( inMessage.bid, true ); PersistentArrayList<DataTypeArrayList> askSide = createSide( inMessage.ask, false ); inTransaction.putValue( MarketDataConstants.FIELD_BID, new DataTypeArrayList(bidSide) ); inTransaction.putValue( MarketDataConstants.FIELD_ASK, new DataTypeArrayList(askSide) ); } private final PersistentArrayList<DataTypeArrayList> createSide( Double inQuote, boolean inBid ) { PersistentArrayList<DataTypeArrayList> side = new PersistentArrayList<DataTypeArrayList>(); BigDecimal pips = BigDecimals.getInt( 10000 ).get(); for( int i = 1; i < 5; i++ ) { PersistentArrayList<DataTypeBigDecimal> entry = new PersistentArrayList<DataTypeBigDecimal>(); BigDecimal bid = new BigDecimal( inQuote ).setScale( 4, RoundingMode.HALF_UP ); BigDecimal adjust = BigDecimals.getInt( i ).get().divide( pips ); if( !inBid ) adjust = adjust.multiply(BigDecimals.MINUS_ONE.get()); adjust = adjust.setScale( 4, RoundingMode.HALF_UP ); bid = bid.add( adjust ); BigDecimal amt = new BigDecimal(1000000).multiply( new BigDecimal(i) ); amt = amt.setScale( 1, RoundingMode.HALF_UP ); entry = entry.add( new DataTypeBigDecimal( bid ) ); entry = entry.add( new DataTypeBigDecimal( amt ) ); entry = entry.add( FXDataConstants.getSourceCode( source ) ); DataTypeArrayList sideData = new DataTypeArrayList(entry); side = side.add( sideData ); } return side; } @Override public void deliverKey( ISTMEntryKey inDataKey, Object inTag ) { keys.add( inDataKey ); stm.subscribeToData( inDataKey, this ); } @Override public void queryNotAvailible( Object inTag ) { stm.logError( "Could not find object for tag "+inTag); } public void updateData( ISTMEntryKey inKey, final ISTMEntry inData, boolean inFullUpdate ) { if( inData.getStatus() == Status.ON_REQUEST ) return; stm.commit( new STMTransaction( entryKey, LiquidityPoolProducer.this, inFullUpdate ) { @Override public void execute() { putValue( DataConstants.FIELD_TIMESTAMP, inData.getValue( DataConstants.FIELD_TIMESTAMP ) ); PersistentArrayList<DataTypeArrayList> bidSide = recalculateLiquiditySide( true ); PersistentArrayList<DataTypeArrayList> askSide = recalculateLiquiditySide( false ); if( bidSide == null || askSide == null ) { dispose(); return; } putValue( FXDataConstants.FIELD_BID, new DataTypeArrayList( bidSide ) ); putValue( FXDataConstants.FIELD_ASK, new DataTypeArrayList( askSide ) ); setStatus( Status.OK ); } } ); } /** * @param inBid * @return */ private final PersistentArrayList<DataTypeArrayList> recalculateLiquiditySide( boolean inBid ) { Set<DataTypeArrayList> entries = new TreeSet<DataTypeArrayList>( inBid ? priceEntryComparatorBid : priceEntryComparatorAsk ); String field = inBid ? FXDataConstants.FIELD_BID : FXDataConstants.FIELD_ASK; for( ISTMEntryKey key : keys ) { ISTMEntry liquidity = stm.getData( key.getKey() ); DataTypeArrayList list = (DataTypeArrayList)liquidity.getValue( field ); if( list == null ) continue; for( int i = 0; i < list.get().size(); i++ ) { DataTypeArrayList entry = (DataTypeArrayList)list.get().get( i ); entries.add( entry ); } } PersistentArrayList<DataTypeArrayList> side = new PersistentArrayList<DataTypeArrayList>(); for( DataTypeArrayList entry : entries ) { side = side.add( entry ); } return side; } protected void stop() { super.stop(); for( ISTMEntryKey key : keys ) { stm.unsubscribeToData( key, this ); } if( !"*".equals( source )) MarketDataSource.removeSubscriber( this, source ); } }