millistream-mdf 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- millistream_mdf-0.1.0/PKG-INFO +560 -0
- millistream_mdf-0.1.0/README.md +550 -0
- millistream_mdf-0.1.0/pyproject.toml +14 -0
- millistream_mdf-0.1.0/setup.cfg +4 -0
- millistream_mdf-0.1.0/src/millistream_mdf/__init__.py +42 -0
- millistream_mdf-0.1.0/src/millistream_mdf/__main__.py +131 -0
- millistream_mdf-0.1.0/src/millistream_mdf/_constants.py +650 -0
- millistream_mdf-0.1.0/src/millistream_mdf/_libmdf.py +561 -0
- millistream_mdf-0.1.0/src/millistream_mdf/_mappings.py +402 -0
- millistream_mdf-0.1.0/src/millistream_mdf/async_client.py +529 -0
- millistream_mdf-0.1.0/src/millistream_mdf/exceptions.py +142 -0
- millistream_mdf-0.1.0/src/millistream_mdf/message.py +181 -0
- millistream_mdf-0.1.0/src/millistream_mdf/message_builder.py +184 -0
- millistream_mdf-0.1.0/src/millistream_mdf/sync_client.py +502 -0
- millistream_mdf-0.1.0/src/millistream_mdf/types.py +49 -0
- millistream_mdf-0.1.0/src/millistream_mdf.egg-info/PKG-INFO +560 -0
- millistream_mdf-0.1.0/src/millistream_mdf.egg-info/SOURCES.txt +18 -0
- millistream_mdf-0.1.0/src/millistream_mdf.egg-info/dependency_links.txt +1 -0
- millistream_mdf-0.1.0/src/millistream_mdf.egg-info/requires.txt +1 -0
- millistream_mdf-0.1.0/src/millistream_mdf.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: millistream-mdf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A high-level, intuitive Python wrapper for the libmdf C SDK, providing easy access to Millistream Data Feed servers for real-time financial data streaming.
|
|
5
|
+
Author-email: Gustav Frison <gustav.frison@millistream.com>
|
|
6
|
+
Keywords: millistream,mdf,data feed,financial data,real-time data
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: rich>=14.2.0
|
|
10
|
+
|
|
11
|
+
# millistream-mdf
|
|
12
|
+
|
|
13
|
+
<div align="center">
|
|
14
|
+
|
|
15
|
+
<strong>Python wrapper for the Millistream Data Feed (MDF) C SDK</strong>
|
|
16
|
+
|
|
17
|
+
[![PyPI][pypi-badge]][pypi-url]
|
|
18
|
+
[![Python Version][python-badge]][python-url]
|
|
19
|
+
[![Documentation][docs-badge]][docs-url]
|
|
20
|
+
|
|
21
|
+
[pypi-badge]: https://img.shields.io/pypi/v/millistream-mdf.svg
|
|
22
|
+
[pypi-url]: https://pypi.org/project/millistream-mdf/
|
|
23
|
+
[python-badge]: https://img.shields.io/badge/python-3.13+-blue.svg
|
|
24
|
+
[python-url]: https://pypi.org/project/millistream-mdf/
|
|
25
|
+
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
|
|
26
|
+
[docs-url]: https://packages.millistream.com/documents/
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## Table of Contents
|
|
31
|
+
|
|
32
|
+
- [Overview](#overview)
|
|
33
|
+
- [Installation](#installation)
|
|
34
|
+
- [Install Package](#1-install-package)
|
|
35
|
+
- [Install Prerequisites](#2-install-prerequisites)
|
|
36
|
+
- [Quick Start](#quick-start)
|
|
37
|
+
- [API Reference](#api-reference)
|
|
38
|
+
- [MDF Class](#mdf-class)
|
|
39
|
+
- [Message Class](#message-class)
|
|
40
|
+
- [Usage Examples](#usage-examples)
|
|
41
|
+
- [News Streaming](#news-streaming)
|
|
42
|
+
- [Sending Data](#sending-data)
|
|
43
|
+
- [Manual Connection Control](#manual-connection-control)
|
|
44
|
+
- [Available Data Types](#available-data-types)
|
|
45
|
+
- [Request Classes](#request-classes)
|
|
46
|
+
- [Subscription Modes](#subscription-modes)
|
|
47
|
+
- [Error Handling](#error-handling)
|
|
48
|
+
- [Documentation](#documentation)
|
|
49
|
+
- [License](#license)
|
|
50
|
+
- [Support](#support)
|
|
51
|
+
|
|
52
|
+
## Overview
|
|
53
|
+
|
|
54
|
+
A high-level Python wrapper for the libmdf C SDK, providing access to the Millistream Data Feed (MDF) for real-time financial data streaming.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
### 1. Install Package
|
|
59
|
+
|
|
60
|
+
Install with **[uv](https://docs.astral.sh/uv/)**:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv add millistream-mdf
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or with **[pip](https://pip.pypa.io/)**:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install millistream-mdf
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Install Prerequisites
|
|
73
|
+
|
|
74
|
+
**Ubuntu/Debian**
|
|
75
|
+
```bash
|
|
76
|
+
python -m millistream_mdf --install-deps
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For manual installation, refer to the [official documentation](https://packages.millistream.com/Linux/).
|
|
80
|
+
|
|
81
|
+
**macOS**
|
|
82
|
+
|
|
83
|
+
It is recommended to use the latest [libmdf installer](https://packages.millistream.com/macOS/) to install the necessary dependencies for macOS.
|
|
84
|
+
|
|
85
|
+
> **Note:** Will most likely be named `libmdf-x.x.x.pkg`.
|
|
86
|
+
|
|
87
|
+
**Windows**
|
|
88
|
+
|
|
89
|
+
It is recommended to use the latest [libmdf installer](https://packages.millistream.com/Windows/) to install the necessary dependencies for Windows.
|
|
90
|
+
|
|
91
|
+
>**Note:** Will most likely be named `libmdf-x.x.x.exe`.
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from millistream_mdf import MDF, RequestClass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
with MDF(
|
|
100
|
+
url='sandbox.millistream.com',
|
|
101
|
+
port=9100,
|
|
102
|
+
username='sandbox',
|
|
103
|
+
password='sandbox'
|
|
104
|
+
) as session:
|
|
105
|
+
|
|
106
|
+
for message in session.subscribe(
|
|
107
|
+
request_classes=[RequestClass.QUOTE], # Subscrive to 'quote' data
|
|
108
|
+
instruments=[1146], # Volvo B
|
|
109
|
+
timeout=1
|
|
110
|
+
):
|
|
111
|
+
print('raw:', message.fields) # unformatted fields
|
|
112
|
+
print('parsed:', message.parse_fields(remap_keys=True)) # convert types and/or format keys
|
|
113
|
+
|
|
114
|
+
print('---')
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
or using the **asyncio** API:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from millistream_mdf import AsyncMDF, RequestClass
|
|
121
|
+
import asyncio
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def main():
|
|
125
|
+
|
|
126
|
+
async with AsyncMDF(
|
|
127
|
+
url='sandbox.millistream.com',
|
|
128
|
+
port=9100,
|
|
129
|
+
username='sandbox',
|
|
130
|
+
password='sandbox'
|
|
131
|
+
) as session:
|
|
132
|
+
|
|
133
|
+
async for message in session.subscribe(
|
|
134
|
+
request_classes=[RequestClass.QUOTE], # Subscrive to 'quote' data
|
|
135
|
+
instruments=[1146], # Volvo B
|
|
136
|
+
timeout=1
|
|
137
|
+
):
|
|
138
|
+
print('raw:', message.fields) # unformatted fields
|
|
139
|
+
print('parsed:', message.parse_fields(remap_keys=True)) # convert types and/or format keys
|
|
140
|
+
|
|
141
|
+
print('---')
|
|
142
|
+
|
|
143
|
+
asyncio.run(main())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **Tip:** You can use `sandbox.millistream.com:9100` for free to test the MDF with username: `sandbox` and password: `sandbox`. The data will be delayed and might not have access to the full offering.
|
|
147
|
+
|
|
148
|
+
> **Tip:** If you only want to convert the types you can use `parse_fields(remap_keys=False, convert_types=[...])`
|
|
149
|
+
|
|
150
|
+
**Example Output:**
|
|
151
|
+
```
|
|
152
|
+
raw: {5: '272.60', 6: '272.80', 19: '1559', 20: '1988', 7: '272.80', 10: '2139312', 11: '584322621.58', 37: '7116', 8: '275.10', 9: '271.80', 39: '275', 123: '273.07232827', 367: '105139', 368: '28856646.78', 369: None, 370: None, 3: '2025-10-11', 4: '15:29:40'}
|
|
153
|
+
parsed: {'bidprice': 272.6, 'askprice': 272.8, 'bidquantity': 1559.0, 'askquantity': 1988.0, 'lastprice': 272.8, 'quantity': 2139312.0, 'turnover': 584322621.58, 'numtrades': 7116, 'dayhighprice': 275.1, 'daylowprice': 271.8, 'openprice': 275.0, 'vwap': 273.07232827, 'offbookquantity': '105139', 'offbookturnover': '28856646.78', 'darkquantity': None, 'darkturnover': None, 'date': datetime.date(2025, 10, 11), 'time': datetime.time(15, 29, 40)}
|
|
154
|
+
---
|
|
155
|
+
raw: {20: '31', 4: '15:29:45'}
|
|
156
|
+
parsed: {'askquantity': 31.0, 'time': datetime.time(15, 29, 45)}
|
|
157
|
+
---
|
|
158
|
+
raw: {19: '4796', 4: '15:29:57'}
|
|
159
|
+
parsed: {'bidquantity': 4796.0, 'time': datetime.time(15, 29, 57)}
|
|
160
|
+
---
|
|
161
|
+
raw: {19: '3173', 20: '1432', 4: '15:30:02'}
|
|
162
|
+
parsed: {'bidquantity': 3173.0, 'askquantity': 1432.0, 'time': datetime.time(15, 30, 2)}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
> **Note:** Only the differences are broadcasted for efficacy. In the example above a full image is broadcasted at the beginning since [`subscription_mode`](#subcriptions-modes) defaults to `full` (`image` + `stream`).
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
## API Reference
|
|
169
|
+
|
|
170
|
+
### MDF Class
|
|
171
|
+
|
|
172
|
+
The main client class for connecting to MDF servers.
|
|
173
|
+
|
|
174
|
+
#### Constructor Parameters
|
|
175
|
+
|
|
176
|
+
| Name | Type | Description | Default |
|
|
177
|
+
|---------------------|-----------------|--------------------------------------------------|------------|
|
|
178
|
+
| `url` | `str` | Server URL | |
|
|
179
|
+
| `port` | `int` | Server port | 9100 |
|
|
180
|
+
| `username` | `str` | Username for authentication | |
|
|
181
|
+
| `password` | `str` | Password for authentication | |
|
|
182
|
+
| `heartbeat_interval`| `int`, `float` | Heartbeat interval in seconds | 30 |
|
|
183
|
+
| `connect_timeout` | `int`, `float` | Connection timeout in seconds | 10 |
|
|
184
|
+
| `tcp_nodelay` | `bool` | Disable TCP Nagle algorithm | True |
|
|
185
|
+
| `no_encryption` | `bool` | Disable encryption | False |
|
|
186
|
+
|
|
187
|
+
#### Attributes
|
|
188
|
+
|
|
189
|
+
All [constructor parameters](#constructor-parameters)
|
|
190
|
+
|
|
191
|
+
#### Properties
|
|
192
|
+
|
|
193
|
+
| Name | Type | Description | Default |
|
|
194
|
+
|---------------------|-----------------|--------------------------------------------------|------------|
|
|
195
|
+
| `is_connected` | `bool` | Whether the client is connected to the server | False |
|
|
196
|
+
| `is_authenticated` | `bool` | Whether the client is authenticated to the server| False |
|
|
197
|
+
|
|
198
|
+
#### Methods
|
|
199
|
+
|
|
200
|
+
##### `connect()`
|
|
201
|
+
Connect to the MDF server and authenticate.
|
|
202
|
+
|
|
203
|
+
**Raises:**
|
|
204
|
+
- [`MDFConnectionError`](#exception-types): If connection fails
|
|
205
|
+
- [`MDFAuthenticationError`](#exception-types): If authentication fails
|
|
206
|
+
|
|
207
|
+
##### `disconnect()`
|
|
208
|
+
Disconnect from the MDF server.
|
|
209
|
+
|
|
210
|
+
##### `subscribe(request_classes, instruments='*', subscription_mode='full', timeout=1)`
|
|
211
|
+
Subscribe to data streams and yield messages.
|
|
212
|
+
|
|
213
|
+
**Parameters:**
|
|
214
|
+
- `request_classes`: List of request classes to subscribe to (e.g., `[RequestClass.QUOTE, RequestClass.TRADE, RequestClass.BASICDATA]`). Can be string names or integer MREF codes
|
|
215
|
+
- `instruments`: Instrument references to subscribe to. Can be `'*'` for all, or numeric IDs (e.g., `[1146, 1147]`)
|
|
216
|
+
- `subscription_mode`: Subscription mode (`'image'`, `'stream'`, or `'full'`). See [`Subscription Modes`](#subcriptions-modes) for more information.
|
|
217
|
+
- `timeout`: Timeout in seconds for consume operations
|
|
218
|
+
|
|
219
|
+
**Returns:** Generator yielding [`Message`](#message-class) objects
|
|
220
|
+
|
|
221
|
+
##### `stream(timeout=1)`
|
|
222
|
+
Stream messages from the server.
|
|
223
|
+
|
|
224
|
+
**Parameters:**
|
|
225
|
+
- `timeout`: Timeout in seconds for consume operations
|
|
226
|
+
|
|
227
|
+
**Returns:** Generator yielding [`Message`](#message-class) objects
|
|
228
|
+
|
|
229
|
+
##### `send(mref, instrument, fields, delay=0)`
|
|
230
|
+
Send a single message to the server with specified fields.
|
|
231
|
+
|
|
232
|
+
**Parameters:**
|
|
233
|
+
- `mref`: Message reference (e.g., `MessageReference.QUOTE`, `MessageReference.TRADE`)
|
|
234
|
+
- `instrument`: Instrument reference
|
|
235
|
+
- `fields`: Dictionary mapping field names to values
|
|
236
|
+
- `delay`: Optional delay parameter (default: `0`)
|
|
237
|
+
|
|
238
|
+
**Returns:** `True` if the message was sent successfully
|
|
239
|
+
|
|
240
|
+
**Raises:**
|
|
241
|
+
- [`MDFError`](#exception-types): If not connected or authenticated
|
|
242
|
+
- [`MDFMessageError`](#exception-types): If message construction or sending fails
|
|
243
|
+
|
|
244
|
+
**Example:**
|
|
245
|
+
```python
|
|
246
|
+
client.send(
|
|
247
|
+
mref=MessageReference.QUOTE,
|
|
248
|
+
instrument=12345,
|
|
249
|
+
fields={
|
|
250
|
+
Field.BIDPRICE: 100.50,
|
|
251
|
+
Field.ASKPRICE: 100.55,
|
|
252
|
+
Field.BIDQUANTITY: 1000,
|
|
253
|
+
Field.ASKQUANTITY: 500
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
##### `send_batch(messages)`
|
|
259
|
+
Send multiple messages in a single batch for better efficiency.
|
|
260
|
+
|
|
261
|
+
**Parameters:**
|
|
262
|
+
- `messages`: List of message dictionaries with `'mref'`, `'instrument'`, `'fields'`, and optionally `'delay'`
|
|
263
|
+
|
|
264
|
+
**Returns:** `True` if all messages were sent successfully
|
|
265
|
+
|
|
266
|
+
**Example:**
|
|
267
|
+
```python
|
|
268
|
+
client.send_batch([
|
|
269
|
+
{
|
|
270
|
+
'mref': MessageReference.QUOTE,
|
|
271
|
+
'instrument': 12345,
|
|
272
|
+
'fields': {Field.BIDPRICE: 100.50, Field.ASKPRICE: 100.55},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
'mref': MessageReference.TRADE,
|
|
276
|
+
'instrument': 12345,
|
|
277
|
+
'fields': {Field.TRADEPRICE: 100.52, Field.TRADEQUANTITY: 1000},
|
|
278
|
+
}
|
|
279
|
+
])
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
##### `create_message_builder()`
|
|
283
|
+
Create a new `MessageBuilder` for advanced message construction.
|
|
284
|
+
|
|
285
|
+
**Returns:** A new `MessageBuilder` instance (must be used as context manager)
|
|
286
|
+
|
|
287
|
+
**Example:**
|
|
288
|
+
```python
|
|
289
|
+
with client.create_message_builder() as builder:
|
|
290
|
+
builder.add_message(mref=MessageReference.QUOTE, instrument=12345)
|
|
291
|
+
builder.add_field(Field.BIDPRICE, 100.50)
|
|
292
|
+
builder.add_field(Field.ASKPRICE, 100.55)
|
|
293
|
+
builder.send(client._handle)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Message Class
|
|
297
|
+
|
|
298
|
+
Represents a message received from the MDF server.
|
|
299
|
+
|
|
300
|
+
#### Attributes
|
|
301
|
+
|
|
302
|
+
| Name | Type | Description | Default | Example |
|
|
303
|
+
|-----------------|----------------------------------------|------------------------------------------------------------------------|-----------|--------------------------------------------------|
|
|
304
|
+
| `ref` | `int` | What type of message it is (e.g. `MessageReference.NEWSHEADLINE`) | | `MessageReference.QUOTE` |
|
|
305
|
+
| `instrument` | `int` | Instrument reference ID | | `12345` |
|
|
306
|
+
| `fields` | `dict[int, str \| None]` | Dictionary of field: value pairs (raw values) | `{}` | `{Field.BIDPRICE: "100.50", Field.ASKPRICE: "100.55"}` |
|
|
307
|
+
| `delay` | `int` | Message delay type | `0` | `0` |
|
|
308
|
+
|
|
309
|
+
#### Properties
|
|
310
|
+
|
|
311
|
+
| Name | Type | Description | Default | Example |
|
|
312
|
+
|-----------------|----------------------------------------|------------------------------------------------------------------------|-----------|--------------------------------------------------|
|
|
313
|
+
| `parsed_fields` | `dict[str \| int, str \| int \| float \| date \| time \| datetime \| list[str]]` | Dictionary of field: value pairs with parsed types | | `{'bidprice': 100.50, 'askprice': 100.55}` |
|
|
314
|
+
|
|
315
|
+
> **Note:** For a list items are always `str`. The type of each item in the list is not guaranteed. For general type casting the type will have to be guessed.
|
|
316
|
+
|
|
317
|
+
#### Methods
|
|
318
|
+
|
|
319
|
+
##### `parse_fields(remap_keys=True, convert_types=['str', 'int', 'float', 'date', 'time', 'datetime', 'list'], on_field_missing='ignore', list_delimiter=' ')`
|
|
320
|
+
Parse and convert field values to their proper types.
|
|
321
|
+
|
|
322
|
+
**Parameters:**
|
|
323
|
+
- `remap_keys`: If `True`, use lowercase field names as keys; else use field IDs
|
|
324
|
+
- `convert_types`: Which types to convert (`str`, `int`, `float`, `date`, `time`, `datetime`, `list`)
|
|
325
|
+
- `on_field_missing`: How to handle unmapped fields (`'raise'`, `'ignore'`, `'skip'`)
|
|
326
|
+
- `list_delimiter`: Delimiter to split list values on
|
|
327
|
+
|
|
328
|
+
**Returns:** Dictionary with converted values
|
|
329
|
+
|
|
330
|
+
##### `get(field, default=None)`
|
|
331
|
+
Get field value by name with optional default.
|
|
332
|
+
|
|
333
|
+
##### `__getitem__(field)`
|
|
334
|
+
Allow dict-like access to fields: `message[Field.BIDPRICE]`
|
|
335
|
+
|
|
336
|
+
##### `__contains__(field)`
|
|
337
|
+
Check if field exists: `Field.BIDPRICE in message`
|
|
338
|
+
|
|
339
|
+
## Usage Examples
|
|
340
|
+
|
|
341
|
+
### News Streaming
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from millistream_mdf import MDF, RequestClass, MessageReference, Field
|
|
345
|
+
|
|
346
|
+
with MDF(
|
|
347
|
+
url='sandbox.millistream.com',
|
|
348
|
+
port=9100,
|
|
349
|
+
username='sandbox',
|
|
350
|
+
password='sandbox'
|
|
351
|
+
) as session:
|
|
352
|
+
|
|
353
|
+
for message in session.subscribe(
|
|
354
|
+
request_classes=[RequestClass.NEWSHEADLINE, RequestClass.NEWSCONTENT],
|
|
355
|
+
subscription_mode='stream',
|
|
356
|
+
instruments='*',
|
|
357
|
+
timeout=1
|
|
358
|
+
):
|
|
359
|
+
if message.ref == MessageReference.NEWSHEADLINE:
|
|
360
|
+
print(f"Headline: {message.get(Field.HEADLINE)}")
|
|
361
|
+
print(f"Date: {message.get(Field.DATE)}")
|
|
362
|
+
|
|
363
|
+
elif message.ref == MessageReference.NEWSCONTENT:
|
|
364
|
+
print(f'Content: {message.get(Field.TEXTBODY, 'N/A')[:100]}...')
|
|
365
|
+
print('---')
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
> **Tip:** You can use `'*'` for all available instruments.
|
|
369
|
+
|
|
370
|
+
> **Note:** [`RequestClass`](#request-classes) and [`MessageReference`](#message-reference) have overlapping names but serve different purposes and have different integer values.
|
|
371
|
+
|
|
372
|
+
**Example Output:**
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
Headline: Antibiotics Market Size to Surpass USD 55.26 Billion by 2033, Report by DataM Intelligence
|
|
376
|
+
Date: 2025-10-10
|
|
377
|
+
Content: <?xml version="1.0" encoding="UTF-8"?><NewsItem><NewsEnvelope><TransmissionId>202509221001PR_NEWS_EU...
|
|
378
|
+
---
|
|
379
|
+
Headline: Aventis Energy Confirms Strong Radioactivity at Corvo Uranium Project
|
|
380
|
+
Date: 2025-10-10
|
|
381
|
+
Content: <?xml version="1.0" encoding="UTF-8"?><NewsItem><NewsEnvelope><TransmissionId>A3462546</Transmission...
|
|
382
|
+
---
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
> **Note:** Headlines and content are sent in different messages so that the headline can be recieved as quick as possible. You can pair them together using the `Field.NEWSID` field.
|
|
386
|
+
|
|
387
|
+
### Sending Data
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
from millistream_mdf import MDF, MessageReference, Field
|
|
391
|
+
|
|
392
|
+
# Simple message sending
|
|
393
|
+
with MDF(
|
|
394
|
+
url='server.example.com',
|
|
395
|
+
port=9100,
|
|
396
|
+
username='user',
|
|
397
|
+
password='pass'
|
|
398
|
+
) as client:
|
|
399
|
+
|
|
400
|
+
# Send a single quote message
|
|
401
|
+
client.send(
|
|
402
|
+
mref=MessageReference.QUOTE,
|
|
403
|
+
instrument=12345,
|
|
404
|
+
fields={
|
|
405
|
+
Field.BIDPRICE: 100.50,
|
|
406
|
+
Field.ASKPRICE: 100.55,
|
|
407
|
+
Field.BIDQUANTITY: 1000,
|
|
408
|
+
Field.ASKQUANTITY: 500,
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Send multiple messages in a batch (more efficient)
|
|
413
|
+
client.send_batch([
|
|
414
|
+
{
|
|
415
|
+
'mref': MessageReference.QUOTE,
|
|
416
|
+
'instrument': 12345,
|
|
417
|
+
'fields': {Field.BIDPRICE: 100.50, Field.ASKPRICE: 100.55},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
'mref': MessageReference.TRADE,
|
|
421
|
+
'instrument': 12345,
|
|
422
|
+
'fields': {Field.TRADEPRICE: 100.52, Field.TRADEQUANTITY: 1000},
|
|
423
|
+
}
|
|
424
|
+
])
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Manual Connection Control
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
from millistream_mdf import MDF, MDFError
|
|
431
|
+
|
|
432
|
+
session = MDF(
|
|
433
|
+
url='sandbox.millistream.com',
|
|
434
|
+
port=9100,
|
|
435
|
+
username='sandbox',
|
|
436
|
+
password='sandbox'
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
session.connect()
|
|
441
|
+
print("Connected!")
|
|
442
|
+
|
|
443
|
+
# Subscribe to quote data
|
|
444
|
+
session.subscribe(request_classes=[RequestClass.QUOTE], instruments='*')
|
|
445
|
+
|
|
446
|
+
# Stream subscribed data
|
|
447
|
+
for message in session.stream(timeout=1):
|
|
448
|
+
print(message.fields)
|
|
449
|
+
print("---")
|
|
450
|
+
|
|
451
|
+
except MDFError as e:
|
|
452
|
+
print(f"Error: {e}")
|
|
453
|
+
finally:
|
|
454
|
+
session.disconnect()
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Available Data Types
|
|
458
|
+
|
|
459
|
+
### Request Classes
|
|
460
|
+
|
|
461
|
+
- `NEWSHEADLINE`: News headlines
|
|
462
|
+
- `NEWSCONTENT`: Full news content
|
|
463
|
+
- `QUOTE`: Market quotes (bid/ask)
|
|
464
|
+
- `TRADE`: Trade executions
|
|
465
|
+
- `ORDER`: Order book data
|
|
466
|
+
- `BASICDATA`: Instrument basic information
|
|
467
|
+
- `PRICEHISTORY`: Historical price data
|
|
468
|
+
- `CORPORATEACTION`: Corporate actions
|
|
469
|
+
- `FUNDAMENTALS`: Financial fundamentals
|
|
470
|
+
- `PERFORMANCE`: Performance metrics
|
|
471
|
+
- `KEYRATIOS`: Key financial ratios
|
|
472
|
+
- `ESTIMATES`: Analyst estimates
|
|
473
|
+
- `MIFID`: MiFID II data
|
|
474
|
+
- `GREEKS`: Options Greeks
|
|
475
|
+
- And more...
|
|
476
|
+
|
|
477
|
+
### Message Reference
|
|
478
|
+
|
|
479
|
+
- `MESSAGESREFERENCE`: Message reference
|
|
480
|
+
- `LOGON`: Logon
|
|
481
|
+
- `LOGOFF`: Logoff
|
|
482
|
+
- `LOGONGREETING`: Logon greeting
|
|
483
|
+
- `NEWSHEADLINE`: News headline
|
|
484
|
+
- `QUOTE`: Quote
|
|
485
|
+
- `TRADE`: Trade
|
|
486
|
+
- `BIDLEVELINSERT`: Bid level insert
|
|
487
|
+
- `ASKLEVELINSERT`: Ask level insert
|
|
488
|
+
- `BIDLEVELDELETE`: Bid level delete
|
|
489
|
+
- `ASKLEVELDELETE`: Ask level delete
|
|
490
|
+
- `BIDLEVELUPDATE`: Bid level update
|
|
491
|
+
- `ASKLEVELUPDATE`: Ask level update
|
|
492
|
+
- `INSTRUMENTRESET`: Instrument reset
|
|
493
|
+
- And [more](https://packages.millistream.com/documents/MDF%20Messages%20Reference.pdf)...
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
### Subscription Modes
|
|
497
|
+
|
|
498
|
+
- `image`: Snapshot of current values
|
|
499
|
+
- `stream`: Streaming data only
|
|
500
|
+
- `full`: Both image and stream
|
|
501
|
+
|
|
502
|
+
## Error Handling
|
|
503
|
+
|
|
504
|
+
The wrapper provides a comprehensive exception hierarchy:
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
from millistream_mdf import (
|
|
508
|
+
MDFError,
|
|
509
|
+
MDFConnectionError,
|
|
510
|
+
MDFAuthenticationError,
|
|
511
|
+
RequestClass
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
with MDF(
|
|
516
|
+
url='sandbox.millistream.com',
|
|
517
|
+
port=9100,
|
|
518
|
+
username='sandbox',
|
|
519
|
+
password='sandbox'
|
|
520
|
+
) as session:
|
|
521
|
+
for message in session.subscribe(request_classes=[RequestClass.QUOTE], instruments='*'):
|
|
522
|
+
print(message)
|
|
523
|
+
|
|
524
|
+
except MDFConnectionError as e:
|
|
525
|
+
print(f"Connection failed: {e}")
|
|
526
|
+
except MDFAuthenticationError as e:
|
|
527
|
+
print(f"Authentication failed: {e}")
|
|
528
|
+
except MDFError as e:
|
|
529
|
+
print(f"MDF error: {e}")
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Exception Types
|
|
533
|
+
|
|
534
|
+
- `MDFError`: Base exception for all MDF-related errors
|
|
535
|
+
- `MDFConnectionError`: Connection failures
|
|
536
|
+
- `MDFAuthenticationError`: Login failures
|
|
537
|
+
- `MDFTimeoutError`: Timeout errors
|
|
538
|
+
- `MDFMessageError`: Message operation failures
|
|
539
|
+
- `MDFConfigurationError`: Invalid configuration
|
|
540
|
+
- `MDFLibraryError`: Underlying library errors
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
## Documentation
|
|
544
|
+
|
|
545
|
+
For more detailed documentation, visit the [official documentation](https://packages.millistream.com/documents/) or the [millistream sandbox](https://sandbox.millistream.com/sandbox/mdf).
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
## License
|
|
549
|
+
|
|
550
|
+
This wrapper is provided under the LGPL v3 license, the same as the underlying libmdf library.
|
|
551
|
+
|
|
552
|
+
## Support
|
|
553
|
+
|
|
554
|
+
For issues with this Python wrapper:
|
|
555
|
+
|
|
556
|
+
1. Check this documentation
|
|
557
|
+
2. Check error messages and exception types
|
|
558
|
+
3. [Open an issue](https://github.com/mint273/millistream-mdf/issues) on GitHub
|
|
559
|
+
|
|
560
|
+
For *libmdf* library issues, refer to the official Millistream documentation or contact tech@millistream.com.
|