Skip to main content

Point-in-time-safe Python client for Quiver Quantitative data

Project description

quiverfeed

quiverfeed is a point-in-time-safe Python client for Quiver Quantitative data.

The headline feature is not the HTTP wrapper. It is the two canonical date columns added to returned DataFrames:

  • event_time: when the underlying thing happened.
  • available_at: when the data became knowable to the market.

For backtests and historical analysis, use available_at.

Install

uv pip install quiverfeed

For local development:

uv venv
uv pip install -e ".[dev]"

Or, using uv sync against the lockfile:

uv sync --extra dev

If you don't have uv, install it from astral.sh/uv or fall back to plain pip install quiverfeed / pip install -e ".[dev]".

Quickstart

import quiverfeed

client = quiverfeed.Client(token="YOUR_QUIVER_TOKEN")
df = client.fetch("congresstrading")

print(df[["Ticker", "Traded", "Filed", "event_time", "available_at"]].head())

You can also set the token once:

export QUIVER_TOKEN="YOUR_QUIVER_TOKEN"

Then:

import quiverfeed

df = quiverfeed.Client().fetch("congresstrading")

Point-In-Time Analysis

Congressional trade data has at least two important dates:

  • Traded: when the transaction happened.
  • Filed: when the transaction was disclosed.

A backtest that trades on Traded is usually using information that was not available yet. quiverfeed keeps the original columns and adds safe canonical columns:

df = client.fetch("congresstrading")

# Use this for "what did the market know by this date?"
asof = "2025-01-01"
known = df[df["available_at"] <= asof]

# event_time is still useful for describing what happened.
late = known[known["available_at"] > known["event_time"]]

For datasets that do not advertise a separate disclosure date, quiverfeed adds event_time only. It does not fabricate available_at.

validate_pit(df, dataset="...") is the catalog-aware companion to assert_disclosure_dated. It raises a clear error when the named dataset has no disclosure column at all (rather than the generic "missing available_at"), and it asserts the consistency invariant available_at >= event_time:

quiverfeed.validate_pit(df, dataset="congresstrading")
quiverfeed.validate_pit(df, dataset="lobbying")  # raises: not PIT-capable

Timezones

By default, canonical date columns are tz-aware UTC. Projects that pin to a single zone can ask for naive output or a specific zone:

quiverfeed.Client(tz=None)                      # tz-naive
quiverfeed.Client(tz="America/New_York")        # localized to ET

Discovery

import quiverfeed

for name, dataset in quiverfeed.DATASETS.items():
    print(name, dataset.path, dataset.event_col, dataset.disclosure_col)

Dataset names are forgiving for separators:

client.fetch("congress_trading")  # resolves to "congresstrading"

Truly unknown datasets raise UnknownDatasetError.

Custom datasets

Register a Dataset to reach an endpoint not in the built-in catalog or to override a built-in whose schema has drifted:

import quiverfeed

quiverfeed.register_dataset(
    quiverfeed.Dataset(
        name="my_signal",
        path="/beta/bulk/my_signal",
        plan="premium",
        event_col="event_date",
        disclosure_col="disclosed_at",
    )
)

df = quiverfeed.Client().fetch("my_signal")

register_dataset(..., replace=True) overwrites; unregister_dataset(name) removes a registration.

Caching

Successful complete fetches are cached as Parquet under $XDG_CACHE_HOME/quiverfeed or ~/.cache/quiverfeed.

from datetime import timedelta

client = quiverfeed.Client(cache_ttl=timedelta(hours=6))

df = client.fetch("congresstrading")        # API call, then cache write
again = client.fetch("congresstrading")     # cache read, no API call
fresh = client.fetch("congresstrading", force=True)

Partial results from max_pages are not cached, because a normal cache hit should mean "complete for these params."

The cache is intentionally whole-blob: when the TTL expires, quiverfeed re-pulls every page rather than attempting an incremental append. This wastes requests for append-mostly datasets (e.g. congresstrading) but never serves stale data when upstream amends a historical filing. If you want longer effective freshness, raise cache_ttl.

Rate Limits

The default local limiter is conservative:

client = quiverfeed.Client(
    rate_limit_per_hour=20,
    rate_limit_policy="raise",
)

Policies:

  • "raise": fail before making a request when the local bucket is empty.
  • "sleep": block until a request slot is available.
  • "off": disable local pacing and rely on Quiver's 429.

For multiple processes sharing a token:

client = quiverfeed.Client(bucket_file="~/.cache/quiverfeed/bucket.json")

bucket_file= uses POSIX fcntl locking and is not supported on Windows. On non-POSIX platforms, omit bucket_file= and rely on the in-memory bucket. Cross-platform coordination would require a third-party file-lock library; this is intentionally not pulled in.

Pagination

By default, fetch() paginates until it receives a short page. Between pages the client sleeps request_pause_s seconds (default 1.0) to stay under Quiver's per-second/burst behavior, which the hourly token bucket does not catch:

df = client.fetch("congresstrading", page_size=5000)

# Tighter pacing for offline backfills against your own quota:
client = quiverfeed.Client(request_pause_s=2.0)

If you cap pages and the final page is full, the result may be incomplete. The default is to warn and return the partial frame — passing max_pages is treated as opting in to bounded results:

df = client.fetch("congresstrading", max_pages=5)

If you would rather fail loudly on truncation, opt into raising:

df = client.fetch(
    "congresstrading",
    max_pages=5,
    on_truncated="raise",
)

To suppress the warning entirely, use on_truncated="ignore".

Retries

Connection errors and 5xx responses are retried with exponential backoff (0.5s base) up to max_retries (default 2) extra attempts. 401, 403, and 429 are never retried — they are explicit signals from upstream. Retries do not consume extra rate-limit tokens; the bucket charges once per logical page request.

client = quiverfeed.Client(max_retries=2)

Command-line interface

quiverfeed --help
quiverfeed datasets                                   # list the catalog
quiverfeed datasets --json
quiverfeed fetch congresstrading --max-pages 5 --out trades.parquet
quiverfeed fetch insiders --param chamber=senate --format json
quiverfeed diagnose                                   # cached health check
quiverfeed diagnose --force --json
quiverfeed cache --path
quiverfeed cache --clear --yes

The token is read from QUIVER_TOKEN (or --token). Output format is inferred from --out extension (.parquet / .csv / .json); --format overrides. With no --out, results print to stdout — table by default, machine-readable with --format json / csv.

python -m quiverfeed ... works as an alternative if the script entry isn't on PATH.

Catalog Diagnostics

Run a live check against Quiver to see whether the local catalog still matches the API:

import quiverfeed

report = quiverfeed.diagnose()
print(report.to_text())

Reports are cached for one hour by default to avoid burning rate-limit tokens on repeated health checks. Pass force=True to bypass the cache or shorten cache_ttl if you want fresher results:

report = quiverfeed.diagnose(force=True)

This performs real API calls and consumes rate-limit budget.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quiverfeed-0.1.0.tar.gz (28.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quiverfeed-0.1.0-py3-none-any.whl (21.2 kB view details)

Uploaded Python 3

File details

Details for the file quiverfeed-0.1.0.tar.gz.

File metadata

  • Download URL: quiverfeed-0.1.0.tar.gz
  • Upload date:
  • Size: 28.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for quiverfeed-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5d5e441a1add8eefaf96da4e7742a62ee5057b797318277c13f316be65b2a296
MD5 4e85bc27ccb5f3b15adabc2c0157fbb8
BLAKE2b-256 26ccc7101221e01192ce83ece25b5524896621273747f9b69d12c82db481247d

See more details on using hashes here.

Provenance

The following attestation bundles were made for quiverfeed-0.1.0.tar.gz:

Publisher: release.yml on parithosh/quiverfeed

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file quiverfeed-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: quiverfeed-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for quiverfeed-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5f38bd76fa6533e3db5ca015900134dfa521f26c4fce6346b12d11c9b14d4d9
MD5 8ea2a7727bd8763cf675782284602e84
BLAKE2b-256 62c4ea0afd77821281bca77af9b8583be443f427ba87555e431ec8f32486be0c

See more details on using hashes here.

Provenance

The following attestation bundles were made for quiverfeed-0.1.0-py3-none-any.whl:

Publisher: release.yml on parithosh/quiverfeed

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page