OMS streaming
Subscribe to the current trading day's order, trade, portfolio position and cash detail updates over SignalR. You may use any supported SignalR client to stream data.
At a glance
| Scope | oms-streams-api |
| Test | https://test-oms-streams-api.openmarkets.com.au/ |
| Production | https://oms-streams-api.openmarkets.com.au/ |
| Hub path | /streams |
The streaming hub is a separate service from the Order Management REST API. It has its own base URL and its own scope, so an oms-api token will not authenticate against it.
How it works
- Build a hub connection against
/streamson the base URL above, supplying an access token provider. - Register handlers for the events you care about, before starting the connection.
- Start the connection.
- Invoke the subscribe method for each channel you want.
Handlers must be registered before the connection starts, otherwise updates that arrive during startup are dropped.
Establishing a connection
HubConnection connection = new HubConnectionBuilder()
.WithUrl("https://test-oms-streams-api.openmarkets.com.au/streams", options =>
{
options.AccessTokenProvider = GetBearerToken;
})
.AddMessagePackProtocol()
.Build();
await connection.StartAsync();
import * as signalR from '@microsoft/signalr'
import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack'
const connection = new signalR.HubConnectionBuilder()
.withUrl('https://test-oms-streams-api.openmarkets.com.au/streams', {
accessTokenFactory: () => getBearerToken(),
})
.withHubProtocol(new MessagePackHubProtocol())
.build()
await connection.start()
MessagePack is required. A client that negotiates the default JSON protocol will connect but will not be able to deserialize data coming from the hub.
Providing an access token
The hub authenticates with the same kind of bearer token as the REST APIs, so the token provider is just a function that returns a current token carrying the oms-streams-api scope claim. See Authentication.
private async Task<string> GetBearerToken()
{
// Return a cached token, refreshing it when it is close to expiry.
}
async function getBearerToken() {
// Return a cached token, refreshing it when it is close to expiry.
}
The provider is called on connect and on every reconnect, so returning a cached token that you refresh near expiry keeps long-lived connections authenticated without extra token requests.
Channels
None of the OMS subscribe methods take parameters. Each subscribes you to everything associated with your business ID.
| Event | Subscribe method | Payload |
|---|---|---|
OrdersUpdated | SubscribeToOrderUpdates() | Order[] |
TradesUpdated | SubscribeToTradeUpdates() | Trade[] |
PortfolioPositionsUpdated | SubscribeToPortfolioPositionUpdates() | PortfolioPosition[] |
PortfolioCashDetailsUpdated | SubscribeToPortfolioCashDetailUpdates() | PortfolioCashDetail[] |
You receive orders created during the current trading day and associated with your business ID. Use the REST API for anything historical.
Subscribing
Register handlers before starting the connection, then subscribe once it is running.
private async Task HandleOrdersUpdated()
{
this.connection.On<Order[]>("OrdersUpdated", orders =>
{
// Do something with the data
});
}
private async Task SubscribeToOrderUpdates()
{
await this.connection.InvokeAsync("SubscribeToOrderUpdates");
}
// ...
this.HandleOrdersUpdated();
await connection.StartAsync();
this.SubscribeToOrderUpdates();
connection.on('OrdersUpdated', (orders) => {
// Do something with the data
})
await connection.start()
await connection.invoke('SubscribeToOrderUpdates')
The other three channels follow the same shape: register the handler, then invoke the matching subscribe method.
Automatic reconnect
Connections dropped by network issues can be re-established automatically.
HubConnection connection = new HubConnectionBuilder()
.WithUrl("https://test-oms-streams-api.openmarkets.com.au/streams", options =>
{
options.AccessTokenProvider = GetBearerToken;
})
.WithAutomaticReconnect() // this will enable automatic reconnect
.AddMessagePackProtocol()
.Build();
const connection = new signalR.HubConnectionBuilder()
.withUrl('https://test-oms-streams-api.openmarkets.com.au/streams', {
accessTokenFactory: () => getBearerToken(),
})
.withAutomaticReconnect() // this will enable automatic reconnect
.withHubProtocol(new MessagePackHubProtocol())
.build()
The default retry policy makes 4 attempts, waiting 0, 2, 10 and 30 seconds respectively. You can supply your own policy. See Microsoft's client features documentation.
Handling reconnects
A reconnected client does not retain its subscriptions. Resubscribe in a Reconnected handler, or the connection will appear healthy while delivering nothing.
connection.Reconnected += async (connectionId) =>
{
Console.WriteLine("Reconnected.");
// Resubscribe to the channels
await SubscribeToOrderUpdates();
await SubscribeToTradeUpdates();
};
connection.onreconnected(async (connectionId) => {
console.log('Reconnected.')
// Resubscribe to the channels
await subscribeToOrderUpdates()
await subscribeToTradeUpdates()
})
A Reconnecting handler is optional, and is useful if you want to surface connection state in your own application while the client retries.
Order schema
{
"rootParentOrderNumber": { "type": "number" },
"orderNumber": { "type": "number" },
"parentOrderNumber": { "type": "number" },
"accountCode": { "type": "string" },
"securityCode": { "type": "string" },
"exchange": { "type": "string" },
"destination": { "type": "string" },
"subDestination": { "type": "string" },
"pricingInstructions": { "type": "string" },
"orderState": { "type": "string" },
"lastAction": { "type": "string" },
"actionStatus": { "type": "string" },
"orderVolume": { "type": "string" },
"orderPrice": { "type": "string" },
"remainingVolume": { "type": "string" },
"doneVolumeTotal": { "type": "string" },
"doneValueTotal": { "type": "string" },
"uncommittedVolume": { "type": "string" },
"averagePrice": { "type": "string" },
"lifetime": { "type": "string" },
"currency": { "type": "string" },
"expiryDateTime": { "type": "datetime" },
"doneVolumeToday": { "type": "number" },
"doneValueToday": { "type": "number" },
"stateDescription": { "type": "string" },
"createDateTime": { "type": "datetime" },
"updateDateTime": { "type": "datetime" },
"destinationVolume": { "type": "number" },
"destinationPrice": { "type": "number" },
"destinationStatus": { "type": "string" },
"destinationOrderNumber": { "type": "string" },
"principal": { "type": "bool" },
"orderMatchId": { "type": "string" },
"marketDataOrderNumber": { "type": "string" },
"side": { "type": "string" },
"effectiveDoneVolume": { "type": "number" },
"priceMultiplier": { "type": "number" },
"settlementDoneValueTotal": { "type": "number" },
"settlementDoneValueToday": { "type": "number" },
"settlementAveragePrice": { "type": "number" },
"clientSequenceNumber": { "type": "number" },
"marketDetail": { "type": "string" },
"postTradeStatusNumber": { "type": "number" },
"postTradeStatus": { "type": "string" },
"estimatedPrice": { "type": "number" },
"orderValue": { "type": "number" },
"advisorCode": { "type": "string" },
"estimatedVolume": { "type": "number" },
"estimatedValue": { "type": "number" },
"notes": { "type": "string" },
"fixedContingentOrder": {
"triggerSecurity": { "type": "string" },
"triggerPriceType": { "type": "string" },
"triggerPrice": { "type": "number" },
"triggerCondition": { "type": "string" },
"contingentOrderStatus": { "type": "string" }
}
}
Trade schema
{
"advisorCode": { "type": "string" },
"tradeNumber": { "type": "number" },
"orderNumber": { "type": "number" },
"accountCode": { "type": "string" },
"securityCode": { "type": "string" },
"exchange": { "type": "string" },
"destination": { "type": "string" },
"subDestination": { "type": "string" },
"buyOrSell": { "type": "string" },
"tradeVolume": { "type": "number" },
"tradePrice": { "type": "number" },
"tradeValue": { "type": "number" },
"tradeFxRate": { "type": "number" },
"tradeDateTime": { "type": "datetime" },
"principal": { "type": "boolean" },
"opposingBrokerNumber": { "type": "number" },
"primaryClientOrderId": { "type": "string" },
"secondaryClientOrderId": { "type": "string" },
"tradeMarkers": { "type": "string" },
"destinationUserId": { "type": "string" },
"destinationOrderNumber": { "type": "string" },
"destinationTradeNumber": { "type": "string" },
"cancelledByTradeNumber": { "type": "number" },
"marketDataOrderNumber": { "type": "number" },
"marketDataTradeNumber": { "type": "number" },
"fxRateBidPriceOnOrder": { "type": "number" },
"fxRateAskPriceOnOrder": { "type": "number" },
"fxRateBidPriceOnTrade": { "type": "number" },
"fxRateAskPriceOnTrade": { "type": "number" },
"sourcePrice": { "type": "number" },
"sourceCurrency": { "type": "string" },
"sideCode": { "type": "string" },
"orderDetails": { "type": "string" },
"settlementValue": { "type": "number" },
"settlementFxRate": { "type": "number" },
"settlemePrice": { "type": "number" },
"organization": { "type": "string" },
"bookingDestination": { "type": "string" },
"tradeMarketDetail": { "type": "string" },
"postTradeStatusNumber": { "type": "number" },
"tradeSequenceNumber": { "type": "number" },
"tradeDateTimeGmt": { "type": "datetime" },
"exchangeTradeDateTime": { "type": "datetime" },
"localMarketTradeDate": { "type": "datetime" }
}
Portfolio position schema
{
"advisorCode": { "type": "string" },
"accountCode": { "type": "string" },
"sharingCashAccount": { "type": "boolean" },
"portfolioCode": { "type": "string" },
"securityCode": { "type": "string" },
"exchange": { "type": "string" },
"underlyingSecurityCode": { "type": "string" },
"underlyingExchange": { "type": "string" },
"versionStamp": { "type": "number" },
"updateReasonMask": { "type": "number" },
"createDateTime": { "type": "datetime" },
"updateDateTime": { "type": "datetime" },
"volumeStartOfDay": { "type": "number" },
"averagePriceStartOfDay": { "type": "number" },
"averagePrice": { "type": "number" },
"buyVolume": { "type": "number" },
"buyValue": { "type": "number" },
"buyCharges": { "type": "number" },
"sellVolume": { "type": "number" },
"sellValue": { "type": "number" },
"sellCharges": { "type": "number" },
"cfdLodgedVolume": { "type": "number" },
"optionLodgedVolume": { "type": "number" },
"systemLockedVolume": { "type": "number" },
"userLockedVolume": { "type": "number" },
"pledgedTakeOverVolume": { "type": "number" },
"inMarketBuyVolume": { "type": "number" },
"inMarketBuyValue": { "type": "number" },
"inMarketSellVolume": { "type": "number" },
"inMarketSellValue": { "type": "number" },
"availableVolume": { "type": "number" },
"costValue": { "type": "number" },
"marketValue": { "type": "number" },
"totalProfit": { "type": "number" },
"todayProfit": { "type": "number" },
"closedProfit": { "type": "number" },
"exposure": { "type": "number" },
"sfcVariationMargin": { "type": "number" },
"averageBuyPrice": { "type": "number" },
"averageSellPrice": { "type": "number" },
"actualVolume": { "type": "number" },
"shortSellVolume": { "type": "number" },
"historicalPrice": { "type": "number" },
"historicalProfit": { "type": "number" },
"hedgeExp": { "type": "number" },
"actualValue": { "type": "number" },
"unsponsoredBuyVolume": { "type": "number" },
"unsponsoredBuyValue": { "type": "number" },
"unsponsoredSellVolume": { "type": "number" },
"unsponsoredSellValue": { "type": "number" },
"unsponsoredInMarketBuyVolume": { "type": "number" },
"unsponsoredInMarketBuyValue": { "type": "number" },
"unsponsoredInMarketSellVolume": { "type": "number" },
"unsponsoredInMarketSellValue": { "type": "number" },
"unsponsoredStartOfDayVolume": { "type": "number" },
"historicalProfitStartOfDay": { "type": "number" },
"totalHistoricalProfit": { "type": "number" },
"totalDiffMktValProfitDay": { "type": "number" },
"realizedLossAccumulatedTodayValue": { "type": "number" },
"securityType": { "type": "number" }
}
Portfolio cash detail schema
{
"portfolioCode": { "type": "string" },
"accountCode": { "type": "string" },
"advisorCode": { "type": "string" },
"portfolioCashCode": { "type": "string" },
"portfolioCashName": { "type": "string" },
"versionStamp": { "type": "string" },
"createDateTime": { "type": "datetime" },
"updateDateTime": { "type": "datetime" },
"currencyCode": { "type": "string" },
"cashBalance": { "type": "number" },
"unsettledBuyValue": { "type": "number" },
"unsettledBuyCharges": { "type": "number" },
"unsettledSellValue": { "type": "number" },
"unsettledSellCharges": { "type": "number" },
"yesterdayEquitySellValue": { "type": "number" },
"yesterdayEquitySellCharges": { "type": "number" },
"inMarketBuyValue": { "type": "number" },
"inMarketSellValue": { "type": "number" },
"netCash": { "type": "number" },
"uploadSource": { "type": "string" },
"optionUnsettledBuyValue": { "type": "number" },
"optionUnsettledBuyCharges": { "type": "number" },
"optionUnsettledSellValue": { "type": "number" },
"optionUnsettledSellCharges": { "type": "number" },
"clearingHouseMargin": { "type": "number" },
"externalValue": { "type": "string" },
"netUnsettledBuyValueToday": { "type": "number" },
"netUnsettledSellValueToday": { "type": "number" },
"netUnsettledValueToday": { "type": "number" },
"glv": { "type": "number" },
"freeEquity": { "type": "number" },
"totalInitialMargin": { "type": "number" },
"totalCfdRealisedProfit": { "type": "number" },
"totalCfdUnrealisedProfit": { "type": "number" },
"totalCfdCollateralValue": { "type": "number" },
"totalNonCfdMarketValue": { "type": "number" },
"realizedLossStartOfDayValue": { "type": "number" },
"marginLenderTotalFinancedValue": { "type": "number" },
"trustBalance": { "type": "number" },
"totalCfdRealizedProfitInSettlementCurrency": { "type": "number" },
"accruedInterest": { "type": "number" },
"facilityLimit": { "type": "number" },
"multiSettlementCalculationMethod": { "type": "number" },
"defaultCashSettlementDays": { "type": "number" },
"isDefaultCurrency": { "type": "number" }
}

