Native async Python client for the Rithmic R|API.
Project description
pyrithmic
Distribution:
rithmic-rapion PyPI · import name:pyrithmic.
Native async Python client for the Rithmic R|API. Fully typed, fail-loud, and built around a single consistent contract.
pyrithmic is an asyncio-only client for the Rithmic R|API protocol (WebSocket + Protocol Buffers). Every callback delivers a typed, frozen Pydantic model (never a raw dict or a bare protobuf message), so a consumer writes the same code for a fill, a tick, a PnL push, or a historical bar. The public surface is strict, explicit, and verified under pyright --strict.
import asyncio
from pyrithmic import RithmicClient
from pyrithmic.protocol.plants import Plant
from pyrithmic.models.enums import MarketDataType
from pyrithmic.models.market_data import LastTrade
async def main() -> None:
# Reads RITHMIC_USER / RITHMIC_PASSWORD / RITHMIC_SYSTEM_NAME / RITHMIC_GATEWAY_URL
client = RithmicClient.from_env(plants=frozenset({Plant.TICKER}))
@client.on_last_trade
async def _on_trade(trade: LastTrade) -> None: # a typed model, with full IDE autocomplete
print(trade.symbol, trade.trade_price, trade.trade_size)
async with client: # connects, logs in, reconnects on drop
contract = await client.get_front_month_contract("NQ", "CME")
await client.subscribe_market_data(contract.trading_symbol, "CME", MarketDataType.LAST_TRADE)
await asyncio.sleep(10)
asyncio.run(main())
Highlights
- One consistent, typed contract. Every push and every response is decoded into a frozen Pydantic v2 model before it reaches your code. There is no
dictin one place and a protobuf message in another: the same typed, immutable surface everywhere. - Strict, validated signatures. Orders are submitted as validated
OrderRequestmodels (no opaque**kwargs); invalid input is rejected at construction, before it ever touches the wire. - Fail-loud error model. A
PyrithmicErrorhierarchy maps server rejects to named exceptions (OrderRejected,RpcRejected,LoginRejected, and so on). No silent fallbacks, no swallowed errors. - Correct connection topology. One WebSocket connection per plant, the only topology the R|API actually supports, multiplexed transparently behind a single client.
- Resilient by design. Per-plant reconnect with configurable backoff and a finite retry budget; active subscriptions are replayed automatically after a reconnect.
- Race-free RPC layer. UUID-correlated request/response matching, explicit terminal-response detection, idempotent push de-duplication, and lock-protected shared state.
- Broad protocol coverage. Orders (flat / bracket / OCO, modify with fill-race handling, cancel), PnL (snapshot + live stream), market data (quotes, statistics, front-month), and historical bars (replay + live stream).
- Structured, opt-in observability.
structlog-based logging that is silent by default and never logs secrets; your application stays in control of sinks and levels. - Strict typing and lean dependencies. Ships
py.typed, passespyright --strict, and depends only onprotobuf,websockets,pydantic, andstructlog. - Live-gated. Every feature is validated against a live Rithmic gateway across the order, PnL, market-data and historical workflows before it is shipped.
Installation
pip install rithmic-rapi # or: uv add rithmic-rapi
The PyPI distribution is rithmic-rapi; the import name stays pyrithmic (import pyrithmic).
Requires Python 3.11+.
An optional fast extra installs uvloop (a faster event loop) on non-Windows platforms:
pip install "rithmic-rapi[fast]"
pyrithmic stays event-loop-agnostic and never changes the global loop policy itself. Installing the extra only makes uvloop available on your platform; you enable it explicitly in your own entry point (see ADR-0006):
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# ...then run the pyrithmic client on the uvloop-backed loop
Usage
Connecting
Provide credentials explicitly, or load them from the environment with from_env. The client is an async context manager: entering it opens the connections, logs into every requested plant, and starts heartbeat and reconnect supervision; leaving it shuts everything down gracefully.
from pydantic import SecretStr
from pyrithmic import RithmicClient, RithmicCredentials
from pyrithmic.protocol.plants import Plant
creds = RithmicCredentials(
user=SecretStr("my-user"),
password=SecretStr("my-password"),
system_name="Rithmic Paper Trading",
gateway_url="rituz00100.rithmic.com:443", # "wss://" is added for you; an explicit wss:// is also accepted
app_name="my-app",
app_version="1.0.0.0",
)
client = RithmicClient(creds, plants=frozenset({Plant.ORDER, Plant.PNL}))
Each plant is opt-in. A market-data-only client ({Plant.TICKER}) or a monitoring-only client ({Plant.PNL}) logs into just that plant, so you never pay for capabilities you don't use.
Submitting an order
Orders are validated OrderRequest models. You usually want to route to the current front-month contract, so resolve the canonical underlying first (for example NQ to NQM6) and feed the resolved symbol into the request. submit_and_wait then returns the terminal OrderUpdate (filled or cancelled), so the happy path is a single await:
from pyrithmic.models.order import OrderRequest
from pyrithmic.models.enums import TransactionType, OrderType, OrderPlacement
async with client: # client built with Plant.ORDER and Plant.TICKER
# Resolve the underlying to the current front-month contract (e.g. "NQ" -> "NQM6").
contract = await client.get_front_month_contract("NQ", "CME")
req = OrderRequest(
account_id="ACCOUNT-123",
symbol=contract.trading_symbol, # the resolved front-month symbol
exchange=contract.trading_exchange,
transaction_type=TransactionType.BUY,
order_type=OrderType.LIMIT,
quantity=1,
price=21_000.0,
placement=OrderPlacement.MANUAL, # wire manual_or_auto; AUTO for automated routing
)
fill = await client.submit_and_wait(req, timeout=30.0)
print(fill.status, fill.fill_price) # OrderStatus.FILLED, 21000.0
Resolving the front-month contract requires Plant.TICKER in the client's plant set. A malformed request raises at construction; a gateway rejection raises OrderRejected with the reason, never a silent no-op.
Streaming PnL
from pyrithmic.models.pnl import AccountPnL
@client.on_account_pnl_update
async def _on_pnl(pnl: AccountPnL) -> None:
print(pnl.account_id, pnl.day_pnl, pnl.account_balance)
async with client:
await client.subscribe_pnl_updates("ACCOUNT-123")
account, positions = await client.get_pnl_snapshot("ACCOUNT-123")
await asyncio.sleep(60)
Historical bars
from datetime import datetime, timezone
from pyrithmic.models.enums import TimeBarType
async with client: # client built with Plant.HISTORY
bars = await client.replay_time_bars(
"NQM6", "CME",
bar_type=TimeBarType.MINUTE_BAR, period=1,
start=datetime(2026, 6, 1, 14, 0, tzinfo=timezone.utc),
finish=datetime(2026, 6, 1, 15, 0, tzinfo=timezone.utc),
)
for bar in bars: # each is a typed TimeBar
print(bar.bar_start, bar.open_price, bar.high_price, bar.low_price, bar.close_price)
Logging
Logging is silent by default. Opt in from your application with one call (console for humans, JSON for ingestion), and pyrithmic never logs credentials:
from pyrithmic.observability.logging import configure_logging
configure_logging(level="INFO") # or: configure_logging(level="DEBUG", json=True)
Architecture
pyrithmic is layered, with a strict boundary between the wire and the public API:
| Layer | Responsibility |
|---|---|
transport/ |
WebSocket connection, framing, heartbeat, reconnect |
protocol/ |
Protobuf codec and the Template enum (wire layer) |
session/ |
Login handshake, RPC correlation, response semantics |
plants/ |
ORDER / PNL / TICKER / HISTORY / ADMIN domain logic |
models/ |
Frozen Pydantic v2 models, the typed public surface |
client.py |
RithmicClient, the high-level facade |
The protobuf bindings are an internal detail: application code never imports them directly and never sees a raw protobuf message. Design decisions are recorded as Architecture Decision Records and the full layout lives in docs/architecture.md.
Capabilities
| Plant | Coverage |
|---|---|
| Order | submit · bracket · OCO · modify (fill-race aware) · cancel · fills · RMS · order history |
| PnL | account & instrument snapshot · live position/PnL stream |
| Ticker | front-month resolution · last-trade / best-bid-offer · trade & quote statistics |
| History | time / tick / volume-profile bar replay · live bar stream |
| Admin | account discovery · pre-login system & gateway discovery · reference data |
Development
uv sync # install dependencies + create .venv
just test # unit tests
just test-integration # live Rithmic tests (require RITHMIC_* env vars)
just lint # ruff check
just typecheck # pyright --strict
just proto # regenerate protobuf bindings
just build # build wheel + sdist
just ci # lint + format-check + typecheck + tests
The public API is unstable before 0.1.0: breaking changes may land between pre-releases and are recorded in CHANGELOG.md.
License
pyrithmic (transport, codec wrappers, plant abstractions, models, and the client facade) is released under the Apache License 2.0.
Disclaimer & trademark notice
This project is not affiliated with, endorsed by, or sponsored by Rithmic, LLC or Trading Technologies International. "Rithmic" and "R|API" are trademarks of their respective owners.
Protocol bindings
The Python protobuf bindings shipped in the PyPI wheel (pyrithmic.protocol._generated) are compiled from Rithmic's R|API protocol distribution. The Rithmic .proto source files themselves are not redistributed in this repository or in the wheel; they remain proprietary to Rithmic, LLC and must be obtained directly through your own Rithmic developer relationship. If you build from source, place the RProtocolAPI distribution at proto/source/ and run just proto (the build hook does this automatically during uv build).
Takedown contact
If you are an authorized representative of Rithmic, LLC or Trading Technologies International and object to the distribution of compiled protocol bindings derived from your .proto definitions, please open an issue at https://github.com/alexandre-meline/pyrithmic/issues or contact the maintainer directly. Such requests will be honored promptly with PyPI takedown and repository archival.
Use at your own risk
This software interfaces with a live trading protocol. Bugs can lead to incorrect order routing, missed fills, or unintended position state. Always test extensively against a paper account before deploying to live trading. The authors and contributors assume no liability for financial losses arising from the use of this software.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rithmic_rapi-0.2.0.tar.gz.
File metadata
- Download URL: rithmic_rapi-0.2.0.tar.gz
- Upload date:
- Size: 357.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe4e2c217c847b7d4c50c98f00a71ec2fd585491c676f4da49ae2e44aedb4c28
|
|
| MD5 |
8a27e8e4735fdb43f2e48191db210185
|
|
| BLAKE2b-256 |
7b84bec70da283431ff5ee83f5ab1d91f91e29f2e9781618e6a79263fbaa3ecf
|
Provenance
The following attestation bundles were made for rithmic_rapi-0.2.0.tar.gz:
Publisher:
release.yml on alexandre-meline/pyrithmic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rithmic_rapi-0.2.0.tar.gz -
Subject digest:
fe4e2c217c847b7d4c50c98f00a71ec2fd585491c676f4da49ae2e44aedb4c28 - Sigstore transparency entry: 1809120037
- Sigstore integration time:
-
Permalink:
alexandre-meline/pyrithmic@3cff6bae5f3743fb5900765c605ab1a4b2eaeaad -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/alexandre-meline
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3cff6bae5f3743fb5900765c605ab1a4b2eaeaad -
Trigger Event:
push
-
Statement type:
File details
Details for the file rithmic_rapi-0.2.0-py3-none-any.whl.
File metadata
- Download URL: rithmic_rapi-0.2.0-py3-none-any.whl
- Upload date:
- Size: 574.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c75549aa33af8ca7f8b7a1c09d136432eb964e9dfb74b06f31437dbf9cf7600d
|
|
| MD5 |
c4a162976eb1552fac1c82fbf8d97a3e
|
|
| BLAKE2b-256 |
7658741a063016ae5e801567e4463d1925186f5adf0cf8eb3ba9054479962b57
|
Provenance
The following attestation bundles were made for rithmic_rapi-0.2.0-py3-none-any.whl:
Publisher:
release.yml on alexandre-meline/pyrithmic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rithmic_rapi-0.2.0-py3-none-any.whl -
Subject digest:
c75549aa33af8ca7f8b7a1c09d136432eb964e9dfb74b06f31437dbf9cf7600d - Sigstore transparency entry: 1809120046
- Sigstore integration time:
-
Permalink:
alexandre-meline/pyrithmic@3cff6bae5f3743fb5900765c605ab1a4b2eaeaad -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/alexandre-meline
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3cff6bae5f3743fb5900765c605ab1a4b2eaeaad -
Trigger Event:
push
-
Statement type: