Stream processing¶
The SAXO OpenAPI offers steaming support using websockets.
Several endpoints offer streaming capabilities by creating subscriptions. These subscriptions create messages that can be identified by the referenceId.
#!/usr/bin/env python3.6
"""Simple stream processing."""
import asyncio
import websockets
from saxo_openapi.contrib.ws import stream
async def Echo(ContextId, token):
hdrs = {
"Authorization": "Bearer {}".format(token),
}
URL = "wss://streaming.saxotrader.com/sim/openapi/streamingws/connect?" + \
"contextId={ContextId}".format(ContextId=ContextId)
async with websockets.connect(URL, extra_headers=hdrs) as websocket:
async for raw_message in websocket:
# get all messages from the raw message, nr. of messages varies
print("----------------------------------")
for message in stream.decode_ws_msg(raw_message):
print(message)
if __name__ == "__main__":
import sys
with open("token.txt") as I:
token = I.read().strip()
asyncio.get_event_loop().run_until_complete(Echo(ContextId=sys.argv[1],
token=token))
This code will read messages from a message stream. It must be started with a ContextId.
$ python stream_example.py ctxt_20190311
Now the program runs, but no output appears. By using the explorer www.developer.saxo it is possible to create one ore more subscriptions.
To create a subscription for price information:
- click the Trading service group
- click Prices
- click POST /trade/v1/prices/subscriptions
{
"Arguments": {
"Uic": 21,
"AssetType": "FxSpot"
},
"ContextId": "ctxt_20190311",
"ReferenceId": "EUR_USD"
}
Click SEND to submit the POST. Quotes for EUR_USD will show up in the stream. Additional subscriptions can be added and the messages will appear in the stream by their referenceId. The output shows EUR_USD price information and output of a subscription for account balance.
{'refid': b'acctbal', 'msgId': 5004, 'msg': {'InitialMargin': {'MarginAvailable': 98669.15, 'NetEquityForMargin': 100169.15}, 'MarginAvailableForTrading': 98669.15, 'MarginNetExposure': 100000.32, 'NetEquityForMargin': 100169.15, 'TotalValue': 100169.15, 'UnrealizedMarginOpenProfitLoss': 179.14, 'UnrealizedMarginProfitLoss': 179.14, 'UnrealizedPositionsValue': 174.14}} {'refid': b'EUR_USD', 'msgId': 5005, 'msg': {'LastUpdated': '2019-03-11T19:47:54.197000Z', 'Quote': {'Ask': 1.12491, 'Bid': 1.12471, 'Mid': 1.12481}}} {'refid': b'acctbal', 'msgId': 5006, 'msg': {'MarginNetExposure': 100000.88}} {'refid': b'acctbal', 'msgId': 5007, 'msg': {'MarginNetExposure': 100001.44}} {'refid': b'acctbal', 'msgId': 5008, 'msg': {'MarginNetExposure': 100002.0}} {'refid': b'EUR_USD', 'msgId': 5009, 'msg': {'LastUpdated': '2019-03-11T19:48:04.830000Z'}} {'refid': b'acctbal', 'msgId': 5010, 'msg': {'MarginNetExposure': 100001.44}} {'refid': b'acctbal', 'msgId': 5011, 'msg': {'InitialMargin': {'MarginAvailable': 98666.93, 'NetEquityForMargin': 100166.93}, 'MarginAvailableForTrading': 98666.93, 'MarginNetExposure': 100000, 'NetEquityForMargin': 100166.93, 'TotalValue': 100166.93, 'UnrealizedMarginOpenProfitLoss': 176.92, 'UnrealizedMarginProfitLoss': 176.92, 'UnrealizedPositionsValue': 171.92}} {'refid': b'EUR_USD', 'msgId': 5012, 'msg': {'LastUpdated': '2019-03-11T19:48:10.464000Z', 'Quote': {'Ask': 1.1249, 'Bid': 1.1247, 'Mid': 1.1248}}} {'refid': b'EUR_USD', 'msgId': 5013, 'msg': {'LastUpdated': '2019-03-11T19:48:12.482000Z', 'Quote': {'Ask': 1.12487, 'Bid': 1.12467, 'Mid': 1.12477}}} {'refid': b'acctbal', 'msgId': 5014, 'msg': {'InitialMargin': {'MarginAvailable': 98664.71, 'NetEquityForMargin': 100164.71}, 'MarginAvailableForTrading': 98664.71, 'MarginExposureCoveragePct': 100.16, 'MarginNetExposure': 100000.93, 'NetEquityForMargin': 100164.71, 'TotalValue': 100164.71, 'UnrealizedMarginOpenProfitLoss': 174.7, 'UnrealizedMarginProfitLoss': 174.7, 'UnrealizedPositionsValue': 169.7}} {'refid': b'EUR_USD', 'msgId': 5015, 'msg': {'LastUpdated': '2019-03-11T19:48:15.882000Z'}} {'refid': b'acctbal', 'msgId': 5016, 'msg': {'InitialMargin': {'MarginAvailable': 98666.93, 'NetEquityForMargin': 100166.93}, 'MarginAvailableForTrading': 98666.93, 'MarginNetExposure': 100002.59, 'NetEquityForMargin': 100166.93, 'TotalValue': 100166.93, 'UnrealizedMarginOpenProfitLoss': 176.92, 'UnrealizedMarginProfitLoss': 176.92, 'UnrealizedPositionsValue': 171.92}} {'refid': b'EUR_USD', 'msgId': 5017, 'msg': {'LastUpdated': '2019-03-11T19:48:23.501000Z', 'Quote': {'Ask': 1.12486, 'Bid': 1.12466, 'Mid': 1.12476}}} {'refid': b'acctbal', 'msgId': 5018, 'msg': {'InitialMargin': {'MarginAvailable': 98666.94, 'NetEquityForMargin': 100166.94}, 'MarginAvailableForTrading': 98666.94, 'MarginNetExposure': 100003.16, 'NetEquityForMargin': 100166.94, 'TotalValue': 100166.94, 'UnrealizedMarginOpenProfitLoss': 176.93, 'UnrealizedMarginProfitLoss': 176.93, 'UnrealizedPositionsValue': 171.93}} {'refid': b'EUR_USD', 'msgId': 5019, 'msg': {'LastUpdated': '2019-03-11T19:48:24.604000Z', 'Quote': {'Ask': 1.12487, 'Bid': 1.12467, 'Mid': 1.12477}}} {'refid': b'EUR_USD', 'msgId': 5020, 'msg': {'LastUpdated': '2019-03-11T19:48:26.716000Z'}} {'refid': b'acctbal', 'msgId': 5021, 'msg': {'MarginNetExposure': 100003.72}} {'refid': b'EUR_USD', 'msgId': 5022, 'msg': {'LastUpdated': '2019-03-11T19:48:27.088000Z', 'Quote': {'Ask': 1.12486, 'Bid': 1.12466, 'Mid': 1.12476}}} {'refid': b'acctbal', 'msgId': 5023, 'msg': {'MarginNetExposure': 100003.16}}
Subscriptions using saxo_openapi¶
Creating price-subscriptions using the saxo_openapi is easy too.
#!/usr/bin/env python3.6
"""Simple demo program that looks up the Uic for currencypairs entered by name.
For each pair it creates a subscription for price information with the instrumentname
as Referenceid.
The program asumes you have a file with the token locally in token.tok.
Usage: price_subscr.py <contextid> EURUSD EURJPY EURGBP
"""
from saxo_openapi import API
import saxo_openapi.endpoints.trading as tr
import saxo_openapi.endpoints.referencedata as rd
import saxo_openapi.contrib.session as session
import json
def subscribe_for_prices(client, ContextId, instruments):
"""fetch instrument data by the name of the instrument and extract the Uic (Identifier)
and use that to subscribe for prices.
Use the name of the instrument as reference.
"""
_ai = session.account_info(client=client)
# body template for price subscription
body = {
"Arguments": {
"Uic": "",
"AssetType": "FxSpot"
},
"ContextId": "",
"ReferenceId": ""
}
body.update({'ContextId': ContextId})
for instrument in instruments:
params = {'AccountKey': _ai.AccountKey,
'AssetTypes': 'FxSpot',
'Keywords': instrument
}
# create the request to fetch Instrument info
r = rd.instruments.Instruments(params=params)
rv = client.request(r)
if len(rv['Data']) == 1:
body['Arguments'].update({'Uic': rv['Data'][0]['Identifier']})
body.update({"ReferenceId": instrument})
# print("Prepping: ")
# print(json.dumps(body, indent=2))
# create the request to fetch Instrument info
r = tr.prices.CreatePriceSubscription(data=body)
client.request(r)
status = "succesful" if r.status_code == r.expected_status else "failed"
print("Subscription for instrument: {} {}".format(instrument, status))
else:
print("Got multiple instruments for {}, can't choose...skip".format(instrument))
if __name__ == "__main__":
import sys
with open("token.txt") as I:
token = I.read().strip()
client = API(access_token=token)
ContextId = sys.argv[1]
subscribe_for_prices(client, ContextId, sys.argv[2:])
print("check the stream for data ...")
Now create the price subscriptions with the program above:
$ python price_subscr.py ctxt_20190311 EURJPY EURGBP
Subscription for instrument: EURJPY succesful
Subscription for instrument: EURGBP succesful
check the stream for data ...
The new instruments will show up in the stream output.
---------------------------------- {'refid': 'EURAUD', 'msgId': 1, 'msg': {'LastUpdated': '2021-02-22T17:19:07.708000Z'}} ---------------------------------- {'refid': 'EURUSD', 'msgId': 2, 'msg': {'LastUpdated': '2021-02-22T17:19:07.863000Z'}} {'refid': 'EURNZD', 'msgId': 3, 'msg': {'LastUpdated': '2021-02-22T17:19:07.708000Z'}} ---------------------------------- {'refid': 'EURCAD', 'msgId': 4, 'msg': {'LastUpdated': '2021-02-22T17:19:08.546000Z', 'Quote': {'Ask': 1.53309, 'Bid': 1.53219, 'Mid': 1.53264}}} ---------------------------------- {'refid': 'EURCHF', 'msgId': 5, 'msg': {'LastUpdated': '2021-02-22T17:19:08.553000Z', 'Quote': {'Ask': 1.08896, 'Bid': 1.08866, 'Mid': 1.08881}}} ---------------------------------- {'refid': 'EURJPY', 'msgId': 6, 'msg': {'LastUpdated': '2021-02-22T17:19:08.751000Z'}} ---------------------------------- {'refid': 'GBPAUD', 'msgId': 7, 'msg': {'LastUpdated': '2021-02-22T17:19:08.726000Z'}} ---------------------------------- {'refid': 'GBPNZD', 'msgId': 8, 'msg': {'LastUpdated': '2021-02-22T17:19:08.926000Z', 'Quote': {'Ask': 1.92071, 'Bid': 1.91951, 'Mid': 1.92011}}} ---------------------------------- {'refid': 'GBPUSD', 'msgId': 9, 'msg': {'LastUpdated': '2021-02-22T17:19:08.726000Z'}} ---------------------------------- {'refid': 'GBPCAD', 'msgId': 10, 'msg': {'LastUpdated': '2021-02-22T17:19:08.750000Z'}} ---------------------------------- {'refid': 'EURAUD', 'msgId': 11, 'msg': {'LastUpdated': '2021-02-22T17:19:08.546000Z', 'Quote': {'Ask': 1.53641, 'Bid': 1.53571, 'Mid': 1.53606}}} {'refid': 'GBPCHF', 'msgId': 12, 'msg': {'LastUpdated': '2021-02-22T17:19:08.762000Z', 'Quote': {'Ask': 1.26101, 'Bid': 1.26031, 'Mid': 1.26066}}} ---------------------------------- {'refid': 'GBPJPY', 'msgId': 13, 'msg': {'LastUpdated': '2021-02-22T17:19:09.346000Z'}} ---------------------------------- {'refid': 'AUDNZD', 'msgId': 14, 'msg': {'LastUpdated': '2021-02-22T17:19:09.561000Z'}} ---------------------------------- {'refid': 'AUDUSD', 'msgId': 15, 'msg': {'LastUpdated': '2021-02-22T17:19:09.561000Z'}} ---------------------------------- {'refid': 'EURCHF', 'msgId': 16, 'msg': {'LastUpdated': '2021-02-22T17:19:09.312000Z', 'Quote': {'Ask': 1.08894, 'Bid': 1.08864, 'Mid': 1.08879}}} ---------------------------------- {'refid': 'AUDCHF', 'msgId': 17, 'msg': {'LastUpdated': '2021-02-22T17:19:09.704000Z'}} ---------------------------------- {'refid': 'GBPAUD', 'msgId': 18, 'msg': {'LastUpdated': '2021-02-22T17:19:09.883000Z', 'Quote': {'Ask': 1.77896, 'Bid': 1.77806, 'Mid': 1.77851}}} {'refid': 'GBPNZD', 'msgId': 19, 'msg': {'LastUpdated': '2021-02-22T17:19:09.883000Z', 'Quote': {'Ask': 1.92072, 'Bid': 1.91952, 'Mid': 1.92012}}} {'refid': 'AUDJPY', 'msgId': 20, 'msg': {'LastUpdated': '2021-02-22T17:19:09.561000Z'}} ---------------------------------- {'refid': 'GBPUSD', 'msgId': 21, 'msg': {'LastUpdated': '2021-02-22T17:19:10.062000Z', 'Quote': {'Ask': 1.40796, 'Bid': 1.40766, 'Mid': 1.40781}}} {'refid': 'NZDCAD', 'msgId': 22, 'msg': {'LastUpdated': '2021-02-22T17:19:10.073000Z', 'Quote': {'Ask': 0.92461, 'Bid': 0.92381, 'Mid': 0.92421}}} {'refid': 'GBPCAD', 'msgId': 23, 'msg': {'LastUpdated': '2021-02-22T17:19:10.087000Z', 'Quote': {'Ask': 1.77496, 'Bid': 1.77416, 'Mid': 1.77456}}} ---------------------------------- {'refid': 'EURAUD', 'msgId': 24, 'msg': {'LastUpdated': '2021-02-22T17:19:10.297000Z', 'Quote': {'Ask': 1.53638, 'Bid': 1.53568, 'Mid': 1.53603}}} {'refid': 'GBPCHF', 'msgId': 25, 'msg': {'LastUpdated': '2021-02-22T17:19:10.332000Z', 'Quote': {'Ask': 1.26102, 'Bid': 1.26032, 'Mid': 1.26067}}} {'refid': 'NZDCHF', 'msgId': 26, 'msg': {'LastUpdated': '2021-02-22T17:19:10.348000Z', 'Quote': {'Ask': 0.65691, 'Bid': 0.65621, 'Mid': 0.65656}}} ---------------------------------- {'refid': 'GBPJPY', 'msgId': 27, 'msg': {'LastUpdated': '2021-02-22T17:19:10.365000Z', 'Quote': {'Ask': 147.906, 'Bid': 147.836, 'Mid': 147.871}}} {'refid': 'NZDJPY', 'msgId': 28, 'msg': {'LastUpdated': '2021-02-22T17:19:10.348000Z', 'Quote': {'Ask': 77.044, 'Bid': 76.984, 'Mid': 77.014}}} ---------------------------------- {'refid': 'USDCAD', 'msgId': 29, 'msg': {'LastUpdated': '2021-02-22T17:19:10.566000Z'}}