Pricing Integration Adapter (2)

The role of Integration Adapters is to act as an adapter between a “bank system” and the Caplin Platform which is managing communication with the client applications. A Pricing Integration Adapter specifically, is an adapter between a server which is producing prices and the Caplin Platform.

Its role is to receive requests from the Platform for specific instruments or groups of instruments and to respond with the latest price data, and subsequently with any price updates, until the Platform no longer requires that data. It is the responsibility of the adapter to connect to the price server, retrieve the requested data from the server, and parse it into the format expected by the Caplin Platform.

As a slightly more advanced objective, it is the job of the adapter to reflect the status of the underlying bank system. So if the bank system goes down, the adapter should also set itself to down. This will prevent Liberator from requesting data from an adapter which cannot supply it without access to the underlying bank system, and allows Liberator to provide feedback to the client applications that pricing, trading etc are not currently available.

Objectives

In this tutorial you will construct a Pricing Integration Adapter step-by-step:

  • First, you will create an adapter which accepts a request (subscription) for any currency pair and returns a specific price.
  • Next, you will handle container requests by returning a price for all the currency pairs in the container.
  • Finally, you will hook up the adapter to a mock server so that it propagates the price updates for the requested currency pair from the data that is being generated on the mock server.
  • You will also become familiar with how to test your pricing adapter.

Handling subject requests

In the previous tutorial you created a new blade based on Caplin's project template for pricing adapters. In this tutorial we will implement the adapter code that responds to subject requests.

Take a look at the src directory in your project. Here you will find the following files:

  1. A class with your application entry point (named <blade name>). When the adapter is run, it creates a new DataSource object which connects to the peers according to the configuration (in our case this is Transformer) and it creates a new PricingProvider instance.
  2. A PricingProvider class (named <blade name>PricingProvider). This class implements the DataProvider interface and its methods onRequest and onDiscard, which are called when Liberator subscribes and unsubscribes to a subject respectively. The PricingProvider uses a Publisher to create new messages (e.g. records) and send these to the Platform.

Receiving requests

Your PricingProvider implements the DataProvider interface in order to receive requests from Liberator. The method onRequest is called every time a subscription is made to a subject commencing with the prefix “/FX/”. The configuration that instructs Liberator to forward requests to the adapter can be found in the add-data-service block in the file blade/Liberator/etc/rttpd.conf.

Steps

  1. Adjust the onRequest and onDiscard methods so that they log the subject name requested or discarded:
    @Override
    public void onRequest(RequestEvent requestEvent)
    {
         System.out.println("Received request for " + requestEvent.getSubject());
    }
    
    @Override
    public void onDiscard(DiscardEvent discardEvent)
    {
         System.out.println("Received discard for " + discardEvent.getSubject());
    }
  2. Next, restart the adapter.
  3. In the Liberator homepage (http://localhost:18080), click Diagnostics > Liberator Explorer. Login with the default credentials: username 'admin' and password 'admin'. We will use Liberator Explorer to make test subscriptions.
  4. Use the Liberator Explorer to subscribe to any subject e.g. “/FX/GBPUSD” (use the “subject” bar at the top and hit enter). Immediately, your executing adapter should log the subject name: /FX/GBPUSD in the Eclipse console window. If you can't see the Eclipse console, click Window > Show View > Console.
  5. Note that the Liberator Explorer waits 10 seconds for a response from your blade and then times out. Notice the “Callbacks” panel in the Explorer displays a message indicating that Liberator could not find the subject:
    In - ActionFailEvent [subject=/FX/GBPUSD]
    This is because no response was received from the adapter within 10 seconds. You will also see in the console that Liberator sends your adapter a discard event after 10 seconds, indicating that it is no longer interested in the subject.

Responding to requests

Now, adjust the implementation of your onRequest method to create an empty record and to send this to Liberator as soon as a subject is requested.

Steps

  1. The following code creates an empty record (no fields and field values) as soon as the request is received, and sends it to Liberator immediately.
    @Override
    public void onRequest(RequestEvent requestEvent)
    {
        System.out.println("Received request for " + requestEvent.getSubject());
        RecordType1Message initialMessage = publisher.getMessageFactory().createRecordType1Message(requestEvent.getSubject());
        publisher.publishInitialMessage(initialMessage);
    }
  2. Re-run your blade and follow the same steps to request data using the Liberator Explorer. This time the Liberator Explorer receives some response and the “Callbacks” panel indicates that the subscription was successful:
    In - RecordType1DataEvent [subject=/FX/GBPUSD, fields=[Type=211,SID=PricingAdapterPricingSvc1], image=false, timeReceived=1397567812085]

Notice that even though you created an empty message in your adapter, there are two fields on the message when it arrives at the client application. These are special fields added to every record message by Liberator itself:

  • Type=211 indicates that this is a record message.
  • SID=PricingAdapterPricingSvc1, literally "Source ID", indicates which data service provided this record message.

These metadata fields are typically not useful for display in the client application, but can be useful for monitoring and log analysis.

Adding fields to responses

You must always send your initial response by calling the publishInitialMessage() method. This method will send all fields on the message to every requesting peer that has not received any data yet.

However, subsequent price updates must be sent by calling a slightly different method which sends the changed fields to peers who have already received the initial message. Next, you will create a new record immediately after sending the empty initial message and populate it with a set of static (hard-coded) fields. Then send this to the Platform as a data update. As price data for FX currency pairs you may typically want a Bid Price, Ask Price and GFA (Good For Amount).

Steps

  1. Create a new record and set these fields before publishing the message:
    @Override
    public void onRequest(RequestEvent requestEvent)
    {    
        System.out.println("Received request for " + requestEvent.getSubject()); 
        RecordType1Message initialMessage = publisher.getMessageFactory().createRecordType1Message(requestEvent.getSubject());
        publisher.publishInitialMessage(initialMessage);
    
        RecordType1Message updateMessage = publisher.getMessageFactory().createRecordType1Message(requestEvent.getSubject());
        updateMessage.setField("InstrumentName", requestEvent.getSubject());
        updateMessage.setField("BidPrice", "1.23");
        updateMessage.setField("AskPrice", "2.34");
        updateMessage.setField("BidPriceId", "00011");
        updateMessage.setField("AskPriceId", "00022");
        updateMessage.setField("BidGFA", "500000");
        updateMessage.setField("AskGFA", "300000");
        Date date = new Date();
        String timestamp = String.valueOf(date.getTime());
        updateMessage.setField("Timestamp", timestamp);
        publisher.publishToSubscribedPeers(updateMessage);
    }
  2. Re-run your adapter, and try making a request from Liberator Explorer. It fails! Notice that the adapter displays a warning in the Eclipse console and an exception with the text:
    java.lang.IllegalArgumentException: Cannot set field: No entry in field name/number mappings for name InstrumentName
 

Understand

The reason for this is that the fields in records being passed between Caplin backend components are encoded as numbers rather than field names. This greatly reduces the size of data passing over the wire. As a result the fields being used by the blade need to be configured to map to a specific number.

  1. Open the file blade/blade_config/fields.conf. Some fields are already defined in this file, but not the ones we want to use for our price updates. Add the configuration below to the bottom of this file. Note that the field IDs are arbitrary; they simply have to be unique integers. Also, be sure to edit the file in blade/blade_config/fields.conf and not the file global_config/fields.conf.
    add-field InstrumentName          -10003
    add-field BidPrice                -10004
    add-field AskPrice                -10005
    add-field BidPriceId              -10006
    add-field AskPriceId              -10007
    add-field BidGFA                  -10008
    add-field AskGFA                  -10009
    add-field Timestamp               -10010
    
  2. Export the configuration of the blade to the Deployment Framework kits directory, and re-deploy the Pricing Adapter configuration, which now includes the new fields.

    Tip: You can use ./dfw deploy -f to bypass the Deployment Framework's confirmation requests on whether you would like to upgrade the blade.

  3. Restart the deployment framework and try requesting /FX/GBPUSD (or any other instrument with the /FX/ prefix) from the Liberator Explorer. You will now notice that a full record comes through with the hard-coded field values that we set:

Containers

 

Understand

It may sometimes be useful to provide the ability for clients to subscribe to a “container” of objects. A container is basically a list of subjects which can be received via one subscription. An example would be "FX Majors" - a client can subscribe to a container with this name and the adapter can provide records representing all of the currency pairs in this group.

Another example is a trade blotter that shows the trades executed by a client. A client can subscribe to a container blotter subject and receive a record for every historic trade, without having to know the record subjects for every historic trade and subscribe to them individually.

Having implemented the Pricing Integration Adapter to respond to single subject subscriptions, you will now extend it to support container subscriptions.

Upon receiving a request for Liberator, the PricingProvider will check whether it is a container request and, if so, will return a ContainerMessage as the initial messages (instead of a RecordType1Message). The ContainerMessage will contain each of the subjects making up the container. (Liberator will then make an individual subscription for each of the subjects in the container.)

You will need to create a separate publisher for the container as it will have a different subject prefix e.g. “/CONTAINER/FX/MAJOR” as a subject name for the container and “/FX/<currency pair>” for each individual currency pair.

Steps

  1. Declare a new publisher for your container as a field at the top of your class.
    private final Publisher publisher;
    private final Publisher containerPublisher;
  2. Instantiate the container publisher in the PricingProvider’s constructor:
    this.publisher = dataSource.createActivePublisher(new PrefixNamespace("/FX/"), this);
    this.containerPublisher = dataSource.createActivePublisher(new PrefixNamespace("/CONTAINER/FX"), this);
  3. In your onRequest() method, add a new check on the subject name to detect whether a container is being requested. If so, create a container message and add five record subjects to it. Otherwise, fall back to your existing record message code.
    @Override
    public void onRequest(RequestEvent requestEvent)
    {
        System.out.println("Received request for " + requestEvent.getSubject());
        String subject = requestEvent.getSubject();
        if (subject.equals("/CONTAINER/FX/MAJOR")) {
            ContainerMessage containerMsg = containerPublisher.getMessageFactory().createContainerMessage(subject);
            containerMsg.addElement("/FX/GBPUSD");
            containerMsg.addElement("/FX/GBPEUR");
            containerMsg.addElement("/FX/GBPJPY");
            containerMsg.addElement("/FX/GBPAUD");
            containerMsg.addElement("/FX/GBPCHF");
            containerPublisher.publishInitialMessage( containerMsg );
        }
        else {
            RecordType1Message recordType1Message = publisher.getMessageFactory().createRecordType1Message(requestEvent.getSubject());
            publisher.publishInitialMessage(recordType1Message);
            ...
        }
    }
 

Understand

In the project file blade/Liberator/etc/rttpd.conf you will find configuration for Liberator that defines your adapter as a data service for all subjects prefixed with /FX/.

You must add a similar piece of configuration to define your adapter as a data service for subjects prefixed with /CONTAINER/FX.

To update the Liberator and Transformer configuration, to allow subscriptions on the container’s subject:

  1. Find the blade’s Liberator configuration in Blade/Liberator/etc/rttpd.conf. Add a second pattern like this:
    add-data-service
        ...
        include-pattern     "^/FX"
        include-pattern     "^/CONTAINER/FX/"
        ...
    end-data-service
  2. Do the same for the Transformer configuration in Blade/Transformer/etc/transformer.conf.
  3. Remember that once you have updated this configuration file, you’ll need to re-export and re-deploy the blade’s configuration and restart the deployment framework.
  4. In the Liberator Explorer, click on the "record" drop-down in the top right corner and select "container". This is just a limitation of the Liberator Explorer client application, you must tell it what type of data to expect when it subscribes to a subject so that it can render the data correctly. The Liberator Explorer would be improved if it detected the type of object when it arrived and displayed it appropriately without the user having to make a selection in the drop-down menu.
  5. From the Liberator Explorer subscribe again to “/CONTAINER/FX/MAJOR”. You should see all the currencies you added to the container appearing, each having the same data:

Connecting to a Pricing Server

So far we have only replied to requests with one record containing some hard-coded data. In a realistic environment your adapter will be connected to a bank server which is producing prices. Upon receiving a request from the Platform, it should subscribe to the requested data from the bank server propagate the price updates it receives into the Caplin Platform.

In this part of the tutorial you will start up a mock server which represent a bank system, and enhance your adapter to retrieve price data from it.

Running the Mock Server

 

Understand

The mock server provided with this set of tutorials is a rough simulation of a server which generates prices for a set of currency pairs and provides trading functionality for spot trades. It produces prices for any pair of the following currencies: EUR, USD, GBP, JPY, DKK, ZAR, CHF and AUD.

Steps

  1. Download the TrainingTradeProject zip file. Extract “TrainingTradeProject-<version>.zip” and copy the file “SimpleFXServer-<version>.jar” to a location on your hard drive.
  2. Open a new command prompt and navigate to the directory where you unzipped the file.
  3. Type the following command in your command line tool to start the 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
  4. To stop the process, use Ctrl + C.

Connecting to the Mock Server

Each bank server generally provides a library which provides its own interface for connecting, subscribing to data and listening to data updates or server status. The mock server is no different, it has a client library which your adapter can use to communicate with the mock server.

Steps

  1. Copy the file “SimpleFxMessenger-<version>.jar” (contained in the zip file which you extracted in the previous step) to the Blade/DataSource/lib folder in your project. Note: this is not the same jar that you started up in the previous step. There are two jars with similar names, the server jar (which you have used already) and the messenger jar which is the library used for connecting to the server.
  2. Add this jar to the Java build path of your project (right-click on the jar file and select “Build Path > Add to Build Path”).
  3. In order to receive connection and message callbacks from the mock server the PricingAdapterPricingProvider class needs to implement the FXPricingListener interface. This is an interface provided by the mock server's client library.
    public class PricingAdapterPricingProvider implements DataProvider, FXPricingListener {    
  4. When you update your class to implement this interface, Eclipse will warn you that you have not implemented the required methods from the interface. Click on the red error icon to the left of the line of code above, and select "Add unimplemented methods". Eclipse will automatically add the four methods that you need to implement to the bottom of your file.
  5. Update the onConnect callback method to print a message to the console:
    @Override
    public void onConnect() {
        System.out.println("Connected to mock server");
    }
    @Override
    public void onDisconnect() {
        // TODO Auto-generated method stub
    }
    @Override
    public void onPriceUpdate(FXQuote arg0) {
        // TODO Auto-generated method stub
    }
    @Override
    public void onSubscriptionError(String arg0, String arg1) {
        // TODO Auto-generated method stub
    }
  6. Declare a new instance of FxPricingMessenger as a field at the top of your class. In the constructor, instantiate this object and call the connect() method to make a connection to the MockServer. This FXPricingMessenger object is used to communicate with the mock server.
    private final Publisher publisher;
    private final Publisher containerPublisher;
    private final FXPricingMessenger messenger;
    
    public PricingAdapterPricingProvider(DataSource dataSource)
    {
        this.publisher = dataSource.createActivePublisher(new PrefixNamespace("/FX/"), this);
        this.containerPublisher = dataSource.createActivePublisher(new PrefixNamespace("/CONTAINER/FX"), this);
    
        this.messenger = new FXPricingMessenger(this, FXPricingMessenger.DEFAULT_SESSION_ID_1, FXPricingMessenger.DEFAULT_PORT_1);
        this.messenger.connect();
    }
  7. Restart your adapter. In the Eclipse console you should now see the line "Connected to mock server". This means your adapter has successfully connected to the mock server.

Improving the start-up behaviour of your adapter

Your adapter currently connects to Liberator as soon as it starts up. This can create an undesirable situation where the adapter is connected successfully to Liberator but the underlying bank server is down, which is problematic because Liberator will send requests to the adapter which the adapter cannot service.

It is considered best practice for the status of the adapter to reflect the status of the underlying bank server. We are now going to improve the adapter so that it only initialises the connection to Liberator once it has successfully connected to the bank server.

 

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 of the PricingAdapter class. If you want the adapter to be unusable unless it has established a connection with the mock server, you will need to postpone the connection to Liberator and toggle the connection status.

Steps

  1. Remove the dataSource.start() instruction from the main method of the PricingAdapter class.
  2. In the constructor of the PricingAdapterPricingProvider class, store the DataSource object as a field so that it can be accessed from any method in this class. Also add a boolean field that we will use to keep track of whether or not the DataSource has been started. This boolean field will be initialised to false.
    private final Publisher publisher; 
    private final Publisher containerPublisher; 
    private final FXPricingMessenger messenger;
    private final DataSource dataSource;
    
    private boolean dataSourceIsStarted = false;
    
    public PricingAdapterPricingProvider(DataSource dataSource) 
    { 
         this.publisher = dataSource.createActivePublisher(new PrefixNamespace("/FX/"), this);
         this.containerPublisher = dataSource.createActivePublisher(new PrefixNamespace("/CONTAINER/FX"), this);
         this.dataSource = dataSource;
    
         this.messenger = new FXPricingMessenger(this, FXPricingMessenger.DEFAULT_SESSION_ID_1, FXPricingMessenger.DEFAULT_PORT_1);
         this.messenger.connect(); 
    }
  3. In the onConnect method, which is called when the adapter successfully connects to the mock server, start the DataSource if it has not yet been started or toggle its status to UP if it has been started before. You will use your boolean field to know if the DataSource has been started or not.
    @Override
    public void onConnect() {
        System.out.println("Connected to mock server");
        if (dataSourceIsStarted) {  
            dataSource.setStatusUp();
        } else {
            dataSource.start();
            dataSourceIsStarted = true;
        }
    }
  4. In the onDisconnect method, which is called when the adapter loses connection to the mock server, toggle the DataSource status to DOWN.
    @Override
    public void onDisconnect() {
      System.out.println( "Disconnected from mock server" );
      dataSource.setStatusDown();
    }
  5. At this point you should be able to start your adapter and see it turn blue in the CMC, indicating that it has successfuly connected to Liberator.

  6. Try stopping the mock server by pressing Ctrl+C in the command window. You should see the Pricing Adapter turn red in the CMC, indicating that it is unavailable. This is because when you stopped the mock server the onDisconnect() method would have been called, resulting in the adapter toggling its status to DOWN.

  7. Restart the mock server. The Pricing Adapter should turn blue in the CMC, indicating that it is available again. This is because the onConnect() method would have been called when connection to the mock server was re-established, resulting in the adapter toggling its status to UP.

Request Data from the Mock Server

Next, we’ll change the onRequest method so that when a request is made for an FX subject, the adapter requests data from the mock server. (This is instead of returning a record with hard-coded data as we did previously.)

Steps

Subject requests are received from the platform in the form “/FX/<currency pair>” e.g. “/FX/GBPUSD”. This particular mock server, however, expects requests in the form “<currency>/<currency>” e.g. “GBP/USD”. When a request comes in you will need to convert the requested subject to a currency pair that matches the expected format.

  1. Create a new private method that converts subjects to currency pairs. Add a couple of lines to your onRequest() method that will call this method and then subscribe to the resulting currency pair from the mock server.
    @Override
    public void onRequest(RequestEvent requestEvent)
    {
        String subject = requestEvent.getSubject();
        if (subject.equals("/CONTAINER/FX/MAJOR")) {
            ...
        }
        else {
            String instrumentName = convertSubjectToInstrumentName(subject);
            messenger.subscribe(instrumentName);
    
            RecordType1Message initialMessage = publisher.getMessageFactory().createRecordType1Message(requestEvent.getSubject()); 
            publisher.publishInitialMessage(initialMessage); 
            ...
        }
    }
    
    private String convertSubjectToInstrumentName(String subject)
    {
        String[] splittedSubject = subject.split("/");
        String currencyPair = splittedSubject[2];
        return currencyPair.substring(0, 3) + "/" + currencyPair.substring(3, 6);
    }
  2. Re-run your adapter and request a valid currency pair using Liberator Explorer. The result should still be the hard-coded record, but now, in the console window for the mock server, you will notice some messages appearing, every 5 seconds. These correspond to the new price updates being sent to the adapter as a response.

Propagate Data Updates to the Platform

The final step is to receive the data updates coming from the mock server, create a record, and propagate the update to Liberator. To receive incoming messages from the Mock Server you must implement the onPriceUpdate callback method (a method defined in the FXPricingListener interface).

Steps

  1. Create a new private method that converts currency pairs containing forward slashes to subjects. Basically, this is the inverse operation you did previously i.e. convert from “GBP/USD” to “/FX/GBPUSD”).
    private String convertInstrumentNameToSubject(String instrumentName)
    {
        String subject = "/FX" + "/" + instrumentName.substring(0, 3) + instrumentName.substring(4, 7);
        return subject;
    }    
  2. In your onPriceUpdate() method, which is called whenever you receive a price update from the mock server, create a new record message with this subject name. Populate the fields on the message with the data in the FXQuote, using the same field names as in the hard-coded initial response. Then publish the message to the subscribed peers.
  3. @Override
    public void onPriceUpdate(FXQuote fxQuote) 
    {
        String instrumentName = fxQuote.getCurrencyPair();
        String subject = convertInstrumentNameToSubject(instrumentName);
    
        RecordType1Message updateMessage = publisher.getMessageFactory().createRecordType1Message(subject);
        updateMessage.setField("InstrumentName", subject);
        updateMessage.setField("BidPrice", String.valueOf(fxQuote.getBidPrice()));
        updateMessage.setField("AskPrice", String.valueOf(fxQuote.getOfferPrice()));
        updateMessage.setField("BidPriceId", String.valueOf(fxQuote.getBidID()));
        updateMessage.setField("AskPriceId", String.valueOf(fxQuote.getOfferID()));
        updateMessage.setField("BidGFA", String.valueOf(fxQuote.getBidSize()));
        updateMessage.setField("AskGFA", String.valueOf(fxQuote.getOfferSize()));
        Date date = new Date();
        String timestamp = String.valueOf(date.getTime());
        updateMessage.setField("Timestamp", timestamp);
    
        publisher.publishToSubscribedPeers(updateMessage);
    }
    
  4. Remove the hard-coded data update after the initial record request so that you go back to just sending a blank initial response when a request is received.
    @Override 
    public void onRequest(RequestEvent requestEvent) 
    { 
        System.out.println("Received request for " + requestEvent.getSubject()); 
        String subject = requestEvent.getSubject(); 
        if (subject.equals("/CONTAINER/FX/MAJOR")) { 
             ContainerMessage containerMsg = containerPublisher.getMessageFactory().createContainerMessage(subject); 
             containerMsg.addElement("/FX/GBPUSD"); 
             containerMsg.addElement("/FX/GBPEUR"); 
             containerMsg.addElement("/FX/GBPJPY"); 
             containerMsg.addElement("/FX/GBPAUD"); 
             containerMsg.addElement("/FX/GBPCHF"); 
             containerPublisher.publishInitialMessage( containerMsg ); } 
        else {
             String instrumentName = convertSubjectToInstrumentName(subject);
             messenger.subscribe(instrumentName);
     
             RecordType1Message recordType1Message = publisher.getMessageFactory().createRecordType1Message(requestEvent.getSubject()); 
             publisher.publishInitialMessage(recordType1Message);
        } 
    }   
  5. Open Liberator Explorer and, once again, subscribe to the /FX/GBPUSD object and the /CONTAINER/FX/MAJOR container. You should now see data updates appearing with realistic ticking updates from the mock server.

Discarding Subscriptions when the Client Disconnects

 

Understand

One minute after closing the Liberator Explorer window/tab in your browser, Liberator will drop the subscription and trigger the onDiscard() callback in the adapter for each subject subscription. This is your cue to end the subscription with the Mock Server.

Steps

  1. Convert the discarded subject to the instrument required by the mock server (e.g. “/FX/GBPUSD” to “GBP/USD”), then use the messenger to unsubscribe from the instrument.
    @Override
    public void onDiscard(DiscardEvent discardEvent) {
        System.out.println("Received discard for " + discardEvent.getSubject());
        String subject = discardEvent.getSubject();
        String instrumentName = convertSubjectToInstrumentName(subject);
        messenger.unsubscribe(instrumentName);
    }
  2. Make a subscription using Liberator Explorer then close the Explorer browser window. The onDiscard method will be called by Liberator 1 minute after you closed the Explorer window. When this happens and the adapter unsubscribes from the mock server, the mock server’s output log will display:
    pricing.IntervalTaskUpdate  - CANCELING interval update

Review

In this tutorial we looked at the implementation of new Pricing Integration Adapters – how to receive requests from the Platform and respond by sending an initial response. We also looked at how to send price updates to the Platform. We used a mock server to retrieve the price updates, however remember that the way you communicate with the server will differ from one server to another. The way you deal with object and container requests, create new records, and send initial and update records does not change.