Skip to content

Python SDK

mackinac-client normalizes live and historical data from 13 venues — Hyperliquid, dYdX V4, Uniswap V3/V4, Pendle, Spectra, GMX, Vertex, Ostium, Sushi, PancakeSwap, Balancer, and the consolidated AMM book — into a single typed message schema. Every message comes back as a Pydantic v2 model with the same field names regardless of venue. The library ships both a synchronous Mackinac client and an AsyncClient for asyncio code, with automatic reconnect and re-subscribe built in.


Install

Terminal window
pip install mackinac-client

Optional extras:

Terminal window
pip install 'mackinac-client[tables]' # pandas — needed for rate/AMM table examples
pip install 'mackinac-client[wallet]' # eth-account — needed for from_wallet auth

Requires Python 3.10+. Pin pydantic>=2,<3, httpx>=0.27, websockets>=12.


Tiers and limits

Know your cap before you run anything:

TierSymbol capHistory lookback
Anonymous — Mackinac()3 per IP across all sessions24 hours
apifrom_api_key("mk_live_…")highfull since inception

Live trades — no credentials

The sync API requires no event loop. types=PrintMessage filters at the iterator level — no isinstance checks in your loop, no buffering other message types.

from mackinac import Mackinac, PrintMessage
with Mackinac() as m:
with m.subscribe("hl:ETH", types=PrintMessage) as feed:
for trade in feed:
side = "buy " if trade.side == 1 else "sell"
print(f"{side} {trade.price:>10,.2f} x {trade.size}")

This runs on the anonymous tier — no API key needed, 3-symbol cap per IP.


Historical funding rates

history_funding is a generator that auto-paginates. ratePct is annualised percentage. Symbols with slashes (XAU/USD) are URL-encoded automatically on REST calls.

from datetime import datetime, timedelta, timezone
from mackinac import Mackinac
NOTIONAL = 100_000
HOURS = 12
end = datetime.now(timezone.utc)
start = end - timedelta(hours=HOURS)
with Mackinac() as m:
hl = list(m.history_funding("hl", "ETH", start=start, end=end))
xau = list(m.history_funding("ostium", "XAU/USD", start=start, end=end))
def carry(rates):
if not rates:
return 0.0, 0.0
avg = sum(r.ratePct for r in rates) / len(rates) # annualised %
cost = NOTIONAL * (avg / 100) * (HOURS / 8760)
return avg, cost
print(f"HL ETH ann={carry(hl)[0]:>6.2f}% cost=${carry(hl)[1]:>7.2f}")
print(f"Ostium XAU ann={carry(xau)[0]:>6.2f}% cost=${carry(xau)[1]:>7.2f}")

See Funding Rates, Rollover Fees, and the Implicit Carry of Traditional Futures for the full carry-cost analysis this example is drawn from.


Cross-venue basis

The same QuoteMessage model works for a CLOB perpetual (hl), an AMM spot pool (uni), and an oracle perpetual (gmx). QuoteMessage.bids[0].price is the same field name on all three.

from mackinac import Mackinac, QuoteMessage
mids = {}
with Mackinac.from_api_key("mk_live_...") as m:
with m.subscribe("hl:ETH", "uni:WETH/USDC", "gmx:ETH",
types=QuoteMessage) as feed:
for q in feed:
if not (q.bids and q.asks):
continue
mids[f"{q.exchange}:{q.symbol}"] = (q.bids[0].price + q.asks[0].price) / 2
if len(mids) == 3:
hl, uni, gmx = mids["hl:ETH"], mids["uni:WETH/USDC"], mids["gmx:ETH"]
print(f"HL {hl:.2f} Uni {uni:.2f} GMX {gmx:.2f} "
f"HL–Uni {(hl-uni)/uni*1e4:+.1f}bps")
break

from_api_key bypasses the 3-symbol anonymous cap and handles auth on both the WebSocket connection and REST history calls automatically.


Next steps

  • Reference — auth methods, all REST generators, message types, symbol helpers, reconnect behavior, error handling
  • Examples on GitHub — 11 runnable scripts (≤60 lines each)