From 5d93119556932974175e39339a2284809fa3d02e Mon Sep 17 00:00:00 2001 From: Stefan Jansen Date: Sun, 9 Aug 2026 08:32:46 -0400 Subject: [PATCH] Resolve provider qualification blockers --- README.md | 6 +++++- docs/getting-started/provider-selection.md | 8 +++++--- docs/providers/cryptocompare.md | 9 +++++---- docs/providers/index.md | 4 +++- scripts/verify_provider_contract_runs.py | 6 +++++- src/ml4t/data/providers/alpaca.py | 6 ++++-- tests/test_alpaca_provider.py | 17 +++++++++++++++++ tests/test_provider_contract_verification.py | 11 ++++++++++- 8 files changed, 54 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0b279769..7ec8aeb8 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,11 @@ fred = FREDProvider().fetch_series("GDP", "2020-01-01", "2024-12-31") | Massive | US equities, options, futures, forex, crypto | | Finnhub | 70+ global exchanges | | OANDA | Forex broker data | -| CryptoCompare | Crypto market data; free account key required | +| CryptoCompare | Included adapter; not release-qualified for 0.1.0 | + +CryptoCompare account registration was unavailable during the 0.1.0 release review, so no live +contract evidence was obtained. The adapter remains available for evaluation, but CryptoCompare is +not part of the release-qualified provider set until its contract passes on a release commit. ## Specialized Modules diff --git a/docs/getting-started/provider-selection.md b/docs/getting-started/provider-selection.md index 35fc4e01..a62d3e90 100644 --- a/docs/getting-started/provider-selection.md +++ b/docs/getting-started/provider-selection.md @@ -2,6 +2,9 @@ Choose the right data provider for your needs with this decision flowchart. +CryptoCompare is included for evaluation but is not release-qualified for 0.1.0. Account +registration was unavailable during release validation, so no live contract evidence was obtained. + ## Quick Decision Flowchart ```mermaid @@ -17,7 +20,7 @@ flowchart TD %% Crypto Path CryptoChoice -->|No API key| CoinGecko[CoinGecko
Public API] - CryptoChoice -->|API key is acceptable| CryptoCompare[CryptoCompare
Free/Paid] + CryptoChoice -->|API key is acceptable| CryptoCompare[CryptoCompare
Unverified for 0.1.0] %% US Stocks Path USChoice -->|FREE only| USFree{Call volume?} @@ -64,7 +67,7 @@ flowchart TD | Provider | Crypto | US Stocks | Global Stocks | Forex | Futures | API Key | Free Tier | Best For | |----------|--------|-----------|---------------|-------|---------|---------|-----------|----------| | **CoinGecko** | ✅ | ❌ | ❌ | ❌ | ❌ | No | Unlimited | Crypto historical | -| **CryptoCompare** | ✅ | ❌ | ❌ | ❌ | ❌ | Yes | Good | Crypto real-time | +| **CryptoCompare** | ✅ | ❌ | ❌ | ❌ | ❌ | Yes | Unverified | Evaluation only | | **Tiingo** | ✅ | ✅ | ❌ | ❌ | ❌ | Yes | 1000/day | High-quality stocks | | **EODHD** | ❌ | ✅ | ✅ | ❌ | ❌ | Yes | 20 calls/day, one-year history | Global stocks | | **Finnhub** | ✅ | ✅ | ✅ | ✅ | ❌ | Yes | US quotes; OHLC is paid | Quotes and company data | @@ -83,7 +86,6 @@ flowchart TD | **Tiingo** | 1000 calls | 500 symbols | Daily stock updates | | **EODHD** | 20 calls | Account limit | Global stock testing | | **Twelve Data** | 800 calls | ~24K calls | Multi-asset research | -| **CryptoCompare** | Varies | Varies | Crypto real-time | | **Massive Stocks Basic** | 5 calls/minute | Account limit | US aggregate data | #### Paid Tiers (Affordable) diff --git a/docs/providers/cryptocompare.md b/docs/providers/cryptocompare.md index 36e8b127..5234f2c7 100644 --- a/docs/providers/cryptocompare.md +++ b/docs/providers/cryptocompare.md @@ -3,13 +3,15 @@ **Provider**: `CryptoCompareProvider` **Website**: [cryptocompare.com](https://www.cryptocompare.com) **API Key**: Required -**Free Tier**: 250,000 calls/month +**0.1.0 status**: Included for evaluation; not release-qualified --- ## Overview -CryptoCompare provides comprehensive cryptocurrency historical data with good coverage and reasonable free tier. +CryptoCompare account registration was unavailable during the 0.1.0 release review. The adapter is +included for evaluation, but it has no successful live contract evidence for this release. Do not +treat it as a release-qualified provider until a later release records a successful contract run. **Best For**: Crypto historical data, alternative to Binance @@ -60,8 +62,7 @@ Get your API key at [cryptocompare.com/cryptopian/api-keys](https://www.cryptoco ## Rate Limits -- Free: 250,000 calls/month -- Paid: $19.99+/mo for higher limits +Consult CryptoCompare's current terms before use. Access and limits were not verified for 0.1.0. --- diff --git a/docs/providers/index.md b/docs/providers/index.md index 96e409cc..3885a27b 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -25,7 +25,7 @@ ML4T Data supports 20+ live and specialized data providers, plus synthetic and t | [Finnhub](finnhub.md) | US quotes; premium OHLCV | 60 requests/minute | Thread | Yes | | [Binance](binance.md) | Crypto | Unlimited | Native | No | | [OKX](okx.md) | Crypto Perpetuals | No geo-limits | Native | No | -| [CryptoCompare](cryptocompare.md) | Crypto | Good | Native | Required | +| [CryptoCompare](cryptocompare.md) | Crypto | Unverified for 0.1.0 | Native | Required | | [Oanda](oanda.md) | Forex | Demo only | Thread | Yes | ## Async Support @@ -83,6 +83,8 @@ provider's capabilities before passing it to `DataManager` or `async_batch_load( Provider access, coverage, retention, and redistribution terms can differ by account tier. Confirm the provider page and the provider's current terms before selecting it for a production dataset. +CryptoCompare is included for evaluation but is not release-qualified for 0.1.0 because account +registration was unavailable during release validation and no live contract evidence was obtained. ## Authentication diff --git a/scripts/verify_provider_contract_runs.py b/scripts/verify_provider_contract_runs.py index 2e972bdb..6d296b38 100644 --- a/scripts/verify_provider_contract_runs.py +++ b/scripts/verify_provider_contract_runs.py @@ -15,10 +15,12 @@ "tiingo", "massive", "finnhub", - "cryptocompare", "databento", "oanda", ) +UNVERIFIED_CONTRACTS = { + "cryptocompare": "account registration unavailable during 0.1.0 release validation", +} RUN_TITLE_PREFIX = "Provider Contract - " @@ -89,6 +91,8 @@ def main() -> None: ) for provider in REQUIRED_CONTRACTS: print(f"{provider}: {successful[provider]}") + for provider, reason in UNVERIFIED_CONTRACTS.items(): + print(f"{provider}: unverified - {reason}") if __name__ == "__main__": diff --git a/src/ml4t/data/providers/alpaca.py b/src/ml4t/data/providers/alpaca.py index c939ecc4..e535438d 100644 --- a/src/ml4t/data/providers/alpaca.py +++ b/src/ml4t/data/providers/alpaca.py @@ -214,10 +214,12 @@ def __init__( AuthenticationError: If either credential is missing. DataValidationError: If ``feed`` is not a supported value. """ - self.api_key = api_key or os.getenv("ALPACA_API_KEY") or os.getenv("APCA_API_KEY_ID") - self.api_secret = ( + resolved_api_key = api_key or os.getenv("ALPACA_API_KEY") or os.getenv("APCA_API_KEY_ID") + resolved_api_secret = ( api_secret or os.getenv("ALPACA_API_SECRET") or os.getenv("APCA_API_SECRET_KEY") ) + self.api_key = resolved_api_key.strip() if resolved_api_key else None + self.api_secret = resolved_api_secret.strip() if resolved_api_secret else None if not self.api_key or not self.api_secret: raise AuthenticationError( provider="alpaca", diff --git a/tests/test_alpaca_provider.py b/tests/test_alpaca_provider.py index 870fb633..e39dffd8 100644 --- a/tests/test_alpaca_provider.py +++ b/tests/test_alpaca_provider.py @@ -79,6 +79,23 @@ def test_init_with_env_keys(self): assert provider.api_key == "k" assert provider.api_secret == "s" + def test_credentials_strip_surrounding_whitespace(self): + """Copied credentials cannot introduce invalid HTTP header whitespace.""" + provider = AlpacaDataProvider(api_key=" k\n", api_secret="s\r\n") + + assert provider.api_key == "k" + assert provider.api_secret == "s" + assert provider._auth_headers == { + "APCA-API-KEY-ID": "k", + "APCA-API-SECRET-KEY": "s", + } + + @pytest.mark.parametrize("credential", ["", " ", "\r\n"]) + def test_whitespace_only_credentials_raise(self, credential): + """Credentials that normalize to empty are rejected locally.""" + with patch.dict("os.environ", {}, clear=True), pytest.raises(AuthenticationError): + AlpacaDataProvider(api_key=credential, api_secret="s") + def test_init_with_apca_env_fallback(self): """Alpaca SDK/CLI env names are honored as a fallback.""" with patch.dict( diff --git a/tests/test_provider_contract_verification.py b/tests/test_provider_contract_verification.py index 89fdaec7..78cd88dd 100644 --- a/tests/test_provider_contract_verification.py +++ b/tests/test_provider_contract_verification.py @@ -4,7 +4,11 @@ import pytest -from scripts.verify_provider_contract_runs import REQUIRED_CONTRACTS, validate_contract_runs +from scripts.verify_provider_contract_runs import ( + REQUIRED_CONTRACTS, + UNVERIFIED_CONTRACTS, + validate_contract_runs, +) def _run(provider: str, commit: str, conclusion: str = "success") -> dict[str, str]: @@ -25,6 +29,11 @@ def test_all_required_contracts_must_pass_for_the_release_commit() -> None: assert tuple(successful) == REQUIRED_CONTRACTS +def test_unverified_cryptocompare_is_not_release_qualified() -> None: + assert set(UNVERIFIED_CONTRACTS) == {"cryptocompare"} + assert set(REQUIRED_CONTRACTS).isdisjoint(UNVERIFIED_CONTRACTS) + + def test_contracts_from_another_commit_do_not_satisfy_release_policy() -> None: commit = "a" * 40 runs = [_run(provider, "b" * 40) for provider in REQUIRED_CONTRACTS]