FIX Protocol - Implémenter un système de trading avec Java et Spring Boot [Partie 2]
Article rédigé par
Achraf Hasbi
,
Practice Leader,
coach
et expert Java chez MARGO.
Ravi de vous retrouver ! Dans cette deuxième partie de la série « Créer un système de trading avec Java et Spring Boot », nous allons passer à la pratique et créer notre propre Initiator FIX à l'aide de Java, Spring Boot et QuickFIX/J.
Pour comprendre les fondamentaux de ce système, retrouver la partie 1 ici :
FIX Protocol - Implémenter un système de trading avec Java et Spring Boot [Partie 1]
“Pour ce tutoriel, nous travaillerons avec 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 à votre fichier pom.xml. Ces bibliothèques fourniront à notre application Spring Boot le support du moteur FIX dont elle a besoin :
<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) est fourni avec quickfixj-core v2.3.2. Notez également que nous utilisons la spécification FIX 4.2.
Une fois que vous avez mis à jour votre pom.xml, synchronisez vos modifications Maven. Lancez maintenant l'application. Si tout est correctement configuré, elle devrait démarrer et vous verrez s'afficher des logs similaires à ceux-ci :
Avant de nous lancer dans la création de notre initiator FIX, nous avons besoin d'un Acceptor auquel se connecter. Dans ce tutoriel, nous utiliserons FIXimulator comme accepteur. Il est léger et parfait pour les tests.
Vous pouvez le télécharger ici : FIXimulator
Il suffit de télécharger, décompresser et lancer :
java -jar dist/FIXimulator_0.41.jar
Assurez-vous que vous êtes à la racine du projet avant d'exécuter la commande.
Si tout s'est bien passé, FIXimulator devrait se lancer et vous verrez son interface principale.
Regardez dans le coin inférieur gauche : l'état de connexion du client indique « Not Connected ». C'est normal, car notre initiateur FIX n'a pas encore été implémenté et connecté au serveur.
Pour plus de détails sur FIXimulator et ses fonctionnalités, vous pouvez consulter la documentation officielle ici :
FIXimulator Thesis PDF
⚠️ Important : FIXimulator est uniquement compatible avec FIX 4.2.
Tout étant en place, il est temps de nous retrousser les manches et d'implémenter le FIX Initiator !
Création d'un Initiator
Pour créer notre FIX Initiator, nous avons besoin de cinq composants clés :Maintenant que nous connaissons les composants dont nous avons besoin, créons-les et connectons-les ensemble pour notre initiateur FIX.
1. Créer le composant Application
Commençons par l'élément le plus important : l'Application. C'est là que nous gérerons l'envoi et la réception des messages FIX, et elle constitue le cœur de notre initiateur.
@Slf4j
@Service
public class InitiatorService 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);
}
}
onCreate(SessionID sessionId)– Appelé lorsque QuickFIX/J crée une nouvelle session.onLogon(SessionID sessionId)– Déclenché lors d'une connexion réussie avec la contrepartie.onLogout(SessionID sessionId)– Signale lorsqu'une session FIX se déconnecte.toAdmin(Message message, SessionID sessionId)– Gère les messages administratifs envoyés à la contrepartie.toApp(Message message, SessionID sessionId)– Callback pour les messages au niveau de l'application envoyés à la contrepartie.fromAdmin(Message message, SessionID sessionId)– Gère les messages administratifs reçus de la contrepartie.fromApp(Message message, SessionID sessionId)– La méthode la plus importante, car elle sert de point d'entrée central pour les messages au niveau de l'application. Toutes les requêtes provenant de la contrepartie seront reçues ici.
Dans la méthodefromApp, nous appelons la fonctioncrack(message, sessionID), qui provient de la classe de baseMessageCracker. Cette méthode se charge de distribuer les messages FIX entrants à leur gestionnaire approprié.
Nous implémenterons l'une de ces méthodes de gestionnaire plus loin dans le tutoriel.
2. Créer le composant MessageFactory
Pour MessageFactory, nous pouvons utiliser l'implémentation par défaut DefaultMessageFactory. Bien que vous puissiez créer une implémentation personnalisée, il est généralement recommandé de s'en tenir à DefaultMessageFactory, car elle est compatible avec toutes les spécifications FIX.
@Configuration
public class InitiatorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
}
3. Créer le composant SessionSettings
Avant de nous plonger dans le code, nous avons besoin d'un fichier de configuration QuickFIX/J (quickfixj-initiator.cfg) dans le dossier resources pour définir nos paramètres de session :
[DEFAULT]
StartTime=00:00:00
EndTime=00:00:00
HeartBtInt=60
ResetOnLogon=Y
[SESSION]
BeginString=FIX.4.2
SenderCompID=BANZAI
TargetCompID=FIXIMULATOR
ConnectionType=initiator
SocketConnectHost=localhost
SocketConnectPort=9878
}
Vous pouvez définir plusieurs sections [SESSION] si vous souhaitez vous connecter à plusieurs acceptateurs FIX.
Une seule section [DEFAULT] est autorisée. Les paramètres de [DEFAULT] s'appliquent à toutes les sessions, sauf s'ils sont explicitement remplacés dans une session spécifique.
Décomposons le fichier quickfixj-initiator.cfg que nous venons de créer :
- StartTime / EndTime — Les heures d'activité de la session.
00:00:00signifie que la session est toujours active. - HeartBtInt — Intervalle de pulsation en secondes. Ici,
60signifie qu'un message de pulsation est envoyé toutes les 60 secondes pour maintenir la connexion active. - ResetOnLogon — Si la valeur est définie sur
Y, les numéros de séquence sont réinitialisés à chaque connexion à une session. Ceci est utile pour les environnements de test ou de simulation. - BeginString — La version FIX. Étant donné que FIXimulator ne prend en charge que FIX 4.2 , nous l'avons défini sur
FIX.4.2. - SenderCompID — L'ID de votre client (l'initiateur). Dans cet exemple, il s'agit de
BANZAI. - TargetCompID — L'ID du serveur/accepteur. Ici, il s'agit de
FIXIMULATOR. - ConnectionType — Défini sur
initiator, car notre application initie la connexion à l'accepteur. - SocketConnectHost / SocketConnectPort — Hôte et port sur lesquels le serveur FIX (FIXimulator) est exécuté. Nous utilisons
localhost:9878pour les tests.
Pour obtenir la liste complète de tous les paramètres de configuration, consultez la documentation officielle QuickFIX/J : Guide de configuration QuickFIX/J
⚠️ Très important : les valeurs de BeginString, SenderCompID et TargetCompID doivent correspondre à la configuration de l'accepteur.
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 FIXimulator_0.41/config/FIXimulator.cfg pour vous assurer de la compatibilité. Toute incompatibilité entre les identifiants ou les versions FIX empêchera l'établissement de la connexion.
Maintenant que nous avons notre fichier quickfixj-initiator.cfg, nous devons charger ces paramètres dans notre application Spring Boot à l'aide de la classe SessionSettings de QuickFIX/J. Ce composant lira le fichier de configuration et mettra tous les paramètres de session à la disposition de l'initiator.
@Configuration
public class InitiatorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-initiator.cfg");
}
}
4. Créer le composant LogFactory
QuickFIX/J fournit plusieurs implémentations de journalisation intégrées pour répondre à différents besoins :
- ScreenLogFactory : enregistre les messages directement dans la console. Simple et utile pour le développement et le débogage.
- FileLogFactory : enregistre les messages dans un fichier, ce qui permet un stockage persistant et une analyse ultérieure.
- SLF4JLogFactory : s'intègre à SLF4J, permettant l'utilisation de divers frameworks de journalisation tels que Logback ou Log4J pour des capacités de journalisation plus avancées.
- JdbcLogFactory : enregistre les messages dans une base de données, ce qui convient aux environnements où une journalisation centralisée est requise.
Pour plus de simplicité et un retour immédiat pendant le développement, nous utiliserons ScreenLogFactory.
@Configuration
public class InitiatorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-initiator.cfg");
}
@Bean
public LogFactory logFactory() {
return new ScreenLogFactory();
}
}
5. Créer le composant MessageStoreFactory
QuickFIX/J est fourni avec plusieurs implémentations de MessageStoreFactory intégrées, chacune adaptée à différents cas d'utilisation :
- FileStoreFactory — Stocke les messages sous forme de fichiers sur disque. Simple et fiable pour les tests locaux.
- JdbcStoreFactory — Stocke les messages dans une base de données, utile pour le stockage centralisé et la persistance.
- MemoryStoreFactory — Stocke les messages en mémoire à l'aide d'un
HashMap. Rapide mais non persistant ; les messages sont perdus au redémarrage. - NoopStoreFactory — Ne stocke aucun message. Utile pour les tests légers où la persistance n'est pas nécessaire.
Pour plus de simplicité dans ce tutoriel, nous utiliserons MemoryStoreFactory.
@Configuration
public class InitiatorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-initiator.cfg");
}
@Bean
public LogFactory logFactory() {
return new ScreenLogFactory();
}
@Bean
public MessageStoreFactory messageStoreFactory() {
return new MemoryStoreFactory();
}
}
Maintenant que nous avons configuré tous les composants nécessaires, il est temps de tout câbler ensemble et de mettre notre initiateur FIX en service !
@Configuration
public class InitiatorConfiguration {
@Bean
public MessageFactory messageFactory() {
return new DefaultMessageFactory();
}
@Bean
public SessionSettings sessionSettings() throws ConfigError {
return new SessionSettings("quickfixj-initiator.cfg");
}
@Bean
public LogFactory logFactory() {
return new ScreenLogFactory();
}
@Bean
public MessageStoreFactory messageStoreFactory() {
return new MemoryStoreFactory();
}
@Bean
public Initiator initiator(Application application,
MessageStoreFactory messageStoreFactory,
SessionSettings sessionSettings,
LogFactory logFactory,
MessageFactory messageFactory) throws ConfigError {
SocketInitiator socketInitiator = new SocketInitiator(application, messageStoreFactory, sessionSettings, logFactory, messageFactory);
socketInitiator.start();
return socketInitiator;
}
}
Le SocketInitiator par défaut est monothread, mais vous pouvez utiliser ThreadedSocketInitiator si vous avez besoin que votre initiateur gère plusieurs sessions ou messages simultanément.
Il est temps de tester notre initiateur
Nous sommes maintenant prêts à démarrer notre application !
Une fois la connexion établie, un log devrait s'afficher, indiquant la réussite de la connexion au serveur FIX, confirmant que la session est active et prête à envoyer/recevoir des messages :
À ce stade, si vous vérifiez FIXimulator, l’état de la connexion doit maintenant être vert, indiquant que l’initiateur s’est connecté avec succès à l’accepteur.
Simplifier la configuration
Avant de nous plonger dans la création de notre premier ordre, prenons un moment pour simplifier notre configuration.
Jusqu'à présent, nous avons configuré manuellement chaque composant de notre Initiator. C'était intentionnel : cela nous a permis de comprendre les composants clés et de voir ce qui se passe en coulisses. Maintenant que c'est fait, nous pouvons profiter de la configuration automatique de QuickFIX/J .
En ajoutant la propriété suivante à notre fichier application.properties :
quickfixj.client.enabled=true
quickfixj.client.config=classpath:quickfixj-initiator.cfg
Spring Boot configurera automatiquement un initiateur par défaut avec :
- Une MessageStoreFactory de type
MemoryStoreFactory. - Une LogFactory de type
ScreenLogFactory. - Un initiateur de type
SocketInitiator. - Une application de type
EventPublisherApplicationAdapter.
Remarque : pour l’application, je recommande de conserver notre classe Service personnalisée au lieu de EventPublisherApplicationAdapter afin que nous puissions gérer les messages en fonction de notre logique métier.
Avec cette configuration, nous n’avons plus besoin du fichier de configuration manuel complet : nous pouvons conserver uniquement la classe InitiatorService.
Si vous supprimez votre classe de configuration manuelle et redémarrez le service, l'initiator devrait se connecter à l'acceptor comme avant, grâce à la configuration automatique.
Envoi de messages
Pour envoyer notre premier message FIX, nous allons créer un endpoint /order simple qui nous permet de passer un ordre :
@RestController
@RequiredArgsConstructor
public class OrderController {
private final Initiator initiator;
private final QuickFixJTemplate quickFixJTemplate;
@PostMapping("/order")
public void placeOrder(@RequestBody OrderDto order) {
NewOrderSingle newOrder = new NewOrderSingle();
newOrder.set(new ClOrdID(UUID.randomUUID().toString())); newOrder.set(new HandlInst(HandlInst.AUTOMATED_EXECUTION_ORDER_PUBLIC_BROKER_INTERVENTION_OK));
newOrder.set(new HandlInst(HandlInst.AUTOMATED_EXECUTION_ORDER_PUBLIC_BROKER_INTERVENTION_OK));
newOrder.set(new Symbol(order.symbol()));
newOrder.set(new Side(Side.BUY));
newOrder.set(new TransactTime(LocalDateTime.now()));
newOrder.set(new OrdType(OrdType.MARKET));
newOrder.set(new OrderQty(order.quantity()));
SessionID session = initiator.getSessions().getFirst();
quickFixJTemplate.send(newOrder, session);
}
}
Comme notre configuration ne comporte qu'une seule session, nous l'utilisons simplement getFirst() pour la récupérer.
Si vous utilisez plusieurs sessions , assurez-vous de récupérer la session appropriée plutôt que de simplement utiliser la première.
Redémarrez l'application, puis utilisez un client HTTP pour appeler le endpoint. Dans cet exemple, j'ai utilisé Postman pour envoyer la requête :
Enfin, accédez à FIXimulator pour vérifier que l'ordre a bien été créé. Le nouvel ordre devrait apparaître dans l'interface de l'accepteur.
Réception de messages
Pour recevoir des messages du serveur FIX, nous devons ajouter des gestionnaires dans notre classe InitiatorService.
Par exemple, pour gérer les messages ExecutionReport (qui sont utilisés pour les accusés de réception et les exécutions des ordres), nous pouvons définir une méthode comme celle-ci :
@Slf4j
@Service
public class InitiatorService extends MessageCracker implements Application {
...
@Handler
public void onMessage(ExecutionReport executionReport, SessionID sessionID) {
log.info("Received ExecutionReport {}", executionReport);
}
}
Cette méthode sera appelée automatiquement chaque fois qu'un ExecutionReport est reçu, vous permettant de traiter les accusés de réception d'ordres, les exécutions ou les rejets.
⚠️ Important : tout type de message qui n'est pas traité par vous InitiatorService déclenchera un rejet de message.
Redémarrons notre application , puis passons un ordre via le endpoint /order.
Ensuite, utilisez FIXimulator pour accuser réception de l'ordre ou envoyer une exécution.
Vous devriez voir le ExecutionReport correspondant apparaître dans les logs InitiatorService, confirmant que les messages sont reçus et traités correctement.
Surveillance avec Actuator
Enfin, le quickfixj-spring-boot-starter est livré avec des endpoints Actuator intégrés et un HealthIndicator, que vous pouvez utiliser pour surveiller votre client FIX.
Pour activer la surveillance du client QuickFIX/J, ajoutez ce qui suit à votre application.properties :
quickfixj.client.actuator.enabled=true
management.endpoint.quickfixjclient.access=unrestricted
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=quickfixjclient,health
Cette configuration vous permet d'accéder à des informations détaillées sur l'état de votre client FIX et de surveiller son état directement via les endpoints Spring Boot Actuator.
Résumé
Dans ce tutoriel, nous avons appris à implémenter un Initiator FIX à l'aide de Java, Spring Boot et QuickFIX/J .
Nous avons commencé par configurer manuellement les composants clés de l'Initiator afin de comprendre son fonctionnement. Ensuite, nous avons utilisé la configuration automatique pour simplifier l'installation. Au fil du processus, nous avons appris à envoyer et recevoir des messages, et avons utilisé FIXimulator comme Acceptor pour les tests.
Nous avons également exploré la surveillance du client FIX à l'aide de Spring Boot Actuator, permettant des healthcheck au endpoint actuator/quickfixjclientpoint et de garder un œil sur l'état de la session.
Si jamais vous avez rencontré une difficulté ou si vous avez une question, n'hésitez pas à me contacter personellement sur LinkedIn et je serais ravi de vous assister.
Voilà pour ce tutoriel ! J'espère qu'il vous a été utile et agréable.
Restez connectés pour la partie 3 , où nous nous plongerons dans l'implémentation du côté Acceptor de notre système de trading.
References
FIX Protocol: https://www.fixtrading.org/what-is-fix/
QuickFIX/J: https://www.quickfixj.org/