Python wrapper and DuckDB cache for ThetaData option and streaming endpoints.
Project description
ThetaDataRS
ThetaDataRS is a Python wrapper around the thetadata package. It adds a convenience facade, local DuckDB caching, typed option strategy screeners, structured errors, and cache-aware batch helpers for watchlist-scale workflows.
Installation
This project uses Python 3.12 and uv.
uv sync
Create a local .env file with your ThetaData credentials:
EMAIL=you@example.com
PASSWD=your-password
Credentials are loaded by thetadatars.client.create_client() through python-dotenv.
Quick Start
Use ThetaDataRS when you want one object that wraps the existing endpoint functions and automatically supplies the ThetaData client.
from thetadatars.thetadata import ThetaDataRS
theta = ThetaDataRS()
contracts = theta.get_options_contract_list(
"AAPL",
"2026-04-24",
)
print(contracts)
The first call fetches from ThetaData when local data is missing or stale, writes the result to DuckDB, and returns a Polars DataFrame. Later calls read from the local cache until the endpoint's stale_threshold is exceeded.
Option List Examples
from thetadatars.thetadata import ThetaDataRS
theta = ThetaDataRS()
symbols = theta.get_options_symbols_list()
expirations = theta.get_options_expiration_list("AAPL")
strikes = theta.get_options_strike_list("AAPL", "2026-04-24")
contracts = theta.get_options_contract_list("AAPL", "2026-04-24")
Snapshot Example
from thetadatars.thetadata import ThetaDataRS
theta = ThetaDataRS()
quotes = theta.get_snapshot_quote(
ticker="AAPL",
expiration="2026-04-24",
strike="*",
right="both",
)
print(quotes.head())
Snapshot endpoints default to a shorter cache window than historical endpoints because the data changes during the trading day.
Snapshot quote, first-order Greek, and option-expiration list helpers also support explicit cache policies:
quotes = theta.get_snapshot_quote(
ticker="AAPL",
expiration="2026-04-24",
strike="*",
right="put",
strike_range=20,
cache_policy="prefer_cache",
)
Supported cache policies:
prefer_cache: use fresh matching cache data, otherwise fetch from ThetaData and store it.cache_only: read only from DuckDB and raiseCacheMissErrorif no fresh matching cache entry exists.refresh: fetch from ThetaData even when cache data exists, then update DuckDB.no_cache: fetch from ThetaData without reading or writing DuckDB.
Cache matching is parameter-aware. For example, a cached right="put" request will not satisfy right="call", and a narrow strike_range=10 fetch will not satisfy a broader unbounded request. Cache fetch metadata is stored in the cache_fetches DuckDB table with endpoint, request params, status, row count, and fetch timing.
Wildcard strategy screens use the same cache policy for expiration discovery and snapshot data. cache_only will not fetch missing expirations, and no_cache will not read or write the expiration cache.
Batch Snapshot Data
Use cache-aware batch helpers when you need the same snapshot request across a watchlist without running a strategy screen.
from thetadatars.thetadata import ThetaDataRS
from thetadatars.options.batch import RateLimitPolicy, RetryPolicy, TimeoutPolicy
theta = ThetaDataRS()
quotes = theta.get_snapshot_quote_batch(
["AAPL", "MSFT", "NVDA"],
expiration="2026-04-24",
right="both",
strike_range=20,
cache_policy="prefer_cache",
concurrency=3,
retry_policy=RetryPolicy(max_attempts=2, backoff_seconds=0.5),
timeout_policy=TimeoutPolicy(per_ticker_seconds=30),
rate_limit_policy=RateLimitPolicy(min_interval_seconds=0.25),
)
print(quotes.data)
print(quotes.stats)
print(quotes.failures)
The batch helpers delegate to the normal endpoint functions for each ticker, so cache behavior is identical to direct calls:
prefer_cache: use fresh matching cache data per ticker, otherwise fetch and store.cache_only: return per-tickerCacheMissErrorfailures without fetching missing data.refresh: fetch each ticker and update DuckDB.no_cache: fetch each ticker without reading or writing DuckDB.
Batch results do not fail the whole request because one ticker fails. They return:
data: one combined Polars DataFrame for successful tickers.successes: per-ticker row counts and timing.failures: structured errors with ticker, endpoint, params, retryability, and attempts.stats: total, succeeded, failed, and total duration.
Warm cache helpers force cache_policy="refresh" and are useful before running screeners or UI workflows:
warmup = theta.warm_snapshot_greeks_first_order_cache(
["AAPL", "MSFT"],
expiration="2026-04-24",
strike_range=20,
)
print(warmup.stats)
If you pass a shared DuckDB conn, batch helpers run serially to avoid using the same connection from multiple worker threads. Leave conn=None when you want concurrent per-ticker endpoint calls.
Currently supported raw batch helpers are:
get_snapshot_quote_batchget_snapshot_greeks_first_order_batchwarm_snapshot_quote_cachewarm_snapshot_greeks_first_order_cache
Historical Option Example
from thetadatars.thetadata import ThetaDataRS
theta = ThetaDataRS()
eod = theta.get_option_history_eod(
ticker="AAPL",
start_date="2026-04-01",
end_date="2026-04-24",
expiration="2026-04-24",
strike="*",
right="both",
)
print(eod)
Credit Spread Screening
Credit spread screeners can be called through the legacy DataFrame API or through a request object that returns data, stats, and warnings.
from thetadatars.options.screeners.credit_spreads import (
CreditSpreadRequest,
warm_credit_spread_cache,
screen_credit_spreads,
screen_credit_spread_watchlist,
TimeoutPolicy,
RateLimitPolicy,
CircuitBreakerPolicy,
)
request = CreditSpreadRequest(
ticker="AAPL",
expiration="*",
right="put",
max_dte=45,
strike_range=20,
top_n=25,
cache_policy="prefer_cache",
max_candidates_per_expiration=500,
max_candidates_total=2_000,
)
result = screen_credit_spreads(request)
print(result.data)
print(result.stats)
print(result.warnings)
Typed requests guard expensive broad scans. expiration="*" requires max_dte unless allow_full_chain=True. Greek source can be selected with greeks_source="auto", "thetadata", "local", or "none".
You can inspect the expected cache/upstream behavior before running a credit-spread screen:
from thetadatars.options.screeners.credit_spreads import plan_credit_spreads
plan = plan_credit_spreads(request)
print(plan.expected_endpoint)
print(plan.cache_hits, plan.cache_misses, plan.upstream_calls)
print(plan.cost, plan.local_computation)
ScreenerStats includes cache fields such as cache_hits, cache_misses, upstream_calls, and cache_policy so app code can explain whether a result came from cache or required an upstream request.
Warm cache data for a watchlist without building spread candidates:
warmup = warm_credit_spread_cache(
["AAPL", "MSFT"],
request,
concurrency=2,
rate_limit_policy=RateLimitPolicy(min_interval_seconds=0.25),
)
print(warmup.stats)
print(warmup.failures)
Screen watchlists with bounded runtime and failure controls:
batch = screen_credit_spread_watchlist(
["AAPL", "MSFT"],
request,
concurrency=2,
timeout_policy=TimeoutPolicy(per_ticker_seconds=30),
rate_limit_policy=RateLimitPolicy(min_interval_seconds=0.25),
circuit_breaker_policy=CircuitBreakerPolicy(max_failures=3),
)
print(batch.data)
print(batch.failures)
print(batch.stats)
Candidate caps limit strategy-builder work after normal filters have selected otherwise eligible spreads. When caps prune candidates, the result includes a candidate_limit warning and ScreenerStats.pruned_candidate_rows.
Multi-Strategy Screening
All bundled strategy screeners now have typed request/result APIs:
callputcovered_callprotective_putcash_secured_putcredit_spreaddebit_spreadcalendar_spreaddiagonal_spreadstraddlestrangleiron_condoriron_butterfly
Single-leg and stock-linked strategies use the same request/result flow as spreads:
from thetadatars.options.screeners.cash_secured_put import (
CashSecuredPutRequest,
screen_cash_secured_puts,
)
request = CashSecuredPutRequest(
ticker="AAPL",
expiration="*",
max_dte=45,
strike_range=20,
min_delta=0.10,
max_delta=0.35,
top_n=25,
greeks_source="none",
)
result = screen_cash_secured_puts(request)
print(result.data)
print(result.stats)
The generic strategy dispatcher lets backend applications use one watchlist API for first-class typed strategies:
from thetadatars.options.screeners.cash_secured_put import CashSecuredPutRequest
from thetadatars.options.screeners.strategies import (
get_available_strategies,
plan_screener,
screen_watchlist,
warm_cache,
)
request = CashSecuredPutRequest(expiration="*", max_dte=45, strike_range=20)
print(get_available_strategies())
print(plan_screener(request))
warmup = warm_cache(["AAPL", "MSFT"], strategy="cash_secured_put", request=request)
batch = screen_watchlist(["AAPL", "MSFT"], strategy="cash_secured_put", request=request)
The same dispatcher can run multi-leg strategies:
from thetadatars.options.screeners.iron_condor import IronCondorRequest
from thetadatars.options.screeners.strategies import screen_watchlist
request = IronCondorRequest(
expiration="*",
max_dte=45,
strike_range=30,
min_credit=0.50,
max_candidates_per_expiration=500,
max_candidates_total=2_000,
)
batch = screen_watchlist(["AAPL", "MSFT"], strategy="iron_condor", request=request)
print(batch.data)
print(batch.failures)
Legacy get_best_* and find_best_* DataFrame helpers remain available for compatibility. Typed APIs add planning, cache statistics, warm-cache helpers, watchlist batching, structured failures, and candidate pruning warnings.
Streaming Example
Streaming helpers return async iterators. ThetaDataRS automatically applies its stream_url, which defaults to ws://127.0.0.1:25520/v1/events.
import asyncio
from thetadatars.thetadata import ThetaDataRS
async def main():
theta = ThetaDataRS()
async for message in theta.stream_option_trades(
"AAPL",
"2026-04-24",
200,
"C",
max_messages=5,
timeout=10,
):
print(message)
asyncio.run(main())
Direct Function Usage
You can also import endpoint functions directly if you want explicit control over the client.
from thetadatars.client import create_client
from thetadatars.options.list.contracts import get_options_contract_list
client = create_client()
contracts = get_options_contract_list(
"AAPL",
"2026-04-24",
client=client,
)
Local Data
Cached endpoint data is stored in DuckDB through thetadatars.data.db. The default database path is controlled by THETADATARS_DB, the user config file, or the platform config directory.
Do not commit local database files, .env files, credentials, or generated market data.
Development
uv sync
uv run --with pytest pytest
uv build
uv syncinstalls dependencies.uv run --with pytest pytestruns the offline test suite.uv run python test.pyruns the live smoke-test script and may call ThetaData services.uv buildcreates package distributions indist/.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file thetadatars-0.5.0.tar.gz.
File metadata
- Download URL: thetadatars-0.5.0.tar.gz
- Upload date:
- Size: 130.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98e07536865112b0a6ef6469bcfa144a9255a0e36f5f8289cf6c731424a9cbbf
|
|
| MD5 |
36c5cd16e945c6c48d4c371260de2d07
|
|
| BLAKE2b-256 |
d23d497562f57e3d3909cc7a63676306765efe991c01246d70d6ed85c38cb5ab
|
File details
Details for the file thetadatars-0.5.0-py3-none-any.whl.
File metadata
- Download URL: thetadatars-0.5.0-py3-none-any.whl
- Upload date:
- Size: 166.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa6f669713d21210a45d0c07bb7714fb85fd675929c601911bc8ada1fbf628f9
|
|
| MD5 |
026a61ad37a8357f1b1215116c746e20
|
|
| BLAKE2b-256 |
bfc5eef22755d85ce809470ab267aff85bcc3346140a67640862f5cf0e6fc5d5
|