package org.juxtapose.fxtradingsystem.ordermanager; import java.math.BigDecimal; import java.util.Map; import java.util.Random; import java.util.TreeMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import org.juxtapose.fxtradingsystem.constants.FXDataConstants; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.MultiThreadedClaimStrategy; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.SleepingWaitStrategy; import com.lmax.disruptor.dsl.Disruptor; public class ClientConnector { final OrderManager manager; Disruptor<ClientEvent> disruptor; RingBuffer<ClientEvent> ringBuffer; Random rand = new Random(); long tag = 0; long lastRFQTime = 0; long maxTimeBetweenRFQ = 10l * 1000000l; int maxRFQs = 3500; int warmup = 2000; int avgPriceUpdates = 20; // String[][] instruments = new String[][]{{"EUR", "SEK"}, {"EUR", "NOK"}, {"EUR", "USD"}, {"EUR", "DKK"}, {"EUR", "GBP"}, {"EUR", "TRY"}, {"EUR", "RUB"}, {"EUR", "AUD"}, {"EUR", "CHF"},{"EUR", "NZD"}, {"EUR", "CAD"}, {"EUR", "SGD"}, {"EUR", "JPY"}}; String[][] instruments = new String[][]{{"SEK", "NOK"}}; Map<Long, Long> updateToCount = new TreeMap<Long, Long>(); Map<Long, Long> firstTakeToCount = new TreeMap<Long, Long>(); ScheduledThreadPoolExecutor executor; private void init() { initStatsContainer( updateToCount ); initStatsContainer( firstTakeToCount ); executor = new ScheduledThreadPoolExecutor( 2 ); } private void initStatsContainer( Map<Long, Long> inMap ) { inMap.put( 10000l, 0l ); inMap.put( 15000l, 0l ); inMap.put( 20000l, 0l ); inMap.put( 30000l, 0l ); inMap.put( 40000l, 0l ); inMap.put( 60000l, 0l ); inMap.put( 80000l, 0l ); inMap.put( 100000l, 0l ); inMap.put( 150000l, 0l ); inMap.put( 200000l, 0l ); inMap.put( 300000l, 0l ); inMap.put( 500000l, 0l ); inMap.put( 1000000l, 0l ); inMap.put( 2000000l, 0l ); inMap.put( 5000000l, 0l ); inMap.put( 1000000000l, 0l ); } public void printStats() { System.out.println("FirstTake stats: "); printStats( firstTakeToCount ); System.out.println(); System.out.println("Undate stats: "); printStats( updateToCount ); } public void printStats( Map<Long, Long> inMap ) { for( Long benchMark : inMap.keySet() ) { Long count = inMap.get( benchMark ); System.out.println(benchMark+" : "+count); } } private void addSample( Long inTime, Map<Long, Long> inStatContainer ) { for( Long benchMark : inStatContainer.keySet() ) { if( inTime < benchMark ) { Long count = inStatContainer.get( benchMark )+1; inStatContainer.put( benchMark, count ); return; } } } public ClientConnector( OrderManager inManager ) { init(); EventHandler<ClientEvent> clientEventHandler = new EventHandler<ClientEvent>() { @Override public void onEvent(ClientEvent event, long sequence, boolean endOfBatch) throws Exception { RFQMessage inCommingMess = event.message; if( inCommingMess.messageType == RFQMessage.TYPE_PRICING ) { if( inCommingMess.tag > warmup ) { if( inCommingMess.firstTakeTime != null ) { addSample( inCommingMess.firstTakeTime, firstTakeToCount ); // System.out.println( "FirstTakeTime for rfq "+inCommingMess.tag+" = "+inCommingMess.firstTakeTime+" with price "+inCommingMess.bidPrice+" / "+inCommingMess.askPrice+" sequence "+inCommingMess.sequence ); } else { addSample( inCommingMess.updateTime, updateToCount ); // System.out.println( "Price "+inCommingMess.ccy1+inCommingMess.ccy2+" is "+inCommingMess.bidPrice+" / "+inCommingMess.askPrice+" sequence "+inCommingMess.sequence+" updatetime: "+inCommingMess.updateTime+" id: "+inCommingMess.tag); } } if( inCommingMess.sequence == avgPriceUpdates )//rand.nextInt( avgPriceUpdates ) == 1 ) { RFQMessage dr = new RFQMessage( RFQMessage.TYPE_DR, inCommingMess.ccy1, inCommingMess.ccy2, inCommingMess.tag, inCommingMess.bidPrice, inCommingMess.askPrice, null, null, 0, BigDecimal.ONE ); manager.sendDR( dr ); if( inCommingMess.tag == maxRFQs -1 ) { printStats(); } } } } }; disruptor = new Disruptor<ClientEvent>(ClientEvent.EVENT_FACTORY, executor, new MultiThreadedClaimStrategy(2048), new SleepingWaitStrategy()); /**Code to use multiple consumers for disruptor**/ // ClientEventHandler handler1 = new ClientEventHandler(0, 2); // ClientEventHandler handler2 = new ClientEventHandler(1, 2); // disruptor.handleEventsWith(handler1, handler2); disruptor.handleEventsWith( clientEventHandler ); ringBuffer = disruptor.start(); manager = inManager; startRFQThread(); } private void startRFQThread() { Thread rfqThread = new Thread( new Runnable() { @Override public void run() { try { int i = 0; for(;;) { // RFQMessage inCommingMess = incomming.poll( 100, TimeUnit.MILLISECONDS ); // // if( inCommingMess != null ) // { // if( inCommingMess.messageType == RFQMessage.TYPE_PRICING ) // { // if( inCommingMess.firstTakeTime != null ) // System.out.println( "FirstTakeTime for rfq "+inCommingMess.tag+" = "+inCommingMess.firstTakeTime+" with price "+inCommingMess.bidPrice+" / "+inCommingMess.askPrice+" sequence "+inCommingMess.sequence ); // else // System.out.println( "Price is "+inCommingMess.bidPrice+" / "+inCommingMess.askPrice+" sequence "+inCommingMess.sequence+" updatetime: "+inCommingMess.updateTime+" id: "+inCommingMess.tag); // // } // } if( lastRFQTime == 0 ) { sendRFQ(); } else { long now = System.nanoTime(); long timeSinceLast = now - lastRFQTime; if( timeSinceLast > maxTimeBetweenRFQ && tag < maxRFQs ) { // System.out.println("it has been "+timeSinceLast+" since last RFQ.. placing another ("+timeSinceLast+" > "+maxTimeBetweenRFQ+")"); sendRFQ(); } } i++; } } catch ( Throwable t ) { t.printStackTrace(); } } }, "RFQ Requestor"); rfqThread.start(); } private void sendRFQ( ) { lastRFQTime = System.nanoTime(); String[] instrument = instruments[ rand.nextInt( instruments.length ) ]; RFQMessage rfq = new RFQMessage( instrument[0], instrument[1], FXDataConstants.STATE_INSTRUMENT_SPOT, null, null, tag++, new BigDecimal( 7000000 ) ); manager.sendRFQ( rfq ); } public void updateRFQ( RFQMessage inMessage ) { long sequence = ringBuffer.next(); ClientEvent event = ringBuffer.get(sequence); event.setMessage( inMessage ); ringBuffer.publish(sequence); // incomming.offer( inMessage ); } }