fix
This commit is contained in:
parent
a0ab1a9b3e
commit
9347a419e4
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to the **TradingAgents** project will be documented in this file.
|
||||||
|
|
||||||
|
## [Unreleased] - 2026-01-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **API Key Verification**: Added `verify_google_key.py` script to isolate and verify Google API Key functionality for embeddings.
|
||||||
|
- **Environment Management**: Added `load_dotenv` to `cli/main.py` and `verify_google_key.py` to ensure `.env` variables are correctly loaded.
|
||||||
|
- **Start Script Enhancements**: Updated `start.sh` to check for `GOOGLE_API_KEY` existence and warn the user.
|
||||||
|
- **Debug Logging**: Added temporary debug logging (commented out) in `memory.py` for API key verification.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Embedding Model Error**: Fixed `BadRequestError` / `404 Not Found` when using Google (Gemini) provider by explicitly setting `text-embedding-004` and using the Google-compatible OpenAI endpoint (`generativelanguage.googleapis.com`).
|
||||||
|
- **Data Fetching Failure**: Resolved `RuntimeError: All vendor implementations failed for method 'get_fundamentals'` by implementing a fallback to `yfinance` in `tradingagents/dataflows/y_finance.py` and registering it in `interface.py`.
|
||||||
|
- **Report Saving Crash**: Fixed `TypeError: write() argument must be str, not list` in `cli/main.py` by converting structured list content to string before writing to files.
|
||||||
|
- **API Rate Limiting**: Added `max_retries` handling (exponential backoff) to both `ChatGoogleGenerativeAI` (10 retries) and `OpenAI` embedding client (5 retries) to robustly handle `429 RESOURCE_EXHAUSTED` errors.
|
||||||
|
- **Payload Size Error**: Implemented input truncation (max 9000 chars) in `memory.py`'s `get_embedding` method to prevent massive payloads from crashing the API.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **LLM Configuration**: Updated `tradingagents/default_config.py` and `cli/utils.py` to use valid Gemini model names (e.g., `gemini-1.5-flash`, `gemini-1.5-pro`) and `gemini-pro`.
|
||||||
|
- **Vendor Configuration**: Updated default `fundamental_data` vendor to "alpha_vantage, yfinance" to ensure fallback availability.
|
||||||
|
|
@ -789,11 +789,12 @@ def run_analysis():
|
||||||
def wrapper(section_name, content):
|
def wrapper(section_name, content):
|
||||||
func(section_name, content)
|
func(section_name, content)
|
||||||
if section_name in obj.report_sections and obj.report_sections[section_name] is not None:
|
if section_name in obj.report_sections and obj.report_sections[section_name] is not None:
|
||||||
content = obj.report_sections[section_name]
|
raw_content = obj.report_sections[section_name]
|
||||||
if content:
|
if raw_content:
|
||||||
|
content_str = extract_content_string(raw_content)
|
||||||
file_name = f"{section_name}.md"
|
file_name = f"{section_name}.md"
|
||||||
with open(report_dir / file_name, "w") as f:
|
with open(report_dir / file_name, "w") as f:
|
||||||
f.write(content)
|
f.write(content_str)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
message_buffer.add_message = save_message_decorator(message_buffer, "add_message")
|
message_buffer.add_message = save_message_decorator(message_buffer, "add_message")
|
||||||
|
|
|
||||||
17
cli/utils.py
17
cli/utils.py
|
|
@ -140,10 +140,11 @@ def select_shallow_thinking_agent(provider) -> str:
|
||||||
("Claude Sonnet 4 - High performance and excellent reasoning", "claude-sonnet-4-0"),
|
("Claude Sonnet 4 - High performance and excellent reasoning", "claude-sonnet-4-0"),
|
||||||
],
|
],
|
||||||
"google": [
|
"google": [
|
||||||
("Gemini 2.0 Flash-Lite - Cost efficiency and low latency", "gemini-2.0-flash-lite"),
|
("Gemini 2.5 Flash-Lite - Cost efficiency and low latency", "gemini-2.5-flash-lite"),
|
||||||
("Gemini 2.0 Flash - Next generation features, speed, and thinking", "gemini-2.0-flash"),
|
("Gemini 2.5 Flash - Next generation features, speed, and thinking", "gemini-2.5-flash"),
|
||||||
("Gemini 2.5 Flash - Adaptive thinking, cost efficiency", "gemini-2.5-flash-preview-05-20"),
|
("Gemini 3.0 Flash - Next generation features, speed, and thinking", "gemini-3-flash-preview"),
|
||||||
],
|
("Gemini 3.0 Pro - Adaptive thinking, cost efficiency", "gemini-3-pro-preview"),
|
||||||
|
("Gemini 2.5 Pro", "gemini-2.5-pro")],
|
||||||
"openrouter": [
|
"openrouter": [
|
||||||
("Meta: Llama 4 Scout", "meta-llama/llama-4-scout:free"),
|
("Meta: Llama 4 Scout", "meta-llama/llama-4-scout:free"),
|
||||||
("Meta: Llama 3.3 8B Instruct - A lightweight and ultra-fast variant of Llama 3.3 70B", "meta-llama/llama-3.3-8b-instruct:free"),
|
("Meta: Llama 3.3 8B Instruct - A lightweight and ultra-fast variant of Llama 3.3 70B", "meta-llama/llama-3.3-8b-instruct:free"),
|
||||||
|
|
@ -202,10 +203,10 @@ def select_deep_thinking_agent(provider) -> str:
|
||||||
("Claude Opus 4 - Most powerful Anthropic model", " claude-opus-4-0"),
|
("Claude Opus 4 - Most powerful Anthropic model", " claude-opus-4-0"),
|
||||||
],
|
],
|
||||||
"google": [
|
"google": [
|
||||||
("Gemini 2.0 Flash-Lite - Cost efficiency and low latency", "gemini-2.0-flash-lite"),
|
("Gemini 2.5 Flash - Next generation features, speed, and thinking", "gemini-2.5-flash"),
|
||||||
("Gemini 2.0 Flash - Next generation features, speed, and thinking", "gemini-2.0-flash"),
|
("Gemini 3.0 Flash - Next generation features, speed, and thinking", "gemini-3-flash-preview"),
|
||||||
("Gemini 2.5 Flash - Adaptive thinking, cost efficiency", "gemini-2.5-flash-preview-05-20"),
|
("Gemini 3.0 Pro - Adaptive thinking, cost efficiency", "gemini-3-pro-preview"),
|
||||||
("Gemini 2.5 Pro", "gemini-2.5-pro-preview-06-05"),
|
("Gemini 2.5 Pro", "gemini-2.5-pro"),
|
||||||
],
|
],
|
||||||
"openrouter": [
|
"openrouter": [
|
||||||
("DeepSeek V3 - a 685B-parameter, mixture-of-experts model", "deepseek/deepseek-chat-v3-0324:free"),
|
("DeepSeek V3 - a 685B-parameter, mixture-of-experts model", "deepseek/deepseek-chat-v3-0324:free"),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 1. Activate Virtual Environment
|
||||||
|
if [ -d ".venv" ]; then
|
||||||
|
source .venv/bin/activate
|
||||||
|
echo "✅ Virtual Environment (.venv) Activated"
|
||||||
|
else
|
||||||
|
echo "❌ Virtual Environment not found! Run 'uv venv --python 3.13' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Export API Keys (PLACEHOLDERS - PLEASE UPDATE)
|
||||||
|
# You can also load these from a .env file if preferred
|
||||||
|
if [ -f ".env" ]; then
|
||||||
|
export $(grep -v '^#' .env | xargs)
|
||||||
|
echo "✅ Loaded keys from .env"
|
||||||
|
else
|
||||||
|
echo "⚠️ No .env file found. Using default/exported keys."
|
||||||
|
# START: REPLACE WITH YOUR ACTUAL KEYS IF NOT USING .ENV
|
||||||
|
# export OPENAI_API_KEY="sk-your-key-here"
|
||||||
|
# export ALPHA_VANTAGE_API_KEY="your-key-here"
|
||||||
|
# export GOOGLE_API_KEY="your-key-here"
|
||||||
|
# END
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if keys are set
|
||||||
|
if [ -z "$OPENAI_API_KEY" ]; then
|
||||||
|
echo "⚠️ OPENAI_API_KEY is missing! Set it if using OpenAI."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GOOGLE_API_KEY" ]; then
|
||||||
|
echo "⚠️ GOOGLE_API_KEY is missing! Set it if using Gemini."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Start the Shadow Run (Daily Execution)
|
||||||
|
echo "🚀 Starting Shadow Run Daily Execution..."
|
||||||
|
python3 -m cli.main
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import chromadb
|
import chromadb
|
||||||
from chromadb.config import Settings
|
from chromadb.config import Settings
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
|
@ -5,17 +6,44 @@ from openai import OpenAI
|
||||||
|
|
||||||
class FinancialSituationMemory:
|
class FinancialSituationMemory:
|
||||||
def __init__(self, name, config):
|
def __init__(self, name, config):
|
||||||
if config["backend_url"] == "http://localhost:11434/v1":
|
if config.get("llm_provider") == "google":
|
||||||
|
self.embedding = "text-embedding-004"
|
||||||
|
|
||||||
|
google_api_key = os.getenv("GOOGLE_API_KEY")
|
||||||
|
if not google_api_key:
|
||||||
|
raise ValueError("❌ GOOGLE_API_KEY not found in environment. Please add it to your .env file or export it.")
|
||||||
|
|
||||||
|
# Use Google's OpenAI-compatible endpoint with retries
|
||||||
|
self.client = OpenAI(
|
||||||
|
api_key=google_api_key,
|
||||||
|
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
|
||||||
|
max_retries=5
|
||||||
|
)
|
||||||
|
elif config["backend_url"] == "http://localhost:11434/v1" or config.get("llm_provider") == "ollama":
|
||||||
self.embedding = "nomic-embed-text"
|
self.embedding = "nomic-embed-text"
|
||||||
|
self.client = OpenAI(base_url=config["backend_url"])
|
||||||
else:
|
else:
|
||||||
self.embedding = "text-embedding-3-small"
|
self.embedding = "text-embedding-3-small"
|
||||||
self.client = OpenAI(base_url=config["backend_url"])
|
self.client = OpenAI(base_url=config["backend_url"])
|
||||||
|
|
||||||
self.chroma_client = chromadb.Client(Settings(allow_reset=True))
|
self.chroma_client = chromadb.Client(Settings(allow_reset=True))
|
||||||
self.situation_collection = self.chroma_client.create_collection(name=name)
|
self.situation_collection = self.chroma_client.create_collection(name=name)
|
||||||
|
|
||||||
def get_embedding(self, text):
|
def get_embedding(self, text):
|
||||||
"""Get OpenAI embedding for a text"""
|
"""Get OpenAI embedding for a text"""
|
||||||
|
|
||||||
|
# DEBUG: Check API Key
|
||||||
|
if hasattr(self, 'client') and self.client.api_key:
|
||||||
|
masked_key = self.client.api_key[:4] + "..."
|
||||||
|
# print(f"DEBUG: Using API Key: {masked_key}")
|
||||||
|
|
||||||
|
# Truncate text if too long (Google's limit is ~2048 tokens / 8k chars, allow buffer)
|
||||||
|
# OpenAI text-embedding-3 is 8191 tokens (~32k chars)
|
||||||
|
# Use safe limit of 9000 chars
|
||||||
|
if len(text) > 9000:
|
||||||
|
# print(f"WARNING: Truncating text for embedding. Length {len(text)} > 9000")
|
||||||
|
text = text[:9000]
|
||||||
|
|
||||||
response = self.client.embeddings.create(
|
response = self.client.embeddings.create(
|
||||||
model=self.embedding, input=text
|
model=self.embedding, input=text
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from typing import Annotated
|
||||||
|
|
||||||
# Import from vendor-specific modules
|
# Import from vendor-specific modules
|
||||||
from .local import get_YFin_data, get_finnhub_news, get_finnhub_company_insider_sentiment, get_finnhub_company_insider_transactions, get_simfin_balance_sheet, get_simfin_cashflow, get_simfin_income_statements, get_reddit_global_news, get_reddit_company_news
|
from .local import get_YFin_data, get_finnhub_news, get_finnhub_company_insider_sentiment, get_finnhub_company_insider_transactions, get_simfin_balance_sheet, get_simfin_cashflow, get_simfin_income_statements, get_reddit_global_news, get_reddit_company_news
|
||||||
from .y_finance import get_YFin_data_online, get_stock_stats_indicators_window, 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 .y_finance import get_YFin_data_online, get_stock_stats_indicators_window, 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, get_fundamentals as get_fundamentals_yfinance
|
||||||
from .google import get_google_news
|
from .google import get_google_news
|
||||||
from .openai import get_stock_news_openai, get_global_news_openai, get_fundamentals_openai
|
from .openai import get_stock_news_openai, get_global_news_openai, get_fundamentals_openai
|
||||||
from .alpha_vantage import (
|
from .alpha_vantage import (
|
||||||
|
|
@ -78,6 +78,7 @@ VENDOR_METHODS = {
|
||||||
# fundamental_data
|
# fundamental_data
|
||||||
"get_fundamentals": {
|
"get_fundamentals": {
|
||||||
"alpha_vantage": get_alpha_vantage_fundamentals,
|
"alpha_vantage": get_alpha_vantage_fundamentals,
|
||||||
|
"yfinance": get_fundamentals_yfinance,
|
||||||
"openai": get_fundamentals_openai,
|
"openai": get_fundamentals_openai,
|
||||||
},
|
},
|
||||||
"get_balance_sheet": {
|
"get_balance_sheet": {
|
||||||
|
|
|
||||||
|
|
@ -405,3 +405,52 @@ def get_insider_transactions(
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error retrieving insider transactions for {ticker}: {str(e)}"
|
return f"Error retrieving insider transactions for {ticker}: {str(e)}"
|
||||||
|
def get_fundamentals(
|
||||||
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
|
curr_date: Annotated[str, "current date (not used for yfinance info)"] = None
|
||||||
|
):
|
||||||
|
"""Get fundamental data from yfinance ticker.info."""
|
||||||
|
try:
|
||||||
|
ticker_obj = yf.Ticker(ticker.upper())
|
||||||
|
info = ticker_obj.info
|
||||||
|
|
||||||
|
if not info:
|
||||||
|
return f"No fundamental data found for symbol '{ticker}'"
|
||||||
|
|
||||||
|
keys_of_interest = [
|
||||||
|
"shortName", "longName", "sector", "industry", "fullTimeEmployees",
|
||||||
|
"marketCap", "enterpriseValue", "totalRevenue", "profitMargins",
|
||||||
|
"floatShares", "sharesOutstanding", "impliedSharesOutstanding",
|
||||||
|
"bookValue", "priceToBook", "trailingEps", "forwardEps",
|
||||||
|
"pegRatio", "priceToSalesTrailing12Months", "forwardPE", "trailingPE",
|
||||||
|
"dividendRate", "dividendYield", "payoutRatio",
|
||||||
|
"beta", "52WeekChange", "SnP52WeekChange", "lastDividendValue", "lastDividendDate",
|
||||||
|
"currentPrice", "targetHighPrice", "targetLowPrice", "targetMeanPrice",
|
||||||
|
"recommendationMean", "recommendationKey", "numberOfAnalystOpinions",
|
||||||
|
"totalCash", "totalCashPerShare", "ebitda", "totalDebt",
|
||||||
|
"quickRatio", "currentRatio", "revenueGrowth", "debtToEquity",
|
||||||
|
"returnOnAssets", "returnOnEquity", "grossProfits", "freeCashflow",
|
||||||
|
"operatingCashflow", "earningsGrowth", "revenueGrowth", "grossMargins",
|
||||||
|
"ebitdaMargins", "operatingMargins", "auditRisk", "boardRisk",
|
||||||
|
"compensationRisk", "shareHolderRightsRisk", "overallRisk"
|
||||||
|
]
|
||||||
|
|
||||||
|
report_lines = []
|
||||||
|
report_lines.append(f"# Fundamental Data for {ticker.upper()}")
|
||||||
|
report_lines.append(f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} (Real-time/Latest)")
|
||||||
|
report_lines.append(f"# Note: YFinance 'info' provides current data, not historical.")
|
||||||
|
report_lines.append("")
|
||||||
|
|
||||||
|
for key in keys_of_interest:
|
||||||
|
if key in info and info[key] is not None:
|
||||||
|
report_lines.append(f"{key}: {info[key]}")
|
||||||
|
|
||||||
|
if "longBusinessSummary" in info:
|
||||||
|
report_lines.append("")
|
||||||
|
report_lines.append("## Business Summary")
|
||||||
|
report_lines.append(info["longBusinessSummary"])
|
||||||
|
|
||||||
|
return "\n".join(report_lines)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error retrieving fundamentals for {ticker}: {str(e)}"
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ DEFAULT_CONFIG = {
|
||||||
),
|
),
|
||||||
# LLM settings
|
# LLM settings
|
||||||
"llm_provider": "openai",
|
"llm_provider": "openai",
|
||||||
"deep_think_llm": "o4-mini",
|
"deep_think_llm": "gemini-pro",
|
||||||
"quick_think_llm": "gpt-4o-mini",
|
"quick_think_llm": "gemini-pro",
|
||||||
"backend_url": "https://api.openai.com/v1",
|
"backend_url": "https://api.openai.com/v1",
|
||||||
# Debate and discussion settings
|
# Debate and discussion settings
|
||||||
"max_debate_rounds": 1,
|
"max_debate_rounds": 1,
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,16 @@ class TradingAgentsGraph:
|
||||||
self.deep_thinking_llm = ChatAnthropic(model=self.config["deep_think_llm"], base_url=self.config["backend_url"])
|
self.deep_thinking_llm = ChatAnthropic(model=self.config["deep_think_llm"], base_url=self.config["backend_url"])
|
||||||
self.quick_thinking_llm = ChatAnthropic(model=self.config["quick_think_llm"], base_url=self.config["backend_url"])
|
self.quick_thinking_llm = ChatAnthropic(model=self.config["quick_think_llm"], base_url=self.config["backend_url"])
|
||||||
elif self.config["llm_provider"].lower() == "google":
|
elif self.config["llm_provider"].lower() == "google":
|
||||||
self.deep_thinking_llm = ChatGoogleGenerativeAI(model=self.config["deep_think_llm"])
|
self.deep_thinking_llm = ChatGoogleGenerativeAI(
|
||||||
self.quick_thinking_llm = ChatGoogleGenerativeAI(model=self.config["quick_think_llm"])
|
model=self.config["deep_think_llm"],
|
||||||
|
max_retries=10,
|
||||||
|
request_timeout=60
|
||||||
|
)
|
||||||
|
self.quick_thinking_llm = ChatGoogleGenerativeAI(
|
||||||
|
model=self.config["quick_think_llm"],
|
||||||
|
max_retries=10,
|
||||||
|
request_timeout=60
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported LLM provider: {self.config['llm_provider']}")
|
raise ValueError(f"Unsupported LLM provider: {self.config['llm_provider']}")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue