Skip to main content

mts1b-marketdata — public API surface

Adapters implement MarketDataProtocol. Use the registry to switch providers transparently.

Registry

from mts1b_marketdata import PROVIDER_REGISTRY

PROVIDER_REGISTRY
# {"fmp": FmpClient, "polygon": PolygonClient, "finnhub": FinnhubClient,
# "thetadata": ThetaDataClient, "alphavantage": AlphaVantageClient,
# "tiingo": TiingoClient, "yfinance": YfinanceClient}


cls = PROVIDER_REGISTRY["fmp"]
async with cls(api_key="...") as md:
q = await md.get_quote(Symbol("AAPL"))

Multi-source router

from mts1b_marketdata.routing import MultiSourceProvider

md = MultiSourceProvider(
providers=["fmp", "polygon", "alphavantage"],
strategy="failover", # try in order; first success wins
# or "best_quote" — query all; return tightest spread
# or "load_balance" — round-robin within rate limits
# or "cheapest" — pick provider with most free quota left
)

quote = await md.get_quote(Symbol("AAPL"))

The MarketDataProtocol interface

@runtime_checkable
class MarketDataProtocol(Protocol):
@property
def name(self) -> str: ...

async def get_quote(self, symbol: Symbol) -> Quote: ...
async def get_bars(
self, symbol: Symbol, interval: str,
start: datetime, end: datetime | None = None,
) -> list[Bar]: ...
async def get_trades(self, symbol: Symbol, asof: datetime) -> list[Trade]: ...
async def stream_quotes(self, symbols: list[Symbol]) -> AsyncIterator[Quote]: ...

Provider capabilities matrix

ProviderBarsQuotesTradesOptionsFxFuturesWS
fmp
polygon
finnhub
thetadata
alphavantage
tiingo
yfinance

Common usage

from datetime import datetime, timezone
from mts1b_marketdata import PROVIDER_REGISTRY
from mts1b_foundation.symbology import Symbol

async with PROVIDER_REGISTRY["fmp"](api_key="...") as md:
# Latest quote
q = await md.get_quote(Symbol("AAPL"))
print(q.bid, q.ask)

# Daily bars
bars = await md.get_bars(
Symbol("AAPL"), interval="1d",
start=datetime(2024, 1, 1, tzinfo=timezone.utc),
end=datetime(2024, 6, 1, tzinfo=timezone.utc),
)
print(f"got {len(bars)} bars")

# Intraday
minutes = await md.get_bars(Symbol("AAPL"), interval="1m",
start=datetime(2024, 5, 23, 14, 0, tzinfo=timezone.utc),
end=datetime(2024, 5, 23, 16, 0, tzinfo=timezone.utc))

# Recent trades
trades = await md.get_trades(Symbol("AAPL"),
asof=datetime.now(timezone.utc))

# Live stream (Polygon)
async with PROVIDER_REGISTRY["polygon"](api_key="...") as md:
async for quote in md.stream_quotes([Symbol("AAPL"), Symbol("MSFT")]):
print(quote)

Options chains (ThetaData)

from mts1b_marketdata.thetadata import ThetaDataClient

async with ThetaDataClient(api_key="...") as md:
chain = await md.options_chain(
underlying="SPY",
expiry=date(2026, 8, 23),
)
for opt in chain:
print(opt.strike, opt.option_type, opt.bid, opt.ask)

Caching

The router transparently caches behind Redis:

TypeTTL
Quotes1-5s
Bars intradayuntil next bar close
Bars dailyovernight
Options chains30s

Cache hits don't consume provider rate-limit budget.

Rate limits per provider

ProviderFreePaid
FMP250/day30k+/day
Polygon5/minunlimited
Finnhub60/min300+/min
ThetaDatavaries (sub-based)sub-based
AlphaVantage25/day75-1200/min
Tiingo50/hr500-5000/hr
YFinanceunofficialn/a

MultiSourceProvider respects each provider's limit via mts1b_platform.ratelimit.

Adding a new provider

Use mts1b-pluginsdk:

mts1b-plugin new --type marketdata --name my-feed

Implement MarketDataProtocol, register via entry points.

See also