If you have spent any time in a trading organization, whether foreign exchange (FX), commodities, or equities, there's a good chance you've encountered the Financial Information eXchange (FIX) protocol. FIX is a socket-based, asynchronous message protocol designed for electronic trading. The protocol is implemented and used across all the major exchanges, such as NYSE, NASDAQ, CME, and ICE. FIX provides an API to broker-dealers and retail customers to obtain security definitions, submit and manage orders, and receive events such as trades.
This article guides you through the steps to develop a basic application that can exchange financial information over the FIX protocol in a Java application using the QuickFIX/J engine. We'll use Quarkus to gain access to the many conveniences this framework offers.
What is the FIX protocol?
The power of the FIX protocol comes from its standardization and size. FIX messages are defined as groupings of tag=value pairs, where the tags are a standard set of integers corresponding to valid types of fields. For instance, the field identifying the message type has tag 35. Fields are separated by a control character called the Start of Heading
or <SOH>
character, and its value is 1.
The FIX message is broken into three parts: header, body, and trailer. The header contains metadata about the message, including what version of the protocol is being used, what type of message it is, routing information, its sequence number within the current FIX session, and the length of the message. The body contains the message contents based on the header's message type. The trailer contains a checksum for the message and, optionally, an electronic signature.
Here's an example FIX NewOrderSingle
message with the SOH character replaced with a pipe character for readability:
8=FIX.4.4|9=148|35=D|34=1080|49=TESTBUY1|52=20180920-18:14:19.508|56=TESTSELL1|11=636730640278898634|15=USD|21=2|38=7000|40=1|54=1|55=MSFT|60=20180920-18:14:19.492|10=092
A FIX protocol implementation is typically defined as a FIX engine. The FIX engine can be written in any language and provides the building blocks that are required to process messages. These include establishing the transport protocol (typically TCP), creating FIX sessions, and serializing and deserializing messages. A FIX engine can also provide other features, such as session state management, logging, failover protocols, and session scheduling.
Although the protocol itself is bidirectional, each FIX connection must be designated as either an initiator or an acceptor. On an initiator connection, the engine reaches out to another FIX engine using an IP address and port. On an acceptor connection, the engine simply defines a port on which connections are accepted. Within the trading industry, the exchanges usually define their FIX engines as acceptors, allowing their trading counterparties to define their connections as initiators.
Note: Learn more about the FIX specification and related standards.
The QuickFIX/J engine
One of the most popular FIX engine implementations is the QuickFIX family. The QuickFIX engine is available in a myriad of languages, including C++, Python, Ruby, .NET, and Go. For this project, we will use the Java implementation, known as QuickFIX/J.
Much of a QuickFIX/J engine implementation happens in its configuration file. Here's a bare-bones example of a configuration for two different sessions:
[SESSION]
ConnectionType=acceptor
BeginString=FIX.4.4
SenderCompID=TESTSEND1
TargetCompID=TESTTARGET1
SocketAcceptPort=10000
StartTime=00:00:00
EndTime=00:00:00
[SESSION]
ConnectionType=initiator
BeginString=FIX.4.2
SenderCompID=TESTSEND2
TargetCompID=TESTTARGET2
SocketConnectHost=127.0.0.1
SocketConnectPort=1234
StartTime=08:30:00
EndTime=04:30:00
TimeZone=America/New_York
HeartBtInt=30
The first session is an acceptor and uses acceptor configuration options. When the engine starts, it opens port 10000 and begins listening for new connections. It accepts connections only from an initiator that specifies the protocol as version FIX.4.4
. The initiator must also provide the correct SenderCompId
and TargetCompId
information, which is the inverse of the defined session. Another ubiquitous part of a session definition is scheduling information (the StartTime
and EndTime
fields). In this example, the acceptor always accepts connections regardless of the time of day.
The second session definition is an initiator and uses initiator configuration options. When the engine starts, it attempts to connect to another FIX engine based on the supplied SocketConnectHost
and SocketConnectPort
. Again, the FIX version, SenderCompID
, and TargetCompID
must match for the acceptor engine to allow the connection to proceed. The initiator example is a bit different, in this case, because we have set some additional restrictions on when the session can be active by providing the StartTime
, EndTime
, and TimeZone
for the connection. If the engine is running in that time window, it attempts to establish and maintain a connection.
Configuring QuickFIX/J
The QuickFIX/J engine offers building blocks, which you as a developer must configure. Options include how your implementation will store messages, log messages, and serialize and deserialize messages. Table 1 shows some of the available options.
Building block | Options |
---|---|
MessageStoreFactory | Choose from CachedFileStoreFactory , FileStoreFactory , JdbcStoreFactory , or MemoryStoreFactory . |
LogFactory | Choose from FileLogFactory , JdbcLogFactory , SLF4JLogFactory , or ConsoleLogFactory . |
MessageFactory | Select the DefaultMessageFactory or MessageFactory for the given FIX version. |
Application | The interface for message flows. Implement the Application interface and handle the incoming and outgoing message events. |
The application reads the configuration file and builds a SessionSettings
object. You then define the building blocks and have the option to pass the configuration to each of the factories using the SessionSettings
object. Once the building blocks are established, you can create connectors using the building blocks as constructor parameters. The following is a basic example found on the QuickFIX/J website:
import quickfix.*;
import java.io.FileInputStream;
public class MyClass {
public static void main(String args[]) throws Exception {
if (args.length != 1) return;
String fileName = args[0];
// FooApplication is your class that implements the Application interface
Application application = new FooApplication();
SessionSettings settings = new SessionSettings(new FileInputStream(fileName));
MessageStoreFactory storeFactory = new FileStoreFactory(settings);
LogFactory logFactory = new FileLogFactory(settings);
MessageFactory messageFactory = new DefaultMessageFactory();
Acceptor acceptor = new SocketAcceptor
(application, storeFactory, settings, logFactory, messageFactory);
acceptor.start();
// while(condition == true) { do something; }
acceptor.stop();
}
}
Running QuickFIX/J in Quarkus
With Quarkus, developers can now define the QuickFIX/J building blocks using Contexts and Dependency Injection (CDI). In this section, we'll develop a simple Quarkus application that runs QuickFIX/J. The sample code can be found in my GitHub repository. See the Quarkus website for additional guidelines for creating your first Quarkus application.
To get started, let's create a new Quarkus project using the quarkus-maven-plugin
:
$ mvn io.quarkus.platform:quarkus-maven-plugin:2.6.1.Final:create \
-DprojectGroupId=com.redhat.demo \
-DprojectArtifactId=quarkus-quickfixj \
-DclassName="com.redhat.demo.qfj.SessionResource" \
-Dpath="/sessions"
$ cd quarkus-quickfixj
This command creates a simple Quarkus Maven project with the Quarkus RESTEasy extension and demo code. After importing the project into your favorite IDE, you can add the provider definitions for your building blocks.
To keep things simple, we will implement our FIX engine with a few easy-to-configure factory settings: MemoryStoreFactory
, ScreenLogFactory
, and DefaultMessageFactory
. Let's add the Maven dependency for the QuickFIX/J artifact. The quickfix-all
contains all of the artifacts for the project, including the core engine and the message structures for all FIX versions:
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-all</artifactId>
<version>2.3.1</version>
</dependency>
After adding the dependency, define the necessary configurations. First, go to the application.properties
file and add a value for the location of the file containing the SessionSettings
:
quickfixj.sessionSettingsPath=quickfixj.settings
Then, add a very basic settings file in the src/main/resources
directory:
[SESSION]
ConnectionType=acceptor
BeginString=FIX.4.4
SenderCompID=TESTSEND
TargetCompID=TESTTARGET
SocketAcceptPort=10000
StartTime=00:00:00
EndTime=00:00:00
[SESSION]
ConnectionType=initiator
BeginString=FIX.4.4
SenderCompID=TESTTARGET
TargetCompID=TESTSEND
SocketConnectPort=1234
SocketConnectHost=127.0.0.1
StartTime=00:00:00
EndTime=00:00:00
HeartBtInt=30
Now, create the producer class, which will create the building blocks for the FIX engine:
package com.redhat.demo.qfj;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import quickfix.*;
import javax.enterprise.inject.Produces;
public class QuickfixjComponentProducer {
@Produces
public SessionSettings create(@ConfigProperty(name = "quickfixj.sessionSettingsPath") String sessionSettingsPath) throws ConfigError {
return new SessionSettings(getClass().getClassLoader().getResource(sessionSettingsPath).getFile());
}
@Produces
public MessageStoreFactory messageStoreFactory(SessionSettings sessionSettings) {
return new MemoryStoreFactory();
}
@Produces
public LogFactory logFactory(SessionSettings sessionSettings) {
return new ScreenLogFactory();
}
@Produces
public MessageFactory messageFactory(SessionSettings sessionSettings) {
return new DefaultMessageFactory();
}
@Produces
public Application application() {
return new ApplicationAdapter();
}
}
There are a few things to note about the code:
- To provide the configuration file, we will simply embed it in the classpath. In a production example, you should probably read the configuration file from a filesystem.
- We are passing the
SessionSettings
object into each of the producer methods for the components even though we don't actually need them. However, in a production example, theSessionSettings
will be needed for the components, because it contains the properties needed. - We implement the
Application
interface using theApplicationAdapter
, which is an empty implementation of the interface. This interface is where the developer customizes how the message flows should be handled.
Now that the building blocks are present, you can use them to develop the rest of the FIX engine:
package com.redhat.demo.qfj;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quickfix.*;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ApplicationScoped
public class QuickfixjEngine {
private static final Logger LOGGER = LoggerFactory.getLogger(QuickfixjEngine.class);
private final SessionSettings sessionSettings;
private final MessageStoreFactory messageStoreFactory;
private final LogFactory logFactory;
private final MessageFactory messageFactory;
private final Application application;
private Set<Connector> connectorSet = new HashSet<>();
public QuickfixjEngine(SessionSettings sessionSettings, MessageStoreFactory messageStoreFactory, LogFactory logFactory,
MessageFactory messageFactory, Application application) throws ConfigError {
this.sessionSettings = sessionSettings;
this.messageStoreFactory = messageStoreFactory;
this.logFactory = logFactory;
this.messageFactory = messageFactory;
this.application = application;
}
void onStartupEvent(@Observes StartupEvent event) {
LOGGER.info("The QuickfixjService is starting...");
try {
Connector acceptorConnector = new SocketAcceptor(application, messageStoreFactory, sessionSettings, logFactory, messageFactory);
acceptorConnector.start();
Connector initiatorConnector = new SocketInitiator(application, messageStoreFactory, sessionSettings, logFactory, messageFactory);
initiatorConnector.start();
connectorSet.addAll(List.of(acceptorConnector, initiatorConnector));
LOGGER.debug("All connectors are started...");
} catch (ConfigError e) {
//TODO Handle error
throw new RuntimeException(e);
}
}
void onShutdownEvent(@Observes ShutdownEvent event) {
LOGGER.info("The QuickfixjService is stopping...");
connectorSet.forEach(Connector::stop);
}
}
Note the following:
- We are using Quarkus application lifecycle events to start and stop the acceptor and initiator connections. Remember that the connection's start and stop are independent of the connection's schedule. If the FIX sessions have a particular schedule, the connections will open and close per the schedule but will still remain as "RUNNING" in the FIX engine context.
- We are also collecting the acceptor and initiator connections, and keeping track of them in a local
Set
, which allows the connections to be stopped gracefully on normal application termination.
Benefits of using Quarkus with QuickFIX/J
As demonstrated in this article, the basic FIX engine doesn't need anything other than the QuickFIX/J artifacts and a JVM to run. However, by embedding the FIX engine into the Quarkus runtime, you can quickly and easily build the additional components that most FIX engines need to provide business value. You can:
- Extend capabilities for session management by adding a REST API to manage the sessions from either a user interface or any operational dashboards.
- Quickly add messaging infrastructure to the
Application
implementation using Quarkus extensions such as Apache Kafka integration or AMQP. - Easily add metrics and observability to the engine using Micrometer and expose application metrics to Prometheus.
- Add health checks using SmallRye Health.
In short, by plugging the QuickFIX/J FIX engine into the Quarkus runtime, you can supercharge development and reimagine the ways to implement electronic trading applications.
Conclusion
This article introduced the FIX protocol and showed you how to configure the building blocks of a FIX engine using QuickFIX/J. We then developed a Quarkus application with an embedded FIX engine.
Last updated: May 8, 2024