Metadata-Version: 2.5
Name: quantpad-data
Version: 0.2.0
Summary: Official Python client for the QuantPad market-data API
Project-URL: Documentation, https://api.quantpad.ai/external/docs
Author: QuantPad
License: Proprietary
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: pandas<3,>=2.2
Requires-Dist: pyarrow>=18
Requires-Dist: requests>=2.32
Provides-Extra: rth
Requires-Dist: exchange-calendars>=4.5; extra == 'rth'
Provides-Extra: test
Requires-Dist: build>=1.2; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# QuantPad Data Python SDK

Install into the project's existing environment/package manager:

```bash
uv add quantpad-data       # uv project
poetry add quantpad-data   # Poetry project
```

For a plain Python project, create a local virtual environment:

```bash
python3 -m venv .venv
source .venv/bin/activate  # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install quantpad-data
export QUANTPAD_API_KEY=qp_live_...
```

Do not install into Homebrew/system Python or use `--break-system-packages`.
When a project environment already exists, use its package manager or
`python -m pip` rather than creating a second environment. Keep
`QUANTPAD_API_KEY` in the local environment—never source code or chat.

```python
import time
import quantpad_data as qpd

end = int(time.time() * 1000)
bars = qpd.get_bars("ES.FUT", "1m", end - 86_400_000, end)

for chunk in qpd.get_ticks(
    "AAPL", "trades", end - 3_600_000, end, columns=["t", "price", "size"]
):
    print(chunk.head())

# 10-level L2 market-by-price order-book depth. This streams chunks;
# project columns to avoid materializing all 60+ book fields when unnecessary.
for chunk in qpd.get_mbp10(
    "ES.FUT",
    end - 3_600_000,
    end,
    columns=["t", "bid_px_00", "ask_px_00", "bid_sz_00", "ask_sz_00"],
):
    print(chunk.head())

matches = qpd.get_universe("apple", asset_class="equity")
coverage = qpd.get_coverage("AAPL")
```

`QuantPadClient` (also available as `Client`) accepts `api_key=`, `base_url=`,
and `max_retries=`. The default
client honors `Retry-After` and uses exponential backoff for transient failures.
QuantPad notebooks remain compatible through `QUANTPAD_NOTEBOOK_DATA_TOKEN`.

Bars preserve the notebook helper's OHLCV aliases and smart futures
back-adjustment default. Tick timestamps are epoch nanoseconds. Install
`quantpad-data[rth]` for XNYS regular-session helpers.

`mbp-10` is true L2 depth with bid/ask price, size, and order-count fields at
levels 0-9. It is available for supported CME futures, individual CME
options-on-futures contracts, and US equities, subject to `get_coverage()` and
the plan lookback window (currently 30 days).

This SDK intentionally exposes only bars, ticks, universe, coverage, and
symbology. It does not proxy FRED or SEC EDGAR.

## Explicit addressing: `qpd.v2`

The helpers above infer which dataset a symbol lives in and what kind of
symbol it is. That is convenient until it is wrong, and when it is wrong it
fails quietly — asking for a specific futures contract returns an empty frame
that looks exactly like a coverage gap.

`qpd.v2.Historical` removes the inference. It takes the same arguments as
`databento.Historical`, so code written against Databento ports over by
swapping the client:

```python
import quantpad_data as qpd

client = qpd.v2.Historical()

bars = client.timeseries.get_range(
    dataset="GLBX.MDP3",       # required — never guessed
    symbols="ESZ6",            # a specific contract month
    stype_in="raw_symbol",     # raw_symbol | instrument_id | parent | continuous
    schema="ohlcv-1m",
    start="2026-01-05",
    end="2026-01-06",
)

# What actually answered, so you can confirm it was the contract you meant.
print(bars.attrs["quantpad"])
# {'dataset': 'GLBX.MDP3', 'stype_in': 'raw_symbol',
#  'resolved': 'ESZ6=651968', 'clamped': False, ...}
```

Every response reports the address it used, and a window that had to be
shortened comes back with `clamped=True` and a warning rather than quietly
covering a different period than you asked for.

### Large ranges

`get_range` buffers a single response. When that would be too large, it falls
back automatically to reading parquet straight from object storage — the bytes
never traverse the data service. Force either behaviour with
`mode="interactive"` or `mode="bulk"`, or stream day by day:

```python
for day in client.timeseries.iter_range(
    dataset="GLBX.MDP3", symbols="ESZ6", schema="mbp-10",
    start="2026-01-05", end="2026-02-05",
):
    process(day)
```

For ranges too large even for that, submit a batch job, then hydrate it into
the cache so ordinary `get_range` calls serve it from storage:

```python
job = client.batch.submit(
    dataset="GLBX.MDP3", symbols="ESZ6", schema="mbo",
    start="2026-01-05", end="2026-02-05", hydrate=True,
)
client.batch.hydrate(job["job_id"])
```

### Other namespaces

`client.symbology.resolve(...)`, the `client.metadata.*` mirrors
(`list_datasets`, `list_schemas`, `list_fields`, `list_publishers`,
`get_dataset_range`, `get_dataset_condition`, `get_record_count`), and
`client.options.chain(...)` for enumerating strikes and expiries without
downloading a day's entire `definition` schema.

There is no cost or unit-price surface. QuantPad data is included in the plan
rather than metered per request.
