Trading Integration Adapter

Overview

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.

Tutorial 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 from the training branch of the Caplin Project Templates. 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 training branch 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: add your Caplin username and password to your ~/.gradle/gradle.properties file (create the file if it does not exist):

      File: ~/.gradle/gradle.properties
      caplinNexusUser=username
      caplinNexusSecret=password

      In this project’s build.gradle file, uncomment the highlighted section below:

      File: build.gradle
      repositories {
          flatDir {
              dirs 'lib'
          }
          mavenCentral()
          maven {
              credentials {
                  username "$caplinNexusUser"
                  password "$caplinNexusSecret"
              }
              url "https://repository.caplin.com"
          }
      }
    • Manual download: download the latest 7.x release of the following files from the Caplin Download site and copy them to the lib folder in your project.

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

      • Java Trading API: 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.

    If you are following this tutorial on Cygwin, then your project directory is C:/cygwin64/home/username/src/TradingAdapter
  5. Click Finish

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

Running your adapter during development

To run the adapter from your IDE during development, you need to perform two tasks:

  • Deploy your adapter’s configuration to your Deployment Framework. Your adapter project includes configuration files that are required by Liberator and/or Transformer.

  • Configure the environment in which your IDE runs your adapter. This is called a "run configuration" in Eclipse and IntelliJ.

Deploy your adapter’s configuration

The Trading Adapter template includes a Gradle task that builds an installation kit that contains just your adapter’s configuration (no binary). This kit can be deployed to your Deployment Framework like any other installation kit.

Follow the steps below:

  1. From the root of your adapter project, run the command below:

    ./gradlew assemble -PconfigOnly

    The installation kit is written to the directory build/distributions.

  2. Copy the installation kit to the kits directory of your Deployment Framework. Do not extract the archive.

  3. From the root directory of your Deployment Framework, run the command below to deploy the kit:

    ./dfw deploy
  4. From the root directory of your Deployment Framework, run the command below to confirm that your adapter’s configuration has deployed:

    ./dfw versions

Create a run configuration in your IDE

Follow the steps below:

  1. In your IDE, create a new run configuration for the main class of your adapter.

  2. Set the run configuration’s working directory to dfw_location/active_blades/adapter_name/DataSource, where dfw_location is the path to your Deployment Framework, and adapter_name is the name of your adapter.

    On Microsoft Windows, which does not recognise Unix-style symbolic links, use the path dfw_location\kits\adapter_name\adapter_name-version\DataSource
  3. Create a run-configuration environment variable CONFIG_BASE with the value dfw_location/global_config/, where dfw_location is the path to your Deployment Framework.

    The value of CONFIG_BASE must end with a trailing slash.
  4. Add an argument to the command line used to start your adapter: --trading-property-file=etc/trading-provider.properties

  5. Test the new run configuration by running it.

Project structure

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

com/caplin/template/TradingAdapter.java

This is the main class for the project. When the adapter runs, it creates a new DataSource instance and a new TemplateTradingApplicationListener instance.

com/caplin/template/TemplateTradingApplicationListener.java

This class implements the TradingApplicationListener interface and registers itself with a new instance of TradingProvider. As a TradingApplicationListener, this class is notified by the TradingProvider of connection and channel events. When this class is informed that a TradeChannel object has been created, it registers a new instance of TemplateTradeChannelListener as the TradeChannelListener for the channel.

com/caplin/template/TemplateTradeChannelListener.java

A separate instance of this class is registered as a TradeChannelListener with each TradeChannel object. This class is notified when a Trade object is created or closed. When this class is informed that a Trade object has been created, it registers a new instance of TemplateTradeListener as the TradeListener for the Trade.

com/caplin/template/TemplateTradeListener.java

A separate instance of this class is registered as a TradeListener with each Trade object. This class is notified when an event is raised on the Trade object’s trade model.

blade/DataSource/etc/trademodels.xml

The trade model to be used by this adapter’s com.caplin.trading.TradingProvider instance. The name and location of this file are set in the adapter’s configuration file for the Java Trading API: trading-provider.properties

Class diagram:

com.caplin.tradingcom.caplin.datasourceTradingApplicationListenerchannelClosed()channelCreated()peerUp()peerDown()TradeChannelListenertradeClosed()tradeCreated()TradeListenerreceiveEvent()TradingProvidercreateTrade()DataSourceTradingAdaptermain()TemplateTradingApplicationListenerchannelClosed()channelCreated()peerUp()peerDown()TemplateTradeChannelListenertradeClosed()tradeCreated()TemplateTradeListenerreceiveEvent()instantiates

Handle trade-channel messages

In this section of the tutorial, we will handle communication between your adapter and the client application.

On the Caplin Platform, trading is conducted via the exchange of messages on a trade channel. At a high-level, a channel is a subscription that supports the bi-directional exchange of private messages between a client and an adapter. For details of how channels are implemented at a low level (not required for this tutorial), see Channels.

The choreography of messages involved in a trade is managed by a trade model. Front end applications and trading adapters are configured with the same trade models. A change in the state of a trade is communicated by sending a message on the trade channel between a client and the adapter.

Set the trade models recognised by the adapter

Trade models are state machines used to manage trading workflows in the Java Trading API, the C Trading API, and Caplin Trader’s Trading API.

Trade models are defined in XML. Caplin’s front-end libraries (Caplin Trader) and back-end libraries use the same configuration file format, described here: Trade Model XML Schema.

A trading adapter can recognise more than one trade model. The trade model that applies to a trade is set by the TradingProtocol field in the first message of a workflow.

In this tutorial we will configure one trade model for the adapter, called "ESP". A state diagram for the model is shown below:

InitialExecutingTradeCompleteOpenConfirmLegendTransitions initiated by the client are inyellow.Transitions initiated by the server are inblue.

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

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

This trade model is a much simplified version of the Caplin FX Suite’s ESP trade model.

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:

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

Receive trade messages from clients

Trading events are sent by messages 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" state to the "Executing" state and then waiting for the adapter to respond with a "Confirm" 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 highlighted code below to handle "Open" event messages:

    File: TemplateTradeListener.java
    @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. Run the following command from the root of your Deployment Framework to start all components:

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

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

  6. On the test page, click Login, then click Trade. 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.

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 thrown because we have not enabled JMX for the adapter. The error does not indicate that you have followed the tutorial incorrectly.

Send trade messages to clients

TradeEvent objects passed to TemplateTradeListener.receiveEvent include a reference to the original Trade object with which TemplateTradeListener was registered as a TradeListener. You can use this Trade object to send a trade message back to the client.

The first trade event in the trade model is "Open", which is sent by the client when a trade is first opened. The TradeEvent object for the "Open" event includes the details of the trade. Later in this tutorial, we will modify TemplateTradeListener.receiveEvent to pass those details to a mock backend trading system. However, to keep things simple at this stage, we will pretend the trade has executed successfully and send back a "Confirm" trade event.

Follow the steps below:

  1. In the file TemplateTradeListener.receiveEvent, add the highlighted code below to send a "Confirm" event message back to the client when you receive an "Open" event message:

    File: TemplateTradeListener.java
    @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);
    
            // Send a "Confirm" event to the client
            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.

    tutorial tradia client trade confirmed

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

To complete this section, you will need the following two files from the TrainingTradeProject zip file:

  • SimpleFXServer-<version>.jar

  • SimpleFXMessenger-<version>.jar

Connect to the mock server

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

Follow the steps below:

  1. In the directory containing the mock server’s jar file (see Pricing Integration Adapter 2 tutorial), type the following command to start the mock server:

    java -jar SimpleFXServer-<version>.jar

    The following console output indicates that the server is running:

    <timestamp> [main] INFO  quickfix.SocketAcceptor  - SessionTimer started
    <timestamp> [main] INFO  quickfix.mina.NetworkingOptions  - Socket option: SocketTcpNoDelay=true
    <timestamp> [main] INFO  quickfix.mina.NetworkingOptions  - Socket option: SocketSynchronousWrites=false
    <timestamp> [main] INFO  quickfix.mina.NetworkingOptions  - Socket option: SocketSynchronousWriteTimeout=30000
    <timestamp> [main] INFO  quickfix.SocketAcceptor  - Listening for connections at 0.0.0.0/0.0.0.0:14045
  2. Copy the file SimpleFXMessenger-<version>.jar to the lib directory in your adapter project.

  3. Edit the build.gradle file in the root directory of your adapter project, and add SimpleFXMessenger as a dependency in the dependencies { } stanza:

    File: build.gradle
    dependencies {
        compile(group: 'com.caplin.platform.integration.java', name: 'datasource', version: '7.0.+')
        compile(group: 'com.caplin.platform.integration.java', name: 'trading-datasource', version: '7.0.+')
        compile(name: 'SimpleFXMessenger', version: '0+') { transitive = false }
    }
  4. Reload the Gradle configuration for your project in your IDE. In Eclipse, right-click your root project folder and click Gradle > Refresh Gradle Project

  5. In the file TemplateTradingApplicationListener.java, import the Simple FX Messenger classes:

    File: TemplateTradingApplicationListener.java
    import messenger.*;
  6. To communicate with the mock server, your TemplateTradingApplicationListener class will need to implement the messenger.FXTradingListener interface. This interface extends the messenger.FXConnectionListener interface and provides the methods onConnect(), onDisconnect(), and onTradeReport(FXTradeReport).

    File: TemplateTradingApplicationListener.java
    public class TemplateTradingApplicationListener
            implements TradingApplicationListener, FXTradingListener
    {
        private final Logger logger;
        private TemplateTradeChannelListener tradeChannelListener;
    
        // ...
    
        @Override
        public void onConnect() {
            // ...
        }
    
        @Override
        public void onDisconnect() {
            // ...
        }
    
        @Override
        public void onTradeReport(FXTradeReport report) {
            // ...
        }
    
        // ...
    }
  7. Define a new FXTradingMessenger field in your TemplateTradingApplicationListener class. Instantiate the field in the TemplateTradingApplicationListener constructor and call FXTradingMessenger.connect() to connect to the mock server.

    The session ID and the port used in the code below are deliberately different values to the ones used in the Pricing Integration Adapter 2 tutorial, so don’t copy and paste the line from that project.

    File: TemplateTradingApplicationListener.java
    public class TemplateTradingApplicationListener
            implements TradingApplicationListener, FXTradingListener
    {
        // ...
    
        private final FXTradingMessenger messenger;
    
        public TemplateTradingApplicationListener (DataSource dataSource)
                throws IllegalArgumentException, IOException
        {
            this.logger = dataSource.getLogger();
    
            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 2 tutorial.

  1. Remove the dataSource.start() instruction from the main method of the TradingAdapter class.

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

    File: TemplateTradingApplicationListener.java
    public class TemplateTradingApplicationListener
            implements TradingApplicationListener, FXTradingListener
    {
        // ...
    
        private final DataSource dataSource;
    
        private boolean dataSourceIsStarted = false;
    
        public TemplateTradingApplicationListener (DataSource dataSource)
                throws IllegalArgumentException, IOException
        {
            this.logger = dataSource.getLogger();
    
            this.dataSource = dataSource;
    
            new TradingProvider(this, dataSource);
    
            this.messenger = new FXTradingMessenger(this,
                FXTradingMessenger.DEFAULT_SESSION_ID_2,
                FXTradingMessenger.DEFAULT_PORT_2);
            this.messenger.connect();
        }
    
        // ...
    }
  3. In the TemplateTradingApplicationListener.onConnect() method, start the DataSource if it has not yet been started, otherwise toggle the status to UP.

    File: TemplateTradingApplicationListener.java
    @Override
    public void onConnect() {
        if (this.dataSourceIsStarted) {
            this.dataSource.setStatusUp();
        } else {
            this.dataSource.start();
            this.dataSourceIsStarted = true;
        }
        System.out.println("Connected to mock server");
    }
  4. In the TemplateTradingApplicationListener.onDisconnect(), change the DataSource status to DOWN.

    File: TemplateTradingApplicationListener.java
    @Override
    public void onDisconnect() {
        this.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

To test the effects of your changes, follow the steps below:

  1. Stop the CMC if it is already running.

  2. Update the CMC’s configuration:

    ./dfw mon
  3. Start the CMC:

    ./dfw start cmc
  4. Right-click any available white space in the CMC canvas and click Add new DataSource.

  5. In the DataSource Properties dialog, complete the fields according to the values below. Click OK when done.

    Field Value

    Name

    Trading Adapter

    Host

    localhost

    Port

    11009

    Username

    admin

    Password

    admin

    We add the trading adapter to CMC manually because the Deployment Framework command mon cannot automatically detect the trading adapter while you run it in your IDE.
  6. Stop the mock server (Ctrl-C).

  7. Watch both the Pricing Adapter and Trading Adapter go from blue to red in the CMC.

  8. Restart the mock server:

    java -jar SimpleFXServer-<version>.jar
  9. Watch the Pricing Adapter and the Trading Adapter go from red to blue in the CMC.

Make the FXTradingMessenger available to TradeListeners

Currently our TemplateTradingApplicationListener class is the only class that has a reference to the FXTradingMessenger object. To give the TemplateTradeListener.receiveEvent method access to the messenger object to submit trades, we will need to do a little bit of plumbing.

Follow the steps below:

  1. In your TemplateTradingApplicationListener class, change the implementation of TradingApplicationListener.channelCreated() so that it passes the messenger object to TemplateTradeChannelListener as a new constructor argument.

    File: TemplateTradingApplicationListener.java
    @Override
    public void channelCreated(TradeChannel tradeChannel) throws TradeException
    {
        logger.log(Level.INFO, "Trade channel created: " + tradeChannel.toString());
        this.tradeChannelListener =
            new TemplateTradeChannelListener(this.messenger, this.logger);
        tradeChannel.setTradeChannelListener(this.tradeChannelListener);
    }
  2. In the TemplateTradeChannelListener class, import the Simple FX Messenger classes:

    File: TemplateTradeChannelListener.java
    import messenger.*;
  3. Update the TemplateTradeChannelListener constructor so that it expects an FXTradingMessenger instance as an extra argument and sets it as a field.

    File: TemplateTradeChannelListener.java
    public class TemplateTradeChannelListener implements TradeChannelListener
    {
        private final Logger logger;
        private TradeListener tradeListener;
        private final FXTradingMessenger messenger;
    
        // ...
    
        public TemplateTradeChannelListener(FXTradingMessenger messenger, Logger logger)
        {
            this.logger = logger;
            this.messenger = messenger;
        }
    
        // ...
    }
  4. Still in your TemplateTradeChannelListener class, change the tradeCreated() method so that it passes the messenger object to the TemplateTradeListener as a new constructor argument.

    File: TemplateTradeChannelListener.java
    @Override
    public void tradeCreated(Trade trade) throws TradeException
    {
        this.logger.log(Level.INFO, "Trade created");
        this.tradeListener = new TemplateTradeListener(this.messenger, this.logger);
        trade.setTradeListener(this.tradeListener);
    }
  5. In the file TemplateTradeListener.java, import the Simple FX Messenger classes:

    File: TemplateTradeListener.java
    import messenger.*;
  6. Update the TemplateTradeListener constructor so that it expects an FXTradingMessenger as a new constructor argument and sets it as a field.

    File: TemplateTradeListener.java
    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 TemplateTradeListener class now have access the FXTradingMessenger object and can use it to submit trades.

Submit a trade to the mock server

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

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: the FXTradingMessenger.execute method accepts the char constants FXTradeReport.BUY and FXTradeReport.SELL, but our BuySell field is a string ("BUY" or "SELL").

To send trade requests to the trading server, follow the steps below:

  1. In your TemplateTradeListener class, delete the two lines at the bottom of the receiveEvent() method that create and send the "Confirm" event.

  2. Add code to the end of the receiveEvent() method to convert the "BuySell" field value and execute the trade.

    File: TemplateTradeListener.java
    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);
    
            // Convert buySellString to an FXTradeReport constant
            char buySell;
            if (buySellString.equals("BUY")) {
                buySell = FXTradeReport.BUY;
            } else {
                buySell = FXTradeReport.SELL;
            }
    
            // Execute the trade
            messenger.executeTrade(user, requestID, currencyPair,
                amount, buySell, price);
        }
    }
  3. Restart your Adapter.

  4. 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)]

    To locate this message quickly, right-click the console, click 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.

Receive a trade confirmation from the mock server

The FXTradingMessenger.execute method is asynchronous. The response from the trade server is handled by the interface method FXTradingListener.onTradeReport(FXTradeReport), which is implemented by your TemplateTradingApplicationListener class.

The TemplateTradingApplicationListener class receives the response from the server, but it’s the trade’s TemplateTradeListener instance that needs to be informed of the response. We will add code to implement this, working from TemplateTradeListener outwards to TemplateTradingApplicationListener.

To send "Confirm" event to the client when the trade server responds, follow the steps below:

  1. In TemplateTradeListener.receiveEvent, assign the event’s Trade object to a new private instance variable, trade:

    File: TemplateTradeListener.java
    public class TemplateTradeListener implements TradeListener
    {
        private Trade trade;
    
        // ...
    
        @Override
        public void receiveEvent(TradeEvent event) throws TradeException
        {
            logger.log(Level.INFO, "receiveEvent");
            this.trade = event.getTrade();
    
            // ...
    
        }
    
        // ...
    }
    Unlike the other instance fields, Java will not allow you to declare trade as final, because final fields must have their value set inline or in the constructor. We can’t set the value of trade until we get the receiveEvent() callback.
  2. In TemplateTradeListener, add a new onTradeReport method:

    File: TemplateTradeListener.java
    public class TemplateTradeListener implements TradeListener
    {
        // ...
    
        public void onTradeReport(FXTradeReport fxTradeReport)
        {
            try {
                TradeEvent event = trade.createEvent("Confirm");
                event.addField("Message", fxTradeReport.toString());
                this.trade.sendEvent(event);
            } catch (TradeException e) {
                logger.log(Level.SEVERE, e.getMessage());
            }
        }
    
        // ...
    }
  3. In TemplateTradeChannelListener, add a new onTradeReport method and change the datatype of the tradeListener field from the interface TradeListener to the implementation class TemplateTradeListener:

    File: TemplateTradeChannelListener.java
    public class TemplateTradeChannelListener implements TradeChannelListener
    {
        // ...
    
        private TemplateTradeListener tradeListener; (1)
    
        // ...
    
        public void onTradeReport(FXTradeReport fxTradeReport)
        {
            this.tradeListener.onTradeReport(fxTradeReport); (2)
        }
    
        // ...
    }
    1 Data type changed from TradeListener to TemplateTradeListener.
    2 The method onTradeReport is not part of the TradeListener interface.
  4. Still in TemplateTradeChannelListener, update the data type of the tradeListener member variable to TemplateTradeListener. We need to do this because the onTradeListener method

  5. In TemplateTradingApplicationListener, update the onTradeReport method so that it calls onTradeReport on the TemplateTradeChannelListener instance:

    File: TemplateTradingApplicationListener.java
    @Override
    public void onTradeReport(FXTradeReport fxTradeReport)
    {
       this.tradeChannelListener.onTradeReport(fxTradeReport);
    }
  6. Restart the adapter and execute a trade from the test page.

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

    tutorial tradia client trade confirmed 2

    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.

  8. Notice also from the adapter’s log in the Eclipse console that your TemplateTradeChannelListener 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.

Extension question

To simplify this tutorial, the TemplateTradingApplicationListener class maintains a reference to only one TemplateTradingChannelListener. A production adapter, however, would have to handle multiple channels simultaneously.

What changes would you need to make so that the adapter can manage multiple trading channels?

When designing your solution, consider the following points:

  • When TemplateTradingApplicationListener.onTradeReport receives a response from the mock trade server, how will the method know which trade channel the response relates to?

  • What would happen if a user logged in from their mobile and from their desktop and then placed trades simultaneously on both clients?

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. For more examples of trade models, see the trade models used to manage trading-state between Caplin FX Professional and the Caplin FX Integration API:

The second step is to build your trading adapter around the following three classes:

  • TradingProvider establishes the connection to a backend trading server (we used a mock server in this tutorial). This class is informed by the Java Trading API when a user creates or closes a trade channel.

  • TradeChannelListener is informed by the Java Trading API when a user creates a new trade.

  • TradeListener is informed by the Java Trading API when a trade event is received from the client.