Unified market data access for Python — stocks, futures, options, and crypto.
Project description
stockfeed
Unified market data access for Python — stocks, futures, options, and crypto.
stockfeed is a professional-grade library that abstracts multiple data providers behind a single, fully-typed API. It handles provider failover automatically, caches results in an embedded DuckDB database, and exposes identical sync and async interfaces.
Features
- One API, many providers — yfinance (free, always available), Tiingo, Finnhub, Twelve Data, Alpaca, Tradier, CoinGecko
- Transparent failover — if a provider is rate-limited or down, the next one is tried automatically; yfinance is always the final fallback
- Cache-first — all requests check DuckDB first; on a miss, data is fetched, stored, and returned
- Dual interface —
StockFeedClient(sync) andAsyncStockFeedClient(async) with identical method surfaces - Canonical models — every provider returns the same
OHLCVBar,Quote, andTickerInfotypes - Options data — expiration dates, full options chains, and per-contract quotes with greeks (delta, gamma, theta, vega, rho); greeks come from the provider API where available, or are calculated via Black-Scholes
- Both adjusted and raw prices —
OHLCVBar.close_rawandOHLCVBar.close_adjare always separate fields - Streaming —
AsyncStockFeedClient.stream_quote()polls a provider and yields liveQuoteobjects - Dev simulator —
AsyncStockFeedClient.simulate()replays historical bars as a real-time stream for backtesting - Fully typed — passes
mypy --strictacross all source files
Installation
pip install stockfeed
For SSE streaming support:
pip install "stockfeed[streaming]"
Quick start
from stockfeed import StockFeedClient
client = StockFeedClient()
bars = client.get_ohlcv("AAPL", "1d", "2024-01-01", "2024-01-31")
for bar in bars:
print(bar.timestamp.date(), bar.close_raw, bar.close_adj)
# 2024-01-02 185.64 183.73
# 2024-01-03 184.25 182.36
# ...
Date strings are parsed as UTC midnight. datetime objects are also accepted directly.
Usage examples
Quote and company info
client = StockFeedClient()
quote = client.get_quote("MSFT")
print(quote.last, quote.bid, quote.ask)
info = client.get_ticker_info("MSFT")
print(info.name, info.sector, info.market_cap)
Async — fetch multiple tickers concurrently
import asyncio
from stockfeed import AsyncStockFeedClient
async def main():
client = AsyncStockFeedClient()
tasks = [
client.get_ohlcv(t, "1d", "2024-06-01", "2024-06-30")
for t in ["AAPL", "MSFT", "GOOGL", "AMZN"]
]
results = await asyncio.gather(*tasks)
for bars in results:
print(bars[0].ticker, len(bars), "bars")
asyncio.run(main())
Using a paid provider (Tiingo)
from stockfeed import StockFeedClient, StockFeedSettings
from stockfeed.exceptions import ProviderAuthError, TickerNotFoundError
settings = StockFeedSettings(tiingo_api_key="your_tiingo_key")
# Or: STOCKFEED_TIINGO_API_KEY=... in env / .env
client = StockFeedClient(settings=settings)
try:
bars = client.get_ohlcv("SPY", "1d", "2024-01-01", "2024-01-10", provider="tiingo")
except ProviderAuthError:
print("Check your API key")
except TickerNotFoundError as e:
print(f"{e.ticker} not found on {e.provider}")
Live quote streaming (async)
import asyncio
from stockfeed import AsyncStockFeedClient
async def main():
client = AsyncStockFeedClient()
async for quote in client.stream_quote("AAPL", interval=5.0):
print(quote.last, quote.bid, quote.ask)
asyncio.run(main())
Dev simulator — replay bars at speed
import asyncio
from stockfeed import AsyncStockFeedClient
async def main():
client = AsyncStockFeedClient(dev_mode=True)
async for bar in client.simulate("AAPL", "2024-01-01", "2024-01-31", "1d", speed=0):
print(bar.timestamp.date(), bar.close_raw)
asyncio.run(main())
Options data
from datetime import date
from stockfeed import StockFeedClient
client = StockFeedClient()
# List available expiration dates
expirations = client.get_option_expirations("AAPL")
print(expirations[:3]) # [date(2024, 1, 19), date(2024, 2, 16), ...]
# Fetch the options chain for a specific expiration
chain = client.get_options_chain("AAPL", expirations[0])
for contract in chain.contracts[:5]:
print(
contract.symbol,
contract.option_type,
contract.strike,
contract.greeks.delta if contract.greeks else "—",
)
# Quote a specific contract
quote = client.get_option_quote("AAPL240119C00150000")
print(quote.bid, quote.ask, quote.implied_volatility)
Cache management CLI
python -m stockfeed.cache stats
python -m stockfeed.cache clear --ticker AAPL
python -m stockfeed.cache export --format parquet --output ./data/
python -m stockfeed.cache inspect --ticker AAPL --interval 1d
Configuration
Settings load from environment variables (STOCKFEED_ prefix) or a .env file:
STOCKFEED_TIINGO_API_KEY=your_key
STOCKFEED_FINNHUB_API_KEY=your_key
STOCKFEED_TWELVEDATA_API_KEY=your_key
STOCKFEED_ALPACA_API_KEY=your_key
STOCKFEED_ALPACA_SECRET_KEY=your_secret
STOCKFEED_TRADIER_API_KEY=your_key
STOCKFEED_COINGECKO_API_KEY=your_key # optional — free tier works without it
STOCKFEED_OPTIONS_RISK_FREE_RATE=0.05 # used for Black-Scholes greek calculation
STOCKFEED_CACHE_PATH=~/.stockfeed/cache.db
STOCKFEED_CACHE_ENABLED=true
STOCKFEED_LOG_LEVEL=INFO
STOCKFEED_LOG_FORMAT=console # or "json"
Or configure programmatically:
from stockfeed import StockFeedClient, StockFeedSettings
settings = StockFeedSettings(tiingo_api_key="...", cache_path="/tmp/sf.db")
client = StockFeedClient(settings=settings)
Supported intervals
Both string values and the Interval enum are accepted everywhere:
| String | Enum | Description |
|---|---|---|
"1m" |
Interval.ONE_MINUTE |
1-minute bars |
"5m" |
Interval.FIVE_MINUTES |
5-minute bars |
"15m" |
Interval.FIFTEEN_MINUTES |
15-minute bars |
"30m" |
Interval.THIRTY_MINUTES |
30-minute bars |
"1h" |
Interval.ONE_HOUR |
Hourly bars |
"4h" |
Interval.FOUR_HOURS |
4-hour bars |
"1d" |
Interval.ONE_DAY |
Daily bars |
"1w" |
Interval.ONE_WEEK |
Weekly bars |
"1mo" |
Interval.ONE_MONTH |
Monthly bars |
Provider support matrix
| Provider | OHLCV | Quote | Ticker info | Health | Expirations | Chain | Option quote | Greeks | Auth required |
|---|---|---|---|---|---|---|---|---|---|
yfinance |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Calculated (BS) | No |
tiingo |
✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | — | Yes |
finnhub |
✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | Calculated (BS) | Yes |
twelvedata |
✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | — | Yes |
alpaca |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | API greeks | Yes |
tradier |
✅ | ✅ | via yfinance | ✅ | ✅ | ✅ | ✅ | API greeks | Yes |
coingecko |
🔜 | 🔜 | 🔜 | 🔜 | ❌ | ❌ | ❌ | — | Optional |
Providers without API keys configured are skipped during auto-selection. yfinance is always the final fallback.
List all registered providers at runtime:
client = StockFeedClient()
for p in client.list_providers():
print(p.name, "— auth required:", p.requires_auth)
Error handling
All exceptions inherit from StockFeedError and carry structured context fields:
from stockfeed.exceptions import TickerNotFoundError, ProviderUnavailableError
try:
bars = client.get_ohlcv("INVALID", "1d", "2024-01-01", "2024-01-31")
except TickerNotFoundError as e:
print(e.ticker, e.provider, e.suggestion)
except ProviderUnavailableError as e:
print(f"Provider {e.provider} is down")
| Exception | When raised |
|---|---|
ProviderAuthError |
Missing or invalid API key |
ProviderRateLimitError |
Rate limit exceeded (.retry_after seconds) |
ProviderUnavailableError |
Provider unreachable or server error |
TickerNotFoundError |
Ticker doesn't exist on this provider |
UnsupportedIntervalError |
Interval not supported by provider |
CacheReadError / CacheWriteError |
DuckDB cache I/O failure |
ValidationError |
Bad input (ticker format, date range, etc.) |
ConfigurationError |
Missing or invalid configuration |
DevModeError |
Dev-only feature called outside dev mode |
Examples
Working examples are in examples/:
| File | What it shows |
|---|---|
01_ohlcv_yfinance.py |
Daily OHLCV bars with yfinance (no key needed) |
02_quote_and_ticker_info.py |
Live quote and company info |
03_cache.py |
Cache-first access — miss, write, hit, stats |
04_provider_health.py |
Registry inspection and health check |
05_async.py |
Concurrent fetching with asyncio.gather |
06_paid_provider.py |
Tiingo with auth and error handling |
Development
git clone https://github.com/fuzzyalej/stockfeed
cd stockfeed
uv sync
Run checks:
uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run mypy src/
uv run pytest --cov=stockfeed --cov-fail-under=90
See CONTRIBUTING.md for adding new providers.
License
MIT — see LICENSE.
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 stockfeed-0.2.1.tar.gz.
File metadata
- Download URL: stockfeed-0.2.1.tar.gz
- Upload date:
- Size: 448.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
108467e1a59df5f5d43dbaeb8ecc8f219e82a6b0dc8b106e45284f303424c040
|
|
| MD5 |
ef2c6d569f968025ccbce45dbc5fe26e
|
|
| BLAKE2b-256 |
9dbf4faeb2c508dfe0e9f337cb42d666cab8427930a6426c44fdab969060edf1
|
File details
Details for the file stockfeed-0.2.1-py3-none-any.whl.
File metadata
- Download URL: stockfeed-0.2.1-py3-none-any.whl
- Upload date:
- Size: 80.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a0cd20782b358026c976afad1b0ec01fd04ab3ace3aad3e87501d8aa791826d
|
|
| MD5 |
a32618295b19ac127a803159e43553f0
|
|
| BLAKE2b-256 |
29bca21358858a2e15cd78998053193077923b737dbdf786dda9ae7144e9838e
|