This is the documentation of the finding, testing, and fixing of a bug in the popular repo Yahooquery a python library that allows people to freely access financial information from yahoo finance.
When calling any quoteSummary-based property (such as asset_profile, earnings, financial_data, key_stats, etc.) via the yahooquery package, the returned values exhibit inconsistent response structures depending on the ticker and asset type:
- For many well-known tickers (e.g.
AAPL) these properties return a dictionary mapping a normalized ticker key to a nested dict with structured data (expected structure). - For many other tickers (e.g.
EAI) the same properties return a dict mapping the ticker to a plain string error message, e.g.{'EAI': 'No fundamentals data found for symbol: EAI'}(unexpected structure).
This systematic inconsistency affects 15 out of 17 quoteSummary properties and causes downstream code that expects consistent dict-of-dicts structures to fail or misinterpret responses across different asset classes.
This GitHub Actions run is a real-world example of someone expecting a parsable JSON response yet receiving an unexpected response from the API.
Old Response: {'EAI': 'No fundamentals data found for symbol: EAI'} New Response: { "error": { "code": 404, "type": "NotFoundError", "message": "No fundamentals data found for symbol: EAI", "symbol": "EAI" } }
- Create a virtual environment and install
yahooquery. - Run the following script to demonstrate inconsistent behavior across multiple quoteSummary properties:
import yahooquery as yq
# Test multiple quoteSummary properties
properties = ['asset_profile', 'earnings', 'financial_data', 'key_stats']
tickers = ["AAPL", "EAI"] # Major stock vs problematic ticker
for ticker in tickers:
print(f"\n{ticker} Results:")
ticker_obj = yq.Ticker(ticker)
for prop in properties:
result = getattr(ticker_obj, prop)
ticker_data = result.get(ticker, {})
if isinstance(ticker_data, str):
print(f" {prop}: STRING - {ticker_data}")
else:
print(f" {prop}: DICT with {len(ticker_data)} fields")- Expected (AAPL) β All properties return structured data:
# asset_profile
{'AAPL': {'address1': 'One Apple Park Way', 'industry': 'Consumer Electronics', ...}}
# earnings
{'AAPL': {'earningsChart': {...}, 'financialsChart': {...}}}
# financial_data
{'AAPL': {'currentPrice': 150.25, 'targetHighPrice': 200.0, ...}}- Unexpected (EAI) β Same properties return string error messages:
# asset_profile
{'EAI': 'No fundamentals data found for symbol: EAI'}
# earnings
{'EAI': 'No fundamentals data found for symbol: EAI'}
# financial_data
{'EAI': 'No fundamentals data found for symbol: EAI'}- The response shape changes between tickers which forces callers to implement brittle, repetitive checks (isinstance(..., dict) and whether the inner value is a dict vs string).
- This behavior is not clearly documented in the
yahooquerydocs, leading to developer confusion and silent failures.
- Any code that assumes quoteSummary properties return structured dicts (with keys like
longName,industry,currentPrice, etc.) will either raise exceptions or silently skip expected fields. - 88% of quoteSummary properties exhibit mixed behavior, making reliable multi-ticker applications nearly impossible.
- Tests and data pipelines that depend on consistent response structures across different asset classes will break unpredictably.
- Applications handling diverse portfolios (stocks + ETFs + commodities) face systematic reliability issues.
- Universal defensive wrapper for all quoteSummary properties:
def safe_quote_property(ticker_obj, property_name, ticker_symbol):
"""Safely extract quoteSummary property data with consistent error handling"""
try:
result = getattr(ticker_obj, property_name)
ticker_data = result.get(ticker_symbol.upper()) or result.get(ticker_symbol.lower())
if isinstance(ticker_data, dict):
return True, ticker_data # Success: structured data
else:
return False, str(ticker_data) # Failure: string error message
except Exception as e:
return False, f"Exception: {str(e)}"
# Usage for any quoteSummary property:
success, data = safe_quote_property(ticker_obj, 'asset_profile', 'AAPL')
success, data = safe_quote_property(ticker_obj, 'earnings', 'AAPL')
success, data = safe_quote_property(ticker_obj, 'financial_data', 'AAPL')- Batch property checker to validate ticker reliability:
def check_ticker_reliability(ticker_symbol, properties=None):
"""Check which quoteSummary properties work for a given ticker"""
if properties is None:
properties = ['asset_profile', 'earnings', 'financial_data', 'key_stats',
'price', 'summary_detail', 'major_holders']
ticker_obj = yq.Ticker(ticker_symbol)
working = []
failing = []
for prop in properties:
success, _ = safe_quote_property(ticker_obj, prop, ticker_symbol)
if success:
working.append(prop)
else:
failing.append(prop)
reliability = len(working) / len(properties) * 100
return {
'reliability_pct': reliability,
'working_properties': working,
'failing_properties': failing
}- Documentation: Update
yahooquerydocs to clearly state that all quoteSummary properties may return string error messages for certain tickers/asset types, with reliability varying by asset class. - API Normalization: Implement consistent error handling across all quoteSummary properties:
- Return standardized error objects instead of raw strings
- Provide consistent response structure regardless of ticker type
- Add reliability metadata to help developers understand data availability
- Asset Class Awareness: Implement proper asset class detection and appropriate error handling for each type (stocks vs ETFs vs commodities vs forex).
- Reproduced on Windows with Python 3.12 and
yahooquery(version used for test not recorded here β include when filing upstream bug). - Comprehensive testing conducted with
comprehensive_quote_summary_test.pycovering 73 tickers across 17 quoteSummary properties. - Issue affects the entire quoteSummary ecosystem, not just individual properties.
- 1,241 individual API tests demonstrate systematic inconsistencies across asset classes.
An extensive test was conducted using comprehensive_quote_summary_test.py with 73 diverse tickers spanning the entire NYSE range (A-Z) to evaluate the scope and unpredictability of response inconsistencies across all quoteSummary-based properties.
- Total properties tested: 17 quoteSummary properties
- Total tickers tested: 73 (massively expanded coverage)
- Total individual API tests: 1,241
- Execution time: 119.28 seconds (10.4 tests/second)
- Coverage: Full alphabetical range A-Z across multiple asset classes and market caps
Properties with string responses: 0 (0%) π
Properties with consistent behavior: 2 (12%) β
Properties with mixed behavior: 15 (88%)
The bug exhibits highly unpredictable patterns across different asset types:
| Asset Category | String Response Rate | Predictability |
|---|---|---|
| Major Tech Stocks | 0.0% | β Highly Reliable |
| Financial Stocks | 0.0% | β Highly Reliable |
| Energy & Resources | 0.0% | β Highly Reliable |
| Consumer & Retail | 0.0% | β Highly Reliable |
| Healthcare & Pharma | 0.0% | β Highly Reliable |
| Industrial & Defense | 0.0% | β Highly Reliable |
| Technology Services | 0.0% | β Highly Reliable |
| International/ADR | 4.7% | |
| OTC/Penny Stocks | 11.8% | |
| Small/Micro Cap | 13.2% | |
| ETFs | 52.9% | β Highly Unreliable |
| Market Indices | 70.6% | β Very Unreliable |
| Forex Pairs | 76.5% | β Extremely Unreliable |
| Commodities | 76.5% | β Extremely Unreliable |
Most Problematic Tickers (Highest string response rates):
ACRX(88.2%) - Small pharmaceutical stockEURUSD=X(76.5%) - EUR/USD forex pairGBPUSD=X(76.5%) - GBP/USD forex pairGC=F(76.5%) - Gold futuresCL=F(76.5%) - Crude oil futures
Most Reliable Tickers (0% string response rates):
- All major stocks:
AAPL,MSFT,GOOGL,TSLA,NVDA,META,AMZN - All financial stocks:
JPM,BAC,GS,MS,WFC - Most consumer/retail:
KO,PG,WMT,NKE,HD
Almost all properties exhibit unpredictable mixed behavior:
Mixed Behavior Properties:
asset_profile- Works for stocks, fails for forex/commoditiescalendar_events- Inconsistent across asset typesearnings- Works for stocks, mixed for othersearnings_trend- Asset-type dependentesg_scores- Highly variable by ticker typefinancial_data- Mixed success ratesindex_trend- Inconsistent patternsindustry_trend- Asset-class dependentkey_stats- Variable reliabilitymajor_holders- Mixed behavior patternspage_views- Unpredictable responsesprice- Inconsistent across asset typesshare_purchase_activity- Mixed reliabilitysummary_detail- Variable behaviorsummary_profile- Asset-dependent responses
Only 2 Properties with Consistent Behavior:
- β
quote_type- Reliable across all asset types - β
technical_insights- Consistent response structure
-
Asset Type Discrimination: The API behaves completely differently depending on asset class
- Traditional stocks (0% failures) vs. Forex/Commodities (76%+ failures)
- This creates a massive reliability gap across different financial instruments
-
Systematic Unpredictability:
- 88% of properties have mixed behavior that varies by ticker type
- Developers cannot predict which calls will return strings vs. structured data
- The same property can be reliable for one asset class and completely broken for another
-
Scale of the Problem:
- Out of 1,241 individual tests, hundreds exhibit inconsistent behavior
- The bug affects 15 out of 17 properties to varying degrees
- No property (except 2) provides truly reliable cross-asset behavior
-
Production Impact:
- Any application dealing with diverse asset types will experience failures
- ETF analysis tools face 53% unreliability
- Forex/commodities applications face 76%+ failure rates
- Multi-asset portfolio tools cannot rely on consistent data structures
The inconsistency patterns are highly unpredictable and vary dramatically between stock tickers, creating a reliability minefield for developers:
All major US stocks like AAPL, MSFT, JNJ, JPM, PG, KO, WMT, DIS, HD work flawlessly across all 17 properties:
# AAPL (100% Reliable) - All properties return structured data
ticker_obj = yq.Ticker("AAPL")
result = ticker_obj.asset_profile
# {'AAPL': {'address1': 'One Apple Park Way', 'city': 'Cupertino', ...}}
result = ticker_obj.earnings
# {'AAPL': {'earningsChart': {...}, 'financialsChart': {...}}}
result = ticker_obj.key_stats
# {'AAPL': {'enterpriseValue': 3400000000000, 'forwardPE': 28.5, ...}}BTCS Inc (Small blockchain company) - 16/17 properties work, 1 fails:
# BTCS: 94.1% reliable - Only ESG scores fail
ticker_obj = yq.Ticker("BTCS")
# β
WORKS: Returns rich structured data
result = ticker_obj.asset_profile
# {'BTCS': {'address1': '1430 Broadway', 'city': 'New York', ...}}
result = ticker_obj.financial_data
# {'BTCS': {'currentPrice': 2.85, 'targetHighPrice': 10.0, ...}}
# β FAILS: Returns string error message
result = ticker_obj.esg_scores
# {'BTCS': 'No fundamentals data found for symbol: BTCS'}ACRX (AcelRx Pharmaceuticals) - Only 2/17 properties work, 15 fail:
# ACRX: 11.8% reliable - Most properties fail
ticker_obj = yq.Ticker("ACRX")
# β FAILS: Most properties return string errors
result = ticker_obj.asset_profile
# {'ACRX': 'Quote not found for symbol: ACRX'}
result = ticker_obj.earnings
# {'ACRX': 'Quote not found for symbol: ACRX'}
# β
WORKS: Only 2 properties work
result = ticker_obj.quote_type
# {'ACRX': {'quoteType': 'EQUITY', 'symbol': 'ACRX', ...}}
result = ticker_obj.technical_insights
# {'ACRX': {'symbol': 'ACRX', 'upsell': {...}, ...}}| Stock Type | Example | Reliability Rate | Impact |
|---|---|---|---|
| Major US Stocks | AAPL, MSFT, JNJ | 100% | β Perfect |
| Small Growth Stock | BTCS | 94.1% | |
| Micro-Cap Stock | ACRX | 11.8% | β Mostly broken |
The Core Problem: The same API methods produce completely different data types (structured dicts vs error strings) depending on the ticker's market classification, company size, or data availability. This creates an impossible development environment where:
- You cannot predict which stocks will work with which properties
- The same property call might work for Apple but fail for a small biotech
- There's no pattern - some small caps work fine, others fail catastrophically
- Defensive coding is required for every single API call across every ticker type