Skip to content

Python SDK Reference

Full reference for mackinac-client. For installation and worked examples see the Python SDK overview.


Authentication

MethodWhen to useTier
Mackinac()Scripts, demos, notebooks — no credentials neededAnonymous — 3 symbols / IP, 24 h history
Mackinac.from_api_key("mk_live_…")Server-side processes, long-running WS and REST consumersapi — high cap, full history
Mackinac.from_jwt("eyJ…")Browser-issued JWT, short-lived sessionsapi — full REST history at your subscription tier
Mackinac.from_token(token)CLIs and scripts that accept a credential as inputSniffs the prefix and dispatches to the right path
Mackinac.from_wallet(privkey, addr)First-time sign-in flowOne-shot — returns a client holding a fresh JWT

from_token is the most convenient for scripts: pass MACKINAC_TOKEN and it handles JWT vs. API key detection automatically.


REST methods

All methods are available on both Mackinac (sync) and AsyncClient (async) with the same signatures.

# Discovery
m.markets() # dict[str, MarketStatus]
m.market("hl") # MarketStatus
m.live_symbols("hl") # list[str]
m.live_symbols() # dict[str, list[str]]
m.historical_symbols("hl") # list[str]
m.instruments() # dict[str, InstrumentVenues]
m.instrument("ETH") # InstrumentVenues
# History — generators; iterate until exhausted (auto-paginates)
for trade in m.history_trades("hl", "ETH", start=..., end=...): ...
for snap in m.history_quotes("hl", "ETH", start=..., end=...): ...
for fr in m.history_funding("hl", "ETH", start=..., end=...): ...
for rate in m.history_rates(pendle_address, start=..., end=...): ...

start and end accept ISO 8601 strings, epoch-millisecond integers, or datetime objects (timezone-aware recommended). Pagination is transparent — keep iterating and the generator fetches the next cursor automatically.

history_funding returns FundingRecord objects where ratePct is the annualised funding rate in percent (not bps, not a decimal fraction).


WebSocket message types

The FeedMessage discriminated union resolves to one of these types based on the type field:

TypeSource venues
QuoteMessageEvery venue — top-of-book bid/ask updates
PrintMessageEvery CLOB, AMM, and oracle venue that records trades
FundingMessageHL, dYdX, GMX, Vertex, Ostium
DepthMessageAMM concentrated-liquidity tick snapshot + market-impact estimates at $1k / $10k / $100k / $1M
LiquidityMessageAMM mint (LP add) and burn (LP remove) events
RateMarketMessagePendle and Spectra yield markets — implied APY, PT/YT price, TVL
RateDepthMessagePendle depth-at-size APY
AmmBookMessageConsolidated AMM NBBO across all Arbitrum venues (professional tier)
SpreadMessageSame-venue cross-fee-tier spread
ArbFlagMessageEdge-triggered cross-venue arb open/close (admin tier)
SnapshotMessageInitial ring-buffer replay of recent prints on subscribe
ErrorMessageTier gates, cap exhaustion, unknown symbol, etc.
FeedStaleMessageUpstream feed has gone stale — data may be delayed
FeedLiveMessageUpstream feed has recovered

Consuming a feed

with m.subscribe("hl:ETH", "uni:WETH/USDC") as feed:
for msg in feed:
match msg:
case PrintMessage(): ...
case QuoteMessage(): ...
case FundingMessage(): ...
case ErrorMessage(): print("server error:", msg.code, msg.message)

Filtering by type

Pass one type or a tuple of types to types= and the iterator only yields those:

with m.subscribe("hl:ETH", types=(QuoteMessage, PrintMessage)) as feed:
for msg in feed: # PrintMessage and QuoteMessage only; everything else discarded
...

Symbol naming

Each venue uses its native symbol convention. Use the helpers in mackinac.symbols to construct them safely:

from mackinac import symbols
symbols.hl_perp("ETH") # "ETH"
symbols.hl_spot("PURR", "USDC") # "PURR/USDC"
symbols.amm_pair("WETH", "USDC") # "WETH/USDC"
symbols.ostium_pair("XAU", "USD") # "XAU/USD"
symbols.pendle_address("0xABC…") # lowercased 0x address

Slashes in symbols are URL-encoded automatically on REST history calls. WebSocket subscribe takes them literally.


Reconnect, dedup, and backpressure

These are handled by the engine — you don’t need to implement them:

  • Reconnect — exponential backoff (1 s → 2 → … → 30 s cap) on any transport error. All active subscriptions are re-sent on the new connection. The for / async for loop in your code sees no interruption.
  • Server-requested reconnect — when the server sends a server_closing frame, the engine waits the requested delay before reconnecting.
  • Dedup — print messages are de-duplicated across reconnect-snapshot replays using (block, txIndex) for on-chain venues and (time, price, size, side) for HL. Duplicate prints never appear in the iterator.
  • Backpressure — the internal queue caps at 10,000 messages. If a slow consumer fills it, the oldest message is dropped and a warning is logged. Fix: process faster or reduce subscription breadth.

Errors

Library-level exceptions are in mackinac.exceptions:

ExceptionWhen it’s raised
AuthErrorBad or expired credentials — 401 from REST, auth_failed on WebSocket
TierErrorEndpoint requires a higher tier — 403, subscription_required
RateLimitError429 — .retry_after attribute gives the suggested wait in seconds
SymbolLimitErrorTried to subscribe past your symbol cap (also surfaced as ErrorMessage in the stream)
InvalidSymbolErrorUnknown venue or symbol pair
ServerError5xx after three backoff retries
ConnectionErrorTransport-level failure that exhausted reconnect attempts
MackinacErrorBase class — catch this if you don’t need the subtype

In a WebSocket feed, errors that don’t kill the connection arrive as ErrorMessage objects in the iterator stream (e.g. a single unknown symbol in a multi-symbol subscribe). Errors that do kill the connection — auth failure, tier gate — are raised on the next iteration step.


Versioning

import mackinac
print(mackinac.__version__) # library version, e.g. "0.1.0"
print(mackinac.__protocol_version__) # wire-protocol version

Dependency pins: pydantic>=2,<3, httpx>=0.27, websockets>=12.

Semver policy: major bump if a server-side field is removed from a message type; minor bump if a field is added. Patch for bug fixes and non-breaking additions.


Examples in the repo

All examples are ≤60 lines and runnable. Pass an API key as the first argument to unlock the higher symbol cap and longer history.

FilePurpose
subscribe_quotes.pyLive HL ETH top-of-book
live_trades.pyLive HL ETH prints, type-filtered
quotes_and_trades.pyBoth message types from one feed
historical_trades.pyOstium XAU/USD trade history to CSV
historical_quotes.pyOHLC reconstruction from quote snapshots
multi_venue_basis.pyHL / Uni / GMX cross-venue basis
yield_rates.pyPendle + Spectra APY table
rate_table.pyLive rates DataFrame (pandas)
amm_fee_table.pyAMM depth + fee-tier impact table (pandas)
reconnect_pattern.pyIterator surviving a forced disconnect
funding_carry.pyCME vs HL vs Ostium carry comparison