057bc4493e
Wraps the entire yfinance public API behind a single binary `yfin` with
51 subcommands across 13 namespaces (ticker, download, tickers, search,
lookup, market, sector, industry, calendars, screen, live, auth, config).
Output renders as Rich tables on TTY, JSON when piped; --format / --out
support table | json | ndjson | csv | tsv | parquet | yaml.
Highlights:
- 1857 LOC across 18 Python files under cli/yfin/
- Typer + Rich, packaged via hatchling + uv
- Install globally: `cd cli && uv tool install -e .`
- ~/.yfin/config.toml for persistent proxy/cookies/locale defaults
- Friendly YFRateLimitError handler (exit 2, no Rich traceback)
- Validated end-to-end with 33 live tests against Yahoo from a
residential exit (AAPL, MSFT, NVDA, options, screens, calendars,
financials, news, holders, parquet round-trip)
yfinance-tools.yaml is the single source of truth:
- tools (49) — every public yfinance entry point with parameters
- cli (13 groups) — exact CLI invocations, mapped back to tool names
- outputSchemas (27) — actual return shapes captured from live calls
- learnings (19) — IP rate-limit, crumb handshake, Typer gotchas,
screen-build wrapping rules, upstream stubs, etc.
CLAUDE.md documents the upstream codebase layout for future agents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.6 KiB
Python
71 lines
2.6 KiB
Python
"""`yfin download ...` — top-level multi-ticker history shortcut."""
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import List, Optional
|
|
|
|
import typer
|
|
import yfinance as yf
|
|
|
|
from .._shared import parse_symbols, render_via_state, get_state
|
|
|
|
|
|
class GroupBy(str, Enum):
|
|
ticker = "ticker"
|
|
column = "column"
|
|
|
|
|
|
class Period(str, Enum):
|
|
d1 = "1d"; d5 = "5d"; mo1 = "1mo"; mo3 = "3mo"; mo6 = "6mo"
|
|
y1 = "1y"; y2 = "2y"; y5 = "5y"; y10 = "10y"; ytd = "ytd"; max = "max"
|
|
|
|
|
|
class Interval(str, Enum):
|
|
m1 = "1m"; m2 = "2m"; m5 = "5m"; m15 = "15m"; m30 = "30m"
|
|
m60 = "60m"; m90 = "90m"; h1 = "1h"
|
|
d1 = "1d"; d5 = "5d"; wk1 = "1wk"; mo1 = "1mo"; mo3 = "3mo"
|
|
|
|
|
|
def download(
|
|
ctx: typer.Context,
|
|
symbols: List[str] = typer.Argument(..., help="Symbols (space- or comma-separated)."),
|
|
start: Optional[str] = typer.Option(None, "--start"),
|
|
end: Optional[str] = typer.Option(None, "--end"),
|
|
period: Optional[Period] = typer.Option(None, "--period"),
|
|
interval: Interval = typer.Option(Interval.d1, "--interval"),
|
|
group_by: GroupBy = typer.Option(GroupBy.column, "--group-by"),
|
|
actions: bool = typer.Option(False, "--actions/--no-actions"),
|
|
auto_adjust: bool = typer.Option(True, "--auto-adjust/--no-auto-adjust"),
|
|
back_adjust: bool = typer.Option(False, "--back-adjust/--no-back-adjust"),
|
|
repair: bool = typer.Option(False, "--repair/--no-repair"),
|
|
keepna: bool = typer.Option(False, "--keepna/--no-keepna"),
|
|
prepost: bool = typer.Option(False, "--prepost/--no-prepost"),
|
|
threads: bool = typer.Option(True, "--threads/--no-threads"),
|
|
ignore_tz: Optional[bool] = typer.Option(None, "--ignore-tz/--no-ignore-tz"),
|
|
rounding: bool = typer.Option(False, "--rounding/--no-rounding"),
|
|
progress: bool = typer.Option(True, "--progress/--no-progress"),
|
|
multi_level_index: bool = typer.Option(True, "--multi-level-index/--no-multi-level-index"),
|
|
):
|
|
"""Batch OHLCV download (parallel)."""
|
|
st = get_state(ctx)
|
|
tickers = parse_symbols(symbols)
|
|
kw = dict(
|
|
tickers=tickers,
|
|
start=start, end=end,
|
|
interval=interval.value,
|
|
group_by=group_by.value,
|
|
actions=actions, auto_adjust=auto_adjust, back_adjust=back_adjust,
|
|
repair=repair, keepna=keepna, prepost=prepost,
|
|
threads=threads,
|
|
rounding=rounding,
|
|
progress=progress and not st.quiet,
|
|
timeout=st.timeout,
|
|
multi_level_index=multi_level_index,
|
|
)
|
|
if period and not (start or end):
|
|
kw["period"] = period.value
|
|
if ignore_tz is not None:
|
|
kw["ignore_tz"] = ignore_tz
|
|
df = yf.download(**kw)
|
|
render_via_state(ctx, df)
|