High-performance, async-first Python SDK for the Topstep (TopstepX / ProjectX Gateway) trading API.
Project description
topstep-sdk
A high-performance, async-first Python SDK for the Topstep trading API (TopstepX, powered by the ProjectX Gateway API).
Unofficial. This project is not affiliated with, endorsed by, or sponsored by Topstep, LLC or ProjectX Trading, LLC. "Topstep" and "TopstepX" are trademarks of their respective owners.
Building with an AI agent? See
AGENTS.md— a complete capability map plus the non-obvious trading semantics (order side, tick-offset brackets, theliveflag, result ordering) that prevent common mistakes.
Why this SDK
- Async-first core built on
httpx, with a synchronous facade for plain scripts. - Fast models via
msgspec— minimal overhead on high-throughput quote and depth streams. - Realtime SignalR client for both the user hub (accounts, orders, positions, trades) and market hub (quotes, trades, depth), with auto-reconnect and resubscribe.
- Fully typed (
py.typed), tiny dependency footprint (httpx,msgspec,websockets).
Status
🚧 Alpha / under active development. The public API may change before 1.0.
Requirements
- Python 3.12+
- A TopstepX account and an API key (TopstepX → Settings → API).
Installation
pip install topstep-sdk
Provide credentials as process environment variables — from_env() reads
os.environ and does not auto-load a .env file (export them, or load a
.env yourself first):
export TOPSTEP_USERNAME=your_username
export TOPSTEP_API_KEY=your_api_key
Or pass them directly: AsyncTopstepClient(username="...", api_key="...").
Quickstart (async)
This quickstart is read-only — it never places an order:
import asyncio
from datetime import datetime, timedelta, timezone
from topstep_sdk import AsyncTopstepClient, AggregateBarUnit
async def main() -> None:
async with AsyncTopstepClient.from_env() as client: # TOPSTEP_USERNAME / TOPSTEP_API_KEY
accounts = await client.accounts.search() # NOTE: may return >1 tradable account
account = accounts[0] # ...select deliberately in real code
contracts = await client.contracts.search("MNQ") # fuzzy; pick the intended contract
mnq = contracts[0]
now = datetime.now(timezone.utc)
bars = await client.history.retrieve_bars(
mnq.id, unit=AggregateBarUnit.MINUTE, unit_number=5,
start_time=now - timedelta(hours=2), end_time=now, limit=50,
)
print(account.name, account.balance, "| last close:", bars[0].close)
asyncio.run(main())
Placing orders
⚠️ This sends a REAL order to your Topstep account. Test on a practice/evaluation account with
size=1. Rememberaccounts.search()may return more than one tradable account — pick the right one deliberately.
# Market buy 1 with a protective OCO bracket. Pass tick DISTANCES (magnitudes) —
# the SDK signs them for the side (stop-loss below a long, take-profit above):
order_id = await client.orders.buy(
account.id, mnq.id, size=1,
stop_loss_ticks=40, take_profit_ticks=80,
)
# place() returns immediately; the fill arrives via orders.get(...) or the user hub.
await client.positions.close(account.id, mnq.id) # flatten the whole contract position
(For exact control you can instead pass the raw, signed stop_loss_bracket= /
take_profit_bracket= via PlaceOrderBracket — see AGENTS.md.)
Synchronous
from topstep_sdk import TopstepClient
with TopstepClient.from_env() as client:
for account in client.accounts.search():
print(account.id, account.name, account.balance)
Realtime
async with AsyncTopstepClient.from_env() as client:
market = client.market_hub()
@market.on_quote
def _(contract_id: str, quote) -> None:
print(contract_id, quote.best_bid, quote.best_ask)
await market.start()
await market.subscribe_quotes("CON.F.US.MNQ.U26")
await asyncio.sleep(30)
await market.stop()
# User hub: client.user_hub() → on_account / on_order / on_position / on_trade,
# subscribe_orders(account_id), etc. Auto-reconnects and re-subscribes.
Error handling
Everything the SDK raises derives from TopstepError, so except TopstepError
catches the whole surface. Domain rejections (bad order, insufficient funds,
not-found, …) surface as APIError with a numeric error_code;
transport/auth/rate-limit failures have their own types.
from topstep_sdk import APIError, AuthError, RateLimitError, OrderSide, OrderType
from topstep_sdk.enums import PLACE_ORDER_ERRORS
try:
order_id = await client.orders.place(
account.id, mnq.id, side=OrderSide.BUY, type=OrderType.MARKET, size=1
)
except RateLimitError as e:
... # back off; e.retry_after may be set
except AuthError:
... # bad or expired credentials
except APIError as e:
name = PLACE_ORDER_ERRORS.get(e.error_code or -1, "Unknown")
... # e.g. "InsufficientFunds", "OutsideTradingHours", "AccountViolation"
| Exception | Raised when |
|---|---|
AuthError |
401/403, bad key, login/validate failure |
RateLimitError |
HTTP 429 after retries (.retry_after) |
NotFoundError |
HTTP 404 |
ServerError |
HTTP 5xx after retries |
APIError |
domain rejection (success:false); base of the above (.error_code) |
TransportError |
network/timeout after retries |
RealtimeError |
SignalR connect/protocol error |
ConfigError |
missing/invalid credentials |
Lookup methods that model "not found" as a value return None instead of raising:
contracts.search_by_id(...) and orders.get(...).
Coverage
Full REST surface — Account, Contract (search / by-id / available), History
(bars), Order (search / open / by-id / paginated query / place / modify / cancel),
Position (open / close / partial-close), Trade (search) — plus both realtime
user and market hubs. Auth (API-key login, session validate/refresh), client-side
token-bucket rate limiting (50/30s for history, 200/60s otherwise), retry/backoff, and
typed error mapping are built in. Verified against the live TopstepX API; see
docs/RECONCILIATION.md for spec-vs-reality notes.
Roadmap
- Project scaffold, config, error hierarchy, CI
- Auth + HTTP core (login/refresh, rate limiting, retries)
- REST resources (accounts, contracts, history, orders, positions, trades)
- Realtime SignalR (user + market hubs)
- Synchronous facade
- First PyPI release —
pip install topstep-sdk - Live-verify bracket/OCO + trailing-stop placement when markets reopen (see
docs/TESTING.md) - Docs site
Development
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run pyright
License
Project details
Release history Release notifications | RSS feed
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 topstep_sdk-0.1.2.tar.gz.
File metadata
- Download URL: topstep_sdk-0.1.2.tar.gz
- Upload date:
- Size: 39.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
671207925e5a833a93a44a318f87740a187e01a05ae9e80aa0f9614126da201e
|
|
| MD5 |
e24635bc56e2ec7d5eed73920f989cff
|
|
| BLAKE2b-256 |
407f6966f6bd420a75f93d1f56d186328bb5d4ab5a372422f77082f053a58025
|
Provenance
The following attestation bundles were made for topstep_sdk-0.1.2.tar.gz:
Publisher:
release.yml on tarricsookdeo/topstep-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
topstep_sdk-0.1.2.tar.gz -
Subject digest:
671207925e5a833a93a44a318f87740a187e01a05ae9e80aa0f9614126da201e - Sigstore transparency entry: 2207048614
- Sigstore integration time:
-
Permalink:
tarricsookdeo/topstep-sdk@96a09c52f39dbb9a4356ecf02d5ad8f649b70a21 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/tarricsookdeo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@96a09c52f39dbb9a4356ecf02d5ad8f649b70a21 -
Trigger Event:
push
-
Statement type:
File details
Details for the file topstep_sdk-0.1.2-py3-none-any.whl.
File metadata
- Download URL: topstep_sdk-0.1.2-py3-none-any.whl
- Upload date:
- Size: 45.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2029373828a4bfec4a53637ba814f980c15b5d895453cc95b946e6513c6c4d8
|
|
| MD5 |
f00fc48b16b767b160f471f34407ead2
|
|
| BLAKE2b-256 |
a3f8d8d077cf8ef7ecefddb75dc69de6ff1c5de217272475607454c554994d1e
|
Provenance
The following attestation bundles were made for topstep_sdk-0.1.2-py3-none-any.whl:
Publisher:
release.yml on tarricsookdeo/topstep-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
topstep_sdk-0.1.2-py3-none-any.whl -
Subject digest:
d2029373828a4bfec4a53637ba814f980c15b5d895453cc95b946e6513c6c4d8 - Sigstore transparency entry: 2207048635
- Sigstore integration time:
-
Permalink:
tarricsookdeo/topstep-sdk@96a09c52f39dbb9a4356ecf02d5ad8f649b70a21 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/tarricsookdeo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@96a09c52f39dbb9a4356ecf02d5ad8f649b70a21 -
Trigger Event:
push
-
Statement type: