Translating between LUSID's data model and external data models

LUSID has a bi-directional translation engine that can be used as part of a workflow to:

  • Translate LUSID's data model to an external data model in preparation for exporting data from LUSID.

  • Translate an external data model to LUSID's data model in preparation for importing data into LUSID.

The process is as follows:

  1. Upload a translation script to LUSID. This is a Javascript document that translates to and/or from LUSID's data model, depending on the operation you want to perform.

  2. Optionally, create and upload a translation dialect (schema) to validate the output. If you omit the dialect, the translation operation succeeds but the output is unvalidated.

  3. Perform the translation, passing in the data to translate and supplying the translation script to use and, optionally, a dialect.

Example: Translating FxForward instruments from LUSID to a 3rd party and back again

A FxForward in LUSID's data model

The same FxForward in an example data model

{
  "instrumentType": "FxForward",
  "startDate": "2020-01-01T00:00:00.00",
  "maturityDate": "2020-10-01T00:00:00.00",
  "domAmount": 100,
  "domCcy": "GBP",
  "fgnAmount": -120,
  "fgnCcy": "USD",
  "isNdf": true
}
JSON
{
  "instrumentType": "Fx",
  "instrumentDefinition": {
    "fxCode": "GBPUSD",
    "fxType": "FxNonDeliverableForward",
    "fxLegs": [
      {
        "sellCcy": "GBP",
        "sellAmount": 100,
        "buyAmount": 120,
        "buyCcy": "USD",
        "startDate": "2020-01-01T00:00:00+00:00",
        "endDate": "2020-10-01T00:00:00+00:00"
      }
    ]
  }
}
JSON

Uploading a translation script

Every translation script must contain the following code. Note the scriptInterfaceVersion should be set to 1 at the time of writing, though this number may increment as more functionality is introduced:

export function entryPoint()
{
   return {
       scriptInterfaceVersion: 1,
       translate: Translate
   };
}

function Translate(input)
{
   const fromInstr = JSON.parse(input);
   let toInstr;
   <your-translation-code-here>
   return toInstr;
}
JavaScript

The <your-translation-code-here> block must contain all the code necessary to translate key/value pairs in the input data model to key/value pairs in the output data model. 

In our example, since we're translating both to and from LUSID, we'll use a switch statement to differentiate between translation directions. (You could use the same switch statement to distinguish between asset classes in a uni-directional translation script.)

export function entryPoint()
{
   return {
       scriptInterfaceVersion: 1,
       translate: Translate
   };
}

function Translate(input)
{
    const fromInstr = JSON.parse(input);
    let toInstr;
    switch (fromInstr.instrumentType)
    {
        //Translate FxForwards from LUSID to the example data model
        case "FxForward":
            toInstr = TranslateFxForwardLUSIDToExample(fromInstr);
            break;
        //Translate FxForwards from the example data model to LUSID
        case "Fx":
            toInstr = TranslateFxForwardExampleToLUSID(fromInstr);
            break;
        default:
            throw new Error("Instrument type not implemented")
    }
    return toInstr;
}
JavaScript

To translate from LUSID to the example data model, a TranslateFxForwardLUSIDToExample function might look like this:

function TranslateFxForwardLUSIDToExample(lusidFwd)
{
   const contract = {
       instrumentType: "Fx",
       instrumentDefinition: {
           fxCode: `${lusidFwd.domCcy}${lusidFwd.fgnCcy}`,
           fxType: (!lusidFwd.isNdf) ? "FxForward" : "FxNonDeliverableForward",
           fxLegs: [
               {
                   sellCcy: lusidFwd.domCcy,
                   sellAmount: Math.abs(lusidFwd.domAmount),
                   buyAmount: Math.abs(lusidFwd.fgnAmount),
                   buyCcy: lusidFwd.fgnCcy,
                   startDate: lusidFwd.startDate,
                   endDate: lusidFwd.maturityDate
               }
           ]
       }
   };
   return JSON.stringify(contract, null, 4);
}
JavaScript

To translate from the example data model to LUSID, a TranslateFxForwardExampleToLUSID function might look like this:

function TranslateFxForwardExampleToLUSID(exampleFwd)
{
   const contract = {
       instrumentType: "FxForward",
       startDate: exampleFwd.instrumentDefinition.fxLegs[0].startDate,
       maturityDate: exampleFwd.instrumentDefinition.fxLegs[0].endDate,
       domAmount: Math.abs(exampleFwd.instrumentDefinition.fxLegs[0].sellAmount),
       domCcy: exampleFwd.instrumentDefinition.fxLegs[0].sellCcy,
       fgnAmount: -Math.abs(exampleFwd.instrumentDefinition.fxLegs[0].buyAmount),
       fgnCcy: exampleFwd.instrumentDefinition.fxLegs[0].buyCcy,
       isNdf: (exampleFwd.instrumentDefinition.fxType == "FxNonDeliverableForward") ?  true: false
   };
   return JSON.stringify(contract, null, 4);
}
JavaScript

To load a translation script into LUSID we call the UpsertTranslationScript API, specifying in the body of the request:

  • An id that uniquely identifies the script in LUSID, consisting of a scope, code and semantic version.

  • A body that comprises the script as a string. Note double quote characters must be escaped using \.

For example, to upload our bi-directional translation script for FxForwards:

curl -X POST https://<your-domain>.lusid.com/api/api/scriptedtranslation/scripts'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '
{
  "id": {
    "scope": "LUSID",
    "code": "ExampleDataModel",
    "version": "0.0.1"
  },
  "body": "
export function entryPoint()
{
   return {
       scriptInterfaceVersion: 1,
       translate: Translate
   };
}

function Translate(input)
{
    const fromInstr = JSON.parse(input);
    let toInstr;
    switch (fromInstr.instrumentType)
    {
        //Translate FxForwards from LUSID to example data model
        case \"FxForward\":
            toInstr = TranslateFxForwardLUSIDToExample(fromInstr);
            break;
        //Translate FxForwards from example data model to LUSID
        case \"Fx\":
            toInstr = TranslateFxForwardExampleToLUSID(fromInstr);
            break;
        default:
            throw new Error(\"Instrument type not implemented\")
    }
    return toInstr;
}

function TranslateFxForwardLUSIDToExample(lusidFwd)
{
   const contract = {
       instrumentType: \"Fx\",
       instrumentDefinition: {
           fxCode: `${lusidFwd.domCcy}${lusidFwd.fgnCcy}`,
           fxType: (!lusidFwd.isNdf) ? \"FxForward\" : \"FxNonDeliverableForward\",
           fxLegs: [
               {
                   sellCcy: lusidFwd.domCcy,
                   sellAmount: Math.abs(lusidFwd.domAmount),
                   buyAmount: Math.abs(lusidFwd.fgnAmount),
                   buyCcy: lusidFwd.fgnCcy,
                   startDate: lusidFwd.startDate,
                   endDate: lusidFwd.maturityDate
               }
           ]
       }
   };
   return JSON.stringify(contract, null, 4);
}

function TranslateFxForwardExampleToLUSID(exampleFwd)
{
   const contract = {
       instrumentType: \"FxForward\",
       startDate: exampleFwd.instrumentDefinition.fxLegs[0].startDate,
       maturityDate: exampleFwd.instrumentDefinition.fxLegs[0].endDate,
       domAmount: Math.abs(exampleFwd.instrumentDefinition.fxLegs[0].sellAmount),
       domCcy: exampleFwd.instrumentDefinition.fxLegs[0].sellCcy,
       fgnAmount: -Math.abs(exampleFwd.instrumentDefinition.fxLegs[0].buyAmount),
       fgnCcy: exampleFwd.instrumentDefinition.fxLegs[0].buyCcy,
       isNdf: (exampleFwd.instrumentDefinition.fxType == \"FxNonDeliverableForward\") ?  true: false
   };
   return JSON.stringify(contract, null, 4);
}
"
}'
JavaScript

Performing a translation

To translate data item(s) we call the TranslateEntities API, specifying in the body of the request:

  • A scriptId that identifies the translation script to use.

  • An entityPayloads object containing one or more data items to translate. Each must be keyed by an ephemeral ID to track errors in the response. Double quote characters must be escaped using \.

For example, to translate our example FxForward from LUSID to the example data model, we supply the instrument in LUSID's data model as a string:

curl -X POST https://<your-domain>.lusid.com/api/api/scriptedtranslation/translateentities'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "scriptId": {
    "scope": "LUSID",
    "code": "ExampleDataModel",
    "version": "0.0.1"
  },
  "entityPayloads": {
    "myFxFwd-0": {
      "entity": "{\"startDate\":\"2020-01-01T00:00:00.0000000+00:00\",\"domAmount\":100,\"domCcy\":\"GBP\",\"fgnAmount\":-110,\"fgnCcy\":\"USD\",\"instrumentType\":\"FxForward\",\"maturityDate\":\"2020-10-01T00:00:00.0000000+00:00\",\"isNdf\":true}"
    }
  }
}'
JSON

If the translation is successful, the values object in the response contains the FxForward serialised to the example data model (unsuccessful translations are reported in the failed object): 

{
  "values": {
    "myFxFwd-0": {
      "entity": "{\n    \"instrumentType\": \"Fx\",\n    \"instrumentDefinition\": {\n        \"fxCode\": \"GBPUSD\",\n        \"fxType\": \"FxNonDeliverableForward\",\n        \"fxLegs\": [\n            {\n                \"sellCcy\": \"GBP\",\n                \"sellAmount\": 100,\n                \"buyAmount\": 120,\n                \"buyCcy\": \"USD\",\n                \"startDate\": \"2020-01-01T00:00:00.0000000+00:00\",\n                \"endDate\": \"2020-10-01T00:00:00.0000000+00:00\"\n            }\n        ]\n    }\n}",
      "properties": {}
    }
  },
  "failed": {},}
JSON

To translate from the example data model back to LUSID, we supply the FxForward in the example data model as a string:

curl -X POST https://<your-domain>.lusid.com/api/api/scriptedtranslation/translateentities'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{  
  "scriptId": {
    "scope": "LUSID",
    "code": "ExampleDataModel",
    "version": "0.0.1"
  },
  "entityPayloads": {
    "myFxFwd-0": {
      "entity": "{\"instrumentType\":\"Fx\",\"instrumentDefinition\":{\"fxCode\":\"GBPUSD\",\"fxType\":\"FxNonDeliverableForward\",\"fxLegs\":[{\"sellCcy\":\"GBP\",\"sellAmount\":100,\"buyAmount\":120,\"buyCcy\":\"USD\",\"startDate\":\"2020-01-01T00:00:00.0000000+00:00\",\"endDate\":\"2020-10-01T00:00:00.0000000+00:00\"}]}}"
    }
  }
}'
JSON

The values object should now contain the FxForward serialised to the LUSID data model again:

{
  "values": {
    "myFxFwd-0": {
      "entity": "{\n    \"instrumentType\": \"FxForward\",\n    \"startDate\": \"2020-01-01T00:00:00.0000000+00:00\",\n    \"maturityDate\": \"2020-10-01T00:00:00.0000000+00:00\",\n    \"domAmount\": 100,\n    \"domCcy\": \"GBP\",\n    \"fgnAmount\": -120,\n    \"fgnCcy\": \"USD\",\n    \"isNdf\": true\n}",
      "properties": {}
    }
  },
  "failed": {},
  ...
}
JSON