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.
Project description
millistream-mdf
Table of Contents
- Overview
- Installation
- Quick Start
- API Reference
- Usage Examples
- Available Data Types
- Error Handling
- Documentation
- License
- Support
Overview
A high-level Python wrapper for the libmdf C SDK, providing access to the Millistream Data Feed (MDF) for real-time financial data streaming.
Installation
1. Install Package
Install with uv:
uv add millistream-mdf
Or with pip:
pip install millistream-mdf
2. Install Prerequisites
Ubuntu/Debian
python -m millistream_mdf --install-deps
For manual installation, refer to the official documentation.
macOS
It is recommended to use the latest libmdf installer to install the necessary dependencies for macOS.
Note: Will most likely be named
libmdf-x.x.x.pkg.
Windows
It is recommended to use the latest libmdf installer to install the necessary dependencies for Windows.
Note: Will most likely be named
libmdf-x.x.x.exe.
Quick Start
from millistream_mdf import MDF, RequestClass
with MDF(
url='sandbox.millistream.com',
port=9100,
username='sandbox',
password='sandbox'
) as session:
for message in session.subscribe(
request_classes=[RequestClass.QUOTE], # Subscrive to 'quote' data
instruments=[1146], # Volvo B
timeout=1
):
print('raw:', message.fields) # unformatted fields
print('parsed:', message.parse_fields(remap_keys=True)) # convert types and/or format keys
print('---')
or using the asyncio API:
from millistream_mdf import AsyncMDF, RequestClass
import asyncio
async def main():
async with AsyncMDF(
url='sandbox.millistream.com',
port=9100,
username='sandbox',
password='sandbox'
) as session:
async for message in session.subscribe(
request_classes=[RequestClass.QUOTE], # Subscrive to 'quote' data
instruments=[1146], # Volvo B
timeout=1
):
print('raw:', message.fields) # unformatted fields
print('parsed:', message.parse_fields(remap_keys=True)) # convert types and/or format keys
print('---')
asyncio.run(main())
Tip: You can use
sandbox.millistream.com:9100for free to test the MDF with username:sandboxand password:sandbox. The data will be delayed and might not have access to the full offering.
Tip: If you only want to convert the types you can use
parse_fields(remap_keys=False, convert_types=[...])
Example Output:
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'}
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)}
---
raw: {20: '31', 4: '15:29:45'}
parsed: {'askquantity': 31.0, 'time': datetime.time(15, 29, 45)}
---
raw: {19: '4796', 4: '15:29:57'}
parsed: {'bidquantity': 4796.0, 'time': datetime.time(15, 29, 57)}
---
raw: {19: '3173', 20: '1432', 4: '15:30:02'}
parsed: {'bidquantity': 3173.0, 'askquantity': 1432.0, 'time': datetime.time(15, 30, 2)}
Note: Only the differences are broadcasted for efficacy. In the example above a full image is broadcasted at the beginning since
subscription_modedefaults tofull(image+stream).
API Reference
MDF Class
The main client class for connecting to MDF servers.
Constructor Parameters
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Server URL | |
port |
int |
Server port | 9100 |
username |
str |
Username for authentication | |
password |
str |
Password for authentication | |
heartbeat_interval |
int, float |
Heartbeat interval in seconds | 30 |
connect_timeout |
int, float |
Connection timeout in seconds | 10 |
tcp_nodelay |
bool |
Disable TCP Nagle algorithm | True |
no_encryption |
bool |
Disable encryption | False |
Attributes
Properties
| Name | Type | Description | Default |
|---|---|---|---|
is_connected |
bool |
Whether the client is connected to the server | False |
is_authenticated |
bool |
Whether the client is authenticated to the server | False |
Methods
connect()
Connect to the MDF server and authenticate.
Raises:
MDFConnectionError: If connection failsMDFAuthenticationError: If authentication fails
disconnect()
Disconnect from the MDF server.
subscribe(request_classes, instruments='*', subscription_mode='full', timeout=1)
Subscribe to data streams and yield messages.
Parameters:
request_classes: List of request classes to subscribe to (e.g.,[RequestClass.QUOTE, RequestClass.TRADE, RequestClass.BASICDATA]). Can be string names or integer MREF codesinstruments: Instrument references to subscribe to. Can be'*'for all, or numeric IDs (e.g.,[1146, 1147])subscription_mode: Subscription mode ('image','stream', or'full'). SeeSubscription Modesfor more information.timeout: Timeout in seconds for consume operations
Returns: Generator yielding Message objects
stream(timeout=1)
Stream messages from the server.
Parameters:
timeout: Timeout in seconds for consume operations
Returns: Generator yielding Message objects
send(mref, instrument, fields, delay=0)
Send a single message to the server with specified fields.
Parameters:
mref: Message reference (e.g.,MessageReference.QUOTE,MessageReference.TRADE)instrument: Instrument referencefields: Dictionary mapping field names to valuesdelay: Optional delay parameter (default:0)
Returns: True if the message was sent successfully
Raises:
MDFError: If not connected or authenticatedMDFMessageError: If message construction or sending fails
Example:
client.send(
mref=MessageReference.QUOTE,
instrument=12345,
fields={
Field.BIDPRICE: 100.50,
Field.ASKPRICE: 100.55,
Field.BIDQUANTITY: 1000,
Field.ASKQUANTITY: 500
}
)
send_batch(messages)
Send multiple messages in a single batch for better efficiency.
Parameters:
messages: List of message dictionaries with'mref','instrument','fields', and optionally'delay'
Returns: True if all messages were sent successfully
Example:
client.send_batch([
{
'mref': MessageReference.QUOTE,
'instrument': 12345,
'fields': {Field.BIDPRICE: 100.50, Field.ASKPRICE: 100.55},
},
{
'mref': MessageReference.TRADE,
'instrument': 12345,
'fields': {Field.TRADEPRICE: 100.52, Field.TRADEQUANTITY: 1000},
}
])
create_message_builder()
Create a new MessageBuilder for advanced message construction.
Returns: A new MessageBuilder instance (must be used as context manager)
Example:
with client.create_message_builder() as builder:
builder.add_message(mref=MessageReference.QUOTE, instrument=12345)
builder.add_field(Field.BIDPRICE, 100.50)
builder.add_field(Field.ASKPRICE, 100.55)
builder.send(client._handle)
Message Class
Represents a message received from the MDF server.
Attributes
| Name | Type | Description | Default | Example |
|---|---|---|---|---|
ref |
int |
What type of message it is (e.g. MessageReference.NEWSHEADLINE) |
MessageReference.QUOTE |
|
instrument |
int |
Instrument reference ID | 12345 |
|
fields |
dict[int, str | None] |
Dictionary of field: value pairs (raw values) | {} |
{Field.BIDPRICE: "100.50", Field.ASKPRICE: "100.55"} |
delay |
int |
Message delay type | 0 |
0 |
Properties
| Name | Type | Description | Default | Example |
|---|---|---|---|---|
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} |
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.
Methods
parse_fields(remap_keys=True, convert_types=['str', 'int', 'float', 'date', 'time', 'datetime', 'list'], on_field_missing='ignore', list_delimiter=' ')
Parse and convert field values to their proper types.
Parameters:
remap_keys: IfTrue, use lowercase field names as keys; else use field IDsconvert_types: Which types to convert (str,int,float,date,time,datetime,list)on_field_missing: How to handle unmapped fields ('raise','ignore','skip')list_delimiter: Delimiter to split list values on
Returns: Dictionary with converted values
get(field, default=None)
Get field value by name with optional default.
__getitem__(field)
Allow dict-like access to fields: message[Field.BIDPRICE]
__contains__(field)
Check if field exists: Field.BIDPRICE in message
Usage Examples
News Streaming
from millistream_mdf import MDF, RequestClass, MessageReference, Field
with MDF(
url='sandbox.millistream.com',
port=9100,
username='sandbox',
password='sandbox'
) as session:
for message in session.subscribe(
request_classes=[RequestClass.NEWSHEADLINE, RequestClass.NEWSCONTENT],
subscription_mode='stream',
instruments='*',
timeout=1
):
if message.ref == MessageReference.NEWSHEADLINE:
print(f"Headline: {message.get(Field.HEADLINE)}")
print(f"Date: {message.get(Field.DATE)}")
elif message.ref == MessageReference.NEWSCONTENT:
print(f'Content: {message.get(Field.TEXTBODY, 'N/A')[:100]}...')
print('---')
Tip: You can use
'*'for all available instruments.
Note:
RequestClassandMessageReferencehave overlapping names but serve different purposes and have different integer values.
Example Output:
Headline: Antibiotics Market Size to Surpass USD 55.26 Billion by 2033, Report by DataM Intelligence
Date: 2025-10-10
Content: <?xml version="1.0" encoding="UTF-8"?><NewsItem><NewsEnvelope><TransmissionId>202509221001PR_NEWS_EU...
---
Headline: Aventis Energy Confirms Strong Radioactivity at Corvo Uranium Project
Date: 2025-10-10
Content: <?xml version="1.0" encoding="UTF-8"?><NewsItem><NewsEnvelope><TransmissionId>A3462546</Transmission...
---
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.NEWSIDfield.
Sending Data
from millistream_mdf import MDF, MessageReference, Field
# Simple message sending
with MDF(
url='server.example.com',
port=9100,
username='user',
password='pass'
) as client:
# Send a single quote message
client.send(
mref=MessageReference.QUOTE,
instrument=12345,
fields={
Field.BIDPRICE: 100.50,
Field.ASKPRICE: 100.55,
Field.BIDQUANTITY: 1000,
Field.ASKQUANTITY: 500,
}
)
# Send multiple messages in a batch (more efficient)
client.send_batch([
{
'mref': MessageReference.QUOTE,
'instrument': 12345,
'fields': {Field.BIDPRICE: 100.50, Field.ASKPRICE: 100.55},
},
{
'mref': MessageReference.TRADE,
'instrument': 12345,
'fields': {Field.TRADEPRICE: 100.52, Field.TRADEQUANTITY: 1000},
}
])
Manual Connection Control
from millistream_mdf import MDF, MDFError
session = MDF(
url='sandbox.millistream.com',
port=9100,
username='sandbox',
password='sandbox'
)
try:
session.connect()
print("Connected!")
# Subscribe to quote data
session.subscribe(request_classes=[RequestClass.QUOTE], instruments='*')
# Stream subscribed data
for message in session.stream(timeout=1):
print(message.fields)
print("---")
except MDFError as e:
print(f"Error: {e}")
finally:
session.disconnect()
Available Data Types
Request Classes
NEWSHEADLINE: News headlinesNEWSCONTENT: Full news contentQUOTE: Market quotes (bid/ask)TRADE: Trade executionsORDER: Order book dataBASICDATA: Instrument basic informationPRICEHISTORY: Historical price dataCORPORATEACTION: Corporate actionsFUNDAMENTALS: Financial fundamentalsPERFORMANCE: Performance metricsKEYRATIOS: Key financial ratiosESTIMATES: Analyst estimatesMIFID: MiFID II dataGREEKS: Options Greeks- And more...
Message Reference
MESSAGESREFERENCE: Message referenceLOGON: LogonLOGOFF: LogoffLOGONGREETING: Logon greetingNEWSHEADLINE: News headlineQUOTE: QuoteTRADE: TradeBIDLEVELINSERT: Bid level insertASKLEVELINSERT: Ask level insertBIDLEVELDELETE: Bid level deleteASKLEVELDELETE: Ask level deleteBIDLEVELUPDATE: Bid level updateASKLEVELUPDATE: Ask level updateINSTRUMENTRESET: Instrument reset- And more...
Subscription Modes
image: Snapshot of current valuesstream: Streaming data onlyfull: Both image and stream
Error Handling
The wrapper provides a comprehensive exception hierarchy:
from millistream_mdf import (
MDFError,
MDFConnectionError,
MDFAuthenticationError,
RequestClass
)
try:
with MDF(
url='sandbox.millistream.com',
port=9100,
username='sandbox',
password='sandbox'
) as session:
for message in session.subscribe(request_classes=[RequestClass.QUOTE], instruments='*'):
print(message)
except MDFConnectionError as e:
print(f"Connection failed: {e}")
except MDFAuthenticationError as e:
print(f"Authentication failed: {e}")
except MDFError as e:
print(f"MDF error: {e}")
Exception Types
MDFError: Base exception for all MDF-related errorsMDFConnectionError: Connection failuresMDFAuthenticationError: Login failuresMDFTimeoutError: Timeout errorsMDFMessageError: Message operation failuresMDFConfigurationError: Invalid configurationMDFLibraryError: Underlying library errors
Documentation
For more detailed documentation, visit the official documentation or the millistream sandbox.
License
This wrapper is provided under the LGPL v3 license, the same as the underlying libmdf library.
Support
For issues with this Python wrapper:
- Check this documentation
- Check error messages and exception types
- Open an issue on GitHub
For libmdf library issues, refer to the official Millistream documentation or contact tech@millistream.com.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file millistream_mdf-0.1.0.tar.gz.
File metadata
- Download URL: millistream_mdf-0.1.0.tar.gz
- Upload date:
- Size: 37.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d39bfe583dd9a6d6959cde40a99b39bdddc81d0ff8cbd6f842cc47399180ca7
|
|
| MD5 |
40efc89f2768376132421c5e78887c34
|
|
| BLAKE2b-256 |
97920f526b49ef83fabef6394604b39d0dbad0250ed8e2cf7ff55f7bb55e9f25
|
Provenance
The following attestation bundles were made for millistream_mdf-0.1.0.tar.gz:
Publisher:
publish.yml on mint273/millistream-mdf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
millistream_mdf-0.1.0.tar.gz -
Subject digest:
0d39bfe583dd9a6d6959cde40a99b39bdddc81d0ff8cbd6f842cc47399180ca7 - Sigstore transparency entry: 601083784
- Sigstore integration time:
-
Permalink:
mint273/millistream-mdf@f3be30a59006b024497da1f44c88dea7b233447e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mint273
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f3be30a59006b024497da1f44c88dea7b233447e -
Trigger Event:
release
-
Statement type:
File details
Details for the file millistream_mdf-0.1.0-py3-none-any.whl.
File metadata
- Download URL: millistream_mdf-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11b4f5ec60d811ba6989ee3cb28964036edb7600ae38480662b5eb7c865bd237
|
|
| MD5 |
3158a5f018f9961a6cd61ef31fda7bd8
|
|
| BLAKE2b-256 |
d1f6ce8e06920e52ac3c6d12fbcb6bf260bf56c0ced5d3ba4ba4d5a230533a08
|
Provenance
The following attestation bundles were made for millistream_mdf-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on mint273/millistream-mdf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
millistream_mdf-0.1.0-py3-none-any.whl -
Subject digest:
11b4f5ec60d811ba6989ee3cb28964036edb7600ae38480662b5eb7c865bd237 - Sigstore transparency entry: 601083785
- Sigstore integration time:
-
Permalink:
mint273/millistream-mdf@f3be30a59006b024497da1f44c88dea7b233447e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mint273
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f3be30a59006b024497da1f44c88dea7b233447e -
Trigger Event:
release
-
Statement type: