alphadataforge 0.1.0__py3-none-any.whl
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.
- alphadataforge/__init__.py +2 -0
- alphadataforge/config/__init__.py +0 -0
- alphadataforge/config/endpoints.py +52 -0
- alphadataforge/config/settings.py +18 -0
- alphadataforge/core/__init__.py +0 -0
- alphadataforge/core/base_fetcher.py +357 -0
- alphadataforge/core/exceptions.py +19 -0
- alphadataforge/data/fundamental.py +107 -0
- alphadataforge/data/price.py +207 -0
- alphadataforge/features/__init__.py +0 -0
- alphadataforge/providers/__init__.py +0 -0
- alphadataforge/providers/alphavantage_fetcher.py +328 -0
- alphadataforge/providers/fmp_fetcher.py +183 -0
- alphadataforge/providers/hybrid_fetcher.py +87 -0
- alphadataforge/providers/stockfit_fetcher.py +265 -0
- alphadataforge/providers/tiingo_fetcher.py +312 -0
- alphadataforge/providers/yfinance_fetcher.py +282 -0
- alphadataforge/utils/__init__.py +0 -0
- alphadataforge/utils/finance_math.py +125 -0
- alphadataforge/utils/logger.py +30 -0
- alphadataforge-0.1.0.dist-info/METADATA +770 -0
- alphadataforge-0.1.0.dist-info/RECORD +24 -0
- alphadataforge-0.1.0.dist-info/WHEEL +5 -0
- alphadataforge-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: alphadataforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular data fetching and feature engineering pipeline for quantitative trading.
|
|
5
|
+
Project-URL: Homepage, https://github.com/RETCx/alphadataforge
|
|
6
|
+
Project-URL: Issues, https://github.com/RETCx/alphadataforge/issues
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: yfinance
|
|
12
|
+
Requires-Dist: tiingo
|
|
13
|
+
Requires-Dist: google-cloud-bigquery
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: requests-cache
|
|
16
|
+
Requires-Dist: tenacity
|
|
17
|
+
Requires-Dist: scikit-learn
|
|
18
|
+
Requires-Dist: python-dotenv
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-vcr; extra == "dev"
|
|
23
|
+
Provides-Extra: publish
|
|
24
|
+
Requires-Dist: build; extra == "publish"
|
|
25
|
+
Requires-Dist: twine; extra == "publish"
|
|
26
|
+
|
|
27
|
+
# AlphaDataForge
|
|
28
|
+
|
|
29
|
+
AlphaDataForge is a modular data fetching pipeline for quantitative trading and financial analysis. It provides a unified interface to fetch **Price** and **Fundamental** data across multiple providers with automatic caching, rate limit protection, and standardized output formats.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
After the first PyPI release is published, install with:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python -m pip install alphadataforge
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For development or before the first release:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/RETCx/alphadataforge.git
|
|
45
|
+
cd alphadataforge
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Maintainers: see [PUBLISHING.md](PUBLISHING.md) for release instructions.
|
|
50
|
+
|
|
51
|
+
Copy the environment template and add your API keys:
|
|
52
|
+
```bash
|
|
53
|
+
cp .env.example .env
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
# .env
|
|
58
|
+
TIINGO_API_KEY="your_tiingo_key"
|
|
59
|
+
ALPHAVANTAGE_API_KEY="your_alphavantage_key"
|
|
60
|
+
FMP_API_KEY="your_fmp_key"
|
|
61
|
+
STOCKFIT_API_KEY="your_stockfit_key"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
*YFinance does not require an API key.*
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Architecture
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
alphadataforge/
|
|
72
|
+
├── data/
|
|
73
|
+
│ ├── price.py ← Unified Price Facade (Price.get(), Price.compare(), Price.consensus())
|
|
74
|
+
│ └── fundamental.py ← Unified Fundamental Facade (Fundamentals.get_info(), etc.)
|
|
75
|
+
│
|
|
76
|
+
├── providers/
|
|
77
|
+
│ ├── yfinance_fetcher.py
|
|
78
|
+
│ ├── alphavantage_fetcher.py
|
|
79
|
+
│ ├── tiingo_fetcher.py
|
|
80
|
+
│ ├── fmp_fetcher.py
|
|
81
|
+
│ ├── stockfit_fetcher.py ← StockFit API (20yr SEC EDGAR XBRL lineage, health scores)
|
|
82
|
+
│ └── hybrid_fetcher.py ← AlphaVantage (raw) + Tiingo (div/splits) + local math engine
|
|
83
|
+
│
|
|
84
|
+
├── scripts/
|
|
85
|
+
│ └── snapshot_fundamentals.py ← Offline snapshot utility (Parquet & SQLite)
|
|
86
|
+
│
|
|
87
|
+
├── utils/
|
|
88
|
+
│ └── finance_math.py ← Local back-adjustment engine (calculate_adjusted_prices)
|
|
89
|
+
│
|
|
90
|
+
└── core/
|
|
91
|
+
├── base_fetcher.py ← BaseDataFetcher (caching, retry, normalization)
|
|
92
|
+
└── exceptions.py ← AlphaDataForgeError, RateLimitExceededError, etc.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Built-in features (all providers)**:
|
|
96
|
+
- **SQLite Caching** (24h TTL) — auto-stored in OS temp folder, no setup needed.
|
|
97
|
+
- **Auto-Retry** with exponential backoff for network errors (via `tenacity`).
|
|
98
|
+
- **Normalized Output** — all prices return `[Open, High, Low, Close, Volume, Adj Close]` with a `DatetimeIndex`. All financials return standardized columns (`Net_Income`, `Total_Revenue`, etc.).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Module 1: Price
|
|
103
|
+
|
|
104
|
+
**Entry point:** `from alphadataforge.data.price import Price`
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from alphadataforge.data.price import Price
|
|
108
|
+
|
|
109
|
+
# Single symbol
|
|
110
|
+
df = Price.get("AAPL", start_date="2023-01-01", end_date="2023-12-31")
|
|
111
|
+
|
|
112
|
+
# Multiple symbols (returns dict)
|
|
113
|
+
data = Price.get(["AAPL", "GOOGL", "MSFT"], provider="yfinance")
|
|
114
|
+
|
|
115
|
+
# Specific provider
|
|
116
|
+
df = Price.get("TSLA", provider="tiingo", start_date="2022-01-01")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `Price.get()` — All Parameters
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Default | Description |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| `symbols` | `str` or `List[str]` | *required* | Ticker(s) e.g. `"AAPL"` or `["AAPL", "MSFT"]` |
|
|
124
|
+
| `provider` | `str` | `"yfinance"` | `"yfinance"`, `"alphavantage"`, `"tiingo"`, `"fmp"`, `"hybrid_av_tiingo"` |
|
|
125
|
+
| `start_date` | `str` | `None` | `"YYYY-MM-DD"` |
|
|
126
|
+
| `end_date` | `str` | `None` | `"YYYY-MM-DD"` |
|
|
127
|
+
| `provider_params` | `dict` | `{}` | Extra params passed to the underlying provider (see per-provider sections below) |
|
|
128
|
+
|
|
129
|
+
**Returns:** `pd.DataFrame` (single symbol) or `Dict[str, pd.DataFrame]` (multiple symbols)
|
|
130
|
+
|
|
131
|
+
**Output Columns:**
|
|
132
|
+
|
|
133
|
+
| Column | Type | Description |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `Open` | float | Opening price |
|
|
136
|
+
| `High` | float | Daily high |
|
|
137
|
+
| `Low` | float | Daily low |
|
|
138
|
+
| `Close` | float | Closing price |
|
|
139
|
+
| `Volume` | float | Trading volume |
|
|
140
|
+
| `Adj Open` | float | Adjusted opening price (if `adjusted=True`) |
|
|
141
|
+
| `Adj High` | float | Adjusted daily high (if `adjusted=True`) |
|
|
142
|
+
| `Adj Low` | float | Adjusted daily low (if `adjusted=True`) |
|
|
143
|
+
| `Adj Close` | float | Dividend & split adjusted close (if available or `adjusted=True`) |
|
|
144
|
+
| `Adj Volume` | float | Split-adjusted volume (if `adjusted=True`) |
|
|
145
|
+
|
|
146
|
+
> **Note on Adjusted Prices:** If `adjusted=True` is passed (or if the provider returns it natively), AlphaDataForge guarantees the presence of full Adjusted OHLC columns (`Adj Open`, `Adj High`, `Adj Low`, `Adj Close`). For providers like YFinance that only natively return `Adj Close`, our pipeline automatically backfills the remaining adjusted columns using the ratio of `Adj Close` / `Close`.
|
|
147
|
+
|
|
148
|
+
### Hybrid Provider (`hybrid_av_tiingo`)
|
|
149
|
+
AlphaDataForge offers a special `hybrid_av_tiingo` provider for optimal data quality and API usage:
|
|
150
|
+
- **Raw Prices:** Fetched from Alpha Vantage (deepest historical data, 20+ years).
|
|
151
|
+
- **Dividends & Splits:** Fetched from Tiingo (highly accurate, saves Alpha Vantage premium limits).
|
|
152
|
+
- **Processing:** Fully adjusted prices are calculated locally via our `finance_math` engine.
|
|
153
|
+
|
|
154
|
+
### Cross-Provider Adjusted Price Comparison
|
|
155
|
+
To ensure data consistency across all platforms, we tested the fully adjusted prices for `AAPL` on `2026-05-05` across all available providers and the hybrid engine. The results perfectly align:
|
|
156
|
+
|
|
157
|
+
**Adjusted Close:**
|
|
158
|
+
- `yfinance`: 283.9184
|
|
159
|
+
- `tiingo`: 283.9181
|
|
160
|
+
- `fmp`: 283.9200
|
|
161
|
+
- `alphavantage`: 283.9184
|
|
162
|
+
- `hybrid_av_tiingo`: 283.9184
|
|
163
|
+
|
|
164
|
+
**Adjusted Open:**
|
|
165
|
+
- `yfinance`: 276.6751
|
|
166
|
+
- `tiingo`: 276.6698
|
|
167
|
+
- `fmp`: 276.6800
|
|
168
|
+
- `alphavantage`: 276.6701
|
|
169
|
+
- `hybrid_av_tiingo`: 276.6701
|
|
170
|
+
|
|
171
|
+
### `Price.compare()` — Side-by-Side Multi-Provider
|
|
172
|
+
|
|
173
|
+
Fetches from multiple providers **concurrently** (ThreadPoolExecutor) and returns a single **MultiIndex DataFrame** with providers as level-0 columns.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Compare all 5 providers with adjusted prices
|
|
177
|
+
multi_df = Price.compare("AAPL", start_date="2023-01-01", provider_params={"adjusted": True})
|
|
178
|
+
|
|
179
|
+
# Access a specific provider's data
|
|
180
|
+
yfinance_df = multi_df["yfinance"]
|
|
181
|
+
|
|
182
|
+
# Extract one column across all providers
|
|
183
|
+
adj_close_all = multi_df.xs("Adj Close", axis=1, level=1)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Parameter | Type | Default | Description |
|
|
187
|
+
|---|---|---|---|
|
|
188
|
+
| `symbol` | `str` | *required* | Single ticker |
|
|
189
|
+
| `start_date` | `str` | `None` | `"YYYY-MM-DD"` |
|
|
190
|
+
| `end_date` | `str` | `None` | `"YYYY-MM-DD"` |
|
|
191
|
+
| `providers` | `List[str]` | all 5 providers | Limit which providers to query |
|
|
192
|
+
| `provider_params` | `dict` | `{}` | Passed to all providers (e.g. `{"adjusted": True}`) |
|
|
193
|
+
| `custom_data` | `Dict[str, DataFrame]` | `None` | Inject your own DataFrames to compare alongside API data |
|
|
194
|
+
|
|
195
|
+
**Returns:** `pd.DataFrame` with MultiIndex columns `(provider, column_name)`
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### `Price.consensus()` — Aggregated Median/Mean Price
|
|
200
|
+
|
|
201
|
+
Builds on `compare()` and aggregates across all providers to return a **single reliable DataFrame**, neutralizing outliers from any one source.
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
# Median across all providers (recommended — more robust to outliers)
|
|
205
|
+
consensus_df = Price.consensus("AAPL", start_date="2023-01-01", method="median")
|
|
206
|
+
|
|
207
|
+
# Limit to specific providers
|
|
208
|
+
consensus_df = Price.consensus(
|
|
209
|
+
"AAPL",
|
|
210
|
+
providers=["yfinance", "tiingo", "alphavantage"],
|
|
211
|
+
provider_params={"adjusted": True},
|
|
212
|
+
method="median"
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
| Parameter | Type | Default | Description |
|
|
217
|
+
|---|---|---|---|
|
|
218
|
+
| `symbol` | `str` | *required* | Single ticker |
|
|
219
|
+
| `start_date` | `str` | `None` | `"YYYY-MM-DD"` |
|
|
220
|
+
| `end_date` | `str` | `None` | `"YYYY-MM-DD"` |
|
|
221
|
+
| `providers` | `List[str]` | all 5 providers | Limit which providers to query |
|
|
222
|
+
| `provider_params` | `dict` | `{}` | Passed to all providers |
|
|
223
|
+
| `custom_data` | `Dict[str, DataFrame]` | `None` | Inject your own DataFrames into the aggregation |
|
|
224
|
+
| `method` | `str` | `"median"` | `"median"` (robust to outliers) or `"mean"` |
|
|
225
|
+
|
|
226
|
+
**Returns:** `pd.DataFrame` with standard OHLCV columns sorted in standard order
|
|
227
|
+
|
|
228
|
+
#### Injecting Custom External Data (`custom_data`)
|
|
229
|
+
|
|
230
|
+
Both `compare()` and `consensus()` accept a `custom_data` parameter — a dictionary of your own DataFrames to include in the comparison or aggregation. Your DataFrame must have a `DatetimeIndex` and column names that match the AlphaDataForge standard (`Open`, `High`, `Low`, `Close`, `Volume`, `Adj Close`, etc.).
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
import pandas as pd
|
|
234
|
+
from alphadataforge.data.price import Price
|
|
235
|
+
from alphadataforge.providers.tiingo_fetcher import TiingoFetcher
|
|
236
|
+
from alphadataforge.utils.finance_math import calculate_adjusted_prices
|
|
237
|
+
|
|
238
|
+
# Step 1: Load & Normalize column names to AlphaDataForge standard
|
|
239
|
+
df_custom = pd.read_csv('my_data.csv')
|
|
240
|
+
df_custom = df_custom.rename(columns={'Price': 'Close', 'Vol.': 'Volume'})
|
|
241
|
+
df_custom['Date'] = pd.to_datetime(df_custom['Date'])
|
|
242
|
+
df_custom.set_index('Date', inplace=True)
|
|
243
|
+
|
|
244
|
+
# Step 2 (Optional): Back-adjust with Tiingo dividends/splits if your data is raw
|
|
245
|
+
tf = TiingoFetcher()
|
|
246
|
+
df_tiingo = tf.fetch_single('AAPL', start_date='2023-01-01', end_date='2023-12-31')
|
|
247
|
+
dividends_df = df_tiingo[['divCash']].rename(columns={'divCash': 'Dividend'})
|
|
248
|
+
dividends_df = dividends_df[dividends_df['Dividend'] > 0]
|
|
249
|
+
splits_df = df_tiingo[['splitFactor']].rename(columns={'splitFactor': 'SplitFactor'})
|
|
250
|
+
splits_df = splits_df[splits_df['SplitFactor'] != 1.0]
|
|
251
|
+
df_custom_adjusted = calculate_adjusted_prices(df_custom, dividends_df, splits_df)
|
|
252
|
+
|
|
253
|
+
# Step 3: Feed into consensus engine
|
|
254
|
+
consensus_df = Price.consensus(
|
|
255
|
+
"AAPL",
|
|
256
|
+
start_date="2023-01-01",
|
|
257
|
+
providers=["yfinance", "alphavantage"],
|
|
258
|
+
provider_params={"adjusted": True},
|
|
259
|
+
custom_data={"my_local_db": df_custom_adjusted},
|
|
260
|
+
method="median"
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Module 2: Fundamentals
|
|
267
|
+
|
|
268
|
+
**Entry point:** `from alphadataforge.data.fundamental import Fundamentals`
|
|
269
|
+
|
|
270
|
+
### `Fundamentals.get_info()` — Company Profile
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
info = Fundamentals.get_info("AAPL", provider="yfinance")
|
|
274
|
+
print(info["sector"], info["marketCap"])
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
| Parameter | Type | Default | Description |
|
|
278
|
+
|---|---|---|---|
|
|
279
|
+
| `symbol` | `str` | *required* | Ticker e.g. `"AAPL"` |
|
|
280
|
+
| `provider` | `str` | `"yfinance"` | `"yfinance"`, `"stockfit"`, `"alphavantage"`, `"fmp"` |
|
|
281
|
+
|
|
282
|
+
**Returns:** `dict`
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### `Fundamentals.get_financials()` — Financial Statements
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
df = Fundamentals.get_financials("AAPL", statement="income", period="annual", provider="yfinance")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
| Parameter | Type | Default | Description |
|
|
293
|
+
|---|---|---|---|
|
|
294
|
+
| `symbol` | `str` | *required* | Ticker e.g. `"AAPL"` |
|
|
295
|
+
| `statement` | `str` | `"income"` | `"income"`, `"balance"`, `"cashflow"`, `"growth"`, `"scores"`, `"shares_outstanding"` |
|
|
296
|
+
| `period` | `str` | `"annual"` | `"annual"` or `"quarterly"` |
|
|
297
|
+
| `provider` | `str` | `"yfinance"` | `"yfinance"`, `"stockfit"`, `"alphavantage"`, `"fmp"` |
|
|
298
|
+
|
|
299
|
+
**Returns:** `pd.DataFrame` with `DatetimeIndex` and normalized columns
|
|
300
|
+
|
|
301
|
+
**Normalized Columns (Standardized Across All Providers):**
|
|
302
|
+
|
|
303
|
+
| Column | Description |
|
|
304
|
+
|---|---|
|
|
305
|
+
| `Net_Income` | Net profit/loss after tax |
|
|
306
|
+
| `Total_Revenue` | Total revenue |
|
|
307
|
+
| `Operating_Income` | EBIT / Operating profit |
|
|
308
|
+
| `Free_Cash_Flow` | Free cash flow |
|
|
309
|
+
| `Total_Assets` | Total assets |
|
|
310
|
+
| `Total_Liabilities` | Total liabilities |
|
|
311
|
+
| `Total_Equity` | Shareholders' equity |
|
|
312
|
+
| `Shares_Outstanding` | Basic shares outstanding |
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
### `Fundamentals.get_scores()` — Financial Health Scores (StockFit)
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
scores = Fundamentals.get_scores("AAPL", provider="stockfit")
|
|
321
|
+
print("Piotroski F-Score:", scores["piotroskiFScore"])
|
|
322
|
+
# Returns Piotroski details: positiveNetIncome, positiveOperatingCashFlow, risingRoa, etc.
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### SEC XBRL 20-Year Statement History (StockFit)
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
# 20 years of annual statements with SEC filing audit lineage
|
|
329
|
+
df_income = Fundamentals.get_financials("AAPL", statement="income", period="annual", provider="stockfit", limit=100)
|
|
330
|
+
|
|
331
|
+
# 70+ quarters of balance sheet history
|
|
332
|
+
df_balance = Fundamentals.get_financials("AAPL", statement="balance", period="quarterly", provider="stockfit", limit=100)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Snapshot & Offline Storage (`scripts/snapshot_fundamentals.py`)
|
|
336
|
+
|
|
337
|
+
Capture 20-year financial statement history for target symbols into local **Parquet** and **SQLite** storage:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# Snapshot Magnificent 7 (Annual + Quarterly, Profiles, Scores)
|
|
341
|
+
python scripts/snapshot_fundamentals.py --preset mag7
|
|
342
|
+
|
|
343
|
+
# Snapshot top 25 US blue-chips
|
|
344
|
+
python scripts/snapshot_fundamentals.py --preset top25
|
|
345
|
+
|
|
346
|
+
# Snapshot specific tickers
|
|
347
|
+
python scripts/snapshot_fundamentals.py --symbols AAPL,MSFT,NVDA,GOOGL
|
|
348
|
+
```
|
|
349
|
+
Data is saved to `data_store/stockfit_snapshots/parquet/` and `data_store/stockfit_snapshots/fundamentals.db`.
|
|
350
|
+
|
|
351
|
+
### `Fundamentals.get_earnings_calendar()` — Earnings Calendar
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
df = Fundamentals.get_earnings_calendar(horizon="3month", symbol="AAPL", provider="alphavantage")
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
| Parameter | Type | Default | Description |
|
|
358
|
+
|---|---|---|---|
|
|
359
|
+
| `horizon` | `str` | `"3month"` | `"3month"`, `"6month"`, or `"12month"` |
|
|
360
|
+
| `symbol` | `str` | `None` | Filter for specific ticker (optional) |
|
|
361
|
+
| `provider` | `str` | `"alphavantage"` | Only `"alphavantage"` supported |
|
|
362
|
+
|
|
363
|
+
**Returns:** `pd.DataFrame` with columns: `symbol`, `name`, `reportDate`, `fiscalDateEnding`, `estimate`, `currency`
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Provider Reference
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Provider 1: YFinance (`provider="yfinance"`)
|
|
372
|
+
|
|
373
|
+
| | |
|
|
374
|
+
|---|---|
|
|
375
|
+
| **API Key** | No Not required |
|
|
376
|
+
| **Rate Limit** | Practically unlimited |
|
|
377
|
+
| **Best For** | Development, batch fetching, comprehensive fundamentals |
|
|
378
|
+
|
|
379
|
+
#### Price
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
from alphadataforge.data.price import Price
|
|
383
|
+
|
|
384
|
+
# Basic fetch
|
|
385
|
+
df = Price.get("AAPL", start_date="2023-01-01")
|
|
386
|
+
|
|
387
|
+
# With intraday interval (available last 60 days only)
|
|
388
|
+
df = Price.get("AAPL", provider_params={"interval": "1h"})
|
|
389
|
+
|
|
390
|
+
# Crypto & Forex (use yfinance ticker format)
|
|
391
|
+
df = Price.get("BTC-USD") # Bitcoin
|
|
392
|
+
df = Price.get("ETH-USD") # Ethereum
|
|
393
|
+
df = Price.get("EURUSD=X") # EUR/USD Forex
|
|
394
|
+
df = Price.get("^VIX") # VIX Index
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Extra `provider_params` for YFinance Price:**
|
|
398
|
+
|
|
399
|
+
| Param | Type | Default | Description |
|
|
400
|
+
|---|---|---|---|
|
|
401
|
+
| `interval` | `str` | `"1d"` | `"1m"`, `"5m"`, `"15m"`, `"30m"`, `"1h"`, `"1d"`, `"1wk"`, `"1mo"`, `"3mo"`. Intraday (< 1d) only for last 60 days. |
|
|
402
|
+
| `auto_adjust` | `bool` | `False` | Pass `True` if you want yfinance to overwrite the Open/High/Low columns with adjusted prices. |
|
|
403
|
+
|
|
404
|
+
**Under the hood (YFinance):**
|
|
405
|
+
- **API Calls:** 1 call per fetch.
|
|
406
|
+
- **Adjusted Prices:** Natively returns `Adj Close`. If you want `Adj Open/High/Low`, you must pass `provider_params={"auto_adjust": True}`.
|
|
407
|
+
|
|
408
|
+
#### Fundamentals (YFinance)
|
|
409
|
+
|
|
410
|
+
| Method | `statement=` value | Notes |
|
|
411
|
+
|---|---|---|
|
|
412
|
+
| `get_info()` | — | Returns 80+ fields: `sector`, `industry`, `marketCap`, `trailingPE`, `forwardPE`, `dividendYield`, `beta`, `52WeekHigh`, `52WeekLow`, etc. |
|
|
413
|
+
| `get_financials()` | `"income"` | Up to 4 years annual / 8 quarters quarterly |
|
|
414
|
+
| `get_financials()` | `"balance"` | Up to 4 years annual / 8 quarters quarterly |
|
|
415
|
+
| `get_financials()` | `"cashflow"` | Up to 4 years annual / 8 quarters quarterly |
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
from alphadataforge.data.fundamental import Fundamentals
|
|
419
|
+
|
|
420
|
+
# Company overview
|
|
421
|
+
info = Fundamentals.get_info("AAPL", provider="yfinance")
|
|
422
|
+
print(info["sector"]) # "Technology"
|
|
423
|
+
print(info["marketCap"]) # 3_000_000_000_000
|
|
424
|
+
print(info["trailingPE"]) # 33.2
|
|
425
|
+
|
|
426
|
+
# Financial Statements
|
|
427
|
+
df_income = Fundamentals.get_financials("AAPL", statement="income", period="annual", provider="yfinance")
|
|
428
|
+
df_balance = Fundamentals.get_financials("AAPL", statement="balance", period="quarterly", provider="yfinance")
|
|
429
|
+
df_cashflow = Fundamentals.get_financials("AAPL", statement="cashflow", period="annual", provider="yfinance")
|
|
430
|
+
|
|
431
|
+
# Merge all three by date index
|
|
432
|
+
df_all = df_income.join(df_balance, how="outer", rsuffix="_bal").join(df_cashflow, how="outer", rsuffix="_cf")
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
### Provider 2: Alpha Vantage (`provider="alphavantage"`)
|
|
438
|
+
|
|
439
|
+
| | |
|
|
440
|
+
|---|---|
|
|
441
|
+
| **API Key** | Yes Required (`ALPHAVANTAGE_API_KEY`) |
|
|
442
|
+
| **Rate Limit (Free)** | 25 requests / day, 1 request / second |
|
|
443
|
+
| **Auto-Sleep** | Yes Built-in 1.2s delay between each request (no manual sleep needed) |
|
|
444
|
+
| **Data Depth** | Up to 20 years of price history, full SEC-normalized financials |
|
|
445
|
+
| **Best For** | Deep historical financial statements, Earnings Calendar |
|
|
446
|
+
|
|
447
|
+
> Warning **25 requests/day** is the hard ceiling on the free plan. Each call to `get_financials()` or `get_info()` costs 1 request. Plan accordingly.
|
|
448
|
+
|
|
449
|
+
#### Price
|
|
450
|
+
|
|
451
|
+
```python
|
|
452
|
+
from alphadataforge.data.price import Price
|
|
453
|
+
|
|
454
|
+
# Basic EOD price (compact = last 100 days)
|
|
455
|
+
df = Price.get("AAPL", provider="alphavantage")
|
|
456
|
+
|
|
457
|
+
# Full history (~20 years)
|
|
458
|
+
df = Price.get("AAPL", provider="alphavantage", provider_params={"outputsize": "full"})
|
|
459
|
+
|
|
460
|
+
# Adjusted prices (costs 3 API calls: Price + Dividends + Splits)
|
|
461
|
+
df = Price.get("AAPL", provider="alphavantage", provider_params={"adjusted": True, "outputsize": "full"})
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Extra `provider_params` for AlphaVantage Price:**
|
|
465
|
+
|
|
466
|
+
| Param | Type | Default | Description |
|
|
467
|
+
|---|---|---|---|
|
|
468
|
+
| `outputsize` | `str` | `"compact"` | `"compact"` (last 100 days) or `"full"` (full ~20yr history). |
|
|
469
|
+
| `adjusted` | `bool` | `False` | Fetches dividends/splits and back-adjusts all OHLCV. |
|
|
470
|
+
|
|
471
|
+
**Under the hood (Alpha Vantage):**
|
|
472
|
+
- **API Calls (`adjusted=False`):** 1 call (`TIME_SERIES_DAILY`).
|
|
473
|
+
- **API Calls (`adjusted=True`):** **3 calls** (`TIME_SERIES_DAILY`, `DIVIDENDS`, `SPLITS`).
|
|
474
|
+
- **Adjusted Prices:** Since Alpha Vantage made their adjusted endpoint premium, we built a custom math engine. When `adjusted=True`, we fetch raw prices, dividends, and splits on the free endpoints and calculate `Adj Open`, `Adj High`, `Adj Low`, `Adj Close`, `Adj Volume` entirely locally. You get premium-grade adjusted data for free!
|
|
475
|
+
|
|
476
|
+
#### Fundamentals (AlphaVantage)
|
|
477
|
+
|
|
478
|
+
| Method | `statement=` value | Notes |
|
|
479
|
+
|---|---|---|
|
|
480
|
+
| `get_info()` | — | Maps to `OVERVIEW` endpoint. Returns company ratios, P/E, EPS, dividends, 52-week data etc. |
|
|
481
|
+
| `get_financials()` | `"income"` | SEC-normalized annual or quarterly income statement |
|
|
482
|
+
| `get_financials()` | `"balance"` | SEC-normalized annual or quarterly balance sheet |
|
|
483
|
+
| `get_financials()` | `"cashflow"` | SEC-normalized annual or quarterly cash flow |
|
|
484
|
+
| `get_financials()` | `"shares_outstanding"` | Historical quarterly shares (basic + diluted). Note: `period` param ignored — always quarterly. |
|
|
485
|
+
| `get_financials()` | `"earnings"` | Annual/quarterly EPS history with analyst estimates and surprise % |
|
|
486
|
+
| `get_earnings_calendar()` | — | Upcoming earnings dates for next 3/6/12 months |
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
from alphadataforge.data.fundamental import Fundamentals
|
|
490
|
+
|
|
491
|
+
# Company overview (maps to OVERVIEW endpoint)
|
|
492
|
+
info = Fundamentals.get_info("IBM", provider="alphavantage")
|
|
493
|
+
|
|
494
|
+
# Income Statement (annual, goes back 5+ years)
|
|
495
|
+
df_income = Fundamentals.get_financials("IBM", statement="income", period="annual", provider="alphavantage")
|
|
496
|
+
|
|
497
|
+
# Balance Sheet (quarterly)
|
|
498
|
+
df_balance = Fundamentals.get_financials("IBM", statement="balance", period="quarterly", provider="alphavantage")
|
|
499
|
+
|
|
500
|
+
# Cash Flow
|
|
501
|
+
df_cf = Fundamentals.get_financials("IBM", statement="cashflow", period="annual", provider="alphavantage")
|
|
502
|
+
|
|
503
|
+
# Historical Shares Outstanding (quarterly, to track buybacks/dilution)
|
|
504
|
+
df_shares = Fundamentals.get_financials("IBM", statement="shares_outstanding", provider="alphavantage")
|
|
505
|
+
# Returns: Shares_Outstanding (basic), shares_outstanding_diluted
|
|
506
|
+
|
|
507
|
+
# Earnings History with analyst surprise
|
|
508
|
+
df_earnings = Fundamentals.get_financials("IBM", statement="earnings", period="quarterly", provider="alphavantage")
|
|
509
|
+
# Returns: reportedEPS, estimatedEPS, surprise, surprisePercentage, reportTime
|
|
510
|
+
|
|
511
|
+
# Earnings Calendar (upcoming earnings dates)
|
|
512
|
+
df_cal = Fundamentals.get_earnings_calendar(horizon="3month", provider="alphavantage") # all upcoming
|
|
513
|
+
df_cal = Fundamentals.get_earnings_calendar(horizon="12month", symbol="AAPL", provider="alphavantage") # specific ticker
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
### Provider 3: Tiingo (`provider="tiingo"`)
|
|
519
|
+
|
|
520
|
+
| | |
|
|
521
|
+
|---|---|
|
|
522
|
+
| **API Key** | Yes Required (`TIINGO_API_KEY`) |
|
|
523
|
+
| **Rate Limit (Free)** | 500 requests / hour (very generous) |
|
|
524
|
+
| **Best For** | Clean institutional-grade EOD prices, News API with tag filtering |
|
|
525
|
+
|
|
526
|
+
#### Price
|
|
527
|
+
|
|
528
|
+
```python
|
|
529
|
+
from alphadataforge.data.price import Price
|
|
530
|
+
|
|
531
|
+
# Basic EOD price
|
|
532
|
+
df = Price.get("AAPL", provider="tiingo", start_date="2020-01-01")
|
|
533
|
+
|
|
534
|
+
# With weekly frequency
|
|
535
|
+
df = Price.get("AAPL", provider="tiingo", provider_params={"frequency": "weekly"})
|
|
536
|
+
|
|
537
|
+
# Adjusted prices (natively returned — no extra API calls)
|
|
538
|
+
df = Price.get("AAPL", provider="tiingo", provider_params={"adjusted": True})
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Extra `provider_params` for Tiingo Price:**
|
|
542
|
+
|
|
543
|
+
| Param | Type | Default | Description |
|
|
544
|
+
|---|---|---|---|
|
|
545
|
+
| `frequency` | `str` | `"daily"` | `"daily"`, `"weekly"`, `"monthly"`, `"annually"`. Intraday requires paid plan. |
|
|
546
|
+
| `adjusted` | `bool` | `False` | Returns `Adj Open`, `Adj High`, `Adj Low`, `Adj Close`, `Adj Volume` (natively — no extra API calls). |
|
|
547
|
+
|
|
548
|
+
**Under the hood (Tiingo):**
|
|
549
|
+
- **API Calls:** 1 call per fetch.
|
|
550
|
+
- **Adjusted Prices:** The Tiingo API natively sends a full suite of adjusted prices. The resulting DataFrame will always contain `Adj Open`, `Adj High`, `Adj Low`, `Adj Close`, and `Adj Volume`. No manual calculation needed.
|
|
551
|
+
|
|
552
|
+
#### Crypto (Tiingo)
|
|
553
|
+
|
|
554
|
+
```python
|
|
555
|
+
# Tiingo crypto uses different ticker format (no dash): BTCUSD, ETHUSD
|
|
556
|
+
from alphadataforge.providers.tiingo_fetcher import TiingoFetcher
|
|
557
|
+
|
|
558
|
+
tiingo = TiingoFetcher()
|
|
559
|
+
crypto_data = tiingo.fetch_crypto(
|
|
560
|
+
tickers=["BTCUSD", "ETHUSD"],
|
|
561
|
+
start_date="2023-01-01",
|
|
562
|
+
resample_freq="1Day" # "1Day", "1Hour", "30Min"
|
|
563
|
+
)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
#### News (Tiingo)
|
|
567
|
+
|
|
568
|
+
Tiingo has the most powerful news filtering of all providers.
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
from alphadataforge.providers.tiingo_fetcher import TiingoFetcher
|
|
572
|
+
|
|
573
|
+
tiingo = TiingoFetcher()
|
|
574
|
+
news_df = tiingo.fetch_news(
|
|
575
|
+
tickers=["AAPL", "MSFT"], # Filter by ticker (optional)
|
|
576
|
+
tags=["Earnings"], # Filter by tag (optional)
|
|
577
|
+
sources=["reuters.com"], # Filter by source domain (optional)
|
|
578
|
+
start_date="2024-01-01",
|
|
579
|
+
end_date="2024-06-01",
|
|
580
|
+
limit=50
|
|
581
|
+
)
|
|
582
|
+
# Returns columns: publishedDate, title, description, url, tickers, tags
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
#### Fundamentals (Tiingo)
|
|
586
|
+
|
|
587
|
+
| Method | Support | Notes |
|
|
588
|
+
|---|---|---|
|
|
589
|
+
| `get_info()` | Yes Supported | Returns basic ticker metadata (name, description, exchange, startDate) |
|
|
590
|
+
| `get_financials()` | No Not Supported | Returns empty DataFrame. Full financial statements require Tiingo's paid `fundamentals` plan. |
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
### Provider 4: FMP — Financial Modeling Prep (`provider="fmp"`)
|
|
595
|
+
|
|
596
|
+
| | |
|
|
597
|
+
|---|---|
|
|
598
|
+
| **API Key** | Yes Required (`FMP_API_KEY`) |
|
|
599
|
+
| **Rate Limit (Free)** | 250 requests / day |
|
|
600
|
+
| **Best For** | EOD Price data (free), Deep fundamental history (paid) |
|
|
601
|
+
|
|
602
|
+
> Warning **Important:** Financial statements (`get_financials`) are **locked behind FMP's paid plan** and will return a `403 Forbidden` error with a free API key. `get_info()` and `Price.get()` work on the free tier.
|
|
603
|
+
|
|
604
|
+
#### Price
|
|
605
|
+
|
|
606
|
+
```python
|
|
607
|
+
from alphadataforge.data.price import Price
|
|
608
|
+
|
|
609
|
+
# Basic EOD price
|
|
610
|
+
df = Price.get("AAPL", provider="fmp", start_date="2023-01-01")
|
|
611
|
+
|
|
612
|
+
# Dividend & split adjusted
|
|
613
|
+
df = Price.get("AAPL", provider="fmp", provider_params={"adjusted": True})
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Extra `provider_params` for FMP Price:**
|
|
617
|
+
|
|
618
|
+
| Param | Type | Default | Description |
|
|
619
|
+
|---|---|---|---|
|
|
620
|
+
| `adjusted` | `bool` | `False` | `False` = raw non-split-adjusted prices. `True` = fully dividend & split adjusted. |
|
|
621
|
+
|
|
622
|
+
**Under the hood (FMP):**
|
|
623
|
+
- **API Calls:** 1 call per fetch (regardless of `adjusted` flag).
|
|
624
|
+
- **Adjusted Prices:** If `adjusted=True`, we hit FMP's `dividend-adjusted` endpoint, which natively returns adjusted values for Open, High, Low, and Close. The resulting DataFrame will contain `Adj Open`, `Adj High`, `Adj Low`, and `Adj Close`.
|
|
625
|
+
|
|
626
|
+
#### Fundamentals (FMP)
|
|
627
|
+
|
|
628
|
+
| Method | `statement=` value | Free Tier | Notes |
|
|
629
|
+
|---|---|---|---|
|
|
630
|
+
| `get_info()` | — | ✅ Free | Returns company profile: `sector`, `industry`, `mktCap`, `beta`, `description`, `ceo`, etc. |
|
|
631
|
+
| `get_financials()` | `"income"` | ✅ Free | 5 periods returned on free tier |
|
|
632
|
+
| `get_financials()` | `"balance"` | ✅ Free | 5 periods returned on free tier |
|
|
633
|
+
| `get_financials()` | `"cashflow"` | ✅ Free | 5 periods returned on free tier |
|
|
634
|
+
| `get_financials()` | `"shares_float"` | ✅ Free | Returns `freeFloat`, `floatShares`, and `outstandingShares` snapshot |
|
|
635
|
+
|
|
636
|
+
```python
|
|
637
|
+
from alphadataforge.data.fundamental import Fundamentals
|
|
638
|
+
|
|
639
|
+
# Company profile (works on free tier)
|
|
640
|
+
info = Fundamentals.get_info("AAPL", provider="fmp")
|
|
641
|
+
print(info["sector"]) # "Technology"
|
|
642
|
+
print(info["ceo"]) # "Timothy D. Cook"
|
|
643
|
+
|
|
644
|
+
# Financial statements (PAID ONLY — will raise error on free key)
|
|
645
|
+
# df = Fundamentals.get_financials("AAPL", statement="income", provider="fmp")
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
### Provider 5: StockFit (`provider="stockfit"`)
|
|
653
|
+
|
|
654
|
+
| | |
|
|
655
|
+
|---|---|
|
|
656
|
+
| **API Key** | Yes Required (`STOCKFIT_API_KEY`) |
|
|
657
|
+
| **Rate Limit** | 500 requests / minute, 500,000 / day (Pro Plan) |
|
|
658
|
+
| **Data Source** | Direct SEC EDGAR XBRL filings with point-in-time audit lineage |
|
|
659
|
+
| **Best For** | Deep historical US financial statements (20 years), Piotroski Health Scores, Offline Snapshots |
|
|
660
|
+
|
|
661
|
+
#### Financial Statements (20-Year SEC XBRL)
|
|
662
|
+
|
|
663
|
+
StockFit parses official 10-K and 10-Q filings directly, providing up to 20 years of annual statements and 70+ quarters:
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
from alphadataforge.data.fundamental import Fundamentals
|
|
667
|
+
|
|
668
|
+
# 1. Income Statement (20 years)
|
|
669
|
+
df_income = Fundamentals.get_financials("AAPL", statement="income", period="annual", provider="stockfit", limit=100)
|
|
670
|
+
|
|
671
|
+
# 2. Balance Sheet (70+ quarters)
|
|
672
|
+
df_balance = Fundamentals.get_financials("AAPL", statement="balance", period="quarterly", provider="stockfit", limit=100)
|
|
673
|
+
|
|
674
|
+
# 3. Cash Flow
|
|
675
|
+
df_cf = Fundamentals.get_financials("AAPL", statement="cashflow", period="annual", provider="stockfit")
|
|
676
|
+
|
|
677
|
+
# 4. Financial Health Scores (Piotroski F-Score)
|
|
678
|
+
scores = Fundamentals.get_scores("AAPL", provider="stockfit")
|
|
679
|
+
print("Piotroski F-Score:", scores["piotroskiFScore"])
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
#### Cross-Provider Accuracy Benchmark (AAPL)
|
|
683
|
+
|
|
684
|
+
We tested and compared StockFit's normalized financials against YFinance and AlphaVantage:
|
|
685
|
+
|
|
686
|
+
| Metric (2025) | StockFit | YFinance | AlphaVantage | Alignment |
|
|
687
|
+
|---|---|---|---|---|
|
|
688
|
+
| `Total_Revenue` | $416,161,000,000 | $416,161,000,000 | $416,161,000,000 | **100% Identical** |
|
|
689
|
+
| `Net_Income` | $112,010,000,000 | $112,010,000,000 | $112,010,000,000 | **100% Identical** |
|
|
690
|
+
| `Operating_Income` | $133,050,000,000 | $133,050,000,000 | $133,050,000,000 | **100% Identical** |
|
|
691
|
+
| `Total_Assets` | $359,241,000,000 | $359,241,000,000 | $359,241,000,000 | **100% Identical** |
|
|
692
|
+
| `Free_Cash_Flow` | $98,767,000,000 | $98,767,000,000 | N/A | **100% Identical** |
|
|
693
|
+
| **History Depth** | **19–20 Years** | 4 Years | ~5 Years | **StockFit 5x Deeper** |
|
|
694
|
+
|
|
695
|
+
## Provider Comparison Table
|
|
696
|
+
|
|
697
|
+
### Price
|
|
698
|
+
|
|
699
|
+
| Feature | YFinance | AlphaVantage | Tiingo | FMP | Hybrid (AV+Tiingo) |
|
|
700
|
+
|---|---|---|---|---|---|
|
|
701
|
+
| API Key Required | No | Yes | Yes | Yes | Yes (both AV + Tiingo) |
|
|
702
|
+
| Rate Limit | Unlimited | 25/day | 500/hr | 250/day | 3 AV calls + 2 Tiingo calls |
|
|
703
|
+
| Adjusted Prices | Yes | Yes (3 calls) | Yes (native) | Yes (native) | Yes (local math engine) |
|
|
704
|
+
| Intraday | Yes (last 60d) | No | Paid only | No | No |
|
|
705
|
+
| Crypto | Yes (`BTC-USD`) | No | Yes (`BTCUSD`) | No | No |
|
|
706
|
+
| Forex | Yes (`EURUSD=X`) | No | No | No | No |
|
|
707
|
+
| Historical Depth | ~20yr | ~20yr | ~30yr | ~30yr | ~20yr |
|
|
708
|
+
| Batch Fetching | Yes (1 call) | ⚠️ 1 call/symbol | Yes (1 call) | ⚠️ 1 call/symbol | ⚠️ 1 call/symbol |
|
|
709
|
+
|
|
710
|
+
### Fundamentals
|
|
711
|
+
|
|
712
|
+
| Feature | YFinance | StockFit (SEC XBRL) | AlphaVantage | Tiingo | FMP |
|
|
713
|
+
|---|---|---|---|---|---|
|
|
714
|
+
| Historical Depth | 4yr (8 quarters) | **15–20yr (70+ quarters)** | ~5yr | None | Locked |
|
|
715
|
+
| Rate Limit | Unlimited | 500k/day (Pro) | 25/day | 500/hr | 250/day |
|
|
716
|
+
| `get_info()` | Yes Rich (80+ fields) | Yes (SEC details) | Yes Good | Yes Basic | Yes Good |
|
|
717
|
+
| `statement="income"` | Yes (4yr) | **Yes (20yr XBRL)** | Yes (5yr+) | No | No Paid |
|
|
718
|
+
| `statement="balance"` | Yes (4yr) | **Yes (20yr XBRL)** | Yes (5yr+) | No | No Paid |
|
|
719
|
+
| `statement="cashflow"` | Yes (4yr) | **Yes (20yr XBRL)** | Yes (5yr+) | No | No Paid |
|
|
720
|
+
| Health Scores | No | **Yes (Piotroski F-Score)** | No | No | No |
|
|
721
|
+
| SEC Traceability | No | **Yes (10-K/Q Accession #)** | No | No | No |
|
|
722
|
+
| `statement="shares_outstanding"` | Warning (in balance sheet) | Yes Historical | Yes Historical | No | No Paid |
|
|
723
|
+
| `statement="earnings"` | Warning (basic) | Yes | Yes + Analyst Est. | No | No Paid |
|
|
724
|
+
| `get_earnings_calendar()` | No | Yes | Yes | No | No |
|
|
725
|
+
| News | Yes | Yes | No | Yes (Best) | No |
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
## Roadmap
|
|
730
|
+
|
|
731
|
+
- [x] SQLite Caching (24h TTL, OS temp folder)
|
|
732
|
+
- [x] Concurrent batch fetching (ThreadPoolExecutor)
|
|
733
|
+
- [x] Auto Rate Limit protection (AlphaVantage)
|
|
734
|
+
- [x] Custom Exception hierarchy
|
|
735
|
+
- [x] Fundamentals Facade (Income, Balance, Cashflow, Earnings, Shares Outstanding)
|
|
736
|
+
- [x] Universal Adjusted Prices — all providers return full `Adj Open/High/Low/Close/Volume`
|
|
737
|
+
- [x] Local Back-Adjustment Math Engine (`finance_math.calculate_adjusted_prices`)
|
|
738
|
+
- [x] Hybrid Provider (`hybrid_av_tiingo`) — deepest history + accurate dividends/splits
|
|
739
|
+
- [x] `Price.compare()` — concurrent multi-provider comparison (MultiIndex DataFrame)
|
|
740
|
+
- [x] `Price.consensus()` — mean/median aggregation across providers
|
|
741
|
+
- [x] Custom external DataFrame injection (`custom_data` parameter)
|
|
742
|
+
- [x] StockFit Integration (20-year SEC XBRL Statements, Piotroski Scores, Snapshot Utility)
|
|
743
|
+
- [ ] Data Quality Checks (anomaly detection, missing data handling)
|
|
744
|
+
- [ ] Technical Indicators Module (RSI, MACD, Bollinger Bands)
|
|
745
|
+
- [ ] Streaming / real-time price feed
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Testing
|
|
750
|
+
|
|
751
|
+
This project utilizes `pytest-vcr` and `unittest.mock` to ensure robust and comprehensive test coverage without consuming actual API rate limits.
|
|
752
|
+
|
|
753
|
+
### VCR Cassettes (`pytest-vcr`)
|
|
754
|
+
VCR records real HTTP interactions and saves them as `.yaml` files (cassettes) within the `tests/cassettes/` directory.
|
|
755
|
+
|
|
756
|
+
- **Rate Limit Protection**: By replaying recorded responses, our test suite can execute reliably in CI/CD environments (e.g., GitHub Actions) without exhausting API provider quotas.
|
|
757
|
+
- **Security**: Sensitive query parameters, such as API keys, are automatically sanitized and replaced with placeholder strings (`DUMMY`) before being persisted to the cassettes.
|
|
758
|
+
- **Version Control**: The `.yaml` cassette files **must be committed** to version control. They act as the definitive mock data for automated CI pipelines.
|
|
759
|
+
|
|
760
|
+
### Generating New Cassettes
|
|
761
|
+
When adding new tests or modifying existing API requests, you will need to generate new cassettes:
|
|
762
|
+
1. Ensure your valid API keys are configured in the `.env` file.
|
|
763
|
+
2. Execute `pytest` locally. If a cassette for a specific test does not exist, VCR will automatically perform a real HTTP request and record the sanitized response.
|
|
764
|
+
3. Commit the newly generated `.yaml` files to Git.
|
|
765
|
+
|
|
766
|
+
### Running Tests & Checking Coverage
|
|
767
|
+
To run the test suite and see a detailed coverage report for all fetchers:
|
|
768
|
+
```bash
|
|
769
|
+
pytest --cov=src/alphadataforge --cov-report=term-missing
|
|
770
|
+
```
|