Metadata-Version: 2.5
Name: fidelity-trader-api
Version: 0.3.0
Summary: Unofficial Python SDK for Fidelity Trader+ API — Your Fidelity account, your API.
Project-URL: Homepage, https://github.com/brownjosiah/fidelity-trader-api
Project-URL: Repository, https://github.com/brownjosiah/fidelity-trader-api
Project-URL: Issues, https://github.com/brownjosiah/fidelity-trader-api/issues
Project-URL: Documentation, https://brownjosiah.github.io/fidelity-trader-api/
Author: brownjosiah
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Provides-Extra: aws
Requires-Dist: boto3>=1.34; extra == 'aws'
Provides-Extra: cli
Requires-Dist: rich>=13.0; extra == 'cli'
Requires-Dist: typer>=0.12; extra == 'cli'
Provides-Extra: dev
Requires-Dist: boto3>=1.34; extra == 'dev'
Requires-Dist: pyotp>=2.9; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.22; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Provides-Extra: service
Requires-Dist: aiosqlite>=0.20; extra == 'service'
Requires-Dist: cryptography>=43.0; extra == 'service'
Requires-Dist: fastapi>=0.115; extra == 'service'
Requires-Dist: pydantic-settings>=2.0; extra == 'service'
Requires-Dist: uvicorn[standard]>=0.30; extra == 'service'
Provides-Extra: totp
Requires-Dist: pyotp>=2.9; extra == 'totp'
Description-Content-Type: text/markdown

# Fidelity Trader API

**Your Fidelity account, your API.**

Unofficial Python SDK, CLI, and self-hosted REST service for the Fidelity Trader+ API.

> **Disclaimer:** This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or supported by Fidelity Investments. Use at your own risk. Trading involves risk of financial loss. By using this software, you accept full responsibility for any trades placed through your account.

[![Tests](https://github.com/brownjosiah/fidelity-trader-api/actions/workflows/ci.yml/badge.svg)](https://github.com/brownjosiah/fidelity-trader-api/actions)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)

**[Full Documentation](https://brownjosiah.github.io/fidelity-trader-api/)**

---

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
  - [CLI (Fastest)](#cli-fastest)
  - [Python SDK](#python-sdk)
  - [REST Service](#rest-service)
- [Installation](#installation)
- [Safety: Dry-Run Mode](#safety-dry-run-mode)
- [CLI Reference](#cli-reference)
- [SDK Reference](#sdk-reference)
  - [Authentication](#authentication)
  - [Portfolio](#portfolio)
  - [Trading](#trading)
  - [Market Data](#market-data)
  - [Research](#research)
  - [Watchlists & Alerts](#watchlists--alerts)
  - [Real-Time Streaming](#real-time-streaming)
  - [Session Management](#session-management)
- [REST Service](#rest-service-1)
  - [Endpoints](#endpoints)
  - [Streaming (SSE / WebSocket)](#streaming-sse--websocket)
  - [Docker Deployment](#docker-deployment)
- [Client Generation (Go / TypeScript)](#client-generation-go--typescript)
- [Credential Providers](#credential-providers)
- [Error Handling](#error-handling)
- [Architecture](#architecture)
- [API Modules](#api-modules)
- [Development](#development)
- [Project Structure](#project-structure)
- [Roadmap](#roadmap)
- [License](#license)

---

## Features

- **43 API modules** covering portfolio, trading (incl. crypto and batch/mutual-fund orders), market data, research, streaming, alerts, and more
- **CLI tool (`ft`)** with 17 commands — positions, trading, quotes, streaming, all from your terminal
- **Self-hosted REST service** — 72 endpoints via FastAPI, Docker-ready, language-agnostic
- **Dry-run safety** — order placement defaults to preview-only; live trading requires explicit opt-in
- **Real-time WebSocket streaming** — MDDS protocol (quotes, Greeks, 25-level L2) + BEPS event streaming over the AWS AppSync **Events** API (order fills, price alerts, balance updates, crypto activity; 21 event types)
- **Full order lifecycle** — preview, place, cancel (with preview), and modify equity, single-leg option, multi-leg option, conditional, crypto, and batch orders (batch is the only path that supports mutual funds)
- **Pydantic v2 models** for all API responses with type-safe field access + 1,400 auto-generated models from codegen
- **Credential providers** — AWS Secrets Manager, SSM Parameter Store, environment variables
- **Async client** via `AsyncFidelityClient` (wraps sync SDK with `asyncio.to_thread`)
- **Retry transport** with exponential backoff for transient failures
- **Auto session refresh** — background keep-alive for long-running applications
- **1,789 offline tests** with full HTTP mocking via respx, plus **62 opt-in live-API tests** in `tests/live/`

## Quick Start

### CLI (Fastest)

```bash
pip install fidelity-trader-api[cli]

# Login (prompts for credentials, or reads FIDELITY_USERNAME / FIDELITY_PASSWORD env vars)
ft login

# See your positions
ft positions

# Get a quote
ft quote AAPL TSLA

# Preview a trade (dry-run by default — no order placed)
ft buy AAPL 10 --limit 150.00

# Stream live quotes
ft stream AAPL TSLA NVDA
```

### Python SDK

```python
from fidelity_trader import FidelityClient

with FidelityClient() as client:
    client.login(username="your_username", password="your_password")

    # Discover accounts
    accounts = client.accounts.discover_accounts()
    acct_nums = [a.acct_num for a in accounts.accounts]

    # Get positions
    positions = client.positions.get_positions(acct_nums)
    for acct in positions.accounts:
        for p in acct.positions:
            print(f"{p.symbol}: {p.quantity} shares @ ${p.price_detail.last_price}")

    # Get balances
    balances = client.balances.get_balances(acct_nums)
```

### REST Service

```bash
pip install fidelity-trader-api[service]

# Start the service
python -m service

# Or with Docker
docker compose -f docker/docker-compose.yml up -d

# Login
curl -X POST http://localhost:8787/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "...", "password": "..."}'

# Get positions
curl http://localhost:8787/api/v1/accounts/Z12345678/positions
```

## Installation

```bash
pip install fidelity-trader-api
```

### Extras

| Extra | Install | What it adds |
|-------|---------|-------------|
| `cli` | `pip install fidelity-trader-api[cli]` | `ft` command-line tool (typer + rich) |
| `service` | `pip install fidelity-trader-api[service]` | FastAPI REST service + Docker support |
| `totp` | `pip install "fidelity-trader-api[totp]"` | `pyotp` — required only to derive 2FA codes from a base32 `totp_secret`. Passing an already-generated 6-digit code needs no extra. |
| `aws` | `pip install fidelity-trader-api[aws]` | AWS Secrets Manager / SSM credential providers |
| `dev` | `pip install fidelity-trader-api[dev]` | Testing (pytest, pytest-asyncio, respx, boto3, pyotp) |
| `docs` | `pip install fidelity-trader-api[docs]` | mkdocs-material for the docs site |

**Requirements:** Python 3.10+ and a Fidelity brokerage account with Trader+ access.

### Verify CLI Installation

After installing with `[cli]`, verify the `ft` command is available:

```bash
ft --help
```

If you get "command not found", the Python Scripts directory isn't in your PATH:

**Windows (PowerShell):**
```powershell
# Find where pip installed it
python -c "import sysconfig; print(sysconfig.get_path('scripts'))"

# Add to PATH permanently (replace path with your output above)
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\Users\YourUser\AppData\Local\Programs\Python\Python312\Scripts", "User")

# Restart your terminal, then verify
ft --help
```

**Linux / macOS:**
```bash
# Usually ~/.local/bin — add to your shell profile if needed
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```

**Alternative** — invoke without PATH setup:
```bash
python -m fidelity_trader.cli --help
```

### Standalone CLI Binaries (No Python Required)

Pre-built standalone binaries are attached to each [GitHub Release](https://github.com/brownjosiah/fidelity-trader-api/releases):

| Platform | Binary |
|----------|--------|
| **Windows** | `ft-windows-amd64.exe` |
| **Linux** | `ft-linux-amd64` |
| **macOS** | `ft-macos-amd64` |

Download, rename to `ft` (or `ft.exe`), place in your PATH, and run — no Python installation needed.

### Release Artifacts

Every [GitHub Release](https://github.com/brownjosiah/fidelity-trader-api/releases) includes:

| Artifact | Description |
|----------|-------------|
| `ft-{platform}` | Standalone CLI binaries (Windows, Linux, macOS) |
| `openapi.json` | OpenAPI 3.1 spec |
| `openapi-3.0.json` | OpenAPI 3.0.3 spec (Go/Java/C# client generators) |
| `fidelity-trader-api-go-client.tar.gz` | Pre-generated Go client |
| `fidelity-trader-api-ts-client.tar.gz` | Pre-generated TypeScript types |

See [Client Generation](#client-generation-go--typescript) for usage.

## Safety: Dry-Run Mode

**All order placement is blocked by default.** This prevents accidental trades when developing or testing.

| Context | Default | How to enable live trading |
|---------|---------|---------------------------|
| **SDK** | `live_trading=False` | `FidelityClient(live_trading=True)` or `FIDELITY_LIVE_TRADING=true` env var |
| **CLI** | Preview-only | Add `--live` flag: `ft buy AAPL 10 --limit 150 --live` |
| **Service** | Preview-only | Set `FTSERVICE_LIVE_TRADING=true` env var |

In dry-run mode:
- `preview_*` methods work normally
- `place_*` methods raise `DryRunError`
- The CLI shows the preview result and prints "Dry-run mode. Add --live to place this order."
- Cancellation is never blocked (you can always cancel orders)

---

## CLI Reference

The `ft` command-line tool provides 17 commands for interacting with your Fidelity account.

### Authentication

```bash
ft login                          # Interactive login (prompts or reads env vars)
ft login --username X --password Y  # Non-interactive
ft logout                         # Clear saved session
ft status                         # Check session health
```

Sessions are saved to `~/.config/ft/session.json` (Linux/Mac) or `%APPDATA%/ft/session.json` (Windows) and persist across commands. Sessions expire after ~30 minutes of inactivity.

### Portfolio

```bash
ft accounts                       # List all accounts
ft positions                      # Positions (auto-selects account if only one)
ft positions Z12345678             # Positions for specific account
ft balances                       # Account balances
```

### Trading

```bash
# Equity orders (preview by default)
ft buy AAPL 10                    # Market buy — preview only
ft buy AAPL 10 --limit 150.00    # Limit buy — preview only
ft sell TSLA 5 --stop 200.00     # Stop sell — preview only
ft buy AAPL 10 --limit 150 --live  # Actually place the order
ft buy AAPL 10 --limit 150 --live --yes  # Skip confirmation prompt

# Order management
ft orders                         # List open/recent orders
ft cancel CONF123                 # Cancel an order (always works, not gated)

# Options
ft options chain AAPL             # Option chain
ft options buy AAPL250418C00170000 1 --limit 3.50  # Buy call — preview
ft options sell AAPL250418P00160000 1 --live        # Sell put — live
```

### Market Data & Research

```bash
ft quote AAPL TSLA NVDA           # Quick quotes
ft chart AAPL --bars D --days 30  # Historical OHLCV
ft search "apple"                 # Symbol search
ft earnings AAPL MSFT             # Earnings data
ft dividends KO PG               # Dividend data
```

### Streaming

```bash
ft stream AAPL TSLA               # Live streaming quotes (updates in-place)
ft stream AAPL --fields last,bid,ask,volume  # Custom fields
```

### Output Formats

All commands support `--format json` for piping:

```bash
ft positions --format json | jq '.[] | select(.symbol == "AAPL")'
ft quote AAPL --format json
```

---

## SDK Reference

### Authentication

```python
from fidelity_trader import FidelityClient

# Basic login
with FidelityClient() as client:
    client.login(username="your_username", password="your_password")

# With TOTP 2FA — the base32-secret form needs the `totp` extra:
#   pip install "fidelity-trader-api[totp]"
with FidelityClient() as client:
    client.login(
        username="your_username",
        password="your_password",
        totp_secret="YOUR_BASE32_TOTP_SECRET",
    )

# Or pass an already-generated 6-digit code — no extra required:
with FidelityClient() as client:
    client.login(username="...", password="...", totp_secret="123456")
```

The login is a 7-step handshake against `ecaap.fidelity.com`:

1. `GET /prgw/digital/login/atp` — Initialize login, set `SESSION_SCTX` cookie
2. `DELETE /user/session/login` — Clear stale sessions
3. `GET /user/identity/remember/username` — Check remembered user
4. `POST /user/identity/remember/username/1` — Select user, obtain `ET` token
5. `POST /user/factor/password/authentication` — Submit credentials
6. `PUT /user/identity/remember/username` — Update remembered user state
7. `POST /user/session/login` — Create session (sets `ATC`, `FC`, `RC`, `SC` cookies)

### Portfolio

```python
# Account discovery — crypto accounts need the InternalDigital category tag
accounts = client.accounts.discover_accounts(include_crypto=True)

# Account features (HTTP 207 — check .failed_accounts / .has_error).
# Flags live under featureDetails.{eligible,established}.marginOptionSpreadsDetail;
# use the convenience props rather than the old flat marginInd/optionsInd fields.
features = client.account_features.get_features_for_accounts(accounts.accounts)
for f in features.features:
    print(f.acct_num, f.has_margin, f.has_options, f.eligible_for_options)

# Open / closed lots take a CUSIP, NOT a ticker
open_lots = client.open_lots.get_open_lots("Z12345678", "037833100")
closed_lots = client.closed_lots.get_closed_lots("Z12345678", "037833100")

# Hard-to-borrow rates — symbols are CUSIPs; an empty list queries all HTB positions
htb = client.hard_to_borrow.get_borrow_rates("Z12345678", [])

# Positions
positions = client.positions.get_positions(["Z12345678"])

# Balances (includes margin details)
balances = client.balances.get_balances(["Z12345678"])

# Option positions summary
options = client.option_summary.get_option_summary(["Z12345678"])

# Transaction history
transactions = client.transactions.get_transaction_history(["Z12345678"], from_date, to_date)

# Closed positions (gain/loss)
closed = client.closed_positions.get_closed_positions(["Z12345678"], start_date, end_date)

# Loaned securities (fully paid lending rates)
loaned = client.loaned_securities.get_loaned_securities(["Z12345678"])

# Tax lot details
lots = client.tax_lots.get_tax_lots("Z12345678", "AAPL")
```

### Trading

All orders follow a **preview-then-place** workflow. Place methods are blocked by default (dry-run mode).

```python
from fidelity_trader.models.equity_order import EquityOrderRequest

# Enable live trading
with FidelityClient(live_trading=True) as client:
    client.login(username, password)

    # Build equity order
    order = EquityOrderRequest(
        acctNum="Z12345678",
        symbol="AAPL",
        orderActionCode="B",       # B=Buy, S=Sell
        qty=10,
        priceTypeCode="L",         # L=Limit, M=Market, S=Stop
        limitPrice=150.00,
        tifCode="D",               # D=Day, G=GTC
    )

    # Preview
    preview = client.equity_orders.preview_order(order)
    print(f"Validated: {preview.is_validated}")
    print(f"Net amount: {preview.order_confirm_detail.net_amount}")

    # Place
    result = client.equity_orders.place_order(order, preview.conf_num)
    print(f"Order accepted: {result.is_accepted}")
```

**Single-leg options:**
```python
from fidelity_trader.models.single_option_order import SingleOptionOrderRequest

order = SingleOptionOrderRequest(
    acctNum="Z12345678",
    symbol="AAPL250418C00170000",
    orderActionCode="BC",          # BC=Buy Call, BP=Buy Put, SC=Sell Call, SP=Sell Put
    qty=1,
)
preview = client.single_option_orders.preview_order(order)
result = client.single_option_orders.place_order(order, preview.conf_num)
```

**Multi-leg options, cancel-and-replace, conditional orders** — see `examples/full_walkthrough.py`.

**Order management:**
```python
# Check order status
status = client.order_status.get_order_status(["Z12345678"])

# Cancel an order (not gated by dry-run)
client.cancel_order.cancel_order(conf_num="CONF123", acct_num="Z12345678", action_code="B")

# Staged/saved orders (no account argument — scoped by the session)
staged = client.staged_orders.get_staged_orders()
```

### Market Data

```python
# Option chain
chain = client.option_chain.get_option_chain("AAPL")

# Depth of market for a single OPTION symbol (per-exchange quotes)
montage = client.option_chain.get_montage("AAPL250418C00170000")

# Historical chart data — start/end are required.
# Pass date/datetime objects (formatted for you) or "YYYY/MM/DD" strings.
# ISO "YYYY-MM-DD" is rejected by the service.
from datetime import date
chart = client.chart.get_chart("AAPL", date(2026, 1, 2), date(2026, 3, 31), bar_width="D")

# Available markets
markets = client.available_markets.get_available_markets("AAPL", ["Z12345678"])

# Holiday calendar
holidays = client.holiday_calendar.get_holidays()
```

### Research

```python
# Earnings and dividends
earnings = client.research.get_earnings(["AAPL", "MSFT"])
dividends = client.research.get_dividends(["AAPL", "KO"])

# Symbol search
results = client.search.autosuggest("AAPL")

# Short interest, and its historical series.
# get_time_series is multi-series: pick one out with .series_for().
# Points carry asOfDateEpoch (epoch milliseconds), not an ISO date string.
from datetime import date
insights = client.short_insights.get_insights("Z12345678", ["AAPL"])
series = client.short_insights.get_time_series(
    "AAPL", "Z12345678", date(2026, 1, 1), date(2026, 6, 30)
)

# Option analytics
analytics = client.option_analytics.analyze_position("AAPL", legs)

# Stock screener (LiveVol) — 3-step SAML auth, then run a scan by ID
client.screener.authenticate()
scan = client.screener.execute_scan(2)  # e.g. 2 = call volume scan
```

### Watchlists & Alerts

```python
# Watchlists
watchlists = client.watchlists.get_watchlists()
client.watchlists.save_watchlist({
    "watchListName": "Buys",
    "productCode": "WL",
    "watchListTypeCode": "WL",
    "securityDetails": [{"symbol": "AAPL", "shareQuantity": "0"}],
})

# Alert subscription
alert = client.alerts.subscribe()

# Alert messages
messages = client.alerts.get_alerts()

# Price triggers (symbol-scoped, not account-scoped)
triggers = client.price_triggers.get_price_triggers("AAPL")
created = client.price_triggers.create_price_trigger("AAPL", "greaterThan", 200.00)
client.price_triggers.delete_price_triggers([trigger_id])
```

### Real-Time Streaming

```python
from fidelity_trader.streaming.mdds import MDDSClient, MDDS_URL

with FidelityClient() as client:
    client.login(username, password)
    cookie_str = "; ".join(f"{c.name}={c.value}" for c in client._http.cookies.jar)

    mdds = MDDSClient()
    # Connect, subscribe, and parse — see examples/live_streaming.py
```

**MDDS field coverage:**
- **Equities:** last, bid/ask, volume, change, open, high, low, close, 52wk range, market cap
- **Options:** all equity fields + delta, gamma, theta, vega, rho, implied volatility, open interest
- **Time & Sales:** last trade price, size, time, exchange, condition, tick direction
- **L2 Depth:** 25-level order book — price, size and exchange per level, plus `depth_bid`/`depth_ask` totals. There are no per-level timestamps; the service does not send them.

**BEPS account events** (order fills, cancellations, balance/cost-basis updates, price alerts, crypto CRJNL/CRORD) run over the AWS AppSync **Events** API:

```python
from fidelity_trader.streaming.beps import BEPSClient
from fidelity_trader.streaming.beps_auth import BEPS_STREAM_URL, BEPS_SUBPROTOCOL

token = client.beps_auth.get_token()      # JWT
mid = client.mid                          # from the security context
beps = BEPSClient()
# Connect to BEPS_STREAM_URL verbatim, advertising two sub-protocols:
#   "aws-appsync-event-ws" and "header-<base64url(protocol header)>".
# Auth is NOT in the query string. Subscribe to channel /bsep/{appId}/{mid}.
# Frames: subscribe / subscribe_success / subscribe_error / unsubscribe.
# `data` frames carry a top-level `event` field holding a JSON *string*.
# See examples/live_streaming.py.
```

### Session Management

```python
# Auto-refresh (background thread, keeps session alive)
client.enable_auto_refresh(interval=300)  # every 5 minutes
client.disable_auto_refresh()

# Manual keep-alive
client.session_keepalive.extend_session()
client.session_keepalive.is_session_alive()  # True/False

# Async client
from fidelity_trader import AsyncFidelityClient

async with AsyncFidelityClient() as client:
    await client.login(username, password)
    positions = await client.get_positions(["Z12345678"])

# Retry on transient failures
with FidelityClient(max_retries=3, retry_delay=1.0) as client:
    ...  # Retries on 429, 500, 502, 503, 504 with exponential backoff
```

---

## REST Service

The service wraps most of the 43 SDK modules as REST endpoints with session lifecycle management, streaming fan-out, and Docker deployment. The newer `crypto_orders` and `batch_orders` modules are SDK-only for now.

```bash
pip install fidelity-trader-api[service]
python -m service  # Starts on http://localhost:8787
```

### Configuration

All settings via environment variables with `FTSERVICE_` prefix:

| Variable | Default | Description |
|----------|---------|-------------|
| `FTSERVICE_HOST` | `127.0.0.1` | Bind address |
| `FTSERVICE_PORT` | `8787` | Port |
| `FTSERVICE_API_KEY_REQUIRED` | `true` | Require `Authorization: Bearer <key>` |
| `FTSERVICE_ENCRYPTION_KEY` | | Fernet key for credential storage |
| `FTSERVICE_LIVE_TRADING` | `false` | Enable live order placement |
| `FTSERVICE_AUTO_REAUTH` | `true` | Re-authenticate on session expiry |
| `FTSERVICE_SESSION_KEEPALIVE_INTERVAL` | `300` | Keep-alive interval (seconds) |
| `FTSERVICE_LOG_LEVEL` | `INFO` | Logging level |

### Endpoints

**Auth & Session:**
```
POST   /api/v1/auth/login              Login with Fidelity credentials
POST   /api/v1/auth/logout             Logout and clear session
GET    /api/v1/auth/status             Session state
POST   /api/v1/auth/credentials        Store encrypted credentials
DELETE /api/v1/auth/credentials        Remove stored credentials
```

**Accounts & Portfolio (16 endpoints):**
```
GET    /api/v1/accounts                          All accounts
GET    /api/v1/accounts/{acct}/positions         Positions
GET    /api/v1/accounts/{acct}/balances          Balances
GET    /api/v1/accounts/{acct}/transactions      Transaction history
GET    /api/v1/accounts/{acct}/options-summary   Option positions
GET    /api/v1/accounts/{acct}/closed-positions  Closed positions
GET    /api/v1/accounts/{acct}/loaned-securities Loaned securities
GET    /api/v1/accounts/{acct}/tax-lots/{symbol} Tax lots
GET    /api/v1/accounts/{acct}/features          Account feature flags (HTTP 207)
GET    /api/v1/accounts/{acct}/hard-to-borrow    Hard-to-borrow rates
GET    /api/v1/accounts/{acct}/lots/{symbol}     Open lots
GET    /api/v1/accounts/{acct}/closed-lots/{symbol}  Closed lots
GET    /api/v1/accounts/{acct}/short-insights    Short interest
GET    /api/v1/accounts/{acct}/short-insights/{symbol}/timeseries  Short interest history
GET    /api/v1/accounts/{acct}/accruals          Accruals
GET    /api/v1/accounts/{acct}/cash-accruals     Cash accruals
```

**Orders (20 endpoints):**
```
GET    /api/v1/orders/status                     Open/recent orders
GET    /api/v1/orders/{order_id}/detail          Order detail
GET    /api/v1/orders/staged                     Staged orders
POST   /api/v1/orders/staged/save                Save a staged order
DELETE /api/v1/orders/staged/{stage_id}          Delete a staged order
POST   /api/v1/orders/equity/preview             Preview equity order
POST   /api/v1/orders/equity/place               Place equity order
POST   /api/v1/orders/option/preview             Preview single-leg option
POST   /api/v1/orders/option/place               Place single-leg option
POST   /api/v1/orders/options/preview            Preview multi-leg option
POST   /api/v1/orders/options/place              Place multi-leg option
POST   /api/v1/orders/{conf_num}/cancel/preview  Preview cancel
POST   /api/v1/orders/{conf_num}/cancel          Cancel order
POST   /api/v1/orders/replace/preview            Preview cancel-replace
POST   /api/v1/orders/replace/place              Place cancel-replace
POST   /api/v1/orders/conditional/preview        Preview conditional order
POST   /api/v1/orders/conditional/place          Place conditional order
POST   /api/v1/orders/conditional/replace/preview  Preview conditional replace
POST   /api/v1/orders/conditional/replace/place    Place conditional replace
POST   /api/v1/orders/trade-agreement            Accept trade agreement
```

Note: `crypto_orders` and `batch_orders` are SDK-only — they have no REST routes yet.

**Market Data, Research, Watchlists, Preferences, Reference** — 24 more endpoints, plus 4 streaming-control routes. See `/docs` for the full OpenAPI spec.

### Response Format

All responses use a consistent envelope:

```json
{"ok": true, "data": { ... }, "error": null}
```

Error responses:

```json
{"ok": false, "data": null, "error": {"code": "AUTH_REQUIRED", "message": "Not authenticated"}}
```

| Error Code | HTTP | Meaning |
|------------|------|---------|
| `AUTH_REQUIRED` | 401 | Not logged in |
| `SESSION_EXPIRED` | 401 | Session timed out |
| `LIVE_TRADING_DISABLED` | 403 | Dry-run mode active |
| `API_KEY_INVALID` | 403 | Bad or missing API key |
| `FIDELITY_ERROR` | 502 | Upstream Fidelity error |

### Streaming (SSE / WebSocket)

The service fans out a single MDDS WebSocket connection to multiple consumers:

**Server-Sent Events:**
```bash
curl -N "http://localhost:8787/api/v1/streaming/quotes?symbols=AAPL,TSLA"
# event: quote
# data: {"symbol": "AAPL", "last": 195.23, "bid": 195.20, "ask": 195.25}
```

**WebSocket:**
```javascript
const ws = new WebSocket("ws://localhost:8787/api/v1/ws/quotes");
ws.send(JSON.stringify({ action: "subscribe", symbols: ["AAPL", "TSLA"] }));
ws.onmessage = (e) => console.log(JSON.parse(e.data));
```

**REST control:**
```
POST /api/v1/streaming/subscribe      {"symbols": ["AAPL"]}
POST /api/v1/streaming/unsubscribe    {"symbols": ["AAPL"]}
GET  /api/v1/streaming/subscriptions  Current subscriptions
GET  /api/v1/streaming/status         Connection state
```

### Docker Deployment

```bash
# Copy and edit environment config
cp docker/.env.example docker/.env
# Generate encryption key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# Start
docker compose -f docker/docker-compose.yml up -d

# Health check
curl http://localhost:8787/health
```

---

## Client Generation (Go / TypeScript)

The service auto-generates a fully typed [OpenAPI spec](https://swagger.io/specification/) with 268 component schemas. Use it to generate native clients in any language.

### Pre-generated Clients

Ready-to-use clients are included in the repo:

| Language | Location | Lines | Generator |
|----------|----------|-------|-----------|
| **Go** | `clients/go/client.gen.go` | 13,170 | [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) |
| **TypeScript** | `clients/typescript/src/types.ts` | 8,433 | [openapi-typescript](https://github.com/openapi-ts/openapi-typescript) |

### Go Usage

```go
import fidelitytrader "github.com/brownjosiah/fidelity-trader-api/clients/go"

client, err := fidelitytrader.NewClient("http://localhost:8787")
resp, err := client.GetApiV1AccountsAcctPositions(ctx, "Z12345678")
```

### TypeScript Usage

```typescript
import type { paths } from "./types";
import createClient from "openapi-fetch";

const client = createClient<paths>({ baseUrl: "http://localhost:8787" });
const { data } = await client.GET("/api/v1/accounts/{acct}/positions", {
  params: { path: { acct: "Z12345678" } },
});
```

### Regenerate Clients

```bash
# Export the OpenAPI spec
make openapi            # OpenAPI 3.1 (for TypeScript)
make openapi-compat     # OpenAPI 3.0.3 (for Go / openapi-generator)

# Generate clients
make client-ts          # TypeScript types
make client-go          # Go client + types
make clients            # Both
```

### Generate for Other Languages

Export the spec and use any OpenAPI generator:

```bash
make openapi-compat

# Java
npx @openapitools/openapi-generator-cli generate -i openapi.json -g java -o clients/java

# Rust
npx @openapitools/openapi-generator-cli generate -i openapi.json -g rust -o clients/rust

# C#
npx @openapitools/openapi-generator-cli generate -i openapi.json -g csharp -o clients/csharp
```

---

## Credential Providers

Avoid hardcoding credentials:

```python
from fidelity_trader.credentials import (
    EnvProvider,             # FIDELITY_USERNAME, FIDELITY_PASSWORD env vars
    SecretsManagerProvider,  # AWS Secrets Manager
    SSMParameterProvider,    # AWS SSM Parameter Store
    FileProvider,            # JSON file
    DirectProvider,          # Direct (testing only)
)

creds = EnvProvider().get_credentials()
# or: SecretsManagerProvider(secret_name="fidelity/trader").get_credentials()
# or: SSMParameterProvider(prefix="/fidelity/trader").get_credentials()

with FidelityClient() as client:
    client.login(creds.username, creds.password, totp_secret=creds.totp_secret)
```

## Error Handling

```python
from fidelity_trader import (
    FidelityError,          # Base exception
    AuthenticationError,    # Login failed
    SessionExpiredError,    # Session cookies expired
    CSRFTokenError,         # CSRF token error
    APIError,               # API error (has .status_code, .response_body)
    DryRunError,            # Order blocked by dry-run mode
)
```

## Architecture

All 43 API modules share a single `httpx.Client` instance via `FidelityClient`. Cookies (the auth mechanism) propagate automatically.

```
FidelityClient
├── _http: httpx.Client              shared cookie jar
├── _auth: AuthSession               7-step login + CSRF
├── 13 portfolio modules             positions, balances, accounts, features, lots, HTB, accruals, ...
├── 11 order modules                 equity, option, cancel, conditional, crypto, batch, ...
├── 3 market data modules            fastquote (chains + montage), chart, time & sales
├── 6 research modules               earnings, search, analytics, screener, short insights, notebooks
├── streaming                        MDDS quotes + L2, BEPS events, news auth, reconnect/subscription
├── watchlists, alerts, price_triggers, preferences
├── available_markets, holiday_calendar, security_context
├── session_keepalive, auto_refresh
└── close()
```

## API Modules

| # | Accessor | Endpoint | Host |
|---|----------|----------|------|
| 1 | `positions` | `POST /ftgw/dp/position/v2` | dpservice |
| 2 | `balances` | `POST /ftgw/dp/balance/detail/v2` | dpservice |
| 3 | `accounts` | `POST /ftgw/dp/customer-am-acctnxt/v2/accounts` | dpservice |
| 4 | `option_summary` | `POST /ftgw/dp/retail-am-optionsummary/v1` | dpservice |
| 5 | `transactions` | `POST /ftgw/dp/accountmanagement/transaction/history/v2` | dpservice |
| 6 | `closed_positions` | `POST /ftgw/dp/customer-am-position/v1/.../closedposition` | dpservice |
| 7 | `loaned_securities` | `POST /ftgw/dp/retail-am-loanedsecurities/v1/.../rates` | dpservice |
| 8 | `tax_lots` | `POST /ftgw/dp/orderentry/taxlot/v1` | dpservice |
| 9 | `order_status` | `POST /ftgw/dp/retail-order-status/v3` | dpservice |
| 10 | `equity_orders` | `POST /ftgw/dp/orderentry/equity/{preview,place}/v1` | dpservice |
| 11 | `single_option_orders` | `POST /ftgw/dp/orderentry/option/{preview,place}/v2` | dpservice |
| 12 | `option_orders` | `POST /ftgw/dp/orderentry/multilegoption/{preview,place}/v1` | dpservice |
| 13 | `cancel_order` | `POST /ftgw/dp/orderentry/cancel/place/v1` | dpservice |
| 14 | `cancel_replace` | `POST /ftgw/dp/orderentry/cancelandreplace/{preview,place}/v1` | dpservice |
| 15 | `conditional_orders` | `POST /ftgw/dp/orderentry/conditional/{preview,place}/v1` | dpservice |
| 16 | `staged_orders` | `POST /ftgw/dp/ent-research-staging/v1/.../staged-order/get` | dpservice |
| 17 | `option_chain` | `GET fastquote/service/quote/{chainLite,dtmontage}` | fastquote |
| 18 | `chart` | `GET fastquote/service/marketdata/historical/chart/json` | fastquote |
| 19 | `research` | `GET /ftgw/dpdirect/research/{earning,dividend}/v1` | dpservice |
| 20 | `search` | `GET /ftgw/dpdirect/search/autosuggest/v1` | dpservice |
| 21 | `option_analytics` | `POST /ftgw/dp/research/option/positions/analytics/v1` | dpservice |
| 22 | `screener` | `POST fidelity.apps.livevol.com ExecuteScan` | livevol |
| 23 | `streaming` | `POST streaming-news/ftgw/snaz/Authorize` | streaming-news |
| 24 | `watchlists` | `POST /ftgw/dp/retail-watchlist/v1/.../get` | dpservice |
| 25 | `alerts` | `POST ecawsgateway/ftgw/alerts/services/ATBTSubscription` | ecawsgateway |
| 26 | `price_triggers` | `GET+POST /ftgw/dp/retail-price-triggers/v1` | dpservice |
| 27 | `preferences` | `POST /ftgw/dp/.../atn-prefs/{get,save,delete}preference` | dpservice |
| 28 | `available_markets` | `POST /ftgw/dp/reference/security/stock/availablemarket/v1` | dpservice |
| 29 | `holiday_calendar` | `GET /ftgw/dpdirect/market/holidaycalendar/v1` | dpservice |
| 30 | `security_context` | `POST /ftgw/digital/pico/api/v1/context/security` | digital |
| 31 | `session_keepalive` | `GET /ftgw/digital/portfolio/extendsession` | digital |
| 32 | `account_features` | `POST /ftgw/dp/customer-am-feature/v2/accounts/features/get` | dpservice |
| 33 | `hard_to_borrow` | `POST /ftgw/dp/retail-am-hardtoborrow/v1/accounts/positions/rates/borrow` | dpservice |
| 34 | `open_lots` | `POST /ftgw/dp/position/open/lots/v1` | dpservice |
| 35 | `closed_lots` | `POST /ftgw/dp/customer-am-position/v1/accounts/closedlots` | dpservice |
| 36 | `accruals` | `POST /ftgw/dp/retail-am-accountaccruals/{v1,v2}/accounts/accruals` | dpservice |
| 37 | `short_insights` | `POST /ftgw/dp/retail-am-shortinsight/v1/.../insights` + `/ftgw/dp/ent-research-companyinfo/v1/.../securities-lending/time-series` | dpservice |
| 38 | `time_and_sales` | `GET fastquote/rhistory/atng/timeandsalesLite` | fastquote |
| 39 | `notebooks` | `POST /ftgw/dp/research/tool/notebook/note/{detail,delete,update}/v1` | dpservice |
| 40 | `trade_agreement` | `POST /ftgw/dp/trade/useragreement/modify/v1` | dpservice |
| 41 | `crypto_orders` | `POST /ftgw/dp/retail-crypto-currency/v1/orders/{preview,place,cancel/preview,cancel/place}` | dpservice |
| 42 | `batch_orders` | `POST /ftgw/dp/orderentry/batchorder/{preview,place}/v1` | dpservice |
| 43 | `beps_auth` | `GET /ftgw/dp/security/token/authz/generate/1.0` | dpservice |

**MDDS WebSocket** (`wss://mdds-i-tc.fidelity.com`) — real-time quotes, options with Greeks, T&S, 25-level L2 depth.

**BEPS WebSocket** (`wss://spservice.fidelity.com/event/realtime`) — AWS AppSync Events API; account events, order fills, price alerts, crypto activity (21 event types).

## Development

```bash
git clone https://github.com/brownjosiah/fidelity-trader-api.git
cd fidelity-trader-api
pip install -e ".[dev,cli,service]"

# Run tests
pytest                              # all 1,789 offline tests
pytest tests/test_positions.py -v   # single module
pytest --cov=fidelity_trader        # with coverage

# Opt-in live-API suite (62 read-only tests; asserts order placement stays blocked)
pytest tests/live --live            # or FIDELITY_LIVE_TESTS=1 pytest tests/live
# Credentials from AWS Secrets Manager (fidelity/username, fidelity/password,
# fidelity/totp_secret) or the FIDELITY_* env vars. See tests/live/README.md.

# Lint
ruff check src/ service/ tests/
```

### Adding New API Modules

This SDK is built from captured network traffic. The workflow:

1. Start mitmproxy: `mitmweb --listen-port 8080 -w ~/capture.flow`
2. Route Trader+ through the proxy (system proxy + CA cert)
3. Perform the target action in Trader+
4. Analyze the capture: extract endpoints, request/response shapes
5. Create Pydantic model, API module, client integration, and tests

See [`docs/BACKLOG.md`](docs/BACKLOG.md) for the full backlog.

## Project Structure

```
fidelity-trader-api/
├── src/fidelity_trader/           # Python SDK (pip install)
│   ├── client.py                  # FidelityClient — composes all 43 modules
│   ├── async_client.py            # AsyncFidelityClient wrapper
│   ├── _http.py                   # HTTP session factory, base URLs, headers
│   ├── exceptions.py              # Exception hierarchy + DryRunError
│   ├── retry.py                   # RetryTransport with exponential backoff
│   ├── credentials.py             # Credential providers (AWS, env, file)
│   ├── cli/                       # CLI tool (ft command, 13 files)
│   ├── auth/                      # Authentication (login, 2FA, keepalive, auto-refresh)
│   ├── portfolio/                 # 8 portfolio modules
│   ├── orders/                    # 8 order modules
│   ├── market_data/               # Charts, option chains, montage
│   ├── research/                  # Earnings, dividends, search, analytics, screener
│   ├── streaming/                 # MDDS WebSocket (quotes, L2, T&S) + news auth
│   ├── watchlists/                # Watchlist get/save
│   ├── alerts/                    # Alert subscription + price triggers
│   ├── settings/                  # User preferences
│   ├── reference/                 # Available markets, holiday calendar
│   └── models/                    # 30+ hand-written Pydantic response models
│       └── generated/             # 1,400 codegen models (enums, API DTOs, internal)
│
├── service/                       # FastAPI REST service
│   ├── app.py                     # App factory, exception handlers, lifespan
│   ├── config.py                  # Settings (pydantic-settings)
│   ├── dependencies.py            # FastAPI dependency injection
│   ├── auth/                      # API key generation + middleware
│   ├── session/                   # Session manager, credential store, keepalive
│   ├── streaming/                 # MDDS fan-out, SSE, WebSocket
│   ├── routes/                    # 10 route files (72 endpoints)
│   └── models/                    # Request/response schemas
│
├── docker/                        # Docker deployment
│   ├── Dockerfile                 # Multi-stage build
│   ├── docker-compose.yml         # One-command deployment
│   └── .env.example               # Environment template
│
├── tests/                         # 1,789 offline tests + tests/live/ (62 opt-in live-API tests)
├── examples/                      # Usage examples
└── docs/                          # BACKLOG, SERVICE_PLAN, DECISIONS, PRODUCT_VISION
```

## Roadmap

The SDK, CLI, and service are feature-complete for the MVP. Remaining work:

| Priority | Items |
|----------|-------|
| **Medium** | Full priced option chain, news WebSocket, fundamentals, CI smoke tests, REST routes for crypto/batch orders |
| **Low** | HashiCorp Vault provider, live verification of the crypto order path (routes are production-configured but unverified against live traffic) |

See [`docs/BACKLOG.md`](docs/BACKLOG.md) for the full backlog and [`docs/PRODUCT_VISION.md`](docs/PRODUCT_VISION.md) for product strategy.

## License

Apache 2.0 — see [LICENSE](LICENSE).
