Sommaire
Dans cette troisième partie de la série “Implémenter un système de trading avec Java et Spring Boot”, nous passons à la pratique en implémentant notre propre FIX Acceptor à l’aide de Java, Spring Boot et QuickFIX/J
Pour aller plus loin (re)découvrez la Partie 2 de “Implémenter un système de trading avec Java et Spring Boot”Pour ce tutoriel, nous utiliserons Java 21 et Spring Boot 3.5.5.
Configuration de l’application
Commençons par générer un nouveau projet Spring Boot à l’aide de Spring Initializr.
Rendez-vous sur
start.spring.io
et créez un projet avec les paramètres suivants.
Une fois le projet généré, l’étape suivante consiste à ajouter les dépendances QuickFIX/J dans votre fichier pom.xml. Ces bibliothèques fournissent à notre application Spring Boot le moteur FIX nécessaire.
<dependencies>
...
<dependency>
<groupId>io.allune</groupId>
<artifactId>quickfixj-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix42</artifactId>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.allune</groupId>
<artifactId>quickfixj-spring-boot-starter</artifactId>
<version>3.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Le QuickFIX/J Spring Boot Starter (v3.3.0) embarque quickfixj-core v2.3.2. Notez également que nous utilisons la spécification FIX 4.2.
Après avoir mis à jour le pom.xml, synchronisez vos dépendances Maven puis lancez l’application. Si tout est correctement configuré, elle démarrera et vous verrez des logs similaires à ceux-ci :
Avant de construire notre FIX Acceptor, nous avons besoin d’un Initiator auquel nous connecter. Pour ce tutoriel, nous utiliserons Banzai comme initiateur factice : il est léger et parfaitement adapté aux tests.
Télécharger Banzai
Téléchargez, décompressez, puis ajoutez ResetOnLogon=Y dans le fichier banzai.cfg afin de réinitialiser les numéros de séquence à chaque connexion, puis lancez :
java -jar dist/banzai.jar
Assurez-vous d’être à la racine du projet avant d’exécuter la commande.
Si tout se passe bien, Banzai démarre et affiche son interface principale.
⚠️ Important : Banzai est compatible uniquement avec FIX 4.2.
Construction d’un Acceptor
Pour construire notre FIX Acceptor, nous avons besoin de cinq composants clés :
- Application — Le cœur de l’Acceptor, responsable de l’envoi et de la réception des messages
- MessageFactory — Crée les messages selon la spécification FIX
- SessionSettings — Gère la configuration de session (connexion, heartbeat, etc.)
- LogFactory — Gère la journalisation des messages entrants et sortants
- MessageStoreFactory — Stocke les messages et gère les numéros de séquence
1. Création du composant Application
C’est la pièce centrale de notre Acceptor : elle gère l’envoi et la réception des messages FIX.
@Slf4j
@Service
public class AcceptorService
extends MessageCracker
implements Application {
@Override
public void onCreate(SessionID sessionID) {
log.info("Session created {}", sessionID);
}
@Override
public void onLogon(SessionID sessionID) {
log.info("Logon successful {}", sessionID);
}
@Override
public void onLogout(SessionID sessionID) {
log.info("Logout successful {}", sessionID);
}
@Override
public void toAdmin(Message message, SessionID sessionID) {
log.info("To admin {}", sessionID);
}
@Override
public void fromAdmin(Message message, SessionID sessionID) {
log.info("From admin {}", sessionID);
}
@Override
public void toApp(Message message, SessionID sessionID) {
log.info("Sending Message {}", message);
}
@Override
public void fromApp(
Message message,
SessionID sessionID)
throws
UnsupportedMessageType,
IncorrectTagValue,
FieldNotFound {
crack(message, sessionID);
}
}
Rôle des méthodes :
- onCreate(SessionID sessionId) – Appelée lorsque QuickFIX/J crée une nouvelle session.
- onLogon(SessionID sessionId) – Déclenchée après une connexion (logon) réussie avec la contrepartie.
- onLogout(SessionID sessionId) – Notifie qu’une session FIX vient d’être fermée ou déconnectée.
- toAdmin(Message message, SessionID sessionId) – Gère les messages administratifs envoyés à la contrepartie.
- toApp(Message message, SessionID sessionId) – Callback pour les messages applicatifs envoyés à la contrepartie.
- fromAdmin(Message message, SessionID sessionId) – Gère les messages administratifs reçus de la contrepartie.
- fromApp(Message message, SessionID sessionId) – Méthode la plus importante : point d’entrée principal des messages applicatifs. Toutes les requêtes métier provenant de la contrepartie arrivent ici.
Dans la méthode fromApp, nous appelons la fonction crack(message, sessionID), qui provient de la classe de base MessageCracker.
Cette méthode se charge de router automatiquement les messages FIX entrants vers leur handler approprié.
Nous implémenterons l’une de ces méthodes de handler plus loin dans le tutoriel.
2. Création du MessageFactory
Pour le MessageFactory, nous pouvons utiliser l’implémentation par défaut DefaultMessageFactory.
Même s’il est possible de créer une implémentation personnalisée, il est généralement recommandé de conserver DefaultMessageFactory, car elle est compatible avec l’ensemble des spécifications FIX.
@Configuration
public class AcceptorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
}
3. Créer le composant SessionSettings
Avant d’implémenter le code, on doit créer un fichier de configuration QuickFIX/J (quickfixj-acceptor.cfg) dans le dossier resources pour définir les paramètres de session :
[DEFAULT]
StartTime=00:00:00
EndTime=00:00:00
ResetOnLogon=Y
[SESSION]
BeginString=FIX.4.2
SenderCompID=FIXIMULATOR
TargetCompID=BANZAI
ConnectionType=acceptor
SocketAcceptAddress=localhost
SocketAcceptPort=9878
Vous pouvez définir plusieurs sections [SESSION] si vous souhaitez vous connecter à plusieurs initiateurs FIX.
En revanche, une seule section [DEFAULT] est autorisée. Les paramètres définis dans [DEFAULT] s’appliquent à toutes les sessions, sauf s’ils sont explicitement redéfinis dans une section [SESSION] spécifique.
Décomposons le fichier quickfixj-acceptor.cfg que nous venons de créer :
- StartTime / EndTime — Les heures d’activité de la session.
00:00:00signifie que la session est active en permanence. - ResetOnLogon — Si la valeur est
Y, les numéros de séquence sont réinitialisés à chaque logon. C’est utile en environnement de test ou de simulation. - BeginString — La version FIX. Comme Banzai ne supporte que FIX 4.2, nous définissons
FIX.4.2. - SenderCompID — L’identifiant de votre serveur (l’Acceptor). Ici :
FIXIMULATOR. - TargetCompID — L’identifiant de l’initiateur. Ici :
BANZAI. - ConnectionType — Défini à
acceptorpuisque notre application joue le rôle d’accepteur. - SocketAcceptAddress / SocketAcceptPort — L’hôte et le port sur lesquels le serveur FIX écoute. Ici :
localhost:9878.
⚠️ Très important : les valeurs BeginString, SenderCompID et TargetCompID doivent correspondre à la configuration de l’Initiator.
Plus précisément :
- Le
SenderCompIDde l’Initiator doit correspondre auTargetCompIDde l’Acceptor - Le
TargetCompIDde l’Initiator doit correspondre auSenderCompIDde l’Acceptor.
Dans notre cas, vérifiez le fichier banzai/config/banzai.cfg pour vous assurer de la compatibilité. Toute incohérence dans les IDs ou la version FIX empêchera l’établissement de la connexion.
Maintenant que nous avons notre fichier quickfixj-acceptor.cfg, nous devons charger ces paramètres dans notre application Spring Boot en utilisant la classe SessionSettings de QuickFIX/J. Ce composant lit le fichier de configuration et met l’ensemble des paramètres de session à disposition de l’Acceptor.
@Configuration
public class AcceptorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-acceptor.cfg");
}
}
4. Créer le composant LogFactory
QuickFIX/J propose plusieurs implémentations de journalisation intégrées, adaptées à différents besoins :
- ScreenLogFactory: Journalise les messages directement dans la console. Simple et efficace pour le développement et le débogage.
- FileLogFactory: Écrit les messages dans des fichiers, permettant une conservation persistante et une analyse a posteriori.
- SLF4JLogFactory: S’intègre avec SLF4J, ce qui permet d’utiliser des frameworks de logs comme Logback ou Log4J pour une journalisation plus avancée.
- JdbcLogFactory: Enregistre les messages dans une base de données, adapté aux environnements nécessitant une journalisation centralisée.
Par souci de simplicité et pour obtenir un retour immédiat pendant le développement, nous utiliserons ScreenLogFactory.
@Configuration
public class AcceptorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-acceptor.cfg");
}
@Bean
public LogFactory logFactory() {
return new ScreenLogFactory();
}
}
5. Créer le composant MessageStoreFactory
QuickFIX/J fournit plusieurs implémentations de stockage des messages, chacune adaptée à des cas d’usage différents :
- FileStoreFactory — Stocke les messages sous forme de fichiers sur disque. Simple et fiable pour des tests locaux.
- JdbcStoreFactory — Stocke les messages dans une base de données, utile pour une persistance centralisée.
- MemoryStoreFactory — Stocke les messages en mémoire à l’aide d’une HashMap. Rapide mais non persistant : les messages sont perdus au redémarrage.
- NoopStoreFactory — Ne stocke aucun message. Utile pour des tests très légers où la persistance n’est pas nécessaire.
Par souci de simplicité dans ce tutoriel, nous utiliserons MemoryStoreFactory.
@Configuration
public class AcceptorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-acceptor.cfg");
}
@Bean
public LogFactory logFactory() {
return new ScreenLogFactory();
}
@Bean
public MessageStoreFactory messageStoreFactory() {
return new MemoryStoreFactory();
}
}
Maintenant que tous les composants nécessaires sont en place, il est temps de les assembler et de démarrer notre Acceptor FIX.
@Configuration
public class AcceptorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-acceptor.cfg");
}
@Bean
public LogFactory logFactory() {
return new ScreenLogFactory();
}
@Bean
public MessageStoreFactory messageStoreFactory() {
return new MemoryStoreFactory();
}
@Bean
public Acceptor initiator(
Application application,
MessageStoreFactory messageStoreFactory,
SessionSettings sessionSettings,
LogFactory logFactory,
MessageFactory messageFactory) throws ConfigError {
SocketAcceptor socketAcceptor =
new SocketAcceptor(application, messageStoreFactory, sessionSettings, logFactory, messageFactory);
socketAcceptor.start();
return socketAcceptor;
}
}
Le SocketAcceptor par défaut est mono-thread, mais vous pouvez utiliser ThreadedSocketAcceptor si vous avez besoin que votre Acceptor gère plusieurs sessions ou messages en parallèle.
Il est temps de tester notre Acceptor
Nous sommes maintenant prêts à démarrer notre application.
Une fois que l’Acceptor se connecte avec succès, vous devriez voir un log indiquant un logon réussi avec l’Initiator FIX, confirmant que la session est active et prête à envoyer et recevoir des messages.
À ce stade, si vous consultez Banzai, vous pouvez voir la session créée, ce qui indique que l’Initiator s’est connecté avec succès à l’Acceptor.
Simplifier la configuration
Avant de passer à la création de notre premier message d’ordre, prenons un moment pour simplifier notre configuration.
Jusqu’à présent, nous avons configuré manuellement chaque composant de notre Acceptor. C’était volontaire : cela nous a permis de comprendre les composants clés et de voir ce qui se passe réellement en coulisses.
Maintenant que cette étape est faite, nous pouvons tirer parti de l’auto-configuration de QuickFIX/J.
En ajoutant la propriété suivante dans le fichier application.properties :
quickfixj.server.enabled=true
quickfixj.server.config=classpath:quickfixj-acceptor.cfg
Spring Boot configurera automatiquement un Acceptor par défaut avec les composants suivants :
- un MessageStoreFactory de type
MemoryStoreFactory - un LogFactory de type
ScreenLogFactory - un Acceptor de type
SocketAcceptor - une Application de type
EventPublisherApplicationAdapter
Remarque : pour le composant Application, je recommande de conserver notre classe Service personnalisée plutôt que d’utiliser EventPublisherApplicationAdapter, afin de pouvoir gérer les messages en fonction de notre logique métier.
Avec cette configuration, nous n’avons plus besoin de l’intégralité de la configuration manuelle — il suffit de conserver la classe AcceptorService.
Si vous supprimez la classe de configuration manuelle et redémarrez le service, l’Acceptor devrait se connecter à l’Initiator exactement comme auparavant, grâce à l’auto-configuration.
Réception des messages
Pour recevoir des messages provenant de l’Initiator FIX, nous devons ajouter des handlers dans notre classe AcceptorService.
Par exemple, pour gérer les messages NewOrderSingle — utilisés pour la création d’ordres — nous pouvons définir une méthode comme celle-ci :
@Slf4j
@Service
public class AcceptorService
extends MessageCracker
implements Application {
...
@Handler
public void onMessage(NewOrderSingle newOrderSingle, SessionID sessionID) {
log.info("Received NewOrderSingle {}", newOrderSingle);
}
}
Cette méthode sera appelée automatiquement à chaque fois qu’un message NewOrderSingle est reçu, ce qui vous permet de traiter la création d’un ordre.
⚠️ Important : tout type de message qui n’est pas pris en charge par votre AcceptorService entraînera un rejet du message.
Redémarrons maintenant l’application, puis plaçons un ordre via Banzai.
Vous devriez voir le NewOrderSingle correspondant apparaître dans les logs de l’AcceptorService, ce qui confirme que les messages sont bien reçus et traités correctement.
Envoi de messages
Pour envoyer notre premier message FIX, nous allons simplement transmettre un message d’accusé de réception (ExecutionReport) lorsque nous recevons un ordre :
@Slf4j
@Service
public class AcceptorService
extends MessageCracker
implements Application {
...
@Handler
public void onMessage(
NewOrderSingle newOrderSingle,
SessionID sessionID)
throws FieldNotFound, SessionNotFound {
log.info("Received NewOrderSingle {}", newOrderSingle);
String clOrdID = newOrderSingle.getClOrdID().getValue();
char side = newOrderSingle.getSide().getValue();
double orderQty = newOrderSingle.getOrderQty().getValue();
String symbol = newOrderSingle.getSymbol().getValue();
ExecutionReport execReport = new ExecutionReport(
new OrderID("ORDER-" + System.currentTimeMillis()),
new ExecID("EXEC-" + System.currentTimeMillis()),
new ExecTransType(ExecTransType.NEW),
new ExecType(ExecType.NEW),
new OrdStatus(OrdStatus.NEW),
new Symbol(symbol),
new Side(side),
new LeavesQty(orderQty),
new CumQty(0),
new AvgPx(0)
);
execReport.set(new ClOrdID(clOrdID));
execReport.set(new TransactTime(LocalDateTime.now()));
Session.sendToTarget(execReport, sessionID);
System.out.println("Sent ExecutionReport (NEW) to Banzai: " + execReport);
}
}
Redémarrez l’application, déclenchez la création d’un ordre, puis vérifiez que l’accusé de réception de l’ordre a bien été envoyé avec succès.
Supervision avec Actuator
Enfin, le quickfixj-spring-boot-starter fournit des endpoints Actuator intégrés ainsi qu’un HealthIndicator, que vous pouvez utiliser pour surveiller votre serveur FIX.
Pour activer la supervision QuickFIX/J via Actuator, ajoutez les propriétés suivantes dans votre fichier application.properties :
quickfixj.server.actuator.enabled=true
management.endpoint.quickfixjserver.access=unrestricted
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=quickfixjserver,health
Cette configuration vous permet d’accéder à des informations détaillées sur l’état de santé de votre serveur FIX et de surveiller son statut directement via les endpoints Spring Boot Actuator.
Conclusion · Implémenter un système de trading avec Java et Spring Boot [Partie 3]
Dans ce tutoriel, nous avons appris à implémenter un Acceptor FIX à l’aide de Java, Spring Boot et QuickFIX/J.
Nous avons commencé par configurer manuellement les composants clés de l’Acceptor afin de comprendre précisément leur rôle et le fonctionnement interne du moteur FIX. Nous avons ensuite tiré parti de l’auto-configuration pour simplifier la mise en place. Au fil du tutoriel, nous avons vu comment recevoir et envoyer des messages, et nous avons utilisé Banzai comme Initiator pour les tests.
Nous avons également exploré la supervision du serveur FIX à l’aide de Spring Boot Actuator, en activant le HealthIndicator et l’endpoint /actuator/quickfixjserver afin de suivre l’état des sessions.
Le code complet est disponible ici : Achraf-Hasbi/quickfixj.
Si vous rencontrez des problèmes ou avez des questions, n’hésitez pas à m’écrire sur LinkedIn : je me ferai un plaisir de vous aider.