millistream-mdf
Table of Contents
- Overview
- Installation
- Quick Start
- API Reference
- Available Data Types
- Usage Examples
- 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
uv run python -m millistream_mdf --install-deps
Or:
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
A simple websocket connection to listen for quote data for Volvo B:
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], # Subscribe 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], # Subscribe 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: If you only want to convert the types you can use
parse_fields(remap_keys=False, convert_types=[...])
Tip: You can use
sandbox.millistream.comfor no cost to test the MDF with username:sandboxand password:sandbox. The data will be delayed and might not have access to the full offering.
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, 1279])subscription_mode: Subscription mode ('image','stream', or'full'). SeeSubscription Modesfor more information.timeout: Timeout in seconds for consume operations
Returns: Generator yielding Message objects
unsubscribe(request_classes='*', instruments='*')
Unsubscribe from data streams to stop receiving realtime data.
Parameters:
request_classes: List of request classes to unsubscribe from (e.g.,[RequestClass.QUOTE, RequestClass.TRADE]), or'*'for allinstruments: Instrument references to unsubscribe from. Can be'*'for all, or numeric IDs (e.g.,[1146, 1279])
Raises:
MDFError: If not connected or authenticatedMDFMessageError: If unsubscription request fails
Note: You can unsubscribe from a subset of your active subscriptions - the lists don't have to match previous subscription requests exactly.
Example:
# Unsubscribe from specific instruments
session.unsubscribe(
request_classes=[RequestClass.QUOTE],
instruments=[1146, 1279]
)
# Unsubscribe from all quotes
session.unsubscribe(request_classes=[RequestClass.QUOTE], instruments='*')
# Unsubscribe from everything
session.unsubscribe()
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:
session.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:
session.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 session.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(session._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
Available Data Types
Request Classes
The data classes to subscribe to (millistream_mdf.RequestClass):
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 estimatesGREEKS: Options Greeks- And more...
The request class references are available as constants using millistream_mdf.RequestClass:
from millistream_mdf import RequestClass
# a few examples
RequestClass.QUOTE # = 1 (internal reference number)
RequestClass.BASICDATA # = 4
RequestClass.CIHISTORY # = 19
Message Reference
The possible response message types (millistream_mdf.MessageReference):
LOGON: 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...
The message class references are available as constants using millistream_mdf.MessageReference.
Use the ref attribute to check the type of response message.
A request class can return multiple message types.
For instance RequestClass.MBO can return MessageReferences.ORDERBOOKFLUSH, MessageReference.BIDLEVELUPDATE, MessageReference.BIDLEVELUPDATE etc...
for message in session.stream(timeout=1):
if message.ref == MessageReference.ORDERBOOKFLUSH:
...
elif message.ref == MessageReference.BIDLEVELUPDATE:
...
elif message.ref == MessageReference.ASKLEVELUPDATE:
...
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 news agencies.
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='dnode3.sto5.millistream.com',
port=9100,
username='usr',
password='pwd'
) as session:
# Send a single quote message
session.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)
session.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, RequestClass
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()
Subscribe Usage
from millistream_mdf import MDF, RequestClass
import time
with MDF(
url='sandbox.millistream.com',
port=9100,
username='sandbox',
password='sandbox'
) as session:
# Subscribe to quotes and trades for specific instruments
session.subscribe(
request_classes=[RequestClass.QUOTE, RequestClass.TRADE],
instruments=[1146, 1279], # Volvo B, Atlas Copco B
subscription_mode='stream',
timeout=1
)
# Stream for 10 seconds
start_time = time.time()
for message in session.stream(timeout=1):
print(f"Received: {message.ref} for instrument {message.instrument}")
if time.time() - start_time > 10:
break
# Unsubscribe from trades only for instrument 1146
session.unsubscribe(
request_classes=[RequestClass.TRADE],
instruments=[1146]
)
print("Unsubscribed from trades for instrument 1146")
# Continue streaming (will only receive quotes and trades for 1279)
for message in session.stream(timeout=1):
print(f"Received: {message.ref} for instrument {message.instrument}")
if time.time() - start_time > 20:
break
# Unsubscribe from everything
session.unsubscribe()
print("Unsubscribed from all streams")
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.
Release files for millistream-mdf 0.1.12
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| millistream_mdf-0.1.12.tar.gz | 41.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| millistream_mdf-0.1.12-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.3 kB
Release files / millistream_mdf-0.1.12.tar.gz
| Download URL | millistream_mdf-0.1.12.tar.gz |
|---|---|
| Size | 41.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fafc2b47060823ed5fff15d9e4914828af1ec49673a13e4a1acf2aedb8670389
|
|
BLAKE2b-256 checksum How to use checksums |
270e170ce578511f8f18f68938865359f99be8b1bcc07418d0757588a1511e95
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency logRelease files / millistream_mdf-0.1.12-py3-none-any.whl
| Download URL | millistream_mdf-0.1.12-py3-none-any.whl |
|---|---|
| Size | 39.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4035e6e00fbbad43e14d5116e7e7ac7ecbfbda3e58691b118a74d10a9e6193a
|
|
BLAKE2b-256 checksum How to use checksums |
befdc53a8289fb4ce0f3063187acd0290552bc152b2abd18cc68f1539ecd2893
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency log