Metadata-Version: 2.4
Name: qjtrader
Version: 0.4.2
Summary: Official Python client for QJ Trader — Canadian and selected US market data and order entry.
Project-URL: Homepage, https://qjtrader.ai
Project-URL: Documentation, https://docs.qjtrader.ai/docs/ai
Project-URL: Console, https://gateway.qjtrader.ai
Project-URL: Source, https://github.com/QJTrader/qjtrader-python
Project-URL: Issues, https://github.com/QJTrader/qjtrader-python/issues
Project-URL: Changelog, https://github.com/QJTrader/qjtrader-python/blob/main/CHANGELOG.md
Project-URL: MCP server, https://github.com/QJTrader/qjtrader-mcp
Author: QJ Trader
License: Apache-2.0
License-File: LICENSE
Keywords: algotrading,canada,finance,market-data,montreal-exchange,order-entry,trading,tsx
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# qjtrader

Hosted assistants should use the delegated OAuth connector from [QJ Gateway](https://gateway.qjtrader.ai), which keeps the trading client secret out of chat. This SDK remains the full programmatic client for Python services and local agents.

[![PyPI version](https://img.shields.io/pypi/v/qjtrader)](https://pypi.org/project/qjtrader/)
[![Python versions](https://img.shields.io/pypi/pyversions/qjtrader)](https://pypi.org/project/qjtrader/)
[![License](https://img.shields.io/pypi/l/qjtrader)](https://github.com/QJTrader/qjtrader-python/blob/main/LICENSE)

Official Python client for the **QJ Trader AI Trading APIs** — stream real-time Canadian and selected
US market data and send orders through entitled Canadian or US gateway accounts (Montréal Exchange derivatives, and equities across every
lit exchange and dark pool) over one authenticated connection.

```bash
pip install qjtrader
```

- **Free sandbox, no approval.** Create an account at [gateway.qjtrader.ai](https://gateway.qjtrader.ai),
  click *Create sandbox credential*, and you get a `client_id` + `client_secret` that 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.
- **Verifiable releases.** Published straight from this repo via [PyPI Trusted
  Publishing](https://docs.pypi.org/trusted-publishers/) with signed [PEP 740](https://peps.python.org/pep-0740/)
  provenance — no manual uploads, no stored tokens. See [SECURITY.md](SECURITY.md) to verify a release.

## Quickstart

Get a sandbox key from the [console](https://gateway.qjtrader.ai), then:

```bash
export QJ_CLIENT_ID="your-client-id"
export QJ_CLIENT_SECRET="your-client-secret"
```

### Send an order

```python
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:

```python
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

```python
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:RY` is the **consolidated** Canadian equity book (each level tagged with its venue);
  `CA:RY.PT` is **PURE (CSE)** only. Futures like `MX:CRAU26` are venue-native. See the full
  [symbology reference](https://docs.qjtrader.ai/docs/ai/symbology).

### Check what is available

Coverage differs by product and entitlement, especially for US depth. The offline matrix requires
no credential or network connection:

```python
from qjtrader import market_availability

print(market_availability()["markets"]["US"])
```

Verified examples include AAPL L1, SPY L1/L2, and selected US futures L1/L2. AAPL depth, NDX,
and US listed-option depth are not currently available. See
[Market Availability](https://docs.qjtrader.ai/docs/ai/availability).

## Command line

The package installs a `qjtrader` command:

```bash
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.

```python
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"` (or `QJ_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](https://docs.qjtrader.ai/docs/ai/order-entry)**
and **[Market Data](https://docs.qjtrader.ai/docs/ai/market-data)**.

## Use it from an LLM (MCP)

Prefer to drive QJ from Claude or another AI assistant? The companion
[`qjtrader-mcp`](https://github.com/QJTrader/qjtrader-python) 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:

```bash
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://gateway.qjtrader.ai
- 🔤 Symbology: https://docs.qjtrader.ai/docs/ai/symbology
- 🐛 Issues: https://github.com/QJTrader/qjtrader-python/issues

## License

Apache-2.0. See [LICENSE](LICENSE).
