Official Python client for the QJ Trader AI Trading APIs — Canadian market data and order entry.
Project description
qjtrader
Official Python client for the QJ Trader AI Trading APIs — stream real-time Canadian market data and send orders to Canadian venues (Montréal Exchange derivatives, and equities across every lit exchange and dark pool) over one authenticated connection.
pip install qjtrader
- Free sandbox, no approval. Create an account at console.qjtrader.ai,
click Create sandbox credential, and you get a
client_id+client_secretthat stream simulated data and return simulated fills — in the exact production wire format, 24/7. - Sandbox → production with one credential swap. Your code never changes; the credential decides sandbox vs. real, server-side.
- Stdlib only. No dependencies — easy to install, easy to audit.
Quickstart
Get a sandbox key from the console, then:
export QJ_CLIENT_ID="your-client-id"
export QJ_CLIENT_SECRET="your-client-secret"
Send an order
import qjtrader
client = qjtrader.Client() # reads QJ_CLIENT_ID / QJ_CLIENT_SECRET from the environment
with client.orders() as oe:
fill = oe.order_and_wait(
sym="MX:CRAU26", side="buy", qty=1, price=97.00, account="SIM", tif="ioc",
)
print(fill) # {'type': 'exec', 'status': 'filled', 'last_px': 97.0, 'cum_qty': 1, ...}
Lower-level, if you want every message:
with client.orders() as oe:
cid = oe.order(sym="MX:CRAU26", side="buy", qty=1, price=97.00, account="SIM")
for msg in oe.updates(timeout=10):
print(msg) # accepted -> new -> (partial)* -> filled | canceled | replaced
oe.cancel(cid)
print(oe.status()) # open orders + session state
Stream market data
import qjtrader
client = qjtrader.Client()
with client.market_data() as md:
md.subscribe(["CA:RY", "CA:RY.PT", "MX:CRAU26"], depth=5)
for msg in md.messages(timeout=30):
if msg["type"] == "quote":
print(msg["symbol"], msg["data"]["bid"], msg["data"]["ask"])
CA:RYis the consolidated Canadian equity book (each level tagged with its venue);CA:RY.PTis PURE (CSE) only. Futures likeMX:CRAU26are venue-native. See the full symbology reference.
Command line
The package installs a qjtrader command:
qjtrader subscribe CA:RY MX:CRAU26 --watch 30
qjtrader order --sym MX:CRAU26 --side buy --qty 1 --price 97.00 --account SIM --tif ioc
qjtrader status
qjtrader cancel --orig qj-abc123
# strategies: the same file runs in backtest and live
qjtrader backtest examples/strategy_meanreversion.py --symbol MX:CRAU26 --bars 200
qjtrader run examples/strategy_meanreversion.py --symbols MX:CRAU26 --tag mr1
Strategies — one contract, every venue
Subclass Strategy and the same file runs in the backtest engine, a paper run, or
live (plan §10). Backtests are offline and deterministic (no network, no secrets);
qjtrader run hosts it against a live/paper credential, tags every order with the
strategy name (so the journal groups by strategy), and cancels everything on Ctrl-C.
from qjtrader import Strategy, run_backtest, synthetic_bars
class Buy2Percent(Strategy):
def on_bar(self, ctx, bar):
if ctx.position(bar["symbol"]) == 0 and bar["close"] < ctx.param("floor", 0):
ctx.buy(bar["symbol"], 1, bar["close"], tif="ioc")
def on_fill(self, ctx, fill):
ctx.log("filled", fill.get("cid"), "@", fill.get("last_px") or fill.get("price"))
report = run_backtest(Buy2Percent(), synthetic_bars("MX:CRAU26", 200), params={"floor": 95})
print(report["total_pnl"], report["positions"])
The bar-level backtester is for logic; L2 event-replay with queue-model fills (microstructure truth) comes from the paper environment.
Configuration
Client() reads these (constructor args override environment):
| Setting | Env var | Default |
|---|---|---|
| Client ID | QJ_CLIENT_ID |
— (required) |
| Client secret | QJ_CLIENT_SECRET |
— (required) |
| Token endpoint | QJ_TOKEN_URL |
QJ Cognito token URL |
| Market-data host | QJ_DATA_HOST |
data-feed.qjtrader.ai:7000 |
| Order-entry host | QJ_ORDERS_HOST |
orders.qjtrader.ai:7001 |
| Pinned CA/cert | QJ_CA_FILE |
none (standard public-CA validation) |
Tokens are minted for you (OAuth2 client-credentials) and refreshed automatically before they
expire — you never handle them directly. Need a raw token (e.g. for the WebSocket interface)?
client.token(qjtrader.MARKET_DATA_SCOPE).
Pilot note: while order entry is in private pilot it may be reached by IP with a pinned certificate provided at onboarding — pass
ca_file="pilot-server.pem"(orQJ_CA_FILE). Market data uses a standard public certificate.
How it works
Both APIs speak NDJSON over TLS — one JSON object per line, UTF-8, newline-terminated,
authenticated with an OAuth2 JWT sent on the first line. The order lifecycle is a deterministic,
journaled state machine (accepted → new → (partial)* → filled | canceled | replaced), commands are
idempotent per client order id (cid), and the server enforces pre-trade risk checks +
cancel-on-disconnect. Full protocol: Order Entry
and Market Data.
Use it from an LLM (MCP)
Prefer to drive QJ from Claude or another AI assistant? The companion
qjtrader-mcp server exposes these APIs as Model
Context Protocol tools — subscribe to quotes and place simulated orders in plain language, no
code. Order tools refuse a live credential by default (sandbox-only unless you opt in). Add it to
Claude Code with:
claude mcp add qjtrader -e QJ_CLIENT_ID=... -e QJ_CLIENT_SECRET=... -e QJ_ENV=sandbox -- uvx qjtrader-mcp
The console's "Connect your AI" panel generates this for you, pre-filled.
Links
- 📚 Docs: https://docs.qjtrader.ai/docs/ai
- 🔑 Console (get a key): https://console.qjtrader.ai
- 🔤 Symbology: https://docs.qjtrader.ai/docs/ai/symbology
- 🐛 Issues: https://github.com/QJTrader/qjtrader-python/issues
License
Apache-2.0. See 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 qjtrader-0.2.4.tar.gz.
File metadata
- Download URL: qjtrader-0.2.4.tar.gz
- Upload date:
- Size: 42.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23b7d2e8a915b41eb273ee562bb497e7e2a7180e504af0ff072e2ec371419dfd
|
|
| MD5 |
623574699f6ecfba55527ad59ae29716
|
|
| BLAKE2b-256 |
904601a26082398adf94114fcc571a4a88b5e5cc9456cbbc7aa1cd71c3093df4
|
File details
Details for the file qjtrader-0.2.4-py3-none-any.whl.
File metadata
- Download URL: qjtrader-0.2.4-py3-none-any.whl
- Upload date:
- Size: 40.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8acf10b178c89025441db0da72ea91ee97340a2f88b0f592e89406f0cf751a05
|
|
| MD5 |
f3a82f598322d56d761449ef8234ef36
|
|
| BLAKE2b-256 |
53b255e7b600762c83d2ea3d7d1222204efe58e29b4516867f47a77734ec4791
|