280 lines
9.6 KiB
Python
280 lines
9.6 KiB
Python
import time
|
|
|
|
# Import from vendor-specific modules
|
|
from .y_finance import (
|
|
get_YFin_data_online,
|
|
get_stock_stats_indicators_window,
|
|
get_fundamentals as get_yfinance_fundamentals,
|
|
get_balance_sheet as get_yfinance_balance_sheet,
|
|
get_cashflow as get_yfinance_cashflow,
|
|
get_income_statement as get_yfinance_income_statement,
|
|
get_insider_transactions as get_yfinance_insider_transactions,
|
|
)
|
|
from .yfinance_news import get_news_yfinance, get_global_news_yfinance
|
|
from .yfinance_scanner import (
|
|
get_market_movers_yfinance,
|
|
get_gap_candidates_yfinance,
|
|
get_market_indices_yfinance,
|
|
get_sector_performance_yfinance,
|
|
get_industry_performance_yfinance,
|
|
get_topic_news_yfinance,
|
|
)
|
|
from .alpha_vantage_stock import get_stock as get_alpha_vantage_stock
|
|
from .alpha_vantage_indicator import get_indicator as get_alpha_vantage_indicator
|
|
from .alpha_vantage_fundamentals import (
|
|
get_fundamentals as get_alpha_vantage_fundamentals,
|
|
get_balance_sheet as get_alpha_vantage_balance_sheet,
|
|
get_cashflow as get_alpha_vantage_cashflow,
|
|
get_income_statement as get_alpha_vantage_income_statement,
|
|
)
|
|
from .alpha_vantage_news import (
|
|
get_insider_transactions as get_alpha_vantage_insider_transactions,
|
|
get_news as get_alpha_vantage_news,
|
|
get_global_news as get_alpha_vantage_global_news,
|
|
)
|
|
from .alpha_vantage_scanner import (
|
|
get_market_movers_alpha_vantage,
|
|
get_market_indices_alpha_vantage,
|
|
get_sector_performance_alpha_vantage,
|
|
get_industry_performance_alpha_vantage,
|
|
get_topic_news_alpha_vantage,
|
|
)
|
|
from .alpha_vantage_common import AlphaVantageError, AlphaVantageRateLimitError, RateLimitError
|
|
from .finnhub_common import FinnhubError
|
|
from .stockstats_utils import YFinanceError
|
|
from .finnhub_news import get_insider_transactions as get_finnhub_insider_transactions
|
|
from .finnhub_scanner import (
|
|
get_market_indices_finnhub,
|
|
get_sector_performance_finnhub,
|
|
get_topic_news_finnhub,
|
|
get_earnings_calendar_finnhub,
|
|
get_economic_calendar_finnhub,
|
|
)
|
|
|
|
# Configuration and routing logic
|
|
from .config import get_config
|
|
|
|
# Tools organized by category
|
|
TOOLS_CATEGORIES = {
|
|
"core_stock_apis": {
|
|
"description": "OHLCV stock price data",
|
|
"tools": [
|
|
"get_stock_data"
|
|
]
|
|
},
|
|
"technical_indicators": {
|
|
"description": "Technical analysis indicators",
|
|
"tools": [
|
|
"get_indicators"
|
|
]
|
|
},
|
|
"fundamental_data": {
|
|
"description": "Company fundamentals",
|
|
"tools": [
|
|
"get_fundamentals",
|
|
"get_balance_sheet",
|
|
"get_cashflow",
|
|
"get_income_statement",
|
|
"get_ttm_analysis",
|
|
]
|
|
},
|
|
"news_data": {
|
|
"description": "News and insider data",
|
|
"tools": [
|
|
"get_news",
|
|
"get_global_news",
|
|
"get_insider_transactions",
|
|
]
|
|
},
|
|
"scanner_data": {
|
|
"description": "Market-wide scanner data (movers, indices, sectors, industries)",
|
|
"tools": [
|
|
"get_market_movers",
|
|
"get_gap_candidates",
|
|
"get_market_indices",
|
|
"get_sector_performance",
|
|
"get_industry_performance",
|
|
"get_topic_news",
|
|
]
|
|
},
|
|
"calendar_data": {
|
|
"description": "Earnings and economic event calendars",
|
|
"tools": [
|
|
"get_earnings_calendar",
|
|
"get_economic_calendar",
|
|
]
|
|
},
|
|
}
|
|
|
|
VENDOR_LIST = [
|
|
"yfinance",
|
|
"alpha_vantage",
|
|
"finnhub",
|
|
]
|
|
|
|
# Methods where cross-vendor fallback is safe (data contracts are fungible).
|
|
# All other methods fail-fast on primary vendor failure — see ADR 011.
|
|
FALLBACK_ALLOWED = {
|
|
"get_stock_data", # OHLCV is fungible across vendors
|
|
"get_market_indices", # SPY/DIA/QQQ quotes are fungible
|
|
"get_sector_performance", # ETF-based proxy, same approach
|
|
"get_market_movers", # Approximation acceptable for screening
|
|
"get_gap_candidates", # Gap math from market data is fungible enough
|
|
"get_industry_performance", # ETF-based proxy
|
|
}
|
|
|
|
# Mapping of methods to their vendor-specific implementations
|
|
VENDOR_METHODS = {
|
|
# core_stock_apis
|
|
"get_stock_data": {
|
|
"alpha_vantage": get_alpha_vantage_stock,
|
|
"yfinance": get_YFin_data_online,
|
|
},
|
|
# technical_indicators
|
|
"get_indicators": {
|
|
"alpha_vantage": get_alpha_vantage_indicator,
|
|
"yfinance": get_stock_stats_indicators_window,
|
|
},
|
|
# fundamental_data
|
|
"get_fundamentals": {
|
|
"alpha_vantage": get_alpha_vantage_fundamentals,
|
|
"yfinance": get_yfinance_fundamentals,
|
|
},
|
|
"get_balance_sheet": {
|
|
"alpha_vantage": get_alpha_vantage_balance_sheet,
|
|
"yfinance": get_yfinance_balance_sheet,
|
|
},
|
|
"get_cashflow": {
|
|
"alpha_vantage": get_alpha_vantage_cashflow,
|
|
"yfinance": get_yfinance_cashflow,
|
|
},
|
|
"get_income_statement": {
|
|
"alpha_vantage": get_alpha_vantage_income_statement,
|
|
"yfinance": get_yfinance_income_statement,
|
|
},
|
|
# news_data
|
|
"get_news": {
|
|
"alpha_vantage": get_alpha_vantage_news,
|
|
"yfinance": get_news_yfinance,
|
|
},
|
|
"get_global_news": {
|
|
"yfinance": get_global_news_yfinance,
|
|
"alpha_vantage": get_alpha_vantage_global_news,
|
|
},
|
|
"get_insider_transactions": {
|
|
"finnhub": get_finnhub_insider_transactions,
|
|
"alpha_vantage": get_alpha_vantage_insider_transactions,
|
|
"yfinance": get_yfinance_insider_transactions,
|
|
},
|
|
# scanner_data
|
|
"get_market_movers": {
|
|
"yfinance": get_market_movers_yfinance,
|
|
"alpha_vantage": get_market_movers_alpha_vantage,
|
|
},
|
|
"get_gap_candidates": {
|
|
"yfinance": get_gap_candidates_yfinance,
|
|
},
|
|
"get_market_indices": {
|
|
"finnhub": get_market_indices_finnhub,
|
|
"alpha_vantage": get_market_indices_alpha_vantage,
|
|
"yfinance": get_market_indices_yfinance,
|
|
},
|
|
"get_sector_performance": {
|
|
"finnhub": get_sector_performance_finnhub,
|
|
"alpha_vantage": get_sector_performance_alpha_vantage,
|
|
"yfinance": get_sector_performance_yfinance,
|
|
},
|
|
"get_industry_performance": {
|
|
"alpha_vantage": get_industry_performance_alpha_vantage,
|
|
"yfinance": get_industry_performance_yfinance,
|
|
},
|
|
"get_topic_news": {
|
|
"finnhub": get_topic_news_finnhub,
|
|
"alpha_vantage": get_topic_news_alpha_vantage,
|
|
"yfinance": get_topic_news_yfinance,
|
|
},
|
|
# calendar_data — Finnhub only (unique capabilities)
|
|
"get_earnings_calendar": {
|
|
"finnhub": get_earnings_calendar_finnhub,
|
|
},
|
|
"get_economic_calendar": {
|
|
"finnhub": get_economic_calendar_finnhub,
|
|
},
|
|
}
|
|
|
|
def get_category_for_method(method: str) -> str:
|
|
"""Get the category that contains the specified method."""
|
|
for category, info in TOOLS_CATEGORIES.items():
|
|
if method in info["tools"]:
|
|
return category
|
|
raise ValueError(f"Method '{method}' not found in any category")
|
|
|
|
def get_vendor(category: str, method: str = None) -> str:
|
|
"""Get the configured vendor for a data category or specific tool method.
|
|
Tool-level configuration takes precedence over category-level.
|
|
"""
|
|
config = get_config()
|
|
|
|
# Check tool-level configuration first (if method provided)
|
|
if method:
|
|
tool_vendors = config.get("tool_vendors", {})
|
|
if method in tool_vendors:
|
|
return tool_vendors[method]
|
|
|
|
# Fall back to category-level configuration
|
|
return config.get("data_vendors", {}).get(category, "default")
|
|
|
|
def route_to_vendor(method: str, *args, **kwargs):
|
|
"""Route method calls to appropriate vendor implementation with fallback support.
|
|
|
|
Only methods in FALLBACK_ALLOWED get cross-vendor fallback.
|
|
All others fail-fast on primary vendor failure (see ADR 011).
|
|
"""
|
|
category = get_category_for_method(method)
|
|
vendor_config = get_vendor(category, method)
|
|
primary_vendors = [v.strip() for v in vendor_config.split(',')]
|
|
|
|
if method not in VENDOR_METHODS:
|
|
raise ValueError(f"Method '{method}' not supported")
|
|
|
|
if method in FALLBACK_ALLOWED:
|
|
# Build fallback chain: primary vendors first, then remaining available vendors
|
|
all_available_vendors = list(VENDOR_METHODS[method].keys())
|
|
vendors_to_try = primary_vendors.copy()
|
|
for vendor in all_available_vendors:
|
|
if vendor not in vendors_to_try:
|
|
vendors_to_try.append(vendor)
|
|
else:
|
|
# Fail-fast: only try configured primary vendor(s)
|
|
vendors_to_try = primary_vendors
|
|
|
|
from tradingagents.observability import get_run_logger
|
|
|
|
rl = get_run_logger()
|
|
args_summary = ", ".join(str(a)[:80] for a in args) if args else ""
|
|
|
|
last_error = None
|
|
tried = []
|
|
for vendor in vendors_to_try:
|
|
if vendor not in VENDOR_METHODS[method]:
|
|
continue
|
|
tried.append(vendor)
|
|
|
|
vendor_impl = VENDOR_METHODS[method][vendor]
|
|
impl_func = vendor_impl[0] if isinstance(vendor_impl, list) else vendor_impl
|
|
|
|
t0 = time.time()
|
|
try:
|
|
result = impl_func(*args, **kwargs)
|
|
if rl:
|
|
rl.log_vendor_call(method, vendor, True, (time.time() - t0) * 1000, args_summary=args_summary)
|
|
return result
|
|
except (AlphaVantageError, FinnhubError, YFinanceError, ConnectionError, TimeoutError) as exc:
|
|
if rl:
|
|
rl.log_vendor_call(method, vendor, False, (time.time() - t0) * 1000, error=str(exc)[:200], args_summary=args_summary)
|
|
last_error = exc
|
|
continue
|
|
|
|
error_msg = f"All vendors failed for '{method}' (tried: {', '.join(tried)})"
|
|
raise RuntimeError(error_msg) from last_error
|