Trading Integration Adapter

A Trading Integration Adapter sits between a bank server executing clients’ trades and the Caplin Platform.

Its role is to receive trade requests from the Platform for specific clients and to respond with a message from the trade server. Very often 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), or 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.

Trades are modelled on the client and on the adapter using a state machine with transitions modelling the possible trade messages that can be sent between the client and the server.

Objectives

To construct the Trading Integration Adapter you will need to create a trade model which represents the trade workflow.

Then you can implement the trading adapter which receives trade events and sends back trade messages for the correct trade.

You will also use the mock server (which we used in the Pricing Integration Adapter tutorial) to demonstrate the delegation of trade execution which happens in trading adapters.

Recall that in previous tutorials we used Liberator Explorer as a client to see the prices, but it does not support trade execution. So throughout this tutorial we’re going to use a simple trading client page which was developed specifically for this tutorial.

Creating a Trading Adapter Blade

 

Try this Yourself

Create a new Eclipse project, 'TradingAdapter', based on the Trading Adapter Template in the Caplin Project Templates. The process is very similar to how you created the PricingAdapter project. For detailed instructions, see the README.md file in the template.

Your new project should have a structure similar to the one shown below:

 

Note

  • The adapter only has a Liberator configuration because Trading adapters are connected directly to Liberator. Trade messages do not require any manipulation by Transformer modules, so the extra network hop of routing the messages via Transformer would not provide any benefit to balance out the increased latency.
  • The asset class entered in the wizard is used to determine the subject for trade messages passed between Liberator and the adapter. In this case the subject will be “/PRIVATE/TRADE/FX”.
 

Try this Yourself

Export the blade’s configuration and deploy it in the Deployment Framework just as you did with the Pricing Integration Adapter. Then restart the Deployment Framework and run the adapter from Eclipse.

You will notice that the adapter appears as a new Data Source in the Liberator Status page:

 

Try this Yourself

Refer back to the Pricing Integration Adapter tutorial and enable JMX in the new Trading Integration Adapter. This will allow you to see the new adapter in the CMC.

Hint: Remember to use different port numbers to the ones you used for the Pricing Integration Adapter.

Implementing the Trading Adapter

The CIS Eclipse plugin created the skeleton for a Trading Adapter. This includes:

  • A default trade model configuration in Blade/DataSource/etc/trademodels.xml. We need to replace this with the trade model which represents the trade workflow that we intend to use.
  • A class with your application entry point (named <blade name>. When the adapter is run it creates a new DataSource object which connects to Liberator and creates a new TradingProvider instance.
  • A TradingProvider class (named <blade name>TradingProvider). This class implements TradingApplicationListener and the callback methods which it needs to be notified of the connection status with the Liberator and the trade channel status. When the trade channel (/PRIVATE/TRADE/FX) is created a new TradeChannelListener is created to monitor the activity on that trade channel.
  • A TradeChannelListener class is notified when a trade is created or closed on the /PRIVATE/TRADE/FX trade channel for a specific user.
  • A TradeListener class receives a callback 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

 

Understand

A trade model is the key part of your Trading Integration Adapter. It defines the workflow of each of your trades in the form of a state machine which transitions between one state and another with trade messages being received from the client or sent to the client from the adapter. A trade model is defined using XML.

The trade’s workflow which you will use in this tutorial will be 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.

Steps

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 Fields for Trade Messages

 

Understand

You will be relaying messages between the client and the Trading Adapter, so you need to add fields to the fields.conf in your blade. These fields are the ones that will be used as the parameters of the trade.

Steps

Add the following fields to your Blade/blade_config/fields.conf file. After you do this, remember to re-export your configuration and re-deploy it in the Deployment Framework.

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

 

Understand

A trade is created 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. Therefore to begin with, the adapter needs to be able to accept trade messages from clients.  The receiveEvent callback method in the TradeListener is called whenever there is an incoming trade message on the /PRIVATE/TRADE/FX channel.

Steps

  1. Add code to the receiveEvent() method in your TradeListener class so that it detects whether the event received is “Open”, and if so reads the trade parameters from the message as follows:
    public class TradingAdapterTradeListener 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. To make the test client page available, we will use Liberator's web server capability. Download and extract testpage.zip to the following folder:<DeploymentFramework>\kits\Liberator\Liberator-<version>\htdocs.

  3. Navigate to the following URL in a Chrome browser window to bring up the client application:
    http://localhost:18080/testpage

  4. Press F12 to bring up Chrome’s console panel where you can observe 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 Responses

 

Understand

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 Platform and the client.

Steps

  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 and trigger 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.

Connecting to a Trade Server

What you did in the previous section is immediately confirm a trade without first processing and executing the trade on a server. In reality, 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 the Mock Server

In this part of the tutorial you will implement a connection between the adapter and the mock server which provides the trade execution functionality. 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.

Steps

  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 TradingAdapterTradingProvider implements TradingApplicationListener, FXTradingListener
    
  3. Define a new FXTradingMessenger field in your TradingProvider 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 TradingAdapterTradingProvider(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();
    }
 

Understand

The adapter is currently connecting to Liberator as soon as it is started. This is done with the dataSource.start() instruction (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.

  1. Remove the dataSource.start() instruction from the main method of the TradingAdapter class.
  2. In your TradingProvider 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 TradingAdapterTradingProvider(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();
    }
    
  3. 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");
    }
  4. In the onDisconnect method, change the DataSource status to DOWN.
    @Override
    public void onDisconnect() {
        dataSource.setStatusDown();
    }
  5. 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.

Steps

  1. In your TradingApplicationListener 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 TradingAdapterTradeChannelListener(messenger, logger));
        }
            
  2. Update your TradeChannelListener class so that it expects the new constructor argument and sets it as a field.
    public class TradingAdapterTradeChannelListener implements TradeChannelListener
    {
        private final Logger logger;
        private final FXTradingMessenger messenger;
    
        public TradingAdapterTradeChannelListener(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 TradingAdapterTradeListener(messenger, logger));
        }
  4. Update your TradeListener class so that it expects the new constructor argument and sets it as a field.
    public class TradingAdapterTradeListener implements TradeListener
    {
        private final Logger logger;
        private final FXTradingMessenger messenger;
    
        public TradingAdapterTradeListener(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.

Steps

  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 TradingAdapterTradeListener implements TradeListener, FXTradingListener    
  2. 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.
  3. 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.
  4. 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);
        }
    }
  5. Restart your Adapter.
  6. 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 firs time we get the receiveEvent() callback.

Steps

  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 TradingAdapterTradeListener 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);
    
           messenger.executeTrade(this, user, requestID, currencyPair, amount, buySell, price);
       } 
    }
    
  3. The onTradeReport() method is triggered when the mock server has completed a trade. Implement this method so that it creates 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 which contains the report sent by the mock server.
    @Override
    public void onTradeReport(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());
        }
    }
  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.