Skip to content

Commit c511650

Browse files
committed
Update Dockerfile, Python scripts, and Jupyter Notebook for LSEG Real-Time Platform
- Updated Dockerfile to use Python 3.12 and improved multi-stage build process. - Changed maintainer label to "LSEG Developer Relations" and updated build date. - Modified requirements.txt to include specific versions for dependencies. - Updated trcc_posting.py to reflect changes in copyright and improve code consistency. - Changed hostname variable to 'ADS_HOST' for better configurability. - Updated Jupyter Notebook to reflect changes in branding and improve documentation. - Added new requirements_notebook.txt for Jupyter Notebook dependencies. - Updated images to reflect the new branding and content.
1 parent bf95379 commit c511650

File tree

9 files changed

+311
-171
lines changed

9 files changed

+311
-171
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ console_python/market_price.py
33
result/
44
notebook_python/test_ws_post.ipynb
55
notebook_python/test_ws_post2.ipynb
6-
notebook_python/.ipynb_checkpoints
6+
notebook_python/.ipynb_checkpoints
7+
venv/
8+
nbvenv/
9+
run.txt
10+
console_python/nbvenv/

README.md

Lines changed: 44 additions & 33 deletions
Large diffs are not rendered by default.

console_python/Dockerfile

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
1-
FROM python:3.7-alpine
1+
#Build stage
2+
ARG PYTHON_VERSION=3.12
3+
FROM python:${PYTHON_VERSION} AS builder
24

3-
LABEL maintainer="Wasin Waeosri <wasin.waeosri@rifinitiv.com>"
4-
LABEL build_date="2019-07-30"
5+
LABEL maintainer="LSEG Developer Relations"
6+
LABEL build_date="2025-10-06"
57

6-
# Copy requirements.txt first
7-
COPY requirements.txt /
8-
# instruction to be run during image build
9-
RUN pip install -r requirements.txt
8+
#Copy requirements.txt
9+
COPY requirements.txt .
1010

11-
# then copy the application
12-
RUN mkdir /app
13-
COPY trcc_posting.py /app
14-
#COPY market_price.py /app
11+
# install dependencies to the local user directory (eg. /root/.local)
12+
#RUN pip install --no-cache-dir --user -r requirements.txt
13+
RUN pip install --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org --no-cache-dir --user -r requirements.txt
14+
15+
# Run stage
16+
FROM python:${PYTHON_VERSION}-alpine3.22
1517
WORKDIR /app
1618

19+
# Update PATH environment variable + set Python buffer to make Docker print every message instantly.
20+
ENV PATH=/root/.local:$PATH \
21+
PYTHONUNBUFFERED=1\
22+
PYTHONIOENCODING=utf-8\
23+
PYTHONLEGACYWINDOWSSTDIO=utf-8
24+
25+
# copy only the dependencies installation from the 1st stage image
26+
COPY --from=builder /root/.local /root/.local
27+
COPY trcc_posting.py /app
28+
29+
#Run Python
1730
ENTRYPOINT ["python", "-u","/app/trcc_posting.py"]

console_python/requirements.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
###### Requirements Libraries ######
2-
requests
3-
websocket-client>0.49
1+
certifi==2025.8.3
2+
charset-normalizer==3.4.3
3+
idna==3.10
4+
requests==2.32.5
5+
urllib3==2.5.0
6+
websocket-client==1.8.0

console_python/trcc_posting.py

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
# | This source code is provided under the Apache 2.0 license --
33
# | and is provided AS IS with no warranty or guarantee of fit for purpose. --
44
# | See the project's LICENSE.md for details. --
5-
# | Copyright Refinitiv 2020. All rights reserved. --
5+
# | Copyright LSEG 2025. All rights reserved. --
66
# |-----------------------------------------------------------------------------
77

88

99
#!/usr/bin/env python
10-
""" Simple example of posting Market Price JSON data To RCC via Refinitiv Real-Time Distribution System 3.2.x using Websockets """
10+
""" Simple example of posting Market Price JSON data To RCC via LSEG Real-Time Distribution System 3.2.x using Websockets """
1111

1212
import sys
1313
import time
@@ -20,7 +20,7 @@
2020
from threading import Thread, Event
2121

2222
# Global Default Variables
23-
hostname = '127.0.0.1'
23+
hostname = 'ADS_HOST'
2424
port = '15000'
2525
user = 'root'
2626
app_id = '256'
@@ -43,30 +43,30 @@ def process_message(ws, message_json): # Process all incoming messages.
4343
""" Parse at high level and output JSON of message """
4444
message_type = message_json['Type']
4545

46-
if message_type == "Refresh":
46+
if message_type == 'Refresh':
4747
if 'Domain' in message_json:
4848
message_domain = message_json['Domain']
49-
if message_domain == "Login":
49+
if message_domain == 'Login':
5050
process_login_response(ws, message_json)
51-
elif message_type == "Ping":
51+
elif message_type == 'Ping':
5252
pong_json = {'Type': 'Pong'}
5353
ws.send(json.dumps(pong_json))
54-
print("SENT:")
54+
print('SENT:')
5555
print(json.dumps(pong_json, sort_keys=True,
5656
indent=2, separators=(',', ':')))
5757

58-
""" If our TRI stream is now open, we can start sending posts. """
58+
#If our TRI stream is now open, we can start sending posts.
5959
global next_post_time
6060
if ('ID' in message_json and message_json['ID'] == 1 and next_post_time == 0 and
61-
(not 'State' in message_json or message_json['State']['Stream'] == "Open" and message_json['State']['Data'] == "Ok")):
61+
(not 'State' in message_json or message_json['State']['Stream'] == 'Open' and message_json['State']['Data'] == 'Ok')):
6262
next_post_time = time.time() + 3
6363
print('Here')
6464

6565

6666
# Process incoming Login Refresh Response message.
6767
def process_login_response(ws, message_json):
6868
""" Send Off-Stream Post """
69-
print("Sending Off-Stream Post to Real-Time Advanced Distribution Server")
69+
print('Sending Off-Stream Post to Real-Time Advanced Distribution Server')
7070
send_market_price_post(ws)
7171

7272

@@ -76,55 +76,55 @@ def send_market_price_post(ws):
7676
global bid_value
7777
global ask_value
7878
global primact_1_value
79-
""" Send a post message contains a market-price content to RCC """
79+
# Send a post message contains a market-price content to RCC
8080

81-
""" Contribution fields """
81+
# Contribution fields
8282
contribution_fields = {
83-
"BID": bid_value,
84-
"ASK": ask_value,
85-
"PRIMACT_1": primact_1_value
83+
'BID': bid_value,
84+
'ASK': ask_value,
85+
'PRIMACT_1': primact_1_value
8686
}
8787

88-
""" OMM Post msg Key """
88+
# OMM Post msg Key
8989
mp_post_key = {
90-
"Name": post_item_name,
91-
"Service": service_name
90+
'Name': post_item_name,
91+
'Service': service_name
9292
}
9393

94-
""" OMM Post Payload """
94+
# OMM Post Payload
9595
contribution_payload_json = {
96-
"ID": 0,
97-
"Type": "Update",
98-
"Domain": "MarketPrice",
99-
"Fields": contribution_fields,
100-
"Key": {}
96+
'ID': 0,
97+
'Type': 'Update',
98+
'Domain': 'MarketPrice',
99+
'Fields': contribution_fields,
100+
'Key': {}
101101
}
102102

103-
""" OMM Off-Stream Post message """
103+
# OMM Off-Stream Post message
104104
mp_post_json_offstream = {
105-
"Domain": "MarketPrice",
106-
"Ack": True,
107-
"PostID": post_id,
108-
"PostUserInfo": {
109-
"Address": position,
110-
"UserID": int(app_id)
105+
'Domain': 'MarketPrice',
106+
'Ack': True,
107+
'PostID': post_id,
108+
'PostUserInfo': {
109+
'Address': position,
110+
'UserID': int(app_id)
111111
},
112-
"Key": {},
113-
"Message": {},
114-
"Type": "Post",
115-
"ID": login_id
112+
'Key': {},
113+
'Message': {},
114+
'Type': 'Post',
115+
'ID': login_id
116116
}
117117

118-
contribution_payload_json["Key"] = mp_post_key
119-
mp_post_json_offstream["Key"] = mp_post_key
120-
mp_post_json_offstream["Message"] = contribution_payload_json
118+
contribution_payload_json['Key'] = mp_post_key
119+
mp_post_json_offstream['Key'] = mp_post_key
120+
mp_post_json_offstream['Message'] = contribution_payload_json
121121

122122
ws.send(json.dumps(mp_post_json_offstream))
123-
print("SENT:")
123+
print('SENT:')
124124
print(json.dumps(mp_post_json_offstream,
125125
sort_keys=True, indent=2, separators=(',', ':')))
126126

127-
""" increase post data value """
127+
# increase post data value
128128
post_id += 1
129129
bid_value += 1
130130
ask_value += 1
@@ -151,13 +151,13 @@ def send_login_request(ws):
151151
login_json['Key']['Elements']['Position'] = position
152152

153153
ws.send(json.dumps(login_json))
154-
print("SENT:")
154+
print('SENT:')
155155
print(json.dumps(login_json, sort_keys=True, indent=2, separators=(',', ':')))
156156

157157

158158
def on_message(ws, message):
159159
""" Called when message received, parse message into JSON for processing """
160-
print("RECEIVED: ")
160+
print('RECEIVED: ')
161161
message_json = json.loads(message)
162162
print(json.dumps(message_json, sort_keys=True, indent=2, separators=(',', ':')))
163163

@@ -170,56 +170,56 @@ def on_error(ws, error):
170170
print(error)
171171

172172

173-
def on_close(ws):
173+
def on_close(ws, close_status_code, close_msg):
174174
""" Called when websocket is closed """
175175
global web_socket_open
176-
print("WebSocket Closed")
176+
print(f'WebSocket Closed: {close_status_code} {close_msg}')
177177
web_socket_open = False
178178

179179

180180
def on_open(ws):
181181
""" Called when handshake is complete and websocket is open, send login """
182182

183-
print("WebSocket successfully connected!")
183+
print('WebSocket successfully connected!')
184184
global web_socket_open
185185
web_socket_open = True
186186
send_login_request(ws)
187187

188188

189-
if __name__ == "__main__":
189+
if __name__ == '__main__':
190190

191191
# Get command line parameters
192192
try:
193-
opts, args = getopt.getopt(sys.argv[1:], "", [
194-
"help", "hostname=", "port=", "app_id=", "user=", "position=", "item=", "service="])
193+
opts, args = getopt.getopt(sys.argv[1:], '', [
194+
'help', 'hostname=', 'port=', 'app_id=', 'user=', 'position=', 'item=', 'service='])
195195
print(opts)
196196
except getopt.GetoptError:
197197
print(
198198
'Usage: market_price.py [--hostname hostname] [--port port] [--app_id app_id] [--user user] [--position position] [--item post item name] [--service TRCC Post Service] [--help] ')
199199
sys.exit(2)
200200
for opt, arg in opts:
201-
if opt in ("--help"):
201+
if opt in ('--help'):
202202
print(
203203
'Usage: market_price.py [--hostname hostname] [--port port] [--app_id app_id] [--user user] [--position position] [--item post item name] [--service TRCC Post Service] [--help]')
204204
sys.exit(0)
205-
elif opt in ("--hostname"):
205+
elif opt in ('--hostname'):
206206
hostname = arg
207-
elif opt in ("--port"):
207+
elif opt in ('--port'):
208208
port = arg
209-
elif opt in ("--app_id"):
209+
elif opt in ('--app_id'):
210210
app_id = arg
211-
elif opt in ("--user"):
211+
elif opt in ('--user'):
212212
user = arg
213-
elif opt in ("--position"):
213+
elif opt in ('--position'):
214214
position = arg
215-
elif opt in ("--item"):
215+
elif opt in ('--item'):
216216
post_item_name = arg
217-
elif opt in ("--service"):
217+
elif opt in ('--service'):
218218
service_name = arg
219219

220220
# Start websocket handshake
221-
ws_address = "ws://{}:{}/WebSocket".format(hostname, port)
222-
print("Connecting to WebSocket " + ws_address + " ...")
221+
ws_address = f'ws://{hostname}:{port}/WebSocket'
222+
print(f'Connecting to WebSocket {ws_address} .... ')
223223
web_socket_app = websocket.WebSocketApp(ws_address, header=['User-Agent: Python'],
224224
on_message=on_message,
225225
on_error=on_error,

images/diagram_trcc_ws.png

17.4 KB
Loading
17.4 KB
Loading
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
anyio==4.11.0
2+
argon2-cffi==25.1.0
3+
argon2-cffi-bindings==25.1.0
4+
arrow==1.3.0
5+
asttokens==3.0.0
6+
async-lru==2.0.5
7+
attrs==25.3.0
8+
babel==2.17.0
9+
beautifulsoup4==4.14.2
10+
bleach==6.2.0
11+
certifi==2025.8.3
12+
cffi==2.0.0
13+
charset-normalizer==3.4.3
14+
colorama==0.4.6
15+
comm==0.2.3
16+
debugpy==1.8.17
17+
decorator==5.2.1
18+
defusedxml==0.7.1
19+
executing==2.2.1
20+
fastjsonschema==2.21.2
21+
fqdn==1.5.1
22+
h11==0.16.0
23+
httpcore==1.0.9
24+
httpx==0.28.1
25+
idna==3.10
26+
ipykernel==6.30.1
27+
ipython==9.6.0
28+
ipython_pygments_lexers==1.1.1
29+
isoduration==20.11.0
30+
jedi==0.19.2
31+
Jinja2==3.1.6
32+
json5==0.12.1
33+
jsonpointer==3.0.0
34+
jsonschema==4.25.1
35+
jsonschema-specifications==2025.9.1
36+
jupyter-events==0.12.0
37+
jupyter-lsp==2.3.0
38+
jupyter_client==8.6.3
39+
jupyter_core==5.8.1
40+
jupyter_server==2.17.0
41+
jupyter_server_terminals==0.5.3
42+
jupyterlab==4.4.9
43+
jupyterlab_pygments==0.3.0
44+
jupyterlab_server==2.27.3
45+
lark==1.3.0
46+
MarkupSafe==3.0.3
47+
matplotlib-inline==0.1.7
48+
mistune==3.1.4
49+
nbclient==0.10.2
50+
nbconvert==7.16.6
51+
nbformat==5.10.4
52+
nest-asyncio==1.6.0
53+
notebook_shim==0.2.4
54+
packaging==25.0
55+
pandocfilters==1.5.1
56+
parso==0.8.5
57+
platformdirs==4.4.0
58+
prometheus_client==0.23.1
59+
prompt_toolkit==3.0.52
60+
psutil==7.1.0
61+
pure_eval==0.2.3
62+
pycparser==2.23
63+
Pygments==2.19.2
64+
python-dateutil==2.9.0.post0
65+
python-json-logger==4.0.0
66+
pywin32==311 ; platform_system == "Windows"
67+
pywinpty==3.0.2 ; platform_system == "Windows"
68+
PyYAML==6.0.3
69+
pyzmq==27.1.0
70+
referencing==0.36.2
71+
requests==2.32.5
72+
rfc3339-validator==0.1.4
73+
rfc3986-validator==0.1.1
74+
rfc3987-syntax==1.1.0
75+
rpds-py==0.27.1
76+
Send2Trash==1.8.3
77+
setuptools==80.9.0
78+
six==1.17.0
79+
sniffio==1.3.1
80+
soupsieve==2.8
81+
stack-data==0.6.3
82+
terminado==0.18.1
83+
tinycss2==1.4.0
84+
tornado==6.5.2
85+
traitlets==5.14.3
86+
types-python-dateutil==2.9.0.20250822
87+
typing_extensions==4.15.0
88+
uri-template==1.3.0
89+
urllib3==2.5.0
90+
wcwidth==0.2.14
91+
webcolors==24.11.1
92+
webencodings==0.5.1
93+
websocket-client==1.8.0

0 commit comments

Comments
 (0)