Skip to main content

High-performance, async-first Python SDK for the Topstep (TopstepX / ProjectX Gateway) trading API.

Project description

topstep-sdk

CI License: MIT

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, the live flag, 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 variablesfrom_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. Remember accounts.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

MIT

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

topstep_sdk-0.1.1.tar.gz (38.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

topstep_sdk-0.1.1-py3-none-any.whl (45.8 kB view details)

Uploaded Python 3

File details

Details for the file topstep_sdk-0.1.1.tar.gz.

File metadata

  • Download URL: topstep_sdk-0.1.1.tar.gz
  • Upload date:
  • Size: 38.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for topstep_sdk-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6d75e14664551215679acc44ad41497a42e762fdbcb4983875157e8f3b2e9b85
MD5 a18e76af4cbb2fba0eac4fce3b59dc9e
BLAKE2b-256 233b0cb3d6d20a04e6c598dd96cd801784ebf5b6b71aeb6c06d0dc7309b82fd7

See more details on using hashes here.

Provenance

The following attestation bundles were made for topstep_sdk-0.1.1.tar.gz:

Publisher: release.yml on tarricsookdeo/topstep-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file topstep_sdk-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: topstep_sdk-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 45.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for topstep_sdk-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2d31bfa33ed861c78dea0b12dc6a4ec81a7a1e135f18eb519f0114a8e5e2c71c
MD5 4956bff279c2a6c1551b48286cd01c2e
BLAKE2b-256 3c0666cbf02017cd313e89b11aa8fa16876c89ef0a024e6172a38ac5f5e75745

See more details on using hashes here.

Provenance

The following attestation bundles were made for topstep_sdk-0.1.1-py3-none-any.whl:

Publisher: release.yml on tarricsookdeo/topstep-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page