Skip to main content

millistream-mdf

Python wrapper for the Millistream Data Feed (MDF) C SDK

PyPI Python Version Documentation

Table of Contents

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.com for no cost to test the MDF with username: sandbox and 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_mode defaults to full (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

All constructor parameters

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:

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 codes
  • instruments: Instrument references to subscribe to. Can be '*' for all, or numeric IDs (e.g., [1146, 1279])
  • subscription_mode: Subscription mode ('image', 'stream', or 'full'). See Subscription Modes for 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 all
  • instruments: Instrument references to unsubscribe from. Can be '*' for all, or numeric IDs (e.g., [1146, 1279])

Raises:

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 reference
  • fields: Dictionary mapping field names to values
  • delay: Optional delay parameter (default: 0)

Returns: True if the message was sent successfully

Raises:

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: If True, use lowercase field names as keys; else use field IDs
  • convert_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 headlines
  • NEWSCONTENT: Full news content
  • QUOTE: Market quotes (bid/ask)
  • TRADE: Trade executions
  • ORDER: Order book data
  • BASICDATA: Instrument basic information
  • PRICEHISTORY: Historical price data
  • CORPORATEACTION: Corporate actions
  • FUNDAMENTALS: Financial fundamentals
  • PERFORMANCE: Performance metrics
  • KEYRATIOS: Key financial ratios
  • ESTIMATES: Analyst estimates
  • GREEKS: 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: Logon
  • LOGOFF: Logoff
  • LOGONGREETING: Logon greeting
  • NEWSHEADLINE: News headline
  • QUOTE: Quote
  • TRADE: Trade
  • BIDLEVELINSERT: Bid level insert
  • ASKLEVELINSERT: Ask level insert
  • BIDLEVELDELETE: Bid level delete
  • ASKLEVELDELETE: Ask level delete
  • BIDLEVELUPDATE: Bid level update
  • ASKLEVELUPDATE: Ask level update
  • INSTRUMENTRESET: 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: RequestClass and MessageReference have 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.NEWSID field.

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 values
  • stream: Streaming data only
  • full: 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 errors
  • MDFConnectionError: Connection failures
  • MDFAuthenticationError: Login failures
  • MDFTimeoutError: Timeout errors
  • MDFMessageError: Message operation failures
  • MDFConfigurationError: Invalid configuration
  • MDFLibraryError: 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:

  1. Check this documentation
  2. Check error messages and exception types
  3. 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)

Source distribution for millistream-mdf 0.1.12
File Size Uploaded
millistream_mdf-0.1.12.tar.gz 41.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for millistream-mdf 0.1.12
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.12 This release

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page