yfd 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ name: Fundamentals data fetch
2
+
3
+ on:
4
+ schedule:
5
+ # Midnight ET (05:00 UTC) Mon-Fri, after price update
6
+ - cron: "0 5 * * 1-5"
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ fetch:
11
+ runs-on: ubuntu-latest
12
+ timeout-minutes: 150
13
+ permissions:
14
+ contents: write
15
+ env:
16
+ R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
17
+ R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
18
+ R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
19
+ R2_BUCKET: ${{ secrets.R2_BUCKET }}
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - uses: astral-sh/setup-uv@v4
25
+
26
+ - run: uv sync
27
+
28
+ - run: uv run python -m yfd.fundamentals
29
+
30
+ - name: Commit report
31
+ run: |
32
+ git config user.name "github-actions[bot]"
33
+ git config user.email "github-actions[bot]@users.noreply.github.com"
34
+ git add reports/fundamentals/
35
+ git diff --cached --quiet || git commit -m "report: fundamentals $(date +%Y-%m-%d)" && git push
@@ -0,0 +1,35 @@
1
+ name: Nightly data update
2
+
3
+ on:
4
+ schedule:
5
+ # 10 PM ET (03:00 UTC) — after post-market close, before pre-market
6
+ - cron: "0 3 * * 1-5"
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ update:
11
+ runs-on: ubuntu-latest
12
+ timeout-minutes: 120
13
+ permissions:
14
+ contents: write
15
+ env:
16
+ R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
17
+ R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
18
+ R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
19
+ R2_BUCKET: ${{ secrets.R2_BUCKET }}
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - uses: astral-sh/setup-uv@v4
25
+
26
+ - run: uv sync
27
+
28
+ - run: uv run python -m yfd.updater
29
+
30
+ - name: Commit report
31
+ run: |
32
+ git config user.name "github-actions[bot]"
33
+ git config user.email "github-actions[bot]@users.noreply.github.com"
34
+ git add reports/price/
35
+ git diff --cached --quiet || git commit -m "report: nightly price $(date +%Y-%m-%d)" && git push
@@ -0,0 +1,62 @@
1
+ name: Build and Publish Python package to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ name: Build distribution
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+
15
+ steps:
16
+ - name: Check out code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.11"
23
+
24
+ - name: Install build dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install build
28
+
29
+ - name: Build package
30
+ run: python -m build
31
+
32
+ - name: Test built package
33
+ run: |
34
+ pip install dist/*.whl
35
+ python -c "from yfd import get, symbols, types; print('Package imported successfully!')"
36
+
37
+ - name: Store the distribution packages
38
+ uses: actions/upload-artifact@v4
39
+ with:
40
+ name: python-package-distributions
41
+ path: dist/
42
+
43
+ publish-to-pypi:
44
+ name: Publish to PyPI
45
+ needs:
46
+ - build
47
+ runs-on: ubuntu-latest
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/p/yfd
51
+ permissions:
52
+ id-token: write
53
+
54
+ steps:
55
+ - name: Download all the dists
56
+ uses: actions/download-artifact@v4
57
+ with:
58
+ name: python-package-distributions
59
+ path: dist/
60
+
61
+ - name: Publish distribution to PyPI
62
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,43 @@
1
+ name: Test Yahoo Finance API
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ timeout-minutes: 5
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - uses: astral-sh/setup-uv@v4
15
+
16
+ - run: uv sync
17
+
18
+ - name: Test direct Yahoo Finance API
19
+ run: |
20
+ uv run python -c "
21
+ from yfd.yahoo import Yahoo, INCOME_KEYS
22
+
23
+ y = Yahoo()
24
+ print(f'Auth OK, crumb: {y._crumb[:10]}...')
25
+
26
+ # Chart API
27
+ df, events, meta = y.chart('AAPL', start='2026-01-01')
28
+ print(f'AAPL chart: {len(df)} rows, currency={meta.get(\"currency\")}')
29
+
30
+ # Quote summary
31
+ qs = y.quote_summary('AAPL', ['financialData', 'majorHoldersBreakdown'])
32
+ print(f'quoteSummary modules: {list(qs.keys())}')
33
+
34
+ # Timeseries
35
+ ts = y.timeseries('AAPL', INCOME_KEYS, 'annual')
36
+ print(f'Income statement: {ts.shape}')
37
+
38
+ # Quote
39
+ q = y.quote('AAPL')
40
+ print(f'Quote price: {q.get(\"regularMarketPrice\")}')
41
+
42
+ print('\nAll tests passed!')
43
+ "
yfd-0.3.0/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ data/
2
+ *.parquet
3
+ __pycache__/
4
+ *.egg-info/
5
+ .venv/
6
+ .tmp/
7
+ scripts/.tmp/
8
+ .env
yfd-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: yfd
3
+ Version: 0.3.0
4
+ Requires-Python: >=3.11
5
+ Requires-Dist: boto3
6
+ Requires-Dist: curl-cffi
7
+ Requires-Dist: lxml
8
+ Requires-Dist: pandas
9
+ Requires-Dist: pyarrow
10
+ Requires-Dist: python-dotenv
11
+ Requires-Dist: requests
12
+ Requires-Dist: tvscreener>=0.2.2
yfd-0.3.0/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # yfd
2
+
3
+ Offline Yahoo Finance data for 17,000+ US-listed securities (stocks, ETFs, REITs). Daily OHLCV prices, financial statements, holders, analyst estimates, and 39 data types — served from Cloudflare R2, updated nightly.
4
+
5
+ No API keys. No rate limits. No Yahoo dependency at query time.
6
+
7
+ ## Install
8
+
9
+ ```
10
+ pip install yfd
11
+ ```
12
+
13
+ ## Python API
14
+
15
+ ```python
16
+ import yfd
17
+
18
+ # Get price history
19
+ df = yfd.get("AAPL", "price", start="2026-01-01")
20
+
21
+ # Get annual income statement
22
+ inc = yfd.get("AAPL", "income_stmt")
23
+
24
+ # Get company info
25
+ info = yfd.get("AAPL", "info")
26
+
27
+ # Works for ETFs too
28
+ spy = yfd.get("SPY", "price", start="2026-03-01")
29
+
30
+ # List all data types
31
+ yfd.types()
32
+
33
+ # List all symbols
34
+ yfd.symbols() # ["A", "AA", "AAPL", ..., "SPY", "QQQ", ...]
35
+ ```
36
+
37
+ `get()` returns a DataFrame for tabular data, a dict/list for JSON data, or `None` if not found.
38
+
39
+ ## CLI
40
+
41
+ ```bash
42
+ yfd price AAPL --start 2026-03-01
43
+ yfd income-stmt AAPL
44
+ yfd info AAPL --json
45
+ yfd balance-sheet MSFT
46
+ yfd types # list all 39 data types
47
+ yfd symbols # list all symbols
48
+ ```
49
+
50
+ Hyphens and underscores both work: `income-stmt` = `income_stmt`.
51
+
52
+ ## Data Types
53
+
54
+ ### Price & Actions
55
+ | Type | Description |
56
+ |------|-------------|
57
+ | `price` | Daily OHLCV (Open, High, Low, Close, Volume) |
58
+ | `dividends` | Dividend payment history |
59
+ | `splits` | Stock split history |
60
+ | `actions` | Dividends and splits combined |
61
+ | `capital_gains` | Capital gains distributions (ETFs/funds) |
62
+
63
+ ### Financial Statements
64
+ | Type | Description |
65
+ |------|-------------|
66
+ | `income_stmt` | Annual income statement |
67
+ | `income_stmt_quarterly` | Quarterly income statement |
68
+ | `income_stmt_ttm` | Trailing 12-month income statement |
69
+ | `balance_sheet` | Annual balance sheet |
70
+ | `balance_sheet_quarterly` | Quarterly balance sheet |
71
+ | `cashflow` | Annual cash flow statement |
72
+ | `cashflow_quarterly` | Quarterly cash flow statement |
73
+ | `cashflow_ttm` | Trailing 12-month cash flow |
74
+
75
+ ### Holders
76
+ | Type | Description |
77
+ |------|-------------|
78
+ | `major_holders` | Insider/institution ownership % |
79
+ | `institutional_holders` | Top institutional holders |
80
+ | `mutualfund_holders` | Top mutual fund holders |
81
+ | `insider_transactions` | Insider buy/sell transactions |
82
+ | `insider_purchases` | Net insider purchase activity |
83
+ | `insider_roster` | Insider roster with positions |
84
+
85
+ ### Analyst & Estimates
86
+ | Type | Description |
87
+ |------|-------------|
88
+ | `recommendations` | Analyst recommendation trend |
89
+ | `upgrades_downgrades` | Analyst upgrade/downgrade history |
90
+ | `earnings_estimate` | EPS estimates by period |
91
+ | `revenue_estimate` | Revenue estimates by period |
92
+ | `earnings_history` | Historical EPS vs estimates |
93
+ | `eps_trend` | EPS trend |
94
+ | `eps_revisions` | EPS revision history |
95
+ | `growth_estimates` | Growth estimates (stock/industry/sector) |
96
+ | `analyst_price_targets` | Target high/low/mean/median price |
97
+
98
+ ### Company
99
+ | Type | Description |
100
+ |------|-------------|
101
+ | `info` | Full company information |
102
+ | `fast_info` | Quick price/volume stats |
103
+ | `sustainability` | ESG scores |
104
+ | `calendar` | Upcoming earnings/dividend dates |
105
+ | `history_metadata` | Exchange, currency, trading hours |
106
+ | `news` | Latest news articles |
107
+ | `sec_filings` | SEC 10-K/10-Q filing history |
108
+
109
+ ### Other
110
+ | Type | Description |
111
+ |------|-------------|
112
+ | `earnings` | Earnings dates with EPS surprise |
113
+ | `options` | Full options chain (all expiries) |
114
+ | `isin` | ISIN identifier |
115
+ | `funds_data` | ETF/mutual fund details |
116
+
117
+ ## How It Works
118
+
119
+ Two GitHub Actions pipelines run every weeknight:
120
+
121
+ 1. **Price update** (10 PM ET) — fetches daily OHLCV for all 17,000+ symbols via Yahoo Finance chart API, merges into R2
122
+ 2. **Fundamentals update** (midnight ET) — fetches 39 data types per symbol via Yahoo Finance quoteSummary/timeseries APIs, uploads to R2
123
+
124
+ The client library reads directly from R2 public URLs. No Yahoo calls at query time.
125
+
126
+ ## Architecture
127
+
128
+ ```
129
+ Yahoo Finance APIs ──→ GitHub Actions (nightly) ──→ Cloudflare R2
130
+
131
+ yfd CLI / Python ◄───┘
132
+ ```
133
+
134
+ Symbol universe sourced from [TradingView screener](https://github.com/deepentropy/tvscreener) covering NYSE, NASDAQ, AMEX, CBOE, and OTC markets.
yfd-0.3.0/llms.txt ADDED
@@ -0,0 +1,121 @@
1
+ # yfd
2
+
3
+ Offline Yahoo Finance data for 17,000+ US-listed securities (stocks, ETFs, REITs). Daily OHLCV prices, financial statements, holders, analyst estimates, and 39 data types — served from Cloudflare R2, updated nightly. No API keys needed.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ pip install yfd
9
+ ```
10
+
11
+ ## Python API
12
+
13
+ Three functions:
14
+
15
+ ```python
16
+ import yfd
17
+
18
+ # Fetch any data type for a symbol
19
+ yfd.get(symbol, data_type="price", start=None, end=None)
20
+ # Returns: DataFrame (parquet types), dict/list/str (JSON types), or None
21
+
22
+ # List all 39 available data types
23
+ yfd.types()
24
+ # Returns: {"price": "Daily OHLCV price history", ...}
25
+
26
+ # List all available ticker symbols
27
+ yfd.symbols()
28
+ # Returns: ["A", "AA", "AAPL", ...]
29
+ ```
30
+
31
+ ## CLI
32
+
33
+ ```bash
34
+ yfd <data_type> <SYMBOL> [--start YYYY-MM-DD] [--end YYYY-MM-DD] [--json]
35
+ yfd types
36
+ yfd symbols
37
+ ```
38
+
39
+ CLI accepts hyphens: `income-stmt` = `income_stmt`.
40
+
41
+ ## Data Types
42
+
43
+ ### Price & Actions
44
+ - `price` — Daily OHLCV price history (columns: Open, High, Low, Close, Volume)
45
+ - `dividends` — Dividend payment history
46
+ - `splits` — Stock split history
47
+ - `actions` — Dividends and splits combined
48
+ - `capital_gains` — Capital gains distributions (ETFs/funds only)
49
+
50
+ ### Financial Statements
51
+ - `income_stmt` — Annual income statement
52
+ - `income_stmt_quarterly` — Quarterly income statement
53
+ - `income_stmt_ttm` — Trailing 12-month income statement
54
+ - `balance_sheet` — Annual balance sheet
55
+ - `balance_sheet_quarterly` — Quarterly balance sheet
56
+ - `cashflow` — Annual cash flow statement
57
+ - `cashflow_quarterly` — Quarterly cash flow statement
58
+ - `cashflow_ttm` — Trailing 12-month cash flow
59
+
60
+ ### Holders
61
+ - `major_holders` — Insider/institution ownership %
62
+ - `institutional_holders` — Top institutional holders
63
+ - `mutualfund_holders` — Top mutual fund holders
64
+ - `insider_transactions` — Insider buy/sell transactions
65
+ - `insider_purchases` — Net insider purchase activity
66
+ - `insider_roster` — Insider roster with positions
67
+
68
+ ### Analyst & Estimates
69
+ - `recommendations` — Analyst recommendation trend (buy/hold/sell counts)
70
+ - `upgrades_downgrades` — Analyst upgrade/downgrade history
71
+ - `earnings_estimate` — EPS estimates by period
72
+ - `revenue_estimate` — Revenue estimates by period
73
+ - `earnings_history` — Historical EPS vs estimates (surprise %)
74
+ - `eps_trend` — EPS trend
75
+ - `eps_revisions` — EPS revision history
76
+ - `growth_estimates` — Growth estimates (stock/industry/sector)
77
+ - `analyst_price_targets` — Target high/low/mean/median price
78
+
79
+ ### Company
80
+ - `info` — Full company information (sector, industry, officers, description, ...)
81
+ - `fast_info` — Quick price/volume stats (market cap, 52-week range, averages)
82
+ - `sustainability` — ESG scores
83
+ - `calendar` — Upcoming earnings and dividend dates
84
+ - `history_metadata` — Exchange, currency, trading hours
85
+ - `news` — Latest news articles
86
+ - `sec_filings` — SEC 10-K/10-Q filing history
87
+
88
+ ### Other
89
+ - `earnings` — Earnings dates with EPS surprise
90
+ - `options` — Full options chain (all expiries, calls and puts)
91
+ - `isin` — ISIN identifier string
92
+ - `funds_data` — ETF/mutual fund details (holdings, allocations)
93
+
94
+ ## Examples
95
+
96
+ ```python
97
+ import yfd
98
+
99
+ # Get AAPL price for March 2026
100
+ df = yfd.get("AAPL", "price", start="2026-03-01", end="2026-03-31")
101
+
102
+ # Get annual income statement
103
+ inc = yfd.get("AAPL", "income_stmt")
104
+ # DataFrame: rows = line items (TotalRevenue, NetIncome, ...), columns = fiscal year dates
105
+
106
+ # Get company info
107
+ info = yfd.get("AAPL", "info")
108
+ # dict with 180+ keys: sector, industry, marketCap, fullTimeEmployees, ...
109
+
110
+ # Get analyst price targets
111
+ targets = yfd.get("AAPL", "analyst_price_targets")
112
+ # {"currentPrice": 248.8, "highprice": 350.0, "lowprice": 205.0, "meanprice": 295.3, ...}
113
+ ```
114
+
115
+ ```bash
116
+ # CLI equivalents
117
+ yfd price AAPL --start 2026-03-01
118
+ yfd income-stmt AAPL
119
+ yfd info AAPL --json
120
+ yfd analyst-price-targets AAPL
121
+ ```
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "yfd"
3
+ version = "0.3.0"
4
+ requires-python = ">=3.11"
5
+ dependencies = [
6
+ "boto3",
7
+ "curl-cffi",
8
+ "lxml",
9
+ "pandas",
10
+ "pyarrow",
11
+ "python-dotenv",
12
+ "requests",
13
+ "tvscreener>=0.2.2",
14
+ ]
15
+
16
+ [project.scripts]
17
+ yfd = "yfd.cli:main"
18
+
19
+ [build-system]
20
+ requires = ["hatchling"]
21
+ build-backend = "hatchling.build"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["src/yfd"]
@@ -0,0 +1,6 @@
1
+ NIGHTLY PRICE REPORT — 2026-03-28
2
+ Symbols: 6697, Start date: 2026-03-28
3
+ Fetched: 6432, No data: 265, Uploaded: 6432, Upload failed: 0
4
+
5
+ NO DATA (265 symbols, likely delisted):
6
+ AACBR, AACI, ABLV, ACAAU, ADACU, AEAQU, AHL, AIIA, AIMDW, AIRT, AIRTP, AKO, ALCYU, ALFUU, ALIS, ALISR, ALISU, ALOV, ALOVW, ALUB, ALVOW, ANG, APACU, ARCIU, ARTC, ATH, BACC, BANFP, BBLGW, BCGWW, BDMDW, BEBE, BENFW, BF, BFRGW, BFRIW, BHAVU, BIII, BIXIW, BKHA, BKHAR, BLRKU, BLUWU, BLUWW, BML, BPACU, BPOPM, BRK, BRLSW, BRRWW, BSAAR, BTBDW, BZFDW, CAPTW, CCXIW, CCZ, CDR, CGCTW, CHAR, CMIIW, COLAU, CRACR, CRACU, CRACW, CRAQ, CRD, CTAAU, CUBWU, CUBWW, DFSCW, DMAA, DSYWW, DTSQU, DYOR, ETI, EURKR, EVOX, FACTW, FBYDW, FERAR, FERAU, FGII, FGMCU, FORTY, FSHP, FUSEW, GECCG, GECCO, GGROW, GIGGU, GIPRW, GIW, GIWWR, GJR, GJT, GLOP, GPACW, GSRFU, GTERU, HAVA, HAVAU, HCACU, HCICU, HSCSW, HSPTU, HVMC, HVMCW, I, ICR, ICUCW, INACR, IRHOR, ITHA, IVDAW, JBK, JENA, JFBRW, JWEL, K, KBONW, KCHV, KFII, KRAQW, KTH, KTWOU, KVAC, LAFAU, LEGO, LIMNW, LKSPU, LOTWW, LPAA, LPBBW, LPCV, LPCVW, LUCYW, MBAVU, MCGAU, MDRR, MER, MEVO, MKLYR, MKLYU, MLAA, MLAAW, MMTX, MTEX, MYPSW, N, NBRGR, NCPLW, NHIC, NIVFW, NIXXW, NMP, NOEMR, NOEMW, NPACW, NTRBW, NTWO, NTWOW, NVAWW, NVNIW, OABIW, OACCW, OAK, OBA, OCCIO, OCSAW, OIM, ONMDW, ORIQU, OTGAW, OXBRW, OXSQG, P, PAACU, PACH, PBHC, PDPA, PFX, PGACR, PHXE, PIIIW, PLBL, PMTRW, POLEW, PRENW, PRHIZ, PRIF, PTORU, QADRU, QSEAR, QUMSU, RAINW, RANG, RDACU, RDIB, RDZNW, REGCO, RFAMU, RSVRW, SAAQW, SBXD, SCE, SCII, SCIIU, SCPQ, SDHI, SDHIR, SEAL, SENEB, SIM, SOCA, SOHOB, SPEG, SPEGU, SSEA, SSEAU, SVACU, SVAQU, SVAQW, SVCC, SVIV, SWVLW, SXTPW, TACH, TACOW, TAVI, TAVIU, TC, TLNCU, TNONW, TOIIW, TPTA, TRGSU, TRTN, TVACU, TVAI, UHGWW, UYSCR, UYSCU, VACHU, VACHW, VACI, VCICU, VHCPW, VSECU, WALDW, WHLRD, WHLRL, WLACU, WLDSW, WLYB, WSBK, WSTNU, WTG, X, XBPEW, XCBEW, XFLH, XSLL, Y, YCY, ZKPU
@@ -0,0 +1 @@
1
+ from yfd.store import DATA_TYPES, get, symbols, types
@@ -0,0 +1,77 @@
1
+ """CLI entry point: ``yfd <data_type> <symbol> [--start] [--end] [--json]``."""
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+
7
+ import pandas as pd
8
+
9
+ from yfd.store import DATA_TYPES, get, symbols, types
10
+
11
+
12
+ def _format_df(df: pd.DataFrame, as_json: bool) -> str:
13
+ if as_json:
14
+ return df.to_json(orient="split", date_format="iso", indent=2)
15
+ return df.to_string()
16
+
17
+
18
+ def _format_value(val, as_json: bool) -> str:
19
+ if as_json:
20
+ return json.dumps(val, default=str, indent=2)
21
+ if isinstance(val, dict):
22
+ return "\n".join(f"{k}: {v}" for k, v in val.items())
23
+ if isinstance(val, list):
24
+ return json.dumps(val, default=str, indent=2)
25
+ return str(val)
26
+
27
+
28
+ def main():
29
+ parser = argparse.ArgumentParser(
30
+ prog="yfd",
31
+ description="Yahoo Finance data — read from R2 storage",
32
+ )
33
+ parser.add_argument(
34
+ "command",
35
+ help="Data type (e.g. price, income-stmt, info) or: types, symbols",
36
+ )
37
+ parser.add_argument("symbol", nargs="?", help="Ticker symbol (e.g. AAPL)")
38
+ parser.add_argument("--start", help="Start date filter (price only)")
39
+ parser.add_argument("--end", help="End date filter (price only)")
40
+ parser.add_argument("--json", action="store_true", dest="as_json", help="JSON output")
41
+
42
+ args = parser.parse_args()
43
+
44
+ if args.command == "types":
45
+ for name, desc in sorted(types().items()):
46
+ print(f" {name:<25} {desc}")
47
+ return
48
+
49
+ if args.command == "symbols":
50
+ syms = symbols()
51
+ print(f"{len(syms)} symbols: {', '.join(syms[:20])}{'...' if len(syms) > 20 else ''}")
52
+ return
53
+
54
+ data_type = args.command.replace("-", "_")
55
+
56
+ if data_type not in DATA_TYPES:
57
+ print(f"Unknown: {args.command}. Run 'yfd types' for valid types.", file=sys.stderr)
58
+ sys.exit(1)
59
+
60
+ if not args.symbol:
61
+ print(f"Usage: yfd {args.command} <SYMBOL>", file=sys.stderr)
62
+ sys.exit(1)
63
+
64
+ result = get(args.symbol.upper(), data_type, start=args.start, end=args.end)
65
+
66
+ if result is None:
67
+ print(f"No data for {args.symbol.upper()}/{data_type}", file=sys.stderr)
68
+ sys.exit(1)
69
+
70
+ if isinstance(result, pd.DataFrame):
71
+ print(_format_df(result, args.as_json))
72
+ else:
73
+ print(_format_value(result, args.as_json))
74
+
75
+
76
+ if __name__ == "__main__":
77
+ main()
@@ -0,0 +1,62 @@
1
+ """Fetch full daily OHLCV history and save as per-symbol parquet files."""
2
+
3
+ import time
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from pathlib import Path
6
+
7
+ from yfd.yahoo import Yahoo
8
+
9
+
10
+ def fetch_daily_history(
11
+ symbols: list[str],
12
+ output_dir: Path,
13
+ batch_size: int = 50,
14
+ pause: float = 1.0,
15
+ end: str | None = None,
16
+ workers: int = 10,
17
+ ) -> list[str]:
18
+ """Fetch full daily OHLCV history and save as per-symbol parquet files.
19
+
20
+ Returns list of symbols that failed.
21
+ """
22
+ output_dir.mkdir(parents=True, exist_ok=True)
23
+ existing = {p.stem for p in output_dir.glob("*.parquet")}
24
+ remaining = [s for s in symbols if s not in existing]
25
+ if existing:
26
+ print(f"Skipping {len(existing)} already fetched, {len(remaining)} remaining")
27
+
28
+ yahoo = Yahoo()
29
+ failed: list[str] = []
30
+ total_batches = -(-len(remaining) // batch_size)
31
+
32
+ for i in range(0, len(remaining), batch_size):
33
+ batch = remaining[i : i + batch_size]
34
+ batch_num = i // batch_size + 1
35
+ print(f"[{batch_num}/{total_batches}] Fetching {len(batch)} symbols...")
36
+
37
+ saved = 0
38
+
39
+ def _fetch(sym):
40
+ df, _, _ = yahoo.chart(sym, end=end, period="max")
41
+ return sym, df
42
+
43
+ with ThreadPoolExecutor(max_workers=workers) as pool:
44
+ futures = {pool.submit(_fetch, s): s for s in batch}
45
+ for fut in as_completed(futures):
46
+ sym = futures[fut]
47
+ try:
48
+ _, df = fut.result()
49
+ if df is not None and not df.empty:
50
+ df.to_parquet(output_dir / f"{sym}.parquet")
51
+ saved += 1
52
+ else:
53
+ failed.append(sym)
54
+ except Exception:
55
+ failed.append(sym)
56
+
57
+ print(f" Saved {saved}/{len(batch)} symbols")
58
+
59
+ if batch_num < total_batches:
60
+ time.sleep(pause)
61
+
62
+ return failed