# dyfinance

[![CI](https://github.com/kensuke-kaieda/dyfinance/actions/workflows/ci.yml/badge.svg)](https://github.com/kensuke-kaieda/dyfinance/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/dyfinance)](https://pypi.org/project/dyfinance/)
[![Python](https://img.shields.io/pypi/pyversions/dyfinance)](https://pypi.org/project/dyfinance/)
[![License](https://img.shields.io/github/license/kensuke-kaieda/dyfinance)](LICENSE)

Drop-in replacement for [yfinance](https://github.com/ranaroussi/yfinance) with DuckDB OHLCV caching. Generated by Claude.

```python
# Change just this one line
import dyfinance as yf

ticker = yf.Ticker("AAPL")
df = ticker.history(period="1mo")  # cached transparently
```

## Why DuckDB?

Most caching solutions for market data use SQLite, CSV, or Parquet files.
dyfinance uses [DuckDB](https://duckdb.org/) because it is fundamentally better suited for financial time-series data:

- **Columnar storage** -- analytical queries (filtering by date range, aggregating OHLCV columns) run orders of magnitude faster than row-oriented SQLite
- **Single file, zero config** -- like SQLite, DuckDB is an embedded database with no server process; your entire cache lives in one `~/.dyfinance/cache.duckdb` file
- **Native pandas integration** -- DuckDB reads and writes DataFrames without serialization overhead
- **Your cache is a database** -- you can query the cache directly with SQL for custom analysis beyond what yfinance offers:

```python
import duckdb

conn = duckdb.connect("~/.dyfinance/cache.duckdb")
# Your own analysis — no yfinance API limits
conn.sql("""
    SELECT ticker, interval,
           MIN(ts) AS first_date, MAX(ts) AS last_date,
           COUNT(*) AS rows
    FROM ohlcv
    GROUP BY ticker, interval
""").show()
```

## Features

- **100% yfinance-compatible** -- same API signatures, defaults, and output structures
- **Transparent DuckDB caching** -- daily+ OHLCV data is cached locally; redundant fetches are eliminated
- **Full fallback** -- any attribute or method not overridden by dyfinance is delegated to yfinance
- **Spot-check validation** -- stale cache is automatically verified against fresh data and corrected if needed

## Installation

```bash
pip install dyfinance
```

Requires Python >= 3.10.

## Quick Start

```python
import dyfinance as yf

# Single ticker
aapl = yf.Ticker("AAPL")
hist = aapl.history(period="6mo")           # first call fetches, subsequent calls use cache
info = aapl.info                            # delegated to yfinance

# Multiple tickers
data = yf.download(["AAPL", "MSFT"], period="1mo")

# All yfinance features work as-is
aapl.dividends
aapl.financials
aapl.options
```

## Cached Intervals

| Cached (DuckDB)                    | Passthrough (yfinance direct)                 |
|------------------------------------|-----------------------------------------------|
| `1d`, `5d`, `1wk`, `1mo`, `3mo`   | `1m`, `2m`, `5m`, `15m`, `30m`, `60m`, `90m`, `1h` |

## Known Limitations

- **ISIN codes are not supported** in the cached path. yfinance's `download()` auto-converts ISIN codes (e.g. `US0378331005`) to ticker symbols, but dyfinance skips this conversion for cacheable intervals. Use ticker symbols directly (e.g. `AAPL` instead of `US0378331005`).

## Configuration

| Environment Variable       | Default                        | Description                   |
|----------------------------|--------------------------------|-------------------------------|
| `DYFINANCE_DB_PATH`         | `~/.dyfinance/cache.duckdb`     | Cache database file path      |
| `DYFINANCE_STALE_HOURS`     | `6`                            | Hours before spot-check fires |

## Cache Management

```python
import dyfinance as yf

# Clear cache for a specific ticker
yf.Ticker("AAPL").clear_cache()        # all intervals
yf.Ticker("AAPL").clear_cache("1d")    # daily only
```

## Development

```bash
pip install -e ".[dev]"

make test        # offline tests
make test-live   # live yfinance comparison tests (network required)
make lint        # ruff check
make typecheck   # mypy strict
make clean       # remove caches and build artifacts
```

## License

Apache-2.0
