This is the official Python SDK for Market Data. It provides developers with a powerful, easy-to-use interface to obtain real-time and historical financial data. Ideal for building financial applications, trading bots, and investment strategies.
- Real-time Stock Data: Prices, quotes, candles (OHLCV), earnings, and news
- Options Trading Data: Complete options chains, expirations, strikes, quotes, and lookup
- Mutual Funds: Historical candles and pricing data
- Market Status: Real-time market open/closed status for multiple countries
- Utilities: API service status, an echo of your request headers, and your account's credit counters
- Multiple Output Formats: DataFrames (pandas/polars), JSON, CSV, or Python objects
- Built-in Retry Logic: Automatic retry with exponential backoff for reliable data fetching
- Type-Safe: Full Pydantic validation and type hints
- Zero Config: Works out of the box with sensible defaults
- Python >= 3.10
Install from PyPI:
pip install marketdata-sdk-pyOr if you're using uv:
uv pip install marketdata-sdk-pyTo use OutputFormat.DATAFRAME, you need to install at least one DataFrame library. See Optional Dependencies for details.
Install with pandas (recommended):
pip install "marketdata-sdk-py[pandas]"
# or using uv
uv pip install "marketdata-sdk-py[pandas]"Install with polars:
pip install "marketdata-sdk-py[polars]"
# or using uv
uv pip install "marketdata-sdk-py[polars]"Install with both:
pip install "marketdata-sdk-py[pandas,polars]"
# or using uv
uv pip install "marketdata-sdk-py[pandas,polars]"For local development, install from the project directory:
pip install .
# or with optional dependencies
pip install ".[pandas]"The SDK requires a MarketData authentication token. You can provide it in two ways:
Create a .env file in the project root:
MARKETDATA_TOKEN=your_token_hereYou can pass the token when creating a client instance:
from marketdata import MarketDataClient
client = MarketDataClient(token="your_token_here")from marketdata import MarketDataClient
from logging import Logger
# Token will be automatically obtained from MARKETDATA_TOKEN environment variable
client = MarketDataClient()
# Or provide the token explicitly
client = MarketDataClient(token="your_token_here")
# You can also provide a custom logger
custom_logger = get_logger() # Your custom logger setup
client = MarketDataClient(token="your_token_here", logger=custom_logger)Client Initialization Details:
- The client automatically fetches rate limits by making a request to
/user/endpoint during initialization - The client includes a User-Agent header with the format
marketdata-sdk-py/{version}(e.g.,marketdata-sdk-py/1.1.0) in RFC 7231 compliant format - The library version is automatically detected from the installed package
- All requests include an
Authorization: Bearer {token}header - The client uses
httpx.Clientfor HTTP requests with automatic connection pooling
The client automatically fetches and tracks rate limits from the API. Rate limits are initialized when the client is created by making a request to /user/ endpoint, and are updated after each API request based on response headers.
You can access current rate limits:
client = MarketDataClient()
# Access current rate limits
rate_limits = client.rate_limits
# Access individual fields
print(f"Limit: {rate_limits.requests_limit}")
print(f"Remaining: {rate_limits.requests_remaining}")
print(f"Consumed: {rate_limits.requests_consumed}")
print(f"Reset at: {rate_limits.requests_reset}")
# Or use the formatted string representation
print(rate_limits) # Shows: "Rate used X/Y, remaining: Z credits, next reset: ISO timestamp"Note: Rate limits are tracked via the following response headers:
x-api-ratelimit-limit: Total number of requests allowedx-api-ratelimit-remaining: Number of requests remainingx-api-ratelimit-consumed: Number of requests consumedx-api-ratelimit-reset: Unix timestamp when the rate limit resets
The requests_reset field is automatically converted to a datetime.datetime object for easier use.
The SDK provides access to different market data resources:
-
Stocks: Access stock prices, quotes, candles (OHLCV), earnings, and news
- Methods:
prices(),quotes(),candles(),earnings(),news() - See Stocks Documentation for detailed usage
- Methods:
-
Options: Access options chains, expiration data, strikes, quotes, and lookup
- Methods:
chain(),expirations(),strikes(),quotes(),lookup() - See Options Documentation for detailed usage
- Methods:
-
Funds: Access funds candles (OHLC) for mutual funds
- Methods:
candles() - See Funds Documentation for detailed usage
- Methods:
-
Markets: Access market status information (open/closed) for dates and countries
- Methods:
status() - See Markets Documentation for detailed usage
- Methods:
-
Utilities: API service status, request headers echo (with the IP the API detected) and the account's credit counters
- Methods:
status(),headers(),user() - See Utilities Documentation for detailed usage
- Methods:
Note: For stocks, options, and funds resources, the
symbol,symbols, orlookupparameter (depending on the method) can be passed as the first positional argument or as a keyword argument. All other parameters must be keyword-only. For markets resource, all parameters must be keyword-only.
from marketdata import MarketDataClient
client = MarketDataClient()
# Get stock prices (symbols can be passed positionally or as keyword)
df = client.stocks.prices("AAPL")
# or
df = client.stocks.prices(symbols="AAPL")
print(df)
# Get stock candles (symbol can be passed positionally or as keyword)
df = client.stocks.candles("AAPL")
# or
df = client.stocks.candles(symbol="AAPL")
print(df)
# Get options chain (symbol can be passed positionally or as keyword)
chain = client.options.chain("AAPL")
# or
chain = client.options.chain(symbol="AAPL")
print(chain)
# Get options strikes (symbol can be passed positionally or as keyword)
strikes = client.options.strikes("AAPL")
# or
strikes = client.options.strikes(symbol="AAPL")
print(strikes)
# Get options quotes (symbols can be passed positionally or as keyword)
# Note: quotes() takes option symbols (e.g., "AAPL240120C00150000"), not stock symbols
quotes = client.options.quotes("AAPL240120C00150000")
# or
quotes = client.options.quotes(symbols="AAPL240120C00150000")
print(quotes)
# Get options lookup (lookup can be passed positionally or as keyword)
# Format: "SYMBOL DD-MM-YYYY STRIKE SIDE" (e.g., "AAPL 20-12-2024 150.0 call")
lookup = client.options.lookup("AAPL 20-12-2024 150.0 call")
# or
lookup = client.options.lookup(lookup="AAPL 20-12-2024 150.0 call")
print(lookup)
# Get funds candles (symbol can be passed positionally or as keyword)
df = client.funds.candles("VFINX")
# or
df = client.funds.candles(symbol="VFINX")
print(df)
# Get market status (all parameters must be keyword-only)
df = client.markets.status(countback=7)
print(df)The SDK supports multiple output formats for API responses. See the Universal Parameters section for details on how to specify output formats.
OutputFormat.DATAFRAME: Returns a pandas or polars DataFrame (default). Requires installing pandas or polars as an optional dependency. See Optional Dependencies for installation instructions.OutputFormat.INTERNAL: Returns internal Python objects (see resource-specific documentation for details)OutputFormat.JSON: Returns raw JSON data (dictionary)OutputFormat.CSV: Writes CSV data to file and returns filename string
For detailed information about return types and object structures for each resource, see the specific resource documentation:
- Stocks Documentation - Object types:
StockPrice,StockQuote,StockCandle,StockEarnings,StockNews - Options Documentation - Object types:
OptionsExpirations,OptionsChain,OptionsStrikes,OptionsQuotes,OptionsLookup - Funds Documentation - Object type:
FundsCandle - Markets Documentation - Object type:
MarketStatus - Utilities Documentation - Object types:
ServiceStatus,RequestHeaders,User
You can specify the output format when calling resource methods:
from marketdata import MarketDataClient, OutputFormat
client = MarketDataClient()
# Get DataFrame (default) - symbols can be passed positionally or as keyword
df = client.stocks.prices("AAPL")
# or
df = client.stocks.prices(symbols="AAPL")
# Get internal objects
prices = client.stocks.prices("AAPL", output_format=OutputFormat.INTERNAL)
# or
prices = client.stocks.prices(symbols="AAPL", output_format=OutputFormat.INTERNAL)
# Get JSON
json_data = client.stocks.prices("AAPL", output_format=OutputFormat.JSON)
# or
json_data = client.stocks.prices(symbols="AAPL", output_format=OutputFormat.JSON)
# Get CSV
# All methods write to file and return filename string
csv_file = client.stocks.prices("AAPL", output_format=OutputFormat.CSV, filename="prices.csv")
# Get candles as internal objects
candles = client.stocks.candles("AAPL", output_format=OutputFormat.INTERNAL)
# If filename is not provided, a timestamped file is created in output/ directory
csv_file = client.options.chain("AAPL", output_format=OutputFormat.CSV)When using OutputFormat.CSV, all resources write CSV data to a file and return the filename as a string. If filename is not provided, a timestamped file is automatically created in the output/ directory (the directory is created when the file is written, never for other output formats).
Note: When specifying a custom filename, the directory must exist and the file must not already exist. The file is created exclusively: if the path appears between validation and the write, the call fails instead of overwriting it. CSV bytes are written exactly as the API sent them on every platform. See resource-specific documentation for details on CSV output format.
All resource methods support universal parameters that can be used to customize the API request and response:
The format of the returned data. Defaults to OutputFormat.DATAFRAME.
The date format to use in the response. Defaults to DateFormat.UNIX. Available options:
DateFormat.TIMESTAMP: ISO timestamp formatDateFormat.UNIX: Unix timestamp (seconds since epoch)DateFormat.SPREADSHEET: Spreadsheet-compatible format
Specify which columns to include in the response. If not provided, all available columns are returned.
Whether to include headers in the response. Uses API alias headers.
Whether to use human-readable format for values. Uses API alias human.
The data feed mode to use. Available options:
Mode.LIVE: Live market dataMode.CACHED: Cached dataMode.DELAYED: Delayed data
File path for CSV output (only used with output_format=OutputFormat.CSV).
- Must end with
.csv - Directory must exist (if filename is provided)
- File must not already exist
- If not provided, a timestamped file is created in
output/directory (the directory is automatically created if it doesn't exist)
from marketdata import MarketDataClient, OutputFormat, DateFormat, Mode
from pathlib import Path
client = MarketDataClient()
# Use custom date format and mode
df = client.stocks.prices(
"AAPL",
date_format=DateFormat.TIMESTAMP,
mode=Mode.LIVE
)
# Specify columns to include
df = client.stocks.prices(
"AAPL",
columns=["symbol", "mid", "change_percent"]
)
# Save CSV with custom filename
csv_file = client.stocks.prices(
"AAPL",
output_format=OutputFormat.CSV,
filename=Path("data/aapl_prices.csv")
)Resource methods raise on failure; they never return an error object and never return None. Every exception the SDK raises derives from BaseMarketdataException, so one except clause is enough, and every one of them carries the support context described below.
from marketdata import BaseMarketdataException, MarketDataClient
client = MarketDataClient()
try:
quotes = client.stocks.quotes("AAPL")
except BaseMarketdataException as e:
print(e.support_info) # paste this block into a support ticketConnection failures and undecodable bodies are wrapped (NetworkError, ParseError); errors that are not about the API at all (a Pydantic ValidationError for a bad parameter value, a FileExistsError on CSV output) propagate as they are.
Every SDK exception exposes the same six attributes, request_id (the cf-ray header), request_url, status_code, timestamp (US/Eastern), message and exception_type, as plain attributes, as a support_context dict and as the formatted support_info block. Failures that never reached the API (validation, the rate-limit pre-flight) report N/A and 0 for the request fields.
--- MARKET DATA SUPPORT INFO ---
request_id: 8a1b2c3d4e5f6g7h-SJC
request_url: https://api.marketdata.app/v1/stocks/quotes/
status_code: 429
timestamp: 2025-02-21 12:00:00
message: Rate limit exceeded
exception_type: RateLimitError
--------------------------------
All exception classes are importable from marketdata as well as from marketdata.exceptions.
Raised when API rate limits are exceeded (before retry logic):
from marketdata import MarketDataClient
from marketdata.exceptions import RateLimitError
try:
client = MarketDataClient()
df = client.stocks.prices("AAPL")
# or
df = client.stocks.prices(symbols="AAPL")
except RateLimitError as e:
print(f"Rate limit exceeded: {e}")One class per kind of failure, mapped from the HTTP status the API answered (SDK requirements §6.1 and §9.1):
| Status | Exception | Retried |
|---|---|---|
| 400 | BadRequestError |
no |
| 401 | AuthenticationError |
no, fails immediately |
| 403 | ForbiddenError |
no |
| 404 with an error message | NotFoundError |
no |
404 with s: "no_data" |
none: the call returns an empty result (see below) | |
| 429 | RateLimitError, with retry_after in seconds when the API sent it |
no |
| 500 | InternalError |
no |
| 501 and above | ServerError |
yes, exponential backoff |
| connection failure, timeout, protocol or proxy error | NetworkError |
yes, unless the client caused it: a base URL without a scheme, a malformed request, a proxy that refuses the connection |
undecodable body, or a body that does not match its Content-Encoding |
ParseError |
no |
| any other 4xx | MarketdataHttpError |
no |
A 500 means the API itself failed on your request, so retrying would not help; 501 and above mean the API was unavailable or a gateway answered for it, which is why only those are retried. The two are separate classes: catching one never catches the other.
All HTTP classes derive from MarketdataHttpError and keep the underlying httpx objects on request and response. RateLimitError is also raised by the pre-flight credit check, before any request goes out; in that case its request fields read N/A.
When the API has no data for a valid question (candles over a weekend, news for a quiet day) it answers 404 with s: "no_data". The SDK does not raise for that. It returns the natural empty value for the output format you asked for:
| Output format | Empty result |
|---|---|
DATAFRAME |
a DataFrame with the model's columns and no rows |
INTERNAL |
[] for list-shaped resources (prices, quotes, candles, news, markets.status, utilities.status), None for single-object ones (earnings, options.chain, options.expirations, options.lookup, options.quotes, utilities.headers, utilities.user) |
JSON |
the API's {"s": "no_data"} body |
CSV |
a file with the header row only |
For the fan-out calls, a chunk (stocks.candles) or a symbol (options.quotes) with no data is simply absent from the merged result; the whole call is empty only when every part is.
Raised for various validation errors:
- Invalid date formats when parsing timestamps
- Invalid output format values
- Invalid filename format (must end with
.csv, directory must exist, file must not exist) - Invalid input parameters
from marketdata import MarketDataClient, OutputFormat
from pathlib import Path
try:
client = MarketDataClient()
# Invalid filename (doesn't end with .csv)
csv_file = client.stocks.prices(
"AAPL",
output_format=OutputFormat.CSV,
filename=Path("data/invalid.txt")
)
except ValueError as e:
print(f"Validation error: {e}")Raised when date range validation fails (e.g., from_date is greater than to_date), before any request is made:
from marketdata import MarketDataClient
from marketdata.exceptions import MinMaxDateValidationError
import datetime
client = MarketDataClient()
try:
candles = client.stocks.candles(
"AAPL",
from_date=datetime.date(2024, 12, 31),
to_date=datetime.date(2024, 1, 1),
)
except MinMaxDateValidationError as e:
print(f"Date range validation error: {e}")Raised when arguments are passed incorrectly. Only the symbol or symbols parameter can be passed as a positional argument. All other parameters must be keyword-only:
from marketdata import MarketDataClient, OutputFormat
from marketdata.exceptions import KeywordOnlyArgumentError
try:
client = MarketDataClient()
# ❌ This will raise KeywordOnlyArgumentError
df = client.stocks.prices("AAPL", OutputFormat.DATAFRAME)
# ✅ Correct usage
df = client.stocks.prices("AAPL", output_format=OutputFormat.DATAFRAME)
# or
df = client.stocks.prices(symbols="AAPL", output_format=OutputFormat.DATAFRAME)
except KeywordOnlyArgumentError as e:
print(f"Invalid argument usage: {e}")Catch the specific class when you want to react differently; BaseMarketdataException catches them all:
from marketdata import MarketDataClient
from marketdata.exceptions import (
AuthenticationError,
BadRequestError,
BaseMarketdataException,
InternalError,
NetworkError,
RateLimitError,
ServerError,
)
client = MarketDataClient()
try:
prices = client.stocks.prices("AAPL")
except AuthenticationError:
print("Check MARKETDATA_TOKEN")
except RateLimitError as e:
print(f"Out of credits, retry after {e.retry_after} seconds")
except BadRequestError as e:
print(f"The API rejected the request: {e.message}")
except InternalError as e:
print(f"The API failed on this request: {e.message}")
except (ServerError, NetworkError) as e:
print(f"The API stayed unavailable after retries: {e.status_code} {e.message}")
except BaseMarketdataException as e:
print(e.support_info)The SDK includes automatic retry logic for handling transient errors: availability errors above 500 (ServerError) and connection failures or timeouts (NetworkError). Nothing else is retried: a 4xx, a 500 (InternalError, the API itself failed) and a rate limit are final answers.
- Default retry attempts: 3
- Backoff strategy: Exponential with multiplier 0.5, minimum wait 0.5 seconds, maximum wait 5 seconds
- Retried failures: any HTTP status code greater than 500 (502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout, ...) and any connection failure or timeout. A 500 (
InternalError), every 4xx and a 429 are not retried. - Default timeout: 60 seconds per request
The retry mechanism only retries ServerError (501 and above) and NetworkError, and only if the API service status is ONLINE or UNKNOWN. The retry adapter uses the tenacity library and will retry up to the specified number of attempts with exponential backoff between retries; a Retry-After header from the API overrides the computed wait.
Important: Resource methods either return the requested result (DataFrame, list of objects, dict, or the CSV filename) or raise. There is no error return value; see Error Handling.
The SDK includes automatic API status checking for certain resource methods. When a retryable failure occurs, the SDK verifies that the API service is online before retrying the request.
- Automatic checking: Methods with API status checking (
@api_error_handlerdecorator) verify service availability when a retryable failure occurs - Cached status: API status information is cached and refreshed automatically every 4 minutes and 30 seconds
- Service-specific: Each method checks the status of its specific service endpoint
- Retry logic: The SDK only retries requests if the service status is
ONLINEorUNKNOWN. If the service isOFFLINE, the error is raised immediately
All resource methods include API status checking and automatic retry logic. See the specific resource documentation for details on each method's behavior.
If a service is offline when checked, the method raises the original ServerError (or NetworkError) instead of retrying:
from marketdata import MarketDataClient
from marketdata.exceptions import NetworkError, ServerError
client = MarketDataClient()
try:
result = client.stocks.candles("AAPL")
except (ServerError, NetworkError) as e:
print(f"Service unavailable or request failed: {e}")The SDK automatically refreshes the API status cache when:
- The cached status is older than 4 minutes and 30 seconds
- A retryable failure (
ServerError,NetworkError) occurs in a method with status checking
The status refresh request does not count against rate limits (check_rate_limits=False) and does not update rate limit tracking (populate_rate_limits=False), ensuring that status checking does not interfere with your API usage while providing up-to-date service availability information.
You can customize the base URL, API version, logging level, and universal parameters through environment variables:
# Required
MARKETDATA_TOKEN=your_token_here
# API Configuration
MARKETDATA_BASE_URL=https://api.marketdata.app
MARKETDATA_API_VERSION=v1
MARKETDATA_LOGGING_LEVEL=INFO
# Universal Parameters (optional - can also be passed as method arguments)
MARKETDATA_OUTPUT_FORMAT=dataframe
MARKETDATA_DATE_FORMAT=unix
MARKETDATA_COLUMNS=symbol,mid,change_percent
MARKETDATA_ADD_HEADERS=true
MARKETDATA_USE_HUMAN_READABLE=false
MARKETDATA_MODE=liveDefaults:
MARKETDATA_BASE_URL:https://api.marketdata.appMARKETDATA_API_VERSION:v1MARKETDATA_LOGGING_LEVEL:INFO- Universal parameters:
None(uses method defaults)
Note: Universal parameters set via environment variables will be used as defaults for all API calls, but can be overridden by passing them as method arguments. See the Universal Parameters section for available values.
.
├── docs/
│ ├── stocks.md # Stocks resource documentation
│ ├── options.md # Options resource documentation
│ ├── funds.md # Funds resource documentation
│ ├── markets.md # Markets resource documentation
│ └── utilities.md # Utilities resource documentation
├── src/
│ ├── tests/ # Test suite
│ │ ├── conftest.py # Pytest configuration and fixtures
│ │ ├── test_client.py
│ │ ├── test_stocks_prices.py
│ │ ├── test_stocks_candles.py
│ │ ├── test_options_chain.py
│ │ ├── test_options_expirations.py
│ │ ├── test_options_quotes.py
│ │ ├── test_params.py
│ │ └── data/ # Test data fixtures
│ └── marketdata/
│ ├── __init__.py
│ ├── client.py # Main MarketDataClient class
│ ├── exceptions.py # The exception taxonomy (BadRequestError, ServerError, RateLimitError, ...)
│ ├── logger.py # Logging configuration
│ ├── params.py # Parameter decorators and validation (@universal_params)
│ ├── retry.py # Retry mechanism using tenacity
│ ├── settings.py # Configuration and environment variables
│ ├── types.py # Data types (UserRateLimits)
│ ├── utils.py # Utility functions (format_timestamp, initialize_dataframe, etc.)
│ ├── docs.py # Documentation generation utilities
│ ├── internal_settings.py # Internal settings (MAX_CONCURRENT_REQUESTS)
│ ├── input_types/ # Input validation types
│ │ ├── __init__.py
│ │ ├── base.py # Base input type classes (OutputFormat, DateFormat, Mode, UserUniversalAPIParams)
│ │ ├── stocks.py # Stocks input types (StocksPricesInput, StocksQuotesInput, StocksCandlesInput)
│ │ ├── funds.py # Funds input types (FundsCandlesInput)
│ │ ├── markets.py # Markets input types (MarketStatusInput)
│ │ └── options.py # Options input types (OptionsChainInput, OptionsExpirationsInput, OptionsStrikesInput, OptionsQuotesInput, OptionsLookupInput)
│ ├── output_types/ # Output data types
│ │ ├── __init__.py
│ │ ├── stocks_prices.py # Stock prices output types (StockPrice, StockPricesHumanReadable)
│ │ ├── stocks_quotes.py # Stock quotes output types (StockQuote, StockQuotesHumanReadable)
│ │ ├── stocks_candles.py # Stock candles output types (StockCandle, StockCandlesHumanReadable)
│ │ ├── stocks_earnings.py # Stock earnings output types (StockEarnings, StockEarningsHumanReadable)
│ │ ├── stocks_news.py # Stock news output types (StockNews, StockNewsHumanReadable)
│ │ ├── funds_candles.py # Funds candles output types (FundsCandle, FundsCandlesHumanReadable)
│ │ ├── markets_status.py # Markets status output types (MarketStatus, MarketStatusHumanReadable)
│ │ ├── options_chain.py # Options chain output types (OptionsChain)
│ │ ├── options_expirations.py # Options expirations output types (OptionsExpirations)
│ │ ├── options_quotes.py # Options quotes output types (OptionsQuotes)
│ │ ├── options_strikes.py # Options strikes output types (OptionsStrikes)
│ │ └── options_lookup.py # Options lookup output types (OptionsLookup)
│ └── resources/
│ ├── __init__.py
│ ├── base.py # BaseResource class with common functionality
│ ├── stocks/ # Stocks API resource
│ │ ├── __init__.py # StocksResource class definition
│ │ ├── prices.py # Stock prices endpoint
│ │ ├── quotes.py # Stock quotes endpoint
│ │ ├── candles.py # Stock candles endpoint
│ │ ├── earnings.py # Stock earnings endpoint
│ │ └── news.py # Stock news endpoint
│ ├── funds/ # Funds API resource
│ │ ├── __init__.py # FundsResource class definition
│ │ └── candles.py # Funds candles endpoint
│ ├── markets/ # Markets API resource
│ │ ├── __init__.py # MarketsResource class definition
│ │ └── status.py # Markets status endpoint
│ └── options/ # Options API resource
│ ├── __init__.py # OptionsResource class definition
│ ├── chain.py # Options chain endpoint
│ ├── expirations.py # Options expirations endpoint
│ ├── strikes.py # Options strikes endpoint
│ ├── quotes.py # Options quotes endpoint
│ └── lookup.py # Options lookup endpoint
└── pyproject.toml # Project configuration and dependencies
httpx>=0.28.1: HTTP client library for making API requestspydantic>=2.12.5: Data validation and settings managementpydantic-settings>=2.12.0: Configuration management from environment variablestenacity>=9.1.2: Retry logic library for handling transient errors
The SDK supports multiple DataFrame libraries for OutputFormat.DATAFRAME. You must install at least one of the following:
- pandas (recommended):
pandas>=2.3.3 - polars:
polars-lts-cpu>=1.33.1
Install with pandas (recommended):
pip install "marketdata-sdk-py[pandas]"
# or using uv
uv pip install "marketdata-sdk-py[pandas]"Install with polars:
pip install "marketdata-sdk-py[polars]"
# or using uv
uv pip install "marketdata-sdk-py[polars]"Install with both:
pip install "marketdata-sdk-py[pandas,polars]"
# or using uv
uv pip install "marketdata-sdk-py[pandas,polars]"When using OutputFormat.DATAFRAME, the SDK automatically selects an available DataFrame library in the following order:
- pandas (if installed)
- polars (if pandas is not installed)
If neither pandas nor polars is installed, a ValueError will be raised when attempting to use OutputFormat.DATAFRAME:
ValueError: No dataframe output handler foundNote: You can use other output formats (OutputFormat.INTERNAL, OutputFormat.JSON, OutputFormat.CSV) without installing pandas or polars.
black>=25.11.0: Code formatterisort>=7.0.0: Import sorterpandas>=2.3.3: DataFrame library (for testing)polars-lts-cpu>=1.33.1: DataFrame library (for testing)pytest>=9.0.1: Testing frameworkrespx>=0.22.0: HTTPX mocking library for tests
The SDK automatically handles multiple date formats:
- ISO timestamp strings: Parsed using
datetime.fromisoformat() - Unix timestamps: Parsed using
datetime.fromtimestamp()(seconds since epoch) - Spreadsheet dates: Values between 0 and 60000 are treated as Excel-style dates (days since 1899-12-30)
All timestamps in response objects are automatically converted to datetime.datetime objects when using OutputFormat.INTERNAL. When using OutputFormat.DATAFRAME, timestamp conversion behavior varies by resource. See the specific resource documentation for details.
When using OutputFormat.DATAFRAME, the SDK performs automatic data cleaning:
- The
s(status) column is removed from all DataFrames - DataFrames are automatically indexed by their primary identifier (see resource-specific documentation for details)
- Timestamp columns are converted to
datetime.datetimeobjects in most cases (see resource-specific documentation for exceptions)
For detailed information about DataFrame structure and indexing for each method, see the specific resource documentation.
The SDK uses Pydantic for input validation:
- All input parameters are validated using Pydantic models
- Boolean parameters are converted to lowercase strings for the API (
"true"/"false") - List parameters are joined with commas
- Enum parameters use their
.valueattribute - Date parameters are formatted appropriately for the API
The SDK uses concurrent requests for efficient data fetching in specific scenarios:
stocks.candles(): For intraday resolutions (minutely/hourly), large date ranges are automatically split into year-long, non-overlapping chunks and fetched concurrently (up to 50 concurrent requests by default)options.quotes(): Multiple option symbols are fetched concurrently (up to 50 concurrent requests by default)
When concurrent requests are used, responses are automatically merged into a single result. A chunk or symbol with no data is left out of the merge; if every part has no data the call returns an empty result, and if the API answered with nothing usable at all a MarketdataHttpError is raised.
See the specific resource documentation for details on concurrent request behavior.
Rate limits are tracked via response headers and updated after each request:
- Rate limit information is extracted from response headers after every API call
- The
UserRateLimitsobject is updated automatically - Rate limit checking happens before each request (unless
check_rate_limits=False) - If rate limits are exhausted, a
RateLimitErroris raised before making the request
./test.sh # unit suite: mocked HTTP, runs offlineThe live integration suite under src/tests/integration/ calls api.marketdata.app and is excluded from the default run. It needs a token and fails, rather than skips, without one:
MARKETDATA_TOKEN=... uv run pytest src/tests/integration -m integrationIt uses the free-trial symbols (AAPL, VFINX), so a run costs no API credits. CI runs it on every pull request.
./lint.sh./build.shSee the LICENSE file for more details.