Real Time Execution
Real time execution provides access to live rate quotes and an option to book an exchange with a fixed conversion rate (as opposed to the standard exchanges where the exact rate isn't known until the exchange is completed).
SignalR
Most of the RTE functionality is exposed through a SignalR hub /v2/RealTimeFXHub on the V2 API host.
SignalR is a Microsoft technology that allows asynchronous bi-directional server-client communication. SignalR supports multiple transport protocols, but by default it uses WebSockets if available.
Note
All the SignalR code examples are in pseudo JavaScript / JSON format.
Connecting to the hub
To connect to the SignalR hub, you need to create a hub connection:
var connection = new signalR.HubConnectionBuilder()
.withUrl(apiV2BaseUrl + "/v2/RealTimeFXHub", {
accessTokenFactory: () => {
// the same Bearer token that is used for the other API endpoints
return accessToken;
}
})
.build();
Examples for other languages are available at:
- https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client
- https://docs.microsoft.com/en-us/aspnet/core/signalr/java-client
- https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client
- https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration
Freemarket extensions
The current version of the SignalR protocol and the available libraries doesn't support sending of metadata (headers) when the client is invoking server methods or when the server is sending client notifications. Therefore we extended the plain SignalR protocol with our own HTTP like layer that adds metadata support.
In order to call a server method, instead of a plain SignalR invoke:
connection.invoke("Method", "Value1", "Value2");
you need to do:
connection.invoke("Method", {
headers: {"Header": "Value"}, // optional headers
body: {arg1: "Value1", arg2: "Value2"} // the actual invocation request
});
The response will also differ as well, instead of receiving a single return value:
var value = await connection.invoke(...);
You will get a response envelope containing:
var response = await connection.invoke(...);
var status = response.status; // HTTP like status code: 200 - OK, 400 - BadRequest, etc.
var headers = response.headers; // optional headers
var value = response.body; // response payload
All the notifications are wrapped in a envelope as well:
connection.on("Notification", notification => {
var headers = response.headers; // optional headers
var payload = response.body; // notification payload
})
Validation
Validation errors are reported in a standard RFC 7807 problem details format containing an additional errors property describing the validation errors.
Validation errors example:
{
"status": 400,
"body": {
"errors": {
"accountId": [
"The AccountId field is required."
]
},
"type": null,
"title": "One or more validation errors occurred.",
"status": 400,
"detail": null,
"instance": null
}
}
Process
Real Time Execution consist of 2 parts:
- Market data - Provides non tradeable informative market rate quotes.
- Exchange fulfillment - Provides the functionality to request and accept tradeable quotes for customer exchanges.
Market data
Creating a subscription
In order to request market data you need to create a subscription:
connection.invoke("CreateMarketDataQuoteSubscription", {
body: {
accountId: 12345, // the account the subscription will be created for
sourceCurrencyCode: "EUR", // subscription source currency
targetCurrencyCode: "GBP", // subscription target currency
}
})
Response:
{
"status": 200, // OK
"body": {
"id": 123, // id of the new subscription (will be included in the related notifications)
"expireDate": "2020-10-20T07:01:13.4956893Z" // date and time when the subscription will expire
}
}
After the subscription is created you will start receiving MarketDataQuoteReceived notifications:
{
"body": {
"marketDataQuoteSubscriptionId": 123, // the related subscription id
"sourceCurrencyCode": "EUR", // subscription source currency
"targetCurrencyCode": "GBP", // subscription target currency
"forwardRate": 0.8319547, // rate when selling the source currency and buying the target currency
"reverseRate": 0.8334308, // rate when selling the target currency and buying the source currency
"timeStamp": "2020-10-20T06:56:17.4107624Z" // timestamp of the quote
}
}
After the subscription expires you will get a MarketDataQuoteSubscriptionExpired:
{
"body": {
"marketDataQuoteSubscriptionId": 123 // the related subscription id
}
}
If the subscription fails to initialize you will get a MarketDataQuoteSubscriptionFailed:
{
"body": {
"marketDataQuoteSubscriptionId": 123 // the related subscription id
"narrative": "Market data quote request was rejected" // description of the failure
}
}
Cancelling the subscription
connection.invoke("CancelMarketDataQuoteSubscription", {
body: 123 // the subscription id to cancel
})
Response:
{
"status": 200 // OK
}
Exchange fulfillment
Creating a real time exchange
In order to execute a real time exchange you first need to create the exchange (using the V1 API host):
POST /api/v1/Exchanges/CreateRealTime HTTP/1.1
Host: portal.wearefreemarket.com
Authorization: Bearer {authToken}
Content-Type: application/json
{
"AccountId": 12345, // the account that the exchange will be created for
"Goal": 10000, // the amount to exchange (buy or sell)
"GoalType": "Buy", // the type of exchange goal, either "Buy" or "Sell"
"SourceCurrencyCode": "GBP", // the currency to be sold
"TargetCurrencyCode": "EUR" // the currency to be bought
}
Response:
{
"Id": 123,
"AccountId": 12345,
"SourceCurrencyCode": "GBP",
"TargetCurrencyCode": "EUR",
"InstructionTypeId": "RealTime",
"Goal": 10000.0,
"GoalTypeCode": "B",
"GoalType": "Buy",
"Sold": 0.0,
"Bought": 0.0,
"StatusCode": "E",
"Status": "Enabled",
"CreatedOn": "2020-10-20T08:44:59.66",
"UpdatedDate": "2020-10-20T08:44:59.66",
"DepositReference": null
}
Creating a tradeable quote request
After the exchange has been created you can ask for tradeable quotes for that exchange:
connection.invoke("CreateTradeableQuoteRequestForExchange", {
body: {
exchangeId: {exchangeId} // the exchange for which the request is being made
}
})
Response:
{
"status": 200, // OK
"body": {
"id": 123, // id of the quote request (will be included in the related notifications)
"expireDate": "2020-10-20T08:46:05.4532157Z" // date and time when the quote request will expire
}
}
Notifications
After the quote request is created you will start receiving TradeableQuoteReceived notifications:
{
"body": {
"tradeableQuoteId": 1234, // the quote id (used when accepting the quote)
"tradeableQuoteRequestId": 123, // the related quote request id
"sourceCurrencyCode": "GBP", // quote source currency
"targetCurrencyCode": "EUR", // quote source currency
"rate": 1.1996263, // quote rate
"send": 8345.93, // amount of source currency you are selling
"receive": 10000.0, // amount of target currency you are buying
"sourceFees": 10.0, // amount of fees taken in the source currency
"targetFees": 0.0, // amount of fees taken in the target currency
"validUntil": "2020-10-20T08:53:43.3824897", // quote validity
"createdDate": "2020-10-20T08:52:46.9443066Z" // quote created date and time
}
}
After the request expires you will get a TradeableQuoteRequestExpired:
{
"body": {
"tradeableQuoteRequestId": 123 // the related quote request id
}
}
If the request fails to initialize you will get a TradeableQuoteRequestFailed:
{
"body": {
"tradeableQuoteRequestId": 123 // the related quote request id
"narrative": "Quote request was rejected" // description of the failure
}
}
Creating a purchase order
In order to complete the process you need to accept a quote by creating a puchase order from it.
Note
You should use the latest possible quote for this, using a stale quote will lead to the order being rejected even if the quote is still valid according to it's validUntil date.
Call:
connection.invoke("CreatePurchaseOrderFromTradeableQuote", {
body: {
tradeableQuoteId: {tradeableQuoteId} // the quote to be accepted
}
})
Response:
{
"status": 200, // OK
"body": {
"id": 123, // id of the order (will be included in the related notifications)
"status": "Processing", // current status of the order
"statusNarrative": null // optional status description
}
}
Notifications
After the purchase order is created you will receive either a PurchaseOrderCompleted notification:
{
"body": {
"purchaseOrderId": 123 // the related order id
}
}
or a PurchaseOrderFailed notification:
{
"body": {
"purchaseOrderId": 123 // the related order id
"narrative": "Purchase order was rejected" // description of the failure
}
}
You should receive one of these notifications within 20 seconds.
Checking the order status
If the SignalR connection drops while waiting for the confirmation or you don't receive any notifications, you can check the order status using:
GET /v2/PurchaseOrders/{purchaseOrderId} HTTP/1.1
Host: api.wearefreemarket.com
Authorization: Bearer {authToken}
Response:
{
"id": 123, // order id
"status": "Completed", // current status of the order
"statusNarrative": null // optional status description
}
Possible order statuses are:
| Status | Description |
|---|---|
| Processing | Order is being processed |
| Completed | Order was completed successfully |
| Failed | Order failed |
Note
If the order is not completed or failed after 20 seconds you must contact our support to clarify the order status.
After the order is completed, the execution process is done, but the exchange itself is not completed yet. The exchange will be completed asynchronously after the target funds have been credited to the account.
Business hours
The RTE API is available 24 hours a day, 5 days a week, from 00:01 Monday to 23:59 Friday UK time.
You can check the business hours using:
GET /v2/BusinessHours/RTE HTTP/1.1
Host: api.wearefreemarket.com
Authorization: Bearer {authToken}
Response:
{
"isBusinessHour": true,
"closingIn": "03:54:54.4305602",
"closingAt": "2020-10-20T16:00:00Z",
"openingIn": null,
"openingAt": null
}
Example implementations
Example client implementations are available at: