In this article we use wireshark and tcpdump to analyze the latency numbers of a test trading strategy that receives a market data tick through UDP using CoralReactor and places a trade order through FIX using CoralFIX.
Test Details
A pseudo market data generator sends UDP packets containing market data updates (i.e. ticks). Our test trading strategy receives these packets, parses them and places a trade order using the FIX protocol on a pseudo exchange that immediately executes the order. To warm up the test strategy, we initially send 1 million market data updates. Then we proceed to send one packet every 5 seconds. To measure the latency, we use tcpdump to record the UDP packet coming in and the FIX order going out. With the packet capture file from tcpdump we then use wireshark to calculate the difference in the timestamps of the packet arriving and the FIX order leaving.
Results
As you can see from the wireshark screenshot below, the tick-to-trade latencies are around 8-9 microseconds.
Source Code
Note that the source code down below produces zero garbage for the GC.
package com.coralblocks.coralfix.bench.trade; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import com.coralblocks.coralbits.util.PriceUtils; import com.coralblocks.coralfix.FixConstants; import com.coralblocks.coralfix.FixMessage; import com.coralblocks.coralfix.FixTags; import com.coralblocks.coralfix.client.FixApplicationClient; import com.coralblocks.coralreactor.client.Client; import com.coralblocks.coralreactor.client.ClientAdapter; import com.coralblocks.coralreactor.client.receiver.ReceiverUdpClient; import com.coralblocks.coralreactor.nio.NioReactor; import com.coralblocks.coralreactor.util.MapConfiguration; public class TickToTrade extends ClientAdapter { private final NioReactor nio; private final FixApplicationClient fixGateway; private final byte[] symbol = new byte[8]; private long orderIDs = 1; public TickToTrade(NioReactor nio, final Client marketDataFeed, FixApplicationClient fixGateway) { this.nio = nio; this.fixGateway = fixGateway; fixGateway.addListener(new ClientAdapter() { @Override public void onConnectionOpened(Client client) { // gateway is connected and ready to trade... marketDataFeed.open(); // open the feed... marketDataFeed.addListener(TickToTrade.this); // so that onMessage below is called } }); } public void start() { fixGateway.open(); } @Override public void onMessage(Client client, InetSocketAddress fromAddress, ByteBuffer msg) { // parse the market data quote long quoteId = msg.getLong(); boolean isBid = msg.get() == 'B'; msg.get(symbol); long size = msg.getInt(); long price = msg.getLong(); // hit the market data quote FixMessage outFixMsg = fixGateway.getOutFixMessage(FixConstants.MsgTypes.NewOrderSingle); outFixMsg.add(FixTags.ClOrdID, orderIDs++); outFixMsg.add(FixTags.OrigClOrdID, quoteId); outFixMsg.addTrimmed(FixTags.Symbol, symbol); outFixMsg.add(FixTags.Side, isBid ? "2" : "1"); // sell to the bid or buy from the offer outFixMsg.addTimestamp(FixTags.TransactTime, nio.currentTimeMillis()); outFixMsg.add(FixTags.OrderQty, size); outFixMsg.add(FixTags.OrdType, "2"); // limit order outFixMsg.add(FixTags.Price, PriceUtils.toDouble(price)); outFixMsg.add(FixTags.TimeInForce, "3"); // IoC fixGateway.send(outFixMsg); } public static void main(String[] args) { NioReactor nio = NioReactor.create(); Client marketDataFeed = new ReceiverUdpClient(nio, 44444); MapConfiguration config = new MapConfiguration(); config.add("fixVersion", 44); config.add("senderComp", "testClient"); config.add("forceSeqReset", true); FixApplicationClient fixGateway = new FixApplicationClient(nio, args.length > 0 ? args[0] : "localhost", 55555, config); TickToTrade tickToTrade = new TickToTrade(nio, marketDataFeed, fixGateway); tickToTrade.start(); nio.start(); } }