actu-image
Software engineering - 12 Nov 2025

FIX Protocol - Implémenter un système de trading avec Java et Spring Boot [Partie 2]

Fix Protocol Partie 2
Photo Achraf Hasbi

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 :

ligne de code 1

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

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.

FIXimulator 2

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 :
Application — Le cœur de notre Initiator, chargé d'envoyer et de recevoir des messages.
MessageFactory — Crée des messages conformément à la spécification FIX, afin que notre Initiator puisse parler correctement le langage FIX.
SessionSettings — Gère toute la configuration de la session, comme les détails de connexion et les intervalles de pulsation.
LogFactory — Chargé de logger les messages entrants et sortants, ce qui est crucial pour le débogage et l'audit.
MessageStoreFactory — Chargé de stocker les messages entrants et sortants. Il gère également les numéros de séquence afin de garantir que les messages sont correctement suivis et classé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éthode fromApp, nous appelons la fonction crack(message, sessionID), qui provient de la classe de base MessageCracker. 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:00 signifie que la session est toujours active.
  • HeartBtInt — Intervalle de pulsation en secondes. Ici, 60 signifie 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:9878 pour 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 SenderCompID de l'initiator doit correspondre au TargetCompID de l'acceptor.
  • Le TargetCompID de l'initiator doit correspondre au SenderCompID de 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 :

  1. ScreenLogFactory : enregistre les messages directement dans la console. Simple et utile pour le développement et le débogage.
  2. FileLogFactory : enregistre les messages dans un fichier, ce qui permet un stockage persistant et une analyse ultérieure.
  3. 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.
  4. 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.
  • NoopStoreFactoryNe 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 :

ligne de code 2

À 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.

FIXimulator 3

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 :

Postman

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.

FIXimulator 4

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.

FIXimulator 5

Vous devriez voir le ExecutionReport correspondant apparaître dans les logs InitiatorService, confirmant que les messages sont reçus et traités correctement.

ExecutionReport

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.
Spring Boot Actuator endpoints

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.