确认PokieTicker新闻库数据源
This commit is contained in:
@@ -30,6 +30,25 @@ logger = logging.getLogger(__name__)
|
||||
_DATA_DIR = Path(__file__).parent / "ret_data"
|
||||
|
||||
|
||||
def _format_provider_error(exc: Exception) -> str:
|
||||
"""Condense common provider failures into short, readable messages."""
|
||||
message = str(exc).strip().replace("\n", " ")
|
||||
if "429" in message:
|
||||
return "rate limit reached"
|
||||
if "402" in message:
|
||||
return "insufficient credits"
|
||||
if "422" in message or "Missing parameters" in message:
|
||||
return "invalid request parameters"
|
||||
if "Quote not found" in message:
|
||||
return "quote not found"
|
||||
return message
|
||||
|
||||
|
||||
def _has_valid_ticker(ticker: str) -> bool:
|
||||
"""Return whether the normalized ticker is non-empty."""
|
||||
return bool((ticker or "").strip())
|
||||
|
||||
|
||||
class DataProviderRouter:
|
||||
"""Route data requests across configured providers with fallbacks."""
|
||||
|
||||
@@ -56,6 +75,8 @@ class DataProviderRouter:
|
||||
end_date: str,
|
||||
) -> tuple[list[Price], DataSource]:
|
||||
"""Fetch prices using preferred providers with fallback."""
|
||||
if not _has_valid_ticker(ticker):
|
||||
return [], "local_csv"
|
||||
last_error: Optional[Exception] = None
|
||||
|
||||
for source in self.price_sources():
|
||||
@@ -78,7 +99,12 @@ class DataProviderRouter:
|
||||
return prices, source
|
||||
except Exception as exc:
|
||||
last_error = exc
|
||||
logger.warning("Price source %s failed for %s: %s", source, ticker, exc)
|
||||
logger.warning(
|
||||
"Price source %s failed for %s: %s",
|
||||
source,
|
||||
ticker,
|
||||
_format_provider_error(exc),
|
||||
)
|
||||
|
||||
if last_error:
|
||||
raise last_error
|
||||
@@ -92,6 +118,8 @@ class DataProviderRouter:
|
||||
limit: int = 10,
|
||||
) -> tuple[list[FinancialMetrics], DataSource]:
|
||||
"""Fetch financial metrics with API provider fallback."""
|
||||
if not _has_valid_ticker(ticker):
|
||||
return [], "local_csv"
|
||||
last_error: Optional[Exception] = None
|
||||
|
||||
for source in self.api_sources():
|
||||
@@ -126,7 +154,7 @@ class DataProviderRouter:
|
||||
"Financial metrics source %s failed for %s: %s",
|
||||
source,
|
||||
ticker,
|
||||
exc,
|
||||
_format_provider_error(exc),
|
||||
)
|
||||
|
||||
if last_error:
|
||||
@@ -142,6 +170,8 @@ class DataProviderRouter:
|
||||
limit: int = 10,
|
||||
) -> list[LineItem]:
|
||||
"""Line items are only supported via Financial Datasets."""
|
||||
if not _has_valid_ticker(ticker):
|
||||
return []
|
||||
if "financial_datasets" not in self.api_sources():
|
||||
return []
|
||||
try:
|
||||
@@ -155,7 +185,11 @@ class DataProviderRouter:
|
||||
self._record_success("line_items", "financial_datasets")
|
||||
return results
|
||||
except Exception as exc:
|
||||
logger.warning("Line items source failed for %s: %s", ticker, exc)
|
||||
logger.warning(
|
||||
"Line items source failed for %s: %s",
|
||||
ticker,
|
||||
_format_provider_error(exc),
|
||||
)
|
||||
return []
|
||||
|
||||
def get_insider_trades(
|
||||
@@ -166,6 +200,8 @@ class DataProviderRouter:
|
||||
limit: int = 1000,
|
||||
) -> tuple[list[InsiderTrade], DataSource]:
|
||||
"""Fetch insider trades with provider fallback."""
|
||||
if not _has_valid_ticker(ticker):
|
||||
return [], "local_csv"
|
||||
last_error: Optional[Exception] = None
|
||||
|
||||
for source in self.api_sources():
|
||||
@@ -193,7 +229,7 @@ class DataProviderRouter:
|
||||
"Insider trades source %s failed for %s: %s",
|
||||
source,
|
||||
ticker,
|
||||
exc,
|
||||
_format_provider_error(exc),
|
||||
)
|
||||
|
||||
if last_error:
|
||||
@@ -208,6 +244,8 @@ class DataProviderRouter:
|
||||
limit: int = 1000,
|
||||
) -> tuple[list[CompanyNews], DataSource]:
|
||||
"""Fetch company news with provider fallback."""
|
||||
if not _has_valid_ticker(ticker):
|
||||
return [], "local_csv"
|
||||
last_error: Optional[Exception] = None
|
||||
|
||||
for source in self.api_sources():
|
||||
@@ -244,7 +282,7 @@ class DataProviderRouter:
|
||||
"Company news source %s failed for %s: %s",
|
||||
source,
|
||||
ticker,
|
||||
exc,
|
||||
_format_provider_error(exc),
|
||||
)
|
||||
|
||||
if last_error:
|
||||
@@ -258,6 +296,8 @@ class DataProviderRouter:
|
||||
metrics_lookup,
|
||||
) -> tuple[Optional[float], DataSource]:
|
||||
"""Fetch market cap using facts API or financial metrics fallback."""
|
||||
if not _has_valid_ticker(ticker):
|
||||
return None, "local_csv"
|
||||
today = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
if end_date == today and "financial_datasets" in self.api_sources():
|
||||
try:
|
||||
@@ -267,7 +307,7 @@ class DataProviderRouter:
|
||||
logger.warning(
|
||||
"Market cap facts source failed for %s: %s",
|
||||
ticker,
|
||||
exc,
|
||||
_format_provider_error(exc),
|
||||
)
|
||||
|
||||
metrics, source = metrics_lookup(ticker, end_date)
|
||||
|
||||
Reference in New Issue
Block a user