Skip to main content

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

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

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: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.

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_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, 1147])
  • 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

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:

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: 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

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: 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='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 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
  • MIFID: MiFID II data
  • GREEKS: Options Greeks
  • And more...

Message Reference

  • MESSAGESREFERENCE: Message reference
  • 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...

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

millistream_mdf-0.1.0.tar.gz (37.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

millistream_mdf-0.1.0-py3-none-any.whl (36.7 kB view details)

Uploaded Python 3

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

Hashes for millistream_mdf-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0d39bfe583dd9a6d6959cde40a99b39bdddc81d0ff8cbd6f842cc47399180ca7
MD5 40efc89f2768376132421c5e78887c34
BLAKE2b-256 97920f526b49ef83fabef6394604b39d0dbad0250ed8e2cf7ff55f7bb55e9f25

See more details on using hashes here.

Provenance

The following attestation bundles were made for millistream_mdf-0.1.0.tar.gz:

Publisher: publish.yml on mint273/millistream-mdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file millistream_mdf-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for millistream_mdf-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 11b4f5ec60d811ba6989ee3cb28964036edb7600ae38480662b5eb7c865bd237
MD5 3158a5f018f9961a6cd61ef31fda7bd8
BLAKE2b-256 d1f6ce8e06920e52ac3c6d12fbcb6bf260bf56c0ced5d3ba4ba4d5a230533a08

See more details on using hashes here.

Provenance

The following attestation bundles were made for millistream_mdf-0.1.0-py3-none-any.whl:

Publisher: publish.yml on mint273/millistream-mdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page