Skip to content

Commit a7e2f20

Browse files
author
Wasin Waeosri
committed
Change log:
1. clean and add comments to code 2. Update README.md file
1 parent 845e1ac commit a7e2f20

File tree

2 files changed

+119
-24
lines changed

2 files changed

+119
-24
lines changed

README.md

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
## Overview
88

9-
This example shows how to writing an application to subscribe Machine Readable News (MRN) using [Elektron WebSocket API](https://developers.refinitiv.com/elektron/websocket-api) from Thomson Reuters Enterprise Platform (TREP). The example just connects to TREP via a WebSocket connection, then subscribes and display MRN News data in a console. The project are implemented with Python language, but the main concept for consuming and assembling MRN News messages are the same for all technologies.
9+
This example shows how to writing the [Elektron WebSocket API](https://developers.refinitiv.com/elektron/websocket-api) application to subscribe Machine Readable News (MRN) from Thomson Reuters Enterprise Platform (TREP). The example just connects to TREP via a WebSocket connection, then subscribes and display MRN News data in a console. The project are implemented with Python language, but the main concept for consuming and assembling MRN News messages are the same for all technologies.
1010

11-
*Note:* The news message is in UTF-8 JSON string format. Some news messages that contains special unicode character may not be able to show in Windows OS console (cmd, git bash, powershell, etc) due to the OS limitation. Those messages will be print as *UnicodeEncodeError exception. Cannot decode unicode character* message in a console instead.
11+
*Note:* The news message is in UTF-8 JSON string format. Some news messages that contains special unicode character may not be able to show in Windows OS console (cmd, git bash, powershell, etc) due to the OS limitation. Those messages will be print as ```UnicodeEncodeError exception. Cannot decode unicode character``` message in a console instead.
1212

1313
## Machine Readable News Overview
1414

@@ -17,23 +17,23 @@ Refinitiv Machine Readable News (MRN) is an advanced service for automating the
1717
### MRN Data model
1818
MRN is published over Elektron using an Open Message Model (OMM) envelope in News Text Analytics domain messages. The Real-time News content set is made available over MRN_STORY RIC. The content data is contained in a FRAGMENT field that has been compressed, and potentially fragmented across multiple messages, in order to reduce bandwidth and message size.
1919

20-
A FRAGMENT field has a different data type based on a connection:
21-
* RSSL connection: BUFFER type
22-
* WebSocket connection: Base64 string
20+
A FRAGMENT field has a different data type based on a connection type:
21+
* RSSL connection (ESDK [C++](https://developers.refinitiv.com/elektron/elektron-sdk-cc)/[Java](https://developers.refinitiv.com/elektron/elektron-sdk-java)): BUFFER type
22+
* WebSocket connection: Base64 ascii string
2323

2424
The data goes through the following series of transformations:
2525

2626
1. The core content data is a UTF-8 JSON string
2727
2. This JSON string is compressed using gzip
28-
3. The compressed JSON is split into a number of fragments (BUFFER or Base64 string) which each fit into a single update message
28+
3. The compressed JSON is split into a number of fragments (BUFFER or Base64 ascii string) which each fit into a single update message
2929
4. The data fragments are added to an update message as the FRAGMENT field value in a FieldList envelope
3030

31-
Therefore, in order to parse the core content data, the application will need to reverse this process. The WebSocket application also need to convert a received Base64 string in a FRAGMENT field to bytes data before further process this field.
31+
Therefore, in order to parse the core content data, the application will need to reverse this process. The WebSocket application also need to convert a received Base64 string in a FRAGMENT field to bytes data before further process this field. This application uses Python [base64](https://docs.python.org/3/library/base64.html) and [zlib](https://docs.python.org/3/library/zlib.html) modules to decode Base64 string and decompress JSON string.
3232

33-
If you are not familiar with MRN concept, please visit the following resources which will give you a full explanation of the MRN data behavior and how to process it:
33+
If you are not familiar with MRN concept, please visit the following resources which will give you a full explanation of the MRN data model and implementation logic:
3434
* [Webinar Recording: Introduction to Machine Readable News](https://developers.refinitiv.com/news#news-accordion-nid-12045)
3535
* [Introduction to Machine Readable News (MRN) with Elektron Message API (EMA)](https://developers.refinitiv.com/article/introduction-machine-readable-news-mrn-elektron-message-api-ema).
36-
* [Machine Readable News (MRN) & N2_UBMS Comparison and Migration Guide](https://developers.refinitiv.com/article/machine-readable-news-mrn-n2_ubms-comparison-and-migration-guide).
36+
* [MRN Data Models and Elektron Implementation Guide](https://developers.refinitiv.com/elektron/elektron-sdk-java/docs?content=8736&type=documentation_item).
3737

3838
## Prerequisite
3939
This example requires the following dependencies softwares and libraries.
@@ -63,15 +63,108 @@ Please be informed that your TREP server (ADS and ADH) should have a Service tha
6363
2. Run ```$> pip install -r requestments.txt``` in a console to install all the dependencies libraries.
6464
3. Then you can run mrn_console_app.py application with the following command
6565
```
66-
$> python mrn_console_app.py --hostname <ADS server IP Address/Hostname> --port <WebSocket Port> --item <MRN RIC> --service <ADS Contribution Service name>
66+
$> python mrn_console_app.py --hostname <ADS server IP Address/Hostname> --port <WebSocket Port> --ric <MRN RIC name>
6767
```
68+
Optionally, the application subscribes ```MRN_STORY``` RIC code from TREP by default. You can pass your interested MRN RIC code to ```--ric``` parameter on the application command line. The supported MRN RIC codes are ```MRN_STORY```, ```MRN_TRNA```, ```MRN_TRNA_DOC``` and ```MRN_TRSI``` only. the application
69+
70+
## Example Results
71+
### Send MRN_STORY request to TREP
72+
```
73+
SENT:
74+
{
75+
"Domain":"NewsTextAnalytics",
76+
"ID":2,
77+
"Key":{
78+
"Name":"MRN_STORY"
79+
}
80+
}
81+
RECEIVED:
82+
[
83+
{
84+
"Domain":"NewsTextAnalytics",
85+
"Fields":{
86+
"ACTIV_DATE":"2019-07-20",
87+
"CONTEXT_ID":3752,
88+
"DDS_DSO_ID":4232,
89+
"FRAGMENT":null,
90+
"FRAG_NUM":1,
91+
"GUID":null,
92+
"MRN_SRC":"HK1_PRD_A",
93+
"MRN_TYPE":"STORY",
94+
"MRN_V_MAJ":"2",
95+
"MRN_V_MIN":"10",
96+
"PROD_PERM":10001,
97+
"RDN_EXCHD2":"MRN",
98+
"RECORDTYPE":30,
99+
"SPS_SP_RIC":".[SPSML2L1",
100+
"TIMACT_MS":37708132,
101+
"TOT_SIZE":0
102+
},
103+
"ID":2,
104+
"Key":{
105+
"Name":"MRN_STORY",
106+
"Service":"ELEKTRON_DD"
107+
},
108+
"PermData":"AwhCEAAc",
109+
"Qos":{
110+
"Rate":"TickByTick",
111+
"Timeliness":"Realtime"
112+
},
113+
"SeqNumber":32240,
114+
"State":{
115+
"Data":"Ok",
116+
"Stream":"Open",
117+
"Text":"All is well"
118+
},
119+
"Type":"Refresh"
120+
}
121+
]
122+
```
123+
124+
### Receive Update message and assemble News
125+
```
126+
RECEIVED:
127+
[
128+
{
129+
"DoNotCache":true,
130+
"DoNotConflate":true,
131+
"DoNotRipple":true,
132+
"Domain":"NewsTextAnalytics",
133+
"Fields":{
134+
"ACTIV_DATE":"2019-07-25",
135+
"FRAGMENT":"H4sIAAAAAAAC/71UW2/bNhR+36840MNe5sq2HCcx0W5QbTfW4miO5bRp2mCgRcpmLZEaSVnJhv33HUpyUKAvfSgGCfwOb+c7PLd/PJrbiHnEkxGrx+xyUTOv59GKCS5TbjzyyYtXZBqvQ1xGKZrdx95jz9sq9oy3lrRUmkpYUC0QqKUGYpFTAeHBiiOFt1wbsQeGm9eqKJURRsBKaasylQsF8Fl+liFjwgolaY4aLE33BZfWQIqXthwyVUmGQq5q4k7vrS1Jv1+yzNe8skjgp6pwc8lrc0KfmvI38eZsNBhP0tE2G/DRxfhs+HP1ptKSuBNFTr66T4LBcDK4CMbEeeLc3mbLD47t/2KcTB4Wye7E6L6ZqrY5hzQX6QGUBLvncLdeAt2qIwer4Ch43axSbUWac3+Vc2o4SGVxf08tCIlkklugKQbTgDCg+V+V0Jz5EGXwrCrgTyXXTbSh1AoZi+60kLtG+0lJD8pWf6qkqXLrbmvAjVrpA1BWCCmM1dQqDfhbnu6lSDGmpioxS6x705JabiwcXVbgk1QGIVMY4zBFajRYc8q4bu1EF2EeMM5e3rqavYNM5Nz4ABu0LP9Gm7O309Flj9paKiQqybQqoItkXdc+dcRtHLViVWpNn7ZW9J0GY3kZ+Htb5M7ud/igNXd8DGKMZA+66ExP0VGSdwY0HmLckE+uWB7BAY5hEoXNBAdXTwjJxm3MbzcfEZbNzv0qufJ/v36Er1j5kUoLKy0wRJHMlC6oq5bvsIG87vT9Cli+mdDGTvFx+AqsXZd+rwYXr4LxZjggowkZXfrn5+cPeHKPDsjRaz+kwlGfcHwvDebPNu2DKLhYTy7vlju1+uW5vzCjjTpmwTWbpA+35vj28JT9fThzt6Wx1HWjPzLsR9h8cip3Fd0584RrVwVmK043zyUuBTgXRTfxLH+y/RLNlXgOA30UmBu4Hieulbm1aptYaitsdR7SWFIZik7FHVNtv3BMC9cDQzLElatuDMYtzK8bnEYNtJMExxsyvJ01OApaWDQQvm8gvmvAOXpNugC57hqQMJmH8UmMwpN0vwqnrTy9WcUfW3F+s77qJEygVopmLS5Pws0Ga6YV4/mHdSut591FZHthwVx0fd3SA0+wRbh+4JFhz6v0DmXs9aOe1xXad6TQvz/9B7krulxaBgAA",
136+
"FRAG_NUM":1,
137+
"GUID":"Idw5d8Hwd_1907252I27R98ULgoP+y/Hs3Tovf2Kd9cZQsvBkxfzk4",
138+
"MRN_SRC":"HK1_PRD_A",
139+
"MRN_TYPE":"STORY",
140+
"MRN_V_MAJ":"2",
141+
"MRN_V_MIN":"10",
142+
"TIMACT_MS":38378746,
143+
"TOT_SIZE":882
144+
},
145+
"ID":2,
146+
"Key":{
147+
"Name":"MRN_STORY",
148+
"Service":"ELEKTRON_DD"
149+
},
150+
"PermData":"AwhCEBkrEiFLEkMM",
151+
"SeqNumber":32350,
152+
"Type":"Update",
153+
"UpdateType":"Unspecified"
154+
}
155+
]
156+
FRAGMENT length = 882
157+
decompress News FRAGMENT(s) for GUID Idw5d8Hwd_1907252I27R98ULgoP+y/Hs3Tovf2Kd9cZQsvBkxfzk4
158+
News = {'altId': 'nIdw5d8Hwd', 'audiences': ['NP:CNRA', 'NP:IDXN'], 'body': 'Laporan Harian atas Nilai Aktiva Bersih dan Komposisi Portofolio \n\nAdditional attachments can be found below:\n\nhttp://pdf.reuters.com/pdfnews/pdfnews.asp?i=43059c3bf0e37541&u=urn:newsml:reuters.com:20190725:nIdw6tQfLW\n\n\n\nhttp://pdf.reuters.com/pdfnews/pdfnews.asp?i=43059c3bf0e37541&u=urn:newsml:reuters.com:20190725:nIdw99ZHSg\n\n\n\n\n\nDouble click on the URL above to view the article.Please note that internet access is required. If you experience problem accessing the internet, please consult your network administrator or technical support\n\nLatest version of Adobe Acrobat reader is recommended to view PDF files. The latest version of the reader can be obtained from http://www.adobe.com/products/acrobat/readstep2.html\n\nFor Related News, Double Click on one of these codes:[IDXN] [IDX] [ASIA] [ID] [CNRA] [STX] [EQTY] [LID] [XPSG.JK] \n\nFor Relevant Price Information, Double Click on one of these code:<XPSG.JK> ', 'firstCreated': '2019-07-25T10:39:38.666Z', 'headline': 'Laporan Harian atas Nilai Aktiva Bersih dan Komposisi Portofolio ', 'id': 'Idw5d8Hwd_1907252I27R98ULgoP+y/Hs3Tovf2Kd9cZQsvBkxfzk4', 'instancesOf': [], 'language': 'id', 'messageType': 2, 'mimeType': 'text/plain', 'provider': 'NS:IDX', 'pubStatus': 'stat:usable', 'subjects': ['A:1', 'G:1', 'G:25', 'G:2EK', 'G:CI', 'G:K', 'G:S', 'M:1QD', 'M:32', 'M:3H', 'M:AV', 'M:NU', 'M:Z', 'R:XPSG.JK', 'N2:ASEAN', 'N2:ASIA', 'N2:ASXPAC', 'N2:CMPNY', 'N2:EMRG', 'N2:EQTY', 'N2:ID', 'N2:LID', 'N2:MTPDF', 'N2:NEWR', 'N2:REG', 'N2:SEASIA', 'N2:STX'], 'takeSequence': 1, 'urgency': 3, 'versionCreated': '2019-07-25T10:39:38.666Z'}
159+
```
68160
69161
## References
70162
* [Refinitiv Elektron SDK Family page](https://developers.refinitiv.com/elektron) on the [Refinitiv Developer Community](https://developers.thomsonreuters.com/) web site.
71163
* [Refinitiv Elektron WebSocket API page](https://developers.refinitiv.com/websocket-api).
72164
* [Developer Webinar Recording: Introduction to Electron WebSocket API](https://www.youtube.com/watch?v=CDKWMsIQfaw).
73165
* [Machine Readable News (MRN) & N2_UBMS Comparison and Migration Guide](https://developers.refinitiv.com/article/machine-readable-news-mrn-n2_ubms-comparison-and-migration-guide).
74166
* [Introduction to Machine Readable News (MRN) with Elektron Message API (EMA)](https://developers.refinitiv.com/article/introduction-machine-readable-news-mrn-elektron-message-api-ema).
167+
* [MRN Data Models and Elektron Implementation Guide](https://developers.refinitiv.com/elektron/elektron-sdk-java/docs?content=8736&type=documentation_item).
75168
* [Refinitiv-API-Samples/Example.WebSocketAPI.Javascript.NewsMonitor](https://github.com/Refinitiv-API-Samples/Example.WebSocketAPI.Javascript.NewsMonitor).
76169
77170
For any question related to this example or Elektron WebSocket API, please use the Developer Community [Q&A Forum](https://community.developers.refinitiv.com/spaces/152/websocket-api.html).

mrn_console_app.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
position = socket.gethostbyname(socket.gethostname())
2929
mrn_domain = 'NewsTextAnalytics'
3030
mrn_item = 'MRN_STORY'
31-
#mrn_item = 'MRN_TRNA'
3231

3332
# Global Variables
3433
web_socket_app = None
@@ -66,8 +65,6 @@ def processRefresh(ws, message_json):
6665

6766

6867
def processMRNUpdate(ws, message_json): # process incoming News Update messages
69-
#print("RECEIVED: Update Message")
70-
# print(message_json)
7168

7269
fields_data = message_json["Fields"]
7370
# Dump the FieldList first (for informational purposes)
@@ -88,8 +85,6 @@ def processMRNUpdate(ws, message_json): # process incoming News Update messages
8885
#print("FRAG_NUM = %d" % frag_num)
8986
#print("MRN_SRC = %s" % mrn_src)
9087

91-
#fragment_decoded = base64.b64decode(fragment)
92-
9388
if frag_num > 1: # We are now processing more than one part of an envelope - retrieve the current details
9489
guid_index = next((index for (index, d) in enumerate(
9590
_news_envelopes) if d["guid"] == guid), None)
@@ -100,7 +95,7 @@ def processMRNUpdate(ws, message_json): # process incoming News Update messages
10095

10196
#print("fragment before merge = %d" % len(envelop["data"]["fragment"]))
10297

103-
# Merge incoming data to existing envelop and getting FRAGMENT and TOT_SIZE data to local variables
98+
# Merge incoming data to existing news envelop and getting FRAGMENT and TOT_SIZE data to local variables
10499
fragment = envelop["data"]["fragment"] = envelop["data"]["fragment"] + fragment
105100
envelop["data"]["frag_num"] = frag_num
106101
tot_size = envelop["data"]["tot_size"]
@@ -110,7 +105,7 @@ def processMRNUpdate(ws, message_json): # process incoming News Update messages
110105
# The multiple fragments news are not completed, waiting.
111106
if tot_size != len(fragment):
112107
return None
113-
# The multiple fragments news are completed, delete assoiclate GUID dictionary
108+
# The multiple fragments news are completed, delete assoiclate GUID envelop
114109
elif tot_size == len(fragment):
115110
del _news_envelopes[guid_index]
116111
else:
@@ -120,9 +115,10 @@ def processMRNUpdate(ws, message_json): # process incoming News Update messages
120115
else: # FRAG_NUM = 1 The first fragment
121116
tot_size = int(fields_data["TOT_SIZE"])
122117
print("FRAGMENT length = %d" % len(fragment))
123-
if tot_size != len(fragment): # Completed News
118+
# The fragment news is not completed, waiting and add this news data to envelop object.
119+
if tot_size != len(fragment):
124120
print("Add new fragments to news envelop for guid %s" % guid)
125-
_news_envelopes.append({
121+
_news_envelopes.append({ # the envelop object is a Python dictionary with GUID as a key and other fields are data
126122
"guid": guid,
127123
"data": {
128124
"fragment": fragment,
@@ -189,7 +185,6 @@ def process_message(ws, message_json):
189185

190186
def process_login_response(ws, message_json):
191187
""" Send item request """
192-
# send_market_price_request(ws)
193188
send_mrn_request(ws)
194189

195190

@@ -257,15 +252,15 @@ def on_open(ws):
257252
# Get command line parameters
258253
try:
259254
opts, args = getopt.getopt(sys.argv[1:], "", [
260-
"help", "hostname=", "port=", "app_id=", "user=", "position="])
255+
"help", "hostname=", "port=", "app_id=", "user=", "position=", "ric="])
261256
except getopt.GetoptError:
262257
print(
263-
'Usage: market_price.py [--hostname hostname] [--port port] [--app_id app_id] [--user user] [--position position] [--help]')
258+
'Usage: market_price.py [--hostname hostname] [--port port] [--app_id app_id] [--user user] [--position position] [--ric news RIC name] [--help]')
264259
sys.exit(2)
265260
for opt, arg in opts:
266261
if opt in ("--help"):
267262
print(
268-
'Usage: market_price.py [--hostname hostname] [--port port] [--app_id app_id] [--user user] [--position position] [--help]')
263+
'Usage: market_price.py [--hostname hostname] [--port port] [--app_id app_id] [--user user] [--position position] [--ric news RIC name] [--help]')
269264
sys.exit(0)
270265
elif opt in ("--hostname"):
271266
hostname = arg
@@ -277,6 +272,13 @@ def on_open(ws):
277272
user = arg
278273
elif opt in ("--position"):
279274
position = arg
275+
elif opt in ("--ric"):
276+
if arg not in ["MRN_STORY", "MRN_TRNA", "MRN_TRNA_DOC", "MRN_TRSI"]:
277+
print(
278+
"The supported MRN RIC names are MRN_STORY or MRN_TRNA or MRN_TRNA_DOC or MRN_TRSI only")
279+
sys.exit(2)
280+
else:
281+
item = arg
280282

281283
# Start websocket handshake
282284
ws_address = "ws://{}:{}/WebSocket".format(hostname, port)
@@ -294,6 +296,6 @@ def on_open(ws):
294296

295297
try:
296298
while True:
297-
time.sleep(600)
299+
time.sleep(1)
298300
except KeyboardInterrupt:
299301
web_socket_app.close()

0 commit comments

Comments
 (0)