Metadata-Version: 2.4
Name: thetadatadx
Version: 13.0.0rc4
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Free Threading
Classifier: Programming Language :: Rust
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: License :: OSI Approved :: Apache Software License
Requires-Dist: pandas>=2.1 ; extra == 'all'
Requires-Dist: polars>=0.20 ; extra == 'all'
Requires-Dist: pyarrow>=14.0 ; extra == 'all'
Requires-Dist: pyarrow>=14.0 ; extra == 'arrow'
Requires-Dist: pytest>=9 ; extra == 'dev'
Requires-Dist: pytest-timeout>=2.3 ; extra == 'dev'
Requires-Dist: pandas>=2.1 ; extra == 'pandas'
Requires-Dist: pyarrow>=14.0 ; extra == 'pandas'
Requires-Dist: polars>=0.20 ; extra == 'polars'
Requires-Dist: pyarrow>=14.0 ; extra == 'polars'
Provides-Extra: all
Provides-Extra: arrow
Provides-Extra: dev
Provides-Extra: pandas
Provides-Extra: polars
Summary: No-JVM ThetaData Terminal — native Rust SDK for direct market data access (Python bindings)
Keywords: thetadata,market-data,options,streaming,grpc
Home-Page: https://github.com/userFRM/ThetaDataDx
Author: userFRM
License: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Discord, https://discord.thetadata.us/
Project-URL: Documentation, https://userfrm.github.io/ThetaDataDx/reference/
Project-URL: Homepage, https://github.com/userFRM/ThetaDataDx
Project-URL: Repository, https://github.com/userFRM/ThetaDataDx

# thetadatadx (Python)

The Python SDK for [ThetaData](https://thetadata.us) market data. Pull US stock, option, index, and rate data three ways — point-in-time **history**, real-time **streaming**, and whole-universe **flat files** — all from a single authenticated client. Connects straight to ThetaData; nothing to install and run locally, no local proxy.

[![PyPI](https://img.shields.io/pypi/v/thetadatadx?logo=python&logoColor=white)](https://pypi.org/project/thetadatadx)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/userFRM/ThetaDataDx/blob/main/LICENSE)
[![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg?logo=python&logoColor=white)](https://www.python.org)
[![Discord](https://img.shields.io/badge/Discord-community-5865F2.svg?logo=discord&logoColor=white)](https://discord.thetadata.us/)

> [!IMPORTANT]
> A valid [ThetaData](https://thetadata.us) subscription is required. The SDK
> authenticates against ThetaData's Nexus API using your account credentials.

## Features

- **Complete coverage** — stocks, options, indices, and rates across 65 typed endpoints.
- **Three access modes, one client** — point-in-time history, real-time streaming, and bulk flat-file downloads.
- **DataFrames built in** — every result chains straight to Polars, pandas, or Arrow over a zero-copy boundary.
- **Greeks without a round-trip** — first- through third-order Black-Scholes Greeks and an implied-volatility solver, computed locally.
- **Typed all the way down** — every tick is a typed object with attribute access and IDE completion, not a dict.
- **No terminal to run** — a direct connection to ThetaData; nothing to install and babysit locally.

## Install

```bash
pip install thetadatadx

pip install thetadatadx[polars]   # Polars DataFrames
pip install thetadatadx[pandas]   # pandas DataFrames (Arrow-backed)
pip install thetadatadx[arrow]    # raw pyarrow.Table
pip install thetadatadx[all]      # every optional adapter
```

Binary wheels ship for Linux, macOS, and Windows and require no Rust toolchain. Wheels use CPython's stable ABI (`abi3`), so one wheel per platform covers Python 3.12 and up; the free-threaded (`3.14t`) build runs with the GIL disabled and is selected automatically by `pip`.

## Quick start

> [!TIP]
> The cleaner way to sign in is an API key: generate one from your
> [ThetaData user portal](https://www.thetadata.net/), set `THETADATA_API_KEY`,
> and use `Credentials.from_env_or_file("creds.txt")`. It reads the key from the
> environment when set and falls back to the file otherwise. To pass the key
> directly, use `Credentials.from_api_key(key)` (or `Credentials.from_api_key_with_email(email, key)`).
> Email and password still works: a `creds.txt` file (email on line 1, password
> on line 2), an inline `Credentials("you@example.com", "password")`, or the
> `THETADATA_EMAIL` / `THETADATA_PASSWORD` environment variables.

```python
from thetadatadx import Client, Credentials, Config

client = Client(Credentials.from_file("creds.txt"), Config.production())

# First-order Greeks for every strike on SPY's 2026-06-19 expiry, as of 2024-03-15
greeks = client.historical.option_history_greeks_first_order("SPY", "20260619", "20240315")

df = greeks.to_polars()
print(df.select(["strike", "right", "delta", "gamma", "theta", "vega"]).head())
```

Every historical method returns a typed list — iterate it, index it, or convert it to a dataframe:

```python
eod = client.historical.stock_history_eod("AAPL", "20240101", "20240301")
for tick in eod:
    print(f"{tick.date}: O={tick.open:.2f} H={tick.high:.2f} "
          f"L={tick.low:.2f} C={tick.close:.2f} V={tick.volume}")

bars = client.historical.stock_history_ohlc("AAPL", "20240315", interval="1m")   # 1-minute bars
exps = client.historical.option_list_expirations("SPY")
strikes = client.historical.option_list_strikes("SPY", exps[0])
```

## DataFrames

Every result converts directly to a dataframe — no row-by-row iteration:

```python
greeks.to_polars()   # polars.DataFrame
greeks.to_pandas()   # pandas.DataFrame   (pip install thetadatadx[pandas])
greeks.to_arrow()    # pyarrow.Table      (zero-copy)
greeks.to_list()     # list[GreeksTick]
```

The `.to_arrow()` terminal hands the underlying Arrow buffers to pyarrow over the [Arrow C Data Interface](https://arrow.apache.org/docs/format/CDataInterface.html) — zero-copy at the boundary — so the table plugs straight into DuckDB, polars, cuDF, or Arrow Flight:

```python
import duckdb

table = client.historical.stock_history_eod("AAPL", "20240101", "20240301").to_arrow()
con = duckdb.connect()
con.register("eod", table)                 # zero-copy into DuckDB
con.sql("SELECT AVG(close) FROM eod").show()
```

List endpoints (`stock_list_symbols`, `option_list_expirations`, …) return a `StringList` with the same terminals; the single column is named by the endpoint (`symbol`, `expiration`, …). Empty results still convert to a zero-row frame with the full typed schema.

For multi-day backfills, stream the response instead of buffering it. Every historical builder exposes `.stream(handler)` / `.stream_async(handler)` alongside the buffered `.list()` / `.list_async()` terminals; the handler is called once per chunk with a typed list, and the previous chunk is freed before the next is fetched, so peak memory stays flat regardless of total size:

```python
def on_chunk(ticks):
    for t in ticks:
        ...   # write to Parquet, push to a bus, accumulate stats

(client.historical.option_history_quote_builder("QQQ", "20260516", "20260516")
    .interval("tick")
    .strike_range(5)
    .stream(on_chunk))
```

## Streaming

Real-time quotes and trades flow through the same client. Register a callback and match on typed event classes — `Trade`, `Quote`, `Ohlcvc`, `OpenInterest` for market data, plus one typed class per lifecycle event (`Connected`, `LoginSuccess`, `Disconnected`, `Reconnecting`, …):

```python
import time
from thetadatadx import Contract, Quote, Trade

def format_contract(contract):
    parts = [contract.symbol]
    if contract.expiration is not None:
        parts.append(str(contract.expiration))
    if contract.strike is not None:
        parts.append(f"{contract.strike:g}")
    if contract.right is not None:
        parts.append(contract.right)
    return " ".join(parts)

def on_event(event):
    match event:
        case Trade(price=px, size=sz, exchange=ex, ms_of_day=ms, sequence=seq, condition=cond, contract=c):
            print(
                f"{format_contract(c)} trade price={px:.2f} size={sz} "
                f"exchange={ex} ms_of_day={ms} sequence={seq} condition={cond}"
            )
        case Quote(bid=b, ask=a, bid_size=bs, ask_size=asz, bid_exchange=bx, ask_exchange=ax, ms_of_day=ms, contract=c):
            print(
                f"{format_contract(c)} quote bid={b:.2f} ask={a:.2f} "
                f"bid_size={bs} ask_size={asz} bid_exchange={bx} "
                f"ask_exchange={ax} ms_of_day={ms}"
            )

spy_call = Contract.option("SPY", expiration="20260620", strike="550", right="C")

with client.streaming(on_event) as session:
    session.subscribe_many([spy_call.quote(), spy_call.trade()])
    time.sleep(60)   # park the main thread while events flow into on_event
```

Build subscriptions with the fluent `Contract` API and pass them — one at a time or in bulk — to `subscribe` / `subscribe_many`. Every subscription is the same typed value, so quotes, trades, open interest, and market value across contracts mix freely in one list:

```python
from thetadatadx import Contract, SecType

stock  = Contract.stock("AAPL")
option = Contract.option("SPY", expiration="20260620", strike="550", right="C")

with client.streaming(on_event) as session:
    session.subscribe(stock.quote())
    session.subscribe_many([option.quote(), option.trade(), option.open_interest(), option.market_value()])
```

The option constructor is `Contract.option(symbol, *, expiration, strike, right)` — the leg parameters are keyword-only, so the call site always reads `expiration=…, strike=…, right=…` and never depends on argument order. Pair it with `Contract.stock(symbol)` for equities.

Or take a whole-market feed — every option trade across the universe, no per-contract setup:

```python
with client.streaming(on_event) as session:
    session.subscribe(SecType.OPTION.full_trades())
    time.sleep(60)   # the callback runs on the streaming thread — keep it fast
```

Watch feed health from the main thread without touching the callback. The session resolves the client's observability getters directly: `millis_since_last_event()` is the staleness clock (a steadily growing value is the earliest sign of a wedged link), `ring_occupancy()` against `ring_capacity()` shows how close the consumer is to falling behind, and `dropped_event_count()` is the cumulative tally of events shed on a full ring:

```python
with client.streaming(on_event) as session:
    session.subscribe(SecType.OPTION.full_trades())
    while True:
        time.sleep(5)
        stale_ms = session.millis_since_last_event()   # None until the first frame
        print(
            f"stale={stale_ms}ms "
            f"ring={session.ring_occupancy()}/{session.ring_capacity()} "
            f"dropped={session.dropped_event_count()}"
        )
```

> [!TIP]
> The `with client.streaming(callback)` block opens the session on entry and drains
> it cleanly on exit, so the callback has stopped firing by the time the block
> returns. On an involuntary disconnect the client recovers on its own —
> exponential backoff with jitter, host failover, then a paced re-subscribe of
> every active contract.

## Greeks calculator

A full Black-Scholes calculator — first- through third-order Greeks plus an implied-volatility solver — runs locally, no request required:

```python
from thetadatadx import all_greeks, implied_volatility

g = all_greeks(
    spot=450.0, strike=455.0, rate=0.05, div_yield=0.015,
    tte=30 / 365, option_price=8.50, right="C",
)
print(f"IV={g.iv:.4f} delta={g.delta:.4f} gamma={g.gamma:.6f} vega={g.vega:.4f}")

iv, err = implied_volatility(450.0, 455.0, 0.05, 0.015, 30 / 365, 8.50, "C")
```

`right` accepts `"C"` / `"P"` or `"call"` / `"put"` (case-insensitive).

## Flat files

Whole-universe daily snapshots for one `(security type, request type, date)` at a time. The decoded schema follows the request type, so flat-file results chain through the same DataFrame terminals as history:

```python
rows = client.flat_files.option_trade_quote(date="20260428")
print(len(rows))
df = rows.to_polars()                       # or .to_pandas() / .to_arrow() / .to_list()

# Generic dispatcher when security type / request type come from config
oi = client.flat_files.request("OPTION", "OPEN_INTEREST", "20260428")

# Or write the raw vendor file straight to disk — no decode, no row materialise
path = client.flatfile_to_path("OPTION", "TRADE_QUOTE", "20260428",
                            "/tmp/option-trade-quote", format="csv")
```

The flat-file distribution serves a fixed set of datasets: option `trade_quote` / `open_interest` / `eod` and stock `trade_quote` / `eod`. Available `flat_files.*` methods: `option_trade_quote`, `option_open_interest`, `option_eod`, `stock_trade_quote`, `stock_eod`, plus `request(sec_type, req_type, date)`. The generic `request(...)` and `flatfile_to_path(...)` paths reject an unserved `(security, request)` pair with a typed invalid-parameter error.

## Endpoint coverage

65 typed endpoints across stocks, options, indices, the market calendar, and interest rates, plus real-time streaming and the local Greeks calculator.

| Category | Endpoints | Examples |
|---|---|---|
| Stock | 16 | EOD, OHLC, trades, quotes, snapshots, at-time |
| Option | 36 | Every stock surface plus five Greeks tiers, open interest, contract lists |
| Index | 9 | EOD, OHLC, price, snapshots |
| Calendar | 3 | Market open/close, holidays, early closes |
| Interest rate | 1 | EOD rate history |

Every endpoint is a method on `Client`. The full per-method list with signatures lives in the [API reference](https://userfrm.github.io/ThetaDataDx/reference/); `Config.dev()` and `Config.stage()` target the non-production environments.

## Errors

Every call raises a typed exception under a common `ThetaDataError` base — `AuthenticationError`, `RateLimitError`, `NotFoundError`, `DeadlineExceededError`, `InvalidParameterError`, and the rest — so the same cases are catchable here exactly as they are in every other binding.

## Documentation

- [Documentation site](https://userfrm.github.io/ThetaDataDx/) — getting started, API reference, streaming, flat files
- [Repository, issues, contributing](https://github.com/userFRM/ThetaDataDx)
- Community discussion on the [ThetaData Discord](https://discord.thetadata.us/)

## License

Licensed under the Apache License, Version 2.0.

