Positions Upload

How to upload your Adapptr Positions File

Upload Positions in Adapptr

Using the POST /v2/task/positions endpoint

Upload daily positions. This method expects a CSV format (example Adapptr position files). The response includes a taskId and a trackingEndpoint that can then be polled via the GET method to monitor the progress of the task through the Adapptr service.

Request Parameters

The positions parameter must be the file that you need to upload.

The snapshotDate parameter must be included as a parameter in the format yyyy-mm-dd. This is the snapshot date of the positions being uploaded in the csv file.

The dataProvider is an integer parameter that must be included to select a specific data provider. A list of all supported providers can be obtained from the Available Nomenclatures endpoint.

The services [optional] parameter can be included if you need different from the default Shareholding Disclosure service. It expects the Id of a service(s), that could be obtained from the Available Nomenclatures endpoint.

The primaryIdentifier [optional] parameter can be included if you need to specify which identifier from your positions file you wish to use to query against the data provider. For example if a position has both ISIN and SEDOL this parameter indicates which of the two identifiers should be used when requesting data from your market data provider. If not populated, the default is ISIN. The value could be obtained from the Available Nomenclatures endpoint.

The secondaryIdentifier [optional] parameter can be included if you need to specify which identifier to fall back to if your primaryIdentifier is not populated. For example if your primaryIdentifier is set to 1 for ISIN and you have a position which doesn't have an ISIN but has a SEDOL, setting the secondaryIdentifier to 2 (SEDOL) would tell Adapptr to use SEDOL to query data from your market data provider if ISIN is not populated. By default this is empty. The value could be obtained from the Available Nomenclatures endpoint.

The tertiaryIdentifier [optional] parameter can be included if you need to specify which identifier to fall back to if your secondaryIdentifier is not populated. For example if your secondaryIdentifier is set to 1 for ISIN and you have a position which doesn't have an ISIN but has a SEDOL, setting the tertiaryIdentifier to 2 (SEDOL) would tell Adapptr to use SEDOL to query data from your market data provider if ISIN is not populated. By default this is empty. The value could be obtained from the Available Nomenclatures endpoint.

The excludeErroredAssets [optional] is a boolean parameter that can be set if you need to send the positions despite errors due to incomplete data from your market data provider. Default value: false

The copyDownParentInstrumentData [optional] is a boolean parameter that can be set if you find the Bloomberg component data not complete enough. Setting the value to true will copy all data from the parent instrument to the instrument of the component. The resulting component instrument will be Equity. Default value: false

The populateExecutionVenueWithMarket [optional] is a boolean parameter that can be set if you need to use your data provider's Market field to populate ExecutionVenue of your assets. Default value: false

📘

We advise to enable the "ExcludeErroredAssets" functionality to allow errored assets to be dropped and validated assets to continue the flow. Please see below for an example.

Retrieving The Task Status - Checking your file status

GET /v2/task/:taskID/status

GET the task status of any long-running task. This method returns a status of the requested TaskId and if the task has failed, the errors that have contributed to the failure.

This task is important to check whether assets are erroring and are excluded in the Adapptr process.

There are 4 status ids:

idstatusExplanation
1AcceptedJob just received; not processed yet.
2EnrichedData is being requested from data provider, then enriched with the csv file and the xml file generated but not yet sent.
3Transmittedxml file sent to fundapps.
4Enriched With ExclusionsData is being requested from data provider, then enriched with the csv file and the xml file generated but not yet sent, some positions skipped due to incomplete data.
5Transmitted With Exclusionsxml file sent to fundapps, some positions skipped due to incomplete data.
6Waiting ExtractionsA request is sent to the data provider, but there is no response yet.
500FailedJob has failed. Please read errors to identify cause of job failure.

Once transmitted, the request will give the trackingEndpoint URL which can be polled to check the status of the XML positions file upload to FundApps.

Example File

PortfolioId,AssetId,AssetName,ISIN,SEDOL,DataProviderId,ComponentDataProviderId,Quantity,MarketValue,SFTType,CUSIP,Price,InstrumentCurrency,AssetClass,ComponentISIN,IsCashSettled
11,US0378331005,Apple Inc,US0378331005,B0YQ5W0,,,10000,10000000,Normal,037833100,100,USD,,,
11,B10RVH1,Microsoft Inc,,B10RVH1,,,355310000,355310000,Normal,,10,USD,,,
11,DE0005190003,BMW AG,DE0005190003,,,,500,10000,Normal,,14,EUR,,,
11,GB0006043169,Aldi,GB0006043169,,,,425500,42550000,Normal,,10,GBP,,,
11,US49639K1016,Kingsoft Cloud ADR,US49639K1016,,,,3999940,3914832480,Normal,,10.4,USD,,,
11,UnlistedSwap,OTC Swap with exchange traded component,,,,,30000,422409000,Normal,,5,USD,Swap,GB0006043169,FALSE
11,UnlistedSwapVod,OTC Swap with exchange traded component VOD,,,,VOD.L,30000,422409000,Normal,,5,USD,Swap,,FALSE
11,VOD_EQ_XLON,Vodafone,,,VOD.L,,20000,2012400,Normal,,100.62,GBP,,,
11,ALTG_O_XMOD,ALTUS GROUP LIMITED (AIF) OPTIONS,,,AIFL152305000.M,,10000,1000000,Normal,,100,CAD,,,

Example Upload Script

# FundApps Example PowerShell Adapptr Upload Positions
#
# Usage: First run the script, then call the function below. You might need to run the script with . .\upload-positions.ps1 instead of .\upload-positions.ps1
# Import-Positions -User "[USERNAME]" -Password "[PASSWORD]" -File "[PATH_TO_FILE]" -ClientEnvironment "[ALIAS]"
# For how to encrypt passwords on a machine before using as a parameter see here: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/convertto-securestring?view=powershell-7

$isDotSourced = $MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -eq ''

if (-not $isDotSourced)
{
    Write-Host "You need to source this script, please run using '. .\upload-positions.ps1'. The 'dot space' has been omitted."
    Break 1
}

Write-Host "Install functions"

function API-Post {
    Param ($Uri, $User, $Password, $File, $ClientEnvironment)
    $basicAuth = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($($User) + ":" + $($Password)));

    $fileBytes = [System.IO.File]::ReadAllBytes($File);
    $fileEnc = [System.Text.Encoding]::GetEncoding('UTF-8').GetString($fileBytes);
    $boundary = [System.Guid]::NewGuid().ToString();
    $LF = "`r`n";
    $snapshotDate = Get-Date -Format "yyyy-MM-dd";


    # please refer to the documentation for more information on parameters that you can use: https://github.com/fundapps/TechDocs/blob/main/Adapptr/versions/v2.md#upload-positions-post-v2taskpositions

    $dataProvider = "1";

    $bodyLines = (
        "--$boundary",
        "Content-Disposition: form-data; name=`"snapshotDate`"$LF",
        "$snapshotDate$LF",
        "--$boundary",
        "Content-Disposition: form-data; name=`"dataProvider`"$LF",
        "$dataProvider$LF",
        "--$boundary",
        "Content-Disposition: form-data; name=`"positions`"; filename=`"positions.csv`"",
        "Content-Type: application/octet-stream$LF",
        $fileEnc,
        "--$boundary--$LF"
    ) -join $LF

    $params = @{
        Uri = $Uri
        Method = 'Post'
        ContentType = "multipart/form-data; boundary=`"$boundary`""
        Headers = @{ Authorization = $basicAuth }
        Body = $bodyLines
    }

    try {
        Invoke-RestMethod @params
    }
    catch [System.Net.WebException]{
        if($_.Exception.Response){
            $result = $_.Exception.Response.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($result)
            $reader.BaseStream.Position = 0
            $reader.DiscardBufferedData()
            $responseBody = $reader.ReadToEnd();

            Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
            Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
            Write-Host $_.ErrorDetails.Message
            Write-Host $responseBody
        }
        else{
            throw
        }
    }
}
function Get-Content-Type {
    Param (
        [Parameter(Mandatory=$True)]
        [string] $Filename
    )
    $extension = [System.IO.Path]::GetExtension($Filename)
    $contentType = "Unknown"

    switch($extension)
    {
        '.csv' { $contentType = "text/csv" }
    }

    return $contentType
}

function Import-File {
    Param ($Uri, $User, $Password, $File)
    Write-Host "Request started"
    API-Post -Uri $Uri -User $User -Password $Password -File $File
    Write-Host "Done"
}


function Import-Positions {
    Param ($User, $Password, $File, $ClientEnvironment)
    Import-File -User $User -Password $Password -File $File -Uri "https://$ClientEnvironment-svc.fundapps.co/api/adapptr/v2/task/positions"
}

Write-Host "Functions created"
using FundAppsScripts.DTOs;
using RestSharp;
using RestSharp.Authenticators;
using System;
using System.Net;

namespace FundAppsScripts.Scripts
{
    public partial class AdapptrScripts
    {
        public string UploadPositions(string baseUrl, string username, string password, string pathToFile)
        {
            // the snapshot date of your positions in the format yyyy-MM-dd
            var snapshotDate = DateTime.Today.ToString("yyyy-MM-dd");

            // if dataProvider = 1 it means that the dataProvider is Refinitiv
            // if dataProvider = 2 it means that the dataProvider is Bloomberg
            var dataProvider = 1;

            //Example using RestSharp (https://github.com/restsharp/RestSharp)

            //Create a client which will connect to the HTTPS endpoint with the API credentials you have been provided
            var options = new RestClientOptions(baseUrl)
            {
                Authenticator = new HttpBasicAuthenticator(username, password)
            };
            var client = new RestClient(options);

            // make the HTTP POST request
            var request = new RestRequest($"/v2/task/positions", Method.Post);

            // add body params to the request
            request.AddFile("positions", pathToFile, "text/csv");
            request.AddParameter("snapshotDate", snapshotDate);
            request.AddParameter("dataProvider", dataProvider);

            request.AddHeader("Content-Type", "multipart/form-data");

            var response = client.Execute<TaskProfileResponse>(request);

            // if response comes back with a 200 status, then as task for the positions file was created successfully
            if (response.StatusCode != HttpStatusCode.OK)
            {
                throw new Exception($"Failed to send file. Received a HTTP {(int)response.StatusCode} {response.StatusCode} instead of HTTP 200 OK");
            }

            return response.Data.Id;
        }
    }
}

Example Response with ExcludeErroredAsset Functionality

{
    "id": "9b134cda-81db-44c0-9c52-cfeb897e663a",
    "type": {
        "id": 1,
        "name": "Positions"
    },
    "status": {
        "id": 5,
        "name": "Transmitted With Exclusions",
        "description": "Successfully transmitted. Some positions were skipped, most likely due to incomplete data from your market data provider. Please check the warnings field for a list of all skipped positions as well as the cause reason."
    },
    "dateCreated": "2021-07-22T17:09:01.186+03:00",
    "dateUpdated": "2021-07-22T17:13:19.024+03:00",
    "trackingEndpoint": "https://demo-melon-api.fundapps.co/v1/expost/result/38ba5713-c253-42d1-896a-bd6e00ea5ec3",
    "statusReport": {
        "errors": null,
        "warnings": [
            "Identifier: <Identifier> | Component is required for this instrument. Refinitiv returned null or empty value for UnderlyingISIN and UnderlyingRIC. Consider providing ComponentISIN value in the positions file.",
            "Identifier: <Identifier> | Empty result while getting enrichment data for item. Please check if the identifier is valid.",
            "For component | Parent Identifier: <Identifier> | Identifier: <Identifier> | Unsupported AssetClass: <AssetClass>"
        ]
    }
}

Upload positions without enrichment

Using the POST /v2/task/positions/without-enrichment endpoint

This method converts the Consensys or Adapptr csv file format into the FundApps required format for the Position Limits service only. The response includes a taskId and a trackingEndpoint that can then be polled via the GET method to monitor the progress of the task through the Adapptr service.

Request Parameters

The positions parameter must be the file that you need to upload.

The snapshotDate parameter must be included as a parameter in the format yyyy-mm-dd. This is the snapshot date of the positions being uploaded in the CSV file.

The format [optional] [default value = 2] parameter can be included if you prefer using different file data format. A list of all supported file data formats can be obtained from the Available Nomenclatures endpoint.

🚧

This method works only with the FundApps Position Limits service

Example File

PortfolioId,AssetId,DeliveryMonth,Market,CommoditySymbol,AssetName,AssetClass,ULAssetClass,Quantity,CallorPut,Delta,IsCashSettled,ContractSize,OpenInterestSingleMonth,OpenInterestAllMonth
11,CDEC21,2021-12,XCBT,C,CORN FUTURE DEC21,Future,Commodity,-1362,,,TRUE,100,10000,5000000
12,CA02215R1073,2021-12,IFLL,AQB,SSO - ALTUS GROUP LIMITED (AIF) OPTIONS,Option,Equity,1000,Call,0.4,TRUE,,,
Source System,Account,Account Name,Account Type,Trader,Contract Month,Exchange Code,Product Code,Product Type,Option Type,Exchange Clearing ID,Product Description,Currency,Strike,Strike Type,Expiry Date,Last Trade Date,Option Delta,Buy/Sell,Quantity,Initial Margin,As of Date,Is Cash Settled,Contract Size,FIA Tech Code
Bloomberg,1,1,,,202109,CBT,TYU1 Comdty,F,,TYU1 Comdty,US 10YR NOTE (CBT)Sep21,USD,,,,20210921,,,100,,20210701,TRUE,100,21

Example Upload Script

#
# Usage: First run the script, then call the function below. You might need to run the script with . .\upload-positions-without-enrichment.ps1 instead of .\upload-positions-without-enrichment.ps1
# Import-Positions -User "[USERNAME]" -Password "[PASSWORD]" -File "[PATH_TO_FILE]" -ClientEnvironment "[ALIAS]"
# For how to encrypt passwords on a machine before using as a parameter see here: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/convertto-securestring?view=powershell-7

$isDotSourced = $MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -eq ''

if (-not $isDotSourced)
{
    Write-Host "You need to source this script, please run using '. .\upload-positions-without-enrichment.ps1'. The 'dot space' has been omitted."
    Break 1
}

Write-Host "Install functions"

function API-Post {
    Param ($Uri, $User, $Password, $File)
    $basicAuth = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($($User) + ":" + $($Password)));

    $fileBytes = [System.IO.File]::ReadAllBytes($File);
    $fileEnc = [System.Text.Encoding]::GetEncoding('UTF-8').GetString($fileBytes);
    $boundary = [System.Guid]::NewGuid().ToString();
    $LF = "`r`n";
    $snapshotDate = Get-Date -Format "yyyy-MM-dd";

    # please refer to the documentation for more info on parameters:

    $format = "2";

    $bodyLines = (
        "--$boundary",
        "Content-Disposition: form-data; name=`"snapshotDate`"$LF",
        "$snapshotDate$LF",
        "--$boundary",
        "Content-Disposition: form-data; name=`"format`"$LF",
        "$format$LF",
        "--$boundary",
        "Content-Disposition: form-data; name=`"positions`"; filename=`"positions.csv`"",
        "Content-Type: application/octet-stream$LF",
        $fileEnc,
        "--$boundary--$LF"
    ) -join $LF

    $params = @{
        Uri = $Uri
        Method = 'Post'
        ContentType = "multipart/form-data; boundary=`"$boundary`""
        Headers = @{ Authorization = $basicAuth }
        Body = $bodyLines
    }

    try {
        Invoke-RestMethod @params
    }
    catch [System.Net.WebException]{
        if($_.Exception.Response){
            $result = $_.Exception.Response.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($result)
            $reader.BaseStream.Position = 0
            $reader.DiscardBufferedData()
            $responseBody = $reader.ReadToEnd();

            Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
            Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
            Write-Host $_.ErrorDetails.Message
            Write-Host $responseBody
        }
        else{
            throw
        }

    }
}
function Get-Content-Type {
    Param (
        [Parameter(Mandatory=$True)]
        [string] $Filename
    )
    $extension = [System.IO.Path]::GetExtension($Filename)
    $contentType = "Unknown"

    switch($extension)
    {
        '.csv' { $contentType = "text/csv" }
    }

    return $contentType
}

function Import-File {
  Param ($Uri, $User, $Password, $File)
    Write-Host "Request started"
    API-Post -Uri $Uri -User $User -Password $Password -File $File
    Write-Host "Done"
}


function Import-Positions {
    Param ($User, $Password, $File, $ClientEnvironment)
    Import-File -User $User -Password $Password -File $File -Uri "https://$ClientEnvironment-svc.fundapps.co/api/adapptr/v2/task/positions/without-enrichment"
}

Write-Host "Functions created"

Example Response

{
    "id": "d1188c41-9cd4-467a-9c80-d3d3cba88b1c",
    "type": {
        "id": 1,
        "name": "Positions"
    },
    "status": {
        "id": 1,
        "name": "Accepted",
        "description": "Position file accepted. Please check the tracking endpoint to check for any errors in the file upload and to track the progress of the file enrichment and transmission."
    },
    "dateCreated": "2022-06-24T11:32:22.7427025Z",
    "trackingEndpoint": "/api/adapptr/v2/task/d1188c41-9cd4-467a-9c80-d3d3cba88b1c/status"
}

Upload positions to Adapptr (XML format)

Using the POST /v2/task/positions/expost endpoint

This method uploads the positions and enriches using the latest uploaded composites file from the v2/composites upload.

The positions parameter must be the file that you need to upload. It is required to be a .xml file or a .zip of a .xml file.

The response includes a taskId and a trackingEndpoint that can then be polled via the GET method to monitor the progress of the task through the Adapptr service.

Request Parameters

The positions parameter must be the file that you need to upload.

Example File

<?xml version="1.0" encoding="UTF-8"?>
<Snapshot Date="2022-07-21">
    <Instruments>
        <Equity InstrumentId="FI0009000681" InstrumentName="Nokia Equity" IssuerId="1" IssuerName="Nokia" ISIN="FI0009000681" CountryOfIssue="GB" CountryOfIncorporation="GB" TotalVotingRights="1000" ClassSharesOutstanding="0" TotalSharesOutstanding="1000" TotalSharesInTreasury="0" InstrumentCurrency="GBP" VotesPerShare="1" Market="XLON" Price="10" MarketsListedIn="XLON" LEI = "549300A0JPRWG1KI7U06" />
        <Equity InstrumentId="GB00BH4HKS39" InstrumentName="Vodafone Group PLC" IssuerId="2" IssuerName="Vodafone Group PLC" ISIN="GB00BH4HKS39" CountryOfIssue="GB" CountryOfIncorporation="GB" TotalVotingRights="1000" ClassSharesOutstanding="0" TotalSharesOutstanding="1000" TotalSharesInTreasury="0" InstrumentCurrency="GBP" VotesPerShare="1" Market="XLON" Price="10" MarketsListedIn="XLON" LEI = "213800TB53ELEUKM7Q61" />
        <Unit InstrumentId="MSCI" InstrumentName="MSCI Index" Price="100" InstrumentCurrency="GBP" ISIN="GB0004026257" />
    </Instruments>
    <Portfolios>
        <Portfolio PortfolioId="11">
            <Asset AssetId="MSCIIdx" AssetName="MSCI Index" InstrumentId="MSCI" Quantity="20" SFTType="normal"/>
            <Asset AssetId="Vodafone EQ" AssetName="Vodafone EQ" InstrumentId="GB00BH4HKS39" Quantity="10" SFTType="normal"/>
        </Portfolio>
    </Portfolios>
</Snapshot>

Example Upload Script

// Program.cs
namespace Adapptr.Example;

var uploader = new UploadPositions();
var username = Environment.GetEnvironmentVariable("ADAPPTR_USERNAME");
var password = Environment.GetEnvironmentVariable("ADAPPTR_PASSWORD");
var tenantName = Environment.GetEnvironmentVariable("TENANT_NAME");
var pathToFile = "./positions.xml";

var taskId = await uploader.UploadPositionsAsync($"https://{tenantName}-svc.fundapps.co/api/adapptr", username, password, pathToFile);
Console.WriteLine($"Uploaded file: {taskId}");

// UploadPositions.cs
using System.Net;
using RestSharp;
using RestSharp.Authenticators;

namespace Adapptr.Example;

public record TaskResponse(string Id);

public class UploadPositions
{
    public async Task<string> UploadPositionsAsync(string baseUrl, string username, string password, string pathToFile)
    {
        //Example using RestSharp (https://github.com/restsharp/RestSharp)
        //Create a client which will connect to the HTTPS endpoint with the API credentials you have been provided
        var options = new RestClientOptions(baseUrl)
        {
            Authenticator = new HttpBasicAuthenticator(username, password)
        };
        var client = new RestClient(options);

        // make the HTTP POST request
        var request = new RestRequest("/v2/task/positions/expost", Method.Post);

        // add body params to the request
        request.AddFile("positions", pathToFile, "application/xml");
        // Use the following line if using a zipped file
        //request.AddFile("positions", pathToFile, "application/zip");

        request.AddHeader("Content-Type", "multipart/form-data");

        var response = await client.ExecuteAsync<TaskResponse>(request);

        // if response comes back with a 202 status, then as task for the positions file was accepted successfully
        if (response.StatusCode != HttpStatusCode.Accepted)
        {
            throw new Exception($"Failed to send file. Received a HTTP {(int)response.StatusCode} {response.StatusCode} instead of HTTP 200 OK");
        }

        return response.Data.Id;
    }
}

Example Response

{
    "id": "3b5a2647-b8e5-4cfc-a452-4f877172ac97",
    "type": {
        "id": 3,
        "name": "Expost Positions"
    },
    "status": {
        "id": 1,
        "name": "Accepted",
        "description": "Position file accepted. Please check the tracking endpoint to check for any errors in the file upload and to track the progress of the file enrichment and transmission."
    },
    "dateCreated": "2022-08-31T13:08:08.5288727Z",
    "trackingEndpoint": "/api/adapptr/v2/task/3b5a2647-b8e5-4cfc-a452-4f877172ac97/status"
}