Trading Integration Adapter

A trading integration adapter integrates your bank's trading systems with the Caplin Platform.

Trading requires a flow of interaction between the client and the server, conducted over a messaging channel. A messaging channel is a bi-directional, one-to-one subscription between the client and the adapter.

A trade may require several messages to be passed between the adapter and the client. For example, the adapter may send prices to the client before the client decides to execute a trade (as in RFS trades); the adapter may send message acknowledgements to the server (e.g. Execute Ack messages); or the client may choose to stop the trade while it is executing by sending a message to the adapter.

The message choreography between client and adapter is tracked by a state machine called the trade model. Each message between the client and the adapter causes a transition between states.

Objectives

In this tutorial, you will achieve the following objectives:

  • Create a trade model that represents the trading workflow
  • Create a trading adapter
  • Connect the trading adapter to a mock server
  • Submit trades using a test HTML page and the StreamLink JS library

Create a new trading adapter project

The Caplin Project Templates project in Caplin's GitHub repository provides template projects for pricing adapters, blotter adapters, notification adapters, trading adapters, and Java Transformer modules. Each template contains working code and configuration to get you started.

In this tutorial, you will create a project based on the Trading Adapter Template. The template includes working code for a basic trading adapter. 

To create a new trading adapter project, follow the steps below:

  1. Open a Bash command line
  2. Download the latest version of the Caplin Project Templates project to your home directory:

    wget http://github.com/caplin/project-templates/archive/training.zip
  3. Extract the project:

    unzip training.zip
  4. Create a directory to store this, and future, adapter projects:

    mkdir ~/src
  5. Run the command below to copy the Trading Adapter Template directory to your src directory and rename it to TradingAdapter:

    cp -r ~/project-templates-training/trading-template ~/src/TradingAdapter
  6. Edit the file ~/src/TradingAdapter/settings.gradle. Change the value of the variable rootProject.name to 'TradingAdapter':

    rootProject.name = "TradingAdapter"
  7. Choose one of the methods below to resolve project dependencies:
    • Caplin Software Repository: create a gradle.properties file in the root directory of your project and assign your Caplin username and password to the variables below:

      caplinNexusUser=username
      caplinNexusSecret=password
    • Manual download: download the following files from the Caplin Download site and copy them to the libs folder in your project.

      • DataSource for Java: datasource-java-version-jar-with-dependencies.jar

      • Trading Integration API for Java: trading-datasource-version.jar

Import the adapter project into your IDE

This section includes instructions for importing your project into Eclipse and IntelliJ.

Import the project into Eclipse

To import your project into Eclipse, follow the steps below:

  1. In Eclipse, click File > Import. The Import dialog opens.
  2. From the Import dialog, click Existing Gradle Project. The Import Gradle Project dialog opens.
  3. Under Project location, deselect Use default location

  4. In the Location field, select your pricing adapter's project directory.

    Tip: if you are following this tutorial on Cygwin, your project directory is C:\cygwin64\home\username\src\TradingAdapter
  5. Click Finish

To configure your adapter to run within Eclipse during development, follow the instructions under Running your adapter within an IDE in the Trading Template README.

Import the project into IntelliJ

​To import your project into IntelliJ, follow the steps below:

  1. Click File > New > Project from existing sources
  2. Select the project's Gradle build file: ~/src/TradingAdapter/build.gradle

To configure your adapter to run within IntelliJ during development, follow the instructions under Running your adapter within an IDE in the Trading Template README.

Running your adapter in your IDE during development

To configure your adapter to run within your IDE during development, follow the instructions under Running your adapter within an IDE in the Trading Template README.

Implementing the trading adapter

The project template includes the skeleton code for a trading adapter:

  • A trade model in the file blade/DataSource/etc/trademodels.xml.
  • A TradingAdapter class. This is the main class for the project. When the adapter runs, it creates a new DataSource object which connects to Liberator and creates a new TradingProvider instance.
  • A TemplateTradingApplicationListener class. This class implements the TradingApplicationListener interface and is notified of connection and channel events. When the trade channel (/PRIVATE/TRADE/FX) is created, a new TradeChannelListener is created to monitor the activity on that trade channel.
  • A TemplateTradeChannelListener class. This class is notified when a trade is created or closed on the /PRIVATE/TRADE/FX trade channel for a specific user.
  • A TemplateTradeListener class. This class is notified when a new trade message is received from the Liberator for a specific trade.

We will fill in these classes to implement our Trading Adapter.

Set up the trade model

The trade model is a key part of your Trading Integration Adapter. It defines the trading workflow in the form of a state machine that transitions between states when messages are received by the adapter or sent by the adapter. A trade model is defined in XML.

The trade model for the exaple adapter in this tutorial is very simple. A client sends a trade event to open a new trade for execution. The server sends another trade message confirming the trade’s completion.

To define this trade model open the file blade/DataSource/etc/trademodels.xml and replace the contents with the following:

<?xml version="1.0" encoding="UTF-8" ?>
<tradeModels>
   <tradeModel name="ESP" initialState="Initial">
      <state name="Initial">
         <transition target="Executing" trigger="Open" source="client"></transition>
      </state>
      <state name="Executing">
         <transition target="TradeComplete" trigger="Confirm" source="server"></transition>
      </state>
      <state name="TradeComplete"></state>
   </tradeModel>
</tradeModels>

Configure message fields

Field names in messages are encoded as numbers. In this section, we will map each field name to a number.

Add the following field names and codes to the file blade/blade_config/fields.conf:

add-field Price         -36003
add-field Message       -36004
add-field BaseCurrency  -36005
add-field TermCurrency  -36006
add-field BuySell       -36007
add-field Amount        -36008

Receiving trade messages

Trading events are sent by message on the trade channel between the client and the adapter. A trade begins when a client sends an “Open” event, taking a new trade from the “Initial” to the “Executing” state and then waiting for the adapter to respond with a “Complete” message.

The TemplateTradeListener.receiveEvent method is called whenever there is an incoming trade message on the /PRIVATE/TRADE/FX channel.

Follow the steps below:

  1. Edit the receiveEvent method in your TemplateTradeListener class. Add the code below to handle "Open" event messages:
    public class TemplateTradeListener implements TradeListener 
    {
        @Override
        public void receiveEvent(TradeEvent event) throws TradeException 
        {
            logger.log(Level.INFO, "receiveEvent");
    
            String transition = event.getType();
            if (transition.equals("Open")) {
                // Fields coming from the client
                Map<String, String> fields = event.getFields();
                String user = event.getTrade().getChannel().getUser();
                String requestID = fields.get("RequestID");
                String baseCurrency = fields.get("BaseCurrency");
                String termCurrency = fields.get("TermCurrency");
                String currencyPair = baseCurrency + "/" + termCurrency;
                double amount = Double.parseDouble(fields.get("Amount"));
                double price = Double.parseDouble(fields.get("Price"));
                String buySellString = fields.get("BuySell");
                System.out.println("Open message from user " + user + " to " + buySellString + " " + currencyPair);
            }
        }
    }
  2. Download and extract testpage.zip to Liberator's htdocs folder: <DeploymentFramework>\kits\Liberator\Liberator-<version>\htdocs.

  3. In Google Chrome, navigate to the following URL: http://localhost:18080/testpage

  4. Press F12 to bring up Chrome’s Developer Tools, then click the Console tab to view the log messages produced by the client application.

  5. Click “Login” followed by “Trade” and you will now see that a trade message has been sent to the adapter in your browser’s console window: Publish to /PRIVATE/TRADE/FX succeeded.

    You will also notice from the Trading Adapter’s logs that the adapter receives the message: Open message from user admin to BUY GBP/USD.

Sending trade messages

The “Open” event received is automatically associated with a new trade object. This trade object can be used by the adapter to send trade messages back to the client.

Follow the steps below:

  1. Add the following two lines to send a “Confirm” message back to the client when you receive the "Open" event:
    @Override
    public void receiveEvent(TradeEvent event) throws TradeException 
    {
        logger.log(Level.INFO, "receiveEvent");
    
        String transition = event.getType();
        if (transition.equals("Open")) {
            // Fields coming from the client 
            Map<String, String> fields = event.getFields(); 
            String user = event.getTrade().getChannel().getUser(); 
            String requestID = fields.get("RequestID"); 
            String baseCurrency = fields.get("BaseCurrency"); 
            String termCurrency = fields.get("TermCurrency"); 
            String currencyPair = baseCurrency + "/" + termCurrency; 
            double amount = Double.parseDouble(fields.get("Amount")); 
            double price = Double.parseDouble(fields.get("Price")); 
            String buySellString = fields.get("BuySell"); 
            System.out.println("Open message from user " + user + " to " + buySellString + " " + currencyPair);
    
            TradeEvent tradeEvent = event.getTrade().createEvent("Confirm");
            event.getTrade().sendEvent(tradeEvent);
        }
    }
  2. Restart the adapter
  3. Start a new trade from the test client page. You will notice in the browser console that a new message arrives. If you expand the message and then expand the _fields property, you will see that the incoming message contains the field MsgType with the value "Confirm". This indicates that the trade was completed successfully. In a real application you would of course render these incoming trade messages to the screen rather than inspect them in the browser console.

    Note: Each time a trade is submitted from the test client page, your adapter will throw the following error: 'com.caplin.management.jmx.MBeanServerManagerImpl notifyMBeanAttributeChanged, SEVERE: Error Notifying Bean Change, java.lang.Exception: Bean has not been registered'. This error is related to the fact that we have not enabled JMX for the adapter in this tutorial. The error does not indicate that you have followed the tutorial incorrectly.

Connecting to a trade server

In the previous section, your code immediately confirmed a trade without executing it through a backend trading system. In production, you would establish a connection between the adapter and the bank trading system, then pass on any new trades to be processed by the bank system. You would only send a “Confirm” event when the bank system has completed the trade’s execution. In a more realistic scenario, the trade model would also have a failure transition in case the bank system rejected the trade request.

Connecting to a mock server

In this part of the tutorial you will implement a connection between the adapter and a mock trading server. We will also ensure the trading adapter only establishes the connection to Liberator once it has successfully connected to the mock server, just as we did in the previous tutorial.

  1. Add the SimpleFXMessenger jar to the build path, just like you did for the Pricing Integration Adapter. Remember that this library provides the adapter with the interfaces it needs to implement to communicate with the mock server, and implements the communication.
  2. To communicate with the mock server, your TradingProvider class will need to implement the FXTradingListener interface and its methods onConnect() and onDisconnect(). Just as before, after adding the interface to your class you can hover over the error marker and allow Eclipse to fill in the missing methods from the interface for you.
    public class TemplateTradingApplicationListener implements TradingApplicationListener, FXTradingListener
    
  3. Define a new FXTradingMessenger field in your TemplateTradingApplicationListener class. Then add these two lines to your constructor to instantiate it and connect to the mock server. Note that the session ID and the port used in this connection are different values to the ones you used in the Pricing Integration Adapter, so don't copy and paste the line from that project!

    private final Logger logger;
    private final FXTradingMessenger messenger;
    
    public TemplateTradingApplicationListener (DataSource dataSource) throws IllegalArgumentException, IOException 
    {
        this.logger = dataSource.getLogger();
    
        /**      
         * This is how the trading library is initialised. We pass in a DataSource instance,
         * which is used to receive requests, discards and contributions from Liberator, and
         * a TradingApplicationListener for the trading library to notify of events. It is not
         * necessary to keep a reference to this object after you instantiate it.
         */
         new TradingProvider(this, dataSource);
        
         this.messenger = new FXTradingMessenger(this, FXTradingMessenger.DEFAULT_SESSION_ID_2, FXTradingMessenger.DEFAULT_PORT_2);
         this.messenger.connect();
    }

    The adapter connects to Liberator as soon as the call to dataSource.start() is made in the main method. We will update it to only initialise the connection to Liberator once the connection to the mock server is established, just as we did in the pricing integration adapter.

  4. Remove the dataSource.start() instruction from the main method of the TradingAdapter class.
  5. In your TemplateTradingApplicationListener class, create a new field for the DataSource object so that it can be accessed from any method in this class, and assign the value to it in your constructor. Also create a boolean field to keep track of whether the DataSource has been started or not, and initialise it to false.

    private final Logger logger;
    private final FXTradingMessenger messenger;
    private final DataSource dataSource;
    
    private boolean dataSourceIsStarted = false;
    
    public TemplateTradingApplicationListener (DataSource dataSource) throws IllegalArgumentException, IOException {
        this.logger = dataSource.getLogger();
        this.dataSource = dataSource;
    
        /**
         * This is how the trading library is initialised. We pass in a DataSource instance,
         * which is used to receive requests, discards and contributions from Liberator, and
         * a TradingApplicationListener for the trading library to notify of events. It is not
         * necessary to keep a reference to this object after you instantiate it.
         */
        new TradingProvider(this, dataSource);
    
        this.messenger = new FXTradingMessenger(this, FXTradingMessenger.DEFAULT_SESSION_ID_2, FXTradingMessenger.DEFAULT_PORT_2); 
        this.messenger.connect();
    }
    
  6. In the onConnect method (a method defined in the FXTradingListener interface) start the DataSource if it has not yet been started, otherwise toggle the status to UP.
    @Override
    public void onConnect() {
        if (dataSourceIsStarted) {  
            dataSource.setStatusUp();
        } else {
            dataSource.start();
            dataSourceIsStarted = true;
        }
        System.out.println("Connected to mock server");
    }
  7. In the onDisconnect method, change the DataSource status to DOWN.
    @Override
    public void onDisconnect() {
        dataSource.setStatusDown();
    }
  8. At this point you should be able to start your adapter and see it connect to your mock server:

    Connected to mock server

    Try stopping and starting the mock server to see both the Pricing Adapter and Trading Adapter go from blue to red in the CMC.

Passing the FXTradingMessenger down to the TradeListeners

Next, we will update our TradeListener class so that it submits incoming trade instructions to the mock server rather than simply sending back a hard-coded confirmation. However before we can implement this we need to do a little bit of plumbing.

Currently our TradingApplicationListener class is the only class that has a reference to the FXTradingMessenger object. Recall that the TradingApplicationListener is the top class in a three-class hierarchy:

  1. TradingApplicationListener
  2. TradeChannelListener
  3. TradeListener

Before we can make the TradeListener use the FXTradingMessenger object to submit trades, we will need to pass it down the hierarchy so that the TradeListener class can use it.

Follow the steps below:

  1. In your TemplateTradingApplicationListener class, change the channelCreated() method so that it passes the messenger object to the TradeChannelListener as a new constructor argument.
        @Override
        public void channelCreated(TradeChannel tradeChannel) throws TradeException
        {
            /**
             * This is the most important callback for your TradingApplicationListener to handle.
             * It is called when a new trading channel is created, which typically happens when
             * a user logs in to the application. The responsibility of your class when it
             * receives this callback is to set a TradeChannelListener on the new trade channel.
             */
            logger.log(Level.INFO, "channelCreated");
            tradeChannel.setTradeChannelListener(new TemplateTradeChannelListener(messenger, logger));
        }
            
  2. Update your TradeChannelListener class so that it expects the new constructor argument and sets it as a field.
    public class TemplateTradeChannelListener implements TradeChannelListener
    {
        private final Logger logger;
        private final FXTradingMessenger messenger;
    
        public TemplateTradeChannelListener(FXTradingMessenger messenger, Logger logger)
        {
            this.logger = logger;
            this.messenger = messenger;
        }   
    
  3. Still in your TradeChannelListener class, change the tradeCreated() method so that it passes the messenger object to the TradeListener as a new constructor argument.
        @Override
        public void tradeCreated(Trade trade) throws TradeException
        {
            /** 
             * This callback happens when a user creates a new trade. The responsibility
             * of your class when it receives this callback is to create a 
             * listener for the trade. Often you will use a different type of listener
             * for each type of trade:
             * 
             *         if (trade.getType.equals("RFQ")
             *         {
             *             trade.setTradeListener(new NewTradingAdapterRFQTradeListener);
             *         } ...
             *
             * In this simple example we use the same type of listener for every type of
             * trade.
             */
            logger.log(Level.INFO, "tradeCreated");
            trade.setTradeListener(new TemplateTradeListener(messenger, logger));
        }
  4. Update your TradeListener class so that it expects the new constructor argument and sets it as a field.
    public class TemplateTradeListener implements TradeListener
    {
        private final Logger logger;
        private final FXTradingMessenger messenger;
    
        public TemplateTradeListener (FXTradingMessenger messenger, Logger logger)
        {
            this.logger = logger;
            this.messenger = messenger;
        }
    

Instances of your TradeListener class now have access the FXTradingMessenger object and can use it to submit trades.

Submitting trades to the mock server

Now we are going to change the receiveEvent() implementation so that it uses the FXTradingMessenger to execute the trade instead of immediately confirming the trade itself.

In a real system, there are always some field and formatting differences between how the client application submits a trade instruction and how the bank server expects to receive it, so you have to perform some transformations on the incoming data before you can submit it to the bank. This example is no different: you will have to convert the "BUY" or "SELL" string you receive from the client to a special character expected by the mock server.

Follow the steps below:

  1. Update your TradeListener class so that it implements the FXTradingListener interface. This is another interface from the mock server client library, which has a single method onTradeReport(). Implementing this interface will allow your TradeListener class to receive the confirmation event from the mock server after you submit the trade. As always, after declaring that your class implements the interface you can allow Eclipse to fill in the onTradeReport() method for you.
    public class TemplateTradeListener implements TradeListener
    {
    ...
    
    @Override 
    public void receiveEvent(TradeEvent event) throws TradeException 
    { 
       logger.info("Received trade event: " + event.toString()); 
       trade = event.getTrade();
    ...
    }
    ...
    
    public void sendReport(FXTradeReport fxTradeReport) 
    { 
       try 
       { 
           TradeEvent event = trade.createEvent("Confirm"); 
           event.addField("Message", fxTradeReport.toString()); 
           trade.sendEvent(event); 
       } catch (TradeException e) 
       {  
         logger.log(Level.SEVERE, e.getMessage()); 
       } 
    }
    
    }
  2. Update your TemplateTradeChannelListener and add the the following public method.
    public void onTradeReport(FXTradeReport fxTradeReport)
    { 
       tradeListener.sendReport(fxTradeReport); 
    }
    
  3. In the same class, delete the two lines at the bottom of the receiveEvent() method where you were creating and sending back the “Confirm” event.
  4. You will need to convert the value of the  "BuySell" field on the incoming message to the mock server’s equivalent, which is a char. Then you can use the FXTradingMessenger to instruct the mock server to execute the trade. The fields in the trade message are passed in as parameters to the executeTrade() method.
  5. public void receiveEvent(TradeEvent event) throws TradeException 
    {
        logger.log(Level.INFO, "receiveEvent");
    
        String transition = event.getType(); 
        if (transition.equals("Open")) { 
            // Fields coming from the client   
            Map<String, String> fields = event.getFields();
            String user = event.getTrade().getChannel().getUser();
            String requestID = fields.get("RequestID");
            String baseCurrency = fields.get("BaseCurrency");
            String termCurrency = fields.get("TermCurrency");
            String currencyPair = baseCurrency + "/" + termCurrency;
            double amount = Double.parseDouble(fields.get("Amount"));
            double price = Double.parseDouble(fields.get("Price"));
            String buySellString = fields.get("BuySell");
    
            char buySell;
            if(buySellString.equals("BUY")) {
                buySell = FXTradeReport.BUY;
            } else {
                buySell = FXTradeReport.SELL;
            }
    
            System.out.println("Open message from user " 
                + user + " to " + buySellString + " " + currencyPair);
    
            messenger.executeTrade(user, requestID, currencyPair,
                amount, buySell, price);
        }
    }
  6. Restart your Adapter.
  7. Re-open the test page and execute a trade. Notice that as soon as this is done, the log message below appears in the mock server’s console output:

    <timestamp> INFO  server.GradFIXApplication  - fromApp: [Message Type = Trade Request (OrderSingle)]
    Tip: to locate this message quickly, right-click the console, select find, and search for ‘OrderSingle’.

    This is followed by the mock server sending back a trade report to the adapter, so the adapter knows that the trade was successful. However, the client is not yet aware that the trade was successful because we are no longer sending back a “Confirm” event. The next step is to listen for the confirmation from the mock server and send the “Confirm” trade event on to the client.  The onTradeReport() callback method is triggered when the mock server sends an execution report to the adapter.

Sending a trade confirmation to the client

In order to create and send a “Confirm” trade event when the mock server responds, the TradeListener needs a reference to the Trade object that it is listening to. One way to do that is to save a reference to the Trade object the first time we get the receiveEvent() callback.

Follow the steps below:

  1. Add a new Trade field to your TradeListener class. Note that unlike the other fields Java will not allow you to declare this one as "final", because final fields must have their value set inline or in the constructor. We won't be able to set the value of this field until we get the receiveEvent() callback.

    public class TemplateTradeListener implements TradeListener, FXTradingListener
    {
        private final Logger logger;
        private final FXTradingMessenger messenger;
        private Trade trade;
    
  2. Add a line to your receiveEvent() method so that when you receive the "Open" event you pull out the underlying Trade object and store it in your new field.

    public void receiveEvent(TradeEvent event) throws TradeException 
    { 
       logger.log(Level.INFO, "receiveEvent");
    
       String transition = event.getType();
       if (transition.equals("Open")) {
       
           this.trade = event.getTrade();
    
           // Fields coming from the client
           Map<String, String> fields = event.getFields();
           String user = event.getTrade().getChannel().getUser();
           String requestID = fields.get("RequestID");
           String baseCurrency = fields.get("BaseCurrency");
           String termCurrency = fields.get("TermCurrency");
           String currencyPair = baseCurrency + "/" + termCurrency;
           double amount = Double.parseDouble(fields.get("Amount"));
           double price = Double.parseDouble(fields.get("Price"));
           String buySellString = fields.get("BuySell");
    
           char buySell;
           if(buySellString.equals("BUY")) {
               buySell = FXTradeReport.BUY;
           } else {
               buySell = FXTradeReport.SELL; 
           }
    
           System.out.println("Open message from user "
               + user + " to " + buySellString + " " + currencyPair);
    
           messenger.executeTrade(this, user, requestID, currencyPair,
               amount, buySell, price);
       } 
    }
    
  3. Go back to your TemplateTradingApplicationListener. The onTradeReport() method is triggered when the mock server has completed a trade. You now have to call the TemplateTradeChannelListener sendReport() method which will call the TemplateTradeListener sendReport().
    TemplateTradeListener will create a “Confirm” trade event using the saved trade and send the response back to the client.  Here we are adding a “Message” field to the “Confirm” trade message that contains the report sent by the mock server.

    @Override
    public void onTradeReport(FXTradeReport fxTradeReport) 
    {
        tradeChannelListener.onTradeReport(fxTradeReport);
    }
  4. Restart the adapter and execute a trade from the test page.
  5. Open the browser console. You should see an incoming record message on your trade channel. If you expand this message and then expand the "_fields" property, you will see that you have received your confirmation message from the adapter.

    Note that there are three fields on the message, and they were all set in different ways:

    • MsgType - the value of this field is the name of the transition in the trade model that this message represents. It has the value "Confirm" because you created the message in your adapter code by calling trade.createEvent("Confirm").
    • Message - this is the field that you explicitly set on the message in your adapter code before sending it. You can set any number of these fields.
    • RequestID - this is a unique identifier set automatically by the trading library, to identify the trade that this message relates to. It will have been present on every message that relates to this trade.
  6. Notice also from the adapter’s log in the Eclipse console that your TradeChannelListener received a tradeClosed() callback, because the trade has reached a final state in the model. In this tutorial we won't do anything when we receive this callback, but in a real application you could use it as a trigger to do any clean up or post-trade logging required for your system.

Review

This tutorial covered the basic aspects of the Trading Integration Adapter API and showed how to implement a basic adapter. The first step is to design the trade model which models the sequence of events that can pass between the client and the adapter. The TradingProvider should establish a connection with a mock server, and will be informed when user's create or close trade channels. The TradeChannelListener is informed when a user creates a new trade. Finally, the TradeListener will be notified whenever a trade event is received from the client and should propagate that message to the bank server.