added crypto

This commit is contained in:
Marvin Gabler 2025-10-21 16:11:00 +02:00
parent f26d168239
commit ffc2e92c41
22 changed files with 1177 additions and 123 deletions

216
CONFIG_GUIDE.md Normal file
View File

@ -0,0 +1,216 @@
# Configuration Guide
TradingAgents supports configuration through multiple methods with the following priority:
**Priority (highest to lowest):**
1. Environment variables
2. `config.ini` file
3. Built-in defaults
## Quick Start
### Option 1: Using config.ini (Recommended)
1. Copy the example configuration:
```bash
cp config.example.ini config.ini
```
2. Edit `config.ini` with your preferences:
```ini
[llm]
provider = openai
deep_think_llm = o1-mini
quick_think_llm = gpt-4o-mini
[analysis]
research_depth = 1
default_analysts = market,news,social
[data]
global_news_limit = 15
```
3. Run the CLI - it will use your defaults and skip prompting for configured values:
```bash
uv run python -m cli.main
```
### Option 2: Using Environment Variables
Set environment variables in your shell or `.env` file:
```bash
export LLM_PROVIDER=openai
export DEEP_THINK_LLM=o1-mini
export QUICK_THINK_LLM=gpt-4o-mini
export RESEARCH_DEPTH=1
export GLOBAL_NEWS_LIMIT=20
```
## Configuration Options
### LLM Settings
| Config Key | Env Variable | Description | Default |
|------------|--------------|-------------|---------|
| `provider` | `LLM_PROVIDER` | LLM provider (openai, anthropic, google, ollama) | `openai` |
| `backend_url` | `LLM_BACKEND_URL` | API endpoint URL | `https://api.openai.com/v1` |
| `deep_think_llm` | `DEEP_THINK_LLM` | Model for complex analysis (gpt-5, o1, o3, etc.) | `o1-mini` |
| `quick_think_llm` | `QUICK_THINK_LLM` | Model for simple tasks (gpt-5, gpt-4o-mini, etc.) | `gpt-4o-mini` |
### Analysis Settings
| Config Key | Env Variable | Description | Default |
|------------|--------------|-------------|---------|
| `research_depth` | `RESEARCH_DEPTH` | Depth level (1=shallow, 2=medium, 3=deep) | `1` |
| `default_analysts` | `DEFAULT_ANALYSTS` | Comma-separated list of analysts | `market,news,social` |
### Data Settings
| Config Key | Env Variable | Description | Default |
|------------|--------------|-------------|---------|
| `global_news_limit` | `GLOBAL_NEWS_LIMIT` | Number of global news articles to fetch | `15` |
| `commodity_news_limit` | `COMMODITY_NEWS_LIMIT` | Number of commodity news articles | `50` |
### Data Vendors
Configure which data sources to use:
| Config Key | Env Variable | Description | Options |
|------------|--------------|-------------|---------|
| `stock` | `DATA_VENDOR_STOCK` | Stock price data | yfinance, alpha_vantage |
| `indicators` | `DATA_VENDOR_INDICATORS` | Technical indicators | yfinance, alpha_vantage |
| `fundamentals` | `DATA_VENDOR_FUNDAMENTALS` | Fundamental data | alpha_vantage, openai |
| `news` | `DATA_VENDOR_NEWS` | News data | alpha_vantage, openai, google |
| `commodity` | `DATA_VENDOR_COMMODITY` | Commodity data | alpha_vantage |
## Examples
### Example 1: Minimal config.ini for quick daily use
```ini
[llm]
provider = openai
deep_think_llm = o1-mini
quick_think_llm = gpt-4o-mini
[analysis]
research_depth = 1
default_analysts = market,news
```
**Result:** When you run the CLI, you'll **only be prompted for**:
- ✅ Ticker symbol
- ✅ Analysis date
**Auto-configured (no prompts)**:
- ✅ LLM provider (OpenAI)
- ✅ Models (o1-mini & gpt-4o-mini)
- ✅ Research depth (Shallow)
- ✅ Analysts (Market & News)
Everything else uses your configured defaults!
### Example 2: Using GPT-5
```ini
[llm]
provider = openai
deep_think_llm = gpt-5
quick_think_llm = gpt-5
[analysis]
research_depth = 2
default_analysts = market,news,social
```
### Example 3: Power user with custom news limits
```ini
[llm]
provider = anthropic
deep_think_llm = claude-3-5-sonnet-20241022
quick_think_llm = claude-3-5-haiku-20241022
[analysis]
research_depth = 3
default_analysts = market,news,social,fundamentals
[data]
global_news_limit = 30
commodity_news_limit = 100
[vendors]
news = openai
stock = alpha_vantage
```
### Example 4: Using environment variables
```bash
# In your ~/.zshrc or ~/.bashrc
export LLM_PROVIDER=openai
export DEEP_THINK_LLM=o1-mini
export QUICK_THINK_LLM=gpt-4o-mini
export GLOBAL_NEWS_LIMIT=20
```
## Configuration Priority Example
If you have:
- `config.ini` with `global_news_limit = 15`
- Environment variable `GLOBAL_NEWS_LIMIT=30`
The system will use **30** (environment variable wins).
## How It Works
When you run the CLI with a `config.ini` file, you'll see output like this:
```
Step 1: Ticker Symbol
Enter the ticker symbol to analyze
> AAPL
→ Detected asset class: Equity
Step 2: Analysis date
Enter the analysis date (YYYY-MM-DD)
> 2025-01-15
→ Using configured analysts: market, news, social
→ Using configured research depth: Shallow
→ Using configured LLM provider: OPENAI (https://api.openai.com/v1)
→ Using configured models:
Quick thinking: gpt-4o-mini
Deep thinking: o1-mini
```
The `→` lines show values being used from your config, skipping the prompts!
## Tips
1. **Start with config.ini** - Easier to manage than environment variables
2. **Use env vars for secrets** - Keep API keys in environment variables, not config files
3. **Version control** - Add `config.ini` to `.gitignore`, commit `config.example.ini`
4. **Test changes** - Run CLI after changing config to verify your settings work
5. **Partial config is OK** - You can configure only some values; the rest will be prompted
## Troubleshooting
**CLI still prompts for everything:**
- Check that `config.ini` exists in the TradingAgents directory
- Verify the file has the correct structure (see `config.example.ini`)
- Check for typos in section names and keys
**Wrong model being used:**
- Check priority: env vars override config.ini
- Verify model names match your provider's API exactly
- Check logs for which config values are being loaded
**News limit not working:**
- Limits are read from config on each run
- Clear Python cache: `find . -type d -name "__pycache__" -exec rm -rf {} +`
- Check the tool logs to see what limit is actually being used

View File

@ -14,18 +14,48 @@ KNOWN_COMMODITIES = {
"COFFEE",
}
# Known cryptocurrencies supported by Alpha Vantage
KNOWN_CRYPTOS = {
"BTC",
"ETH",
"BNB",
"XRP",
"ADA",
"SOL",
"DOGE",
"DOT",
"MATIC",
"AVAX",
"LINK",
"UNI",
"ATOM",
"LTC",
"BCH",
"XLM",
"ALGO",
"VET",
"ICP",
"FIL",
}
def detect_asset_class(symbol: str) -> str:
"""
Automatically detect if a symbol is a commodity or equity.
Automatically detect if a symbol is a commodity, crypto, or equity.
Args:
symbol: The ticker symbol (e.g., "BRENT", "AAPL")
symbol: The ticker symbol (e.g., "BRENT", "BTC", "AAPL")
Returns:
"commodity" if the symbol matches a known commodity, "equity" otherwise
"commodity", "crypto", or "equity" based on the symbol
"""
return "commodity" if symbol.upper() in KNOWN_COMMODITIES else "equity"
symbol_upper = symbol.upper()
if symbol_upper in KNOWN_COMMODITIES:
return "commodity"
elif symbol_upper in KNOWN_CRYPTOS:
return "crypto"
else:
return "equity"
def get_asset_class_display_name(asset_class: str) -> str:

View File

@ -50,6 +50,9 @@ message_buffer = MessageBuffer()
def get_user_selections():
"""Get all user selections before starting the analysis display."""
# Load config to check for pre-configured values
from tradingagents.default_config import DEFAULT_CONFIG
# Display ASCII art welcome message
with open("./cli/static/welcome.txt", "r") as f:
welcome_ascii = f.read()
@ -107,41 +110,80 @@ def get_user_selections():
)
analysis_date = get_analysis_date()
# Step 3: Select analysts
console.print(
create_question_box(
"Step 3: Analysts Team", "Select your LLM analyst agents for the analysis"
# Step 3: Select analysts (use config default if available)
if "default_analysts" in DEFAULT_CONFIG and DEFAULT_CONFIG["default_analysts"]:
# Convert analyst names to AnalystType
analyst_map = {
"market": AnalystType.MARKET,
"news": AnalystType.NEWS,
"social": AnalystType.SOCIAL,
"fundamentals": AnalystType.FUNDAMENTALS,
}
selected_analysts = [analyst_map[a] for a in DEFAULT_CONFIG["default_analysts"] if a in analyst_map]
# Filter out fundamentals for commodities
if asset_class == "commodity":
selected_analysts = [a for a in selected_analysts if a != AnalystType.FUNDAMENTALS]
console.print(
f"[dim]→ Using configured analysts: [bold]{', '.join(a.value for a in selected_analysts)}[/bold][/dim]\n"
)
else:
console.print(
create_question_box(
"Step 3: Analysts Team", "Select your LLM analyst agents for the analysis"
)
)
selected_analysts = select_analysts(asset_class)
console.print(
f"[green]Selected analysts:[/green] {', '.join(analyst.value for analyst in selected_analysts)}"
)
)
selected_analysts = select_analysts(asset_class)
console.print(
f"[green]Selected analysts:[/green] {', '.join(analyst.value for analyst in selected_analysts)}"
)
# Step 4: Research depth
console.print(
create_question_box(
"Step 4: Research Depth", "Select your research depth level"
# Step 4: Research depth (use config default if available)
if "max_debate_rounds" in DEFAULT_CONFIG:
selected_research_depth = DEFAULT_CONFIG["max_debate_rounds"]
depth_labels = {1: "Shallow", 2: "Medium", 3: "Deep"}
console.print(
f"[dim]→ Using configured research depth: [bold]{depth_labels.get(selected_research_depth, selected_research_depth)}[/bold][/dim]\n"
)
)
selected_research_depth = select_research_depth()
else:
console.print(
create_question_box(
"Step 4: Research Depth", "Select your research depth level"
)
)
selected_research_depth = select_research_depth()
# Step 5: LLM backend
console.print(
create_question_box(
"Step 5: LLM Backend", "Select which service to talk to"
# Step 5: LLM backend (use config default if available)
if "llm_provider" in DEFAULT_CONFIG and "backend_url" in DEFAULT_CONFIG:
selected_llm_provider = DEFAULT_CONFIG["llm_provider"]
backend_url = DEFAULT_CONFIG["backend_url"]
console.print(
f"[dim]→ Using configured LLM provider: [bold]{selected_llm_provider.upper()}[/bold] ({backend_url})[/dim]\n"
)
)
selected_llm_provider, backend_url = select_llm_provider()
else:
console.print(
create_question_box(
"Step 5: LLM Backend", "Select which service to talk to"
)
)
selected_llm_provider, backend_url = select_llm_provider()
# Step 6: Thinking agents
console.print(
create_question_box(
"Step 6: Thinking Agents", "Select your thinking agents for analysis"
# Step 6: Thinking agents (use config defaults if available)
if "quick_think_llm" in DEFAULT_CONFIG and "deep_think_llm" in DEFAULT_CONFIG:
selected_shallow_thinker = DEFAULT_CONFIG["quick_think_llm"]
selected_deep_thinker = DEFAULT_CONFIG["deep_think_llm"]
console.print(
f"[dim]→ Using configured models:[/dim]\n"
f"[dim] Quick thinking: [bold]{selected_shallow_thinker}[/bold][/dim]\n"
f"[dim] Deep thinking: [bold]{selected_deep_thinker}[/bold][/dim]\n"
)
)
selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider)
selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider)
else:
console.print(
create_question_box(
"Step 6: Thinking Agents", "Select your thinking agents for analysis"
)
)
selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider)
selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider)
return {
"ticker": selected_ticker,

View File

@ -67,10 +67,10 @@ def get_analysis_date() -> str:
def select_analysts(asset_class: str | None = None) -> List[AnalystType]:
"""Select analysts using an interactive checkbox.
If asset_class is 'commodity', hide Fundamentals Analyst.
If asset_class is 'commodity' or 'crypto', hide Fundamentals Analyst.
"""
order = ANALYST_ORDER
if asset_class and asset_class.lower() == "commodity":
if asset_class and asset_class.lower() in ["commodity", "crypto"]:
order = [(d, v) for (d, v) in ANALYST_ORDER if v != AnalystType.FUNDAMENTALS]
choices = questionary.checkbox(
@ -139,6 +139,7 @@ def select_shallow_thinking_agent(provider) -> str:
("GPT-4.1-nano - Ultra-lightweight model for basic operations", "gpt-4.1-nano"),
("GPT-4.1-mini - Compact model with good performance", "gpt-4.1-mini"),
("GPT-4o - Standard model with solid capabilities", "gpt-4o"),
("GPT-5 - Next generation model with enhanced capabilities", "gpt-5"),
],
"anthropic": [
("Claude Haiku 3.5 - Fast inference and standard capabilities", "claude-3-5-haiku-latest"),
@ -196,6 +197,7 @@ def select_deep_thinking_agent(provider) -> str:
("GPT-4.1-nano - Ultra-lightweight model for basic operations", "gpt-4.1-nano"),
("GPT-4.1-mini - Compact model with good performance", "gpt-4.1-mini"),
("GPT-4o - Standard model with solid capabilities", "gpt-4o"),
("GPT-5 - Next generation model with enhanced capabilities", "gpt-5"),
("o4-mini - Specialized reasoning model (compact)", "o4-mini"),
("o3-mini - Advanced reasoning model (lightweight)", "o3-mini"),
("o3 - Full advanced reasoning model", "o3"),

38
config.example.ini Normal file
View File

@ -0,0 +1,38 @@
# TradingAgents Configuration
# Copy this file to config.ini and customize your settings
[llm]
# Provider: openai, anthropic, google, openrouter, ollama
provider = openai
backend_url = https://api.openai.com/v1
# Deep thinking model (for complex analysis, debates, final decisions)
deep_think_llm = o1-mini
# Quick thinking model (for simple tasks, data retrieval)
quick_think_llm = gpt-4o-mini
[analysis]
# Research Depth: 1 (shallow), 2 (medium), 3 (deep)
research_depth = 1
# Default analysts to use (comma-separated): market,news,social,fundamentals
default_analysts = market,news,social
[data]
# Global news limit (number of articles to fetch)
global_news_limit = 15
# Commodity news limit
commodity_news_limit = 50
[vendors]
# Data vendor preferences
stock = yfinance
indicators = yfinance
fundamentals = alpha_vantage
news = alpha_vantage
commodity = alpha_vantage
[storage]
# Directory for analysis results
results_dir = ./results
# Data cache directory
data_cache_dir = ./tradingagents/dataflows/data_cache

25
config.ini Normal file
View File

@ -0,0 +1,25 @@
[llm]
provider = openai
backend_url = https://api.openai.com/v1
deep_think_llm = o4-mini
quick_think_llm = gpt-4o
[analysis]
research_depth = 1
#default_analysts = market,news,social
[data]
global_news_limit = 15
commodity_news_limit = 50
[vendors]
stock = yfinance
indicators = yfinance
fundamentals = alpha_vantage
news = alpha_vantage
commodity = alpha_vantage
crypto = alpha_vantage
[storage]
results_dir = ./results

View File

@ -1,8 +1,10 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import time
import json
from tradingagents.agents.utils.agent_utils import get_stock_data, get_indicators
from tradingagents.agents.utils.commodity_data_tools import get_commodity_data
from tradingagents.agents.utils.agent_utils import (
get_market_data,
get_indicators,
)
from tradingagents.dataflows.config import get_config
@ -12,17 +14,14 @@ def create_market_analyst(llm):
current_date = state["trade_date"]
ticker = state["company_of_interest"]
company_name = state["company_of_interest"]
asset_class = state.get("asset_class", "equity")
if asset_class == "commodity":
tools = [
get_commodity_data,
]
# Use unified tools for all asset classes
if asset_class == "equity":
tools = [get_market_data, get_indicators]
else:
tools = [
get_stock_data,
get_indicators,
]
# Commodities and crypto just use market data (no indicators yet)
tools = [get_market_data]
system_message = (
"""You are a trading assistant tasked with analyzing financial markets. Your role is to select the **most relevant indicators** for a given market condition or trading strategy from the following list. The goal is to choose up to **8 indicators** that provide complementary insights without redundancy. Categories and each category's indicators are:
@ -54,9 +53,11 @@ Volume-Based Indicators:
)
if asset_class == "equity":
system_message += " ""Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names."""
else:
system_message += " ""For commodities, call get_commodity_data to retrieve the price series (value column). You may analyze trends directly on the series or proceed without additional indicators."""
system_message += f""" IMPORTANT: When calling get_market_data, always pass asset_class="{asset_class}". First call get_market_data with asset_class="{asset_class}" to retrieve the CSV data, then use get_indicators with the specific indicator names."""
elif asset_class == "commodity":
system_message += f""" IMPORTANT: When calling get_market_data, always pass asset_class="{asset_class}". Call get_market_data with asset_class="{asset_class}" to retrieve the commodity price series (value column). You may analyze trends directly on the series."""
else: # crypto
system_message += f""" IMPORTANT: When calling get_market_data, always pass asset_class="{asset_class}". Call get_market_data with asset_class="{asset_class}" to retrieve OHLCV data. You may analyze price trends and patterns directly."""
prompt = ChatPromptTemplate.from_messages(
[

View File

@ -1,7 +1,10 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import time
import json
from tradingagents.agents.utils.agent_utils import get_news, get_commodity_news, get_global_news
from tradingagents.agents.utils.agent_utils import (
get_asset_news,
get_global_news_unified as get_global_news,
)
from tradingagents.dataflows.config import get_config
@ -10,34 +13,40 @@ def create_news_analyst(llm):
current_date = state["trade_date"]
ticker = state["company_of_interest"]
asset_class = state.get("asset_class", "equity")
is_commodity = asset_class.lower() == "commodity"
# Branch tools based on asset class
if is_commodity:
tools = [
get_commodity_news,
get_global_news,
]
# Use unified tools for all asset classes
tools = [get_asset_news, get_global_news]
# Asset-specific messaging
if asset_class == "commodity":
system_message = (
f"You are a news researcher tasked with analyzing recent news and trends for the commodity {ticker}. "
"Please write a comprehensive report of relevant news over the past week that impacts this commodity's price. "
"Use the available tools: get_commodity_news(commodity, start_date, end_date) for commodity-specific news (searches by topic like 'energy' for oil, 'economy_macro' for agriculture), "
"and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic context. "
"IMPORTANT: If get_commodity_news returns limited results, make sure to use get_global_news to provide additional market context. "
f"Use the available tools: get_asset_news(symbol, start_date, end_date, asset_class=\"{asset_class}\") for commodity-specific news, "
"and get_global_news(curr_date, look_back_days) for broader macroeconomic context (do NOT specify limit). "
f"IMPORTANT: Always pass asset_class=\"{asset_class}\" when calling get_asset_news. If get_asset_news returns limited results, make sure to use get_global_news to provide additional market context. "
"Focus on supply/demand factors, geopolitical events, weather impacts (for agriculture), and macroeconomic trends. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis."
+ """ Make sure to append a Markdown table at the end of the report to organize key points."""
)
else:
tools = [
get_news,
get_global_news,
]
elif asset_class == "crypto":
system_message = (
f"You are a news researcher tasked with analyzing recent news and trends for the cryptocurrency {ticker}. "
"Please write a comprehensive report of relevant news over the past week that impacts this cryptocurrency's price. "
f"Use the available tools: get_asset_news(symbol, start_date, end_date, asset_class=\"{asset_class}\") for crypto-specific blockchain news, "
"and get_global_news(curr_date, look_back_days) for broader macroeconomic context (do NOT specify limit). "
f"IMPORTANT: Always pass asset_class=\"{asset_class}\" when calling get_asset_news. If get_asset_news returns limited results, make sure to use get_global_news to provide additional market context. "
"Focus on regulatory developments, adoption trends, technological updates, market sentiment, and macroeconomic factors affecting crypto. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis."
+ """ Make sure to append a Markdown table at the end of the report to organize key points."""
)
else: # equity
system_message = (
"You are a news researcher tasked with analyzing recent news and trends over the past week. "
"Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. "
"Use the available tools: get_news(ticker, start_date, end_date) for company-specific or targeted news searches, "
"and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. "
f"Use the available tools: get_asset_news(symbol, start_date, end_date, asset_class=\"{asset_class}\") for company-specific or targeted news searches, "
"and get_global_news(curr_date, look_back_days) for broader macroeconomic news (omit limit parameter). "
f"IMPORTANT: Always pass asset_class=\"{asset_class}\" when calling get_asset_news. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions."
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."""
)

View File

@ -1,7 +1,10 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import time
import json
from tradingagents.agents.utils.agent_utils import get_news, get_commodity_news, get_global_news
from tradingagents.agents.utils.agent_utils import (
get_asset_news,
get_global_news_unified as get_global_news,
)
from tradingagents.dataflows.config import get_config
@ -10,34 +13,38 @@ def create_social_media_analyst(llm):
current_date = state["trade_date"]
ticker = state["company_of_interest"]
asset_class = state.get("asset_class", "equity")
is_commodity = asset_class.lower() == "commodity"
# Branch tools based on asset class
if is_commodity:
tools = [
get_commodity_news,
get_global_news,
]
# Use unified tools for all asset classes
tools = [get_asset_news, get_global_news]
# Asset-specific messaging
if asset_class == "commodity":
system_message = (
f"You are a social media and news researcher/analyst tasked with analyzing recent discussions and sentiment for the commodity {ticker}. "
"Your objective is to write a comprehensive report detailing market sentiment, trader discussions, and public perception over the past week. "
"Use get_commodity_news(commodity, start_date, end_date) to search for commodity-related news and discussions (searches by topic like 'energy' for oil). "
"IMPORTANT: If get_commodity_news returns limited results, supplement with get_global_news(curr_date, look_back_days, limit) for broader market context. "
f"Use get_asset_news(symbol, start_date, end_date, asset_class=\"{asset_class}\") to search for commodity-related news and discussions. "
f"IMPORTANT: Always pass asset_class=\"{asset_class}\" when calling get_asset_news. If get_asset_news returns limited results, supplement with get_global_news(curr_date, look_back_days) for broader market context (do NOT specify limit). "
"Focus on trader sentiment, supply/demand expectations, geopolitical concerns, and market psychology. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis."
+ """ Make sure to append a Markdown table at the end of the report to organize key points."""
)
else:
tools = [
get_news,
get_global_news,
]
elif asset_class == "crypto":
system_message = (
f"You are a social media and news researcher/analyst tasked with analyzing recent discussions and sentiment for the cryptocurrency {ticker}. "
"Your objective is to write a comprehensive report detailing market sentiment, community discussions, and public perception over the past week. "
f"Use get_asset_news(symbol, start_date, end_date, asset_class=\"{asset_class}\") to search for crypto-related news and discussions. "
f"IMPORTANT: Always pass asset_class=\"{asset_class}\" when calling get_asset_news. If get_asset_news returns limited results, supplement with get_global_news(curr_date, look_back_days) for broader market context (do NOT specify limit). "
"Focus on community sentiment, adoption trends, regulatory concerns, developer activity, whale movements, and market psychology. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis."
+ """ Make sure to append a Markdown table at the end of the report to organize key points."""
)
else: # equity
system_message = (
"You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. "
"Your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, "
"analyzing sentiment data of what people feel each day about the company, and looking at recent company news. "
"Use the get_news(ticker, start_date, end_date) tool to search for company-specific news and social media discussions. "
"If needed, use get_global_news(curr_date, look_back_days, limit) for broader market context. "
f"Use the get_asset_news(symbol, start_date, end_date, asset_class=\"{asset_class}\") tool to search for company-specific news and social media discussions. "
f"IMPORTANT: Always pass asset_class=\"{asset_class}\" when calling get_asset_news. If needed, use get_global_news(curr_date, look_back_days) for broader market context (omit limit). "
"Try to look at all sources possible from social media to sentiment to news. Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions."
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."""
)

View File

@ -32,7 +32,7 @@ def build_news_analyst_prompt(asset_class: str, ticker: str) -> str:
f"You are a news researcher tasked with analyzing recent news and trends for the commodity {ticker}. "
"Please write a comprehensive report of relevant news over the past week that impacts this commodity's price. "
f"Use the available tools: {prompt_cfg['primary_tool']} for commodity-specific news ({prompt_cfg['primary_note']}), "
f"and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic context. "
f"and get_global_news(curr_date, look_back_days) for broader macroeconomic context (do NOT specify limit - it uses optimal configured value). "
f"IMPORTANT: {prompt_cfg['fallback_note']} "
f"Focus on {prompt_cfg['focus']}. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis."
@ -43,7 +43,7 @@ def build_news_analyst_prompt(asset_class: str, ticker: str) -> str:
"You are a news researcher tasked with analyzing recent news and trends over the past week. "
"Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. "
f"Use the available tools: {prompt_cfg['primary_tool']} {prompt_cfg['primary_note']}, "
f"and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. "
f"and get_global_news(curr_date, look_back_days) for broader macroeconomic news (omit limit parameter to use configured optimal value). "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions."
" Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."
)
@ -62,7 +62,7 @@ def build_social_media_analyst_prompt(asset_class: str, ticker: str) -> str:
f"You are a social media and news researcher/analyst tasked with analyzing recent discussions and sentiment for the commodity {ticker}. "
"Your objective is to write a comprehensive report detailing market sentiment, trader discussions, and public perception over the past week. "
f"Use {prompt_cfg['primary_tool']} to search for commodity-related news and discussions ({prompt_cfg['primary_note']}). "
f"IMPORTANT: {prompt_cfg['fallback_note']} "
f"IMPORTANT: {prompt_cfg['fallback_note']} When using get_global_news, do NOT specify limit parameter - use configured optimal value. "
f"Focus on {prompt_cfg['focus']}. "
"Do not simply state the trends are mixed, provide detailed and fine-grained analysis."
" Make sure to append a Markdown table at the end of the report to organize key points."
@ -73,7 +73,7 @@ def build_social_media_analyst_prompt(asset_class: str, ticker: str) -> str:
f"Your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this {asset_term}'s current state after looking at social media and what people are saying about that {asset_term}, "
f"analyzing sentiment data of what people feel each day about the {asset_term}, and looking at recent {asset_term} news. "
f"Use the {prompt_cfg['primary_tool']} tool {prompt_cfg['primary_note']}. "
f"{prompt_cfg['fallback_note']} "
f"{prompt_cfg['fallback_note']} When using get_global_news, omit the limit parameter to use the configured optimal value. "
"Try to look at all sources possible from social media to sentiment to news. Do not simply state the trends are mixed, provide detailed and fine-grained analysis and insights that may help traders make decisions."
" Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."
)

View File

@ -50,7 +50,7 @@ class RiskDebateState(TypedDict):
class AgentState(MessagesState):
company_of_interest: Annotated[str, "Company that we are interested in trading"]
trade_date: Annotated[str, "What date we are trading at"]
asset_class: Annotated[str, "Asset class: equity or commodity"]
asset_class: Annotated[str, "Asset class: equity, commodity, or crypto"]
sender: Annotated[str, "Agent that sent this message"]

View File

@ -16,10 +16,20 @@ from tradingagents.agents.utils.fundamental_data_tools import (
from tradingagents.agents.utils.news_data_tools import (
get_news,
get_commodity_news,
get_crypto_news,
get_insider_sentiment,
get_insider_transactions,
get_global_news
)
from tradingagents.agents.utils.crypto_data_tools import (
get_crypto_data
)
from tradingagents.agents.utils.unified_market_tools import (
get_market_data,
get_asset_news,
get_indicators,
get_global_news as get_global_news_unified,
)
def create_msg_delete():
def delete_messages(state):

View File

@ -0,0 +1,34 @@
"""Cryptocurrency data tools for LangChain agents."""
from langchain_core.tools import tool
from typing import Annotated
from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_crypto_data(
symbol: Annotated[str, "Crypto symbol like BTC, ETH, SOL, BNB"],
start_date: Annotated[str, "Start date in YYYY-mm-dd format"],
end_date: Annotated[str, "End date in YYYY-mm-dd format"],
market: Annotated[str, "Market currency like USD, EUR"] = "USD",
) -> str:
"""
Retrieve cryptocurrency OHLCV (Open, High, Low, Close, Volume) price data.
This tool fetches historical cryptocurrency price data for analysis.
Supports major cryptocurrencies like Bitcoin (BTC), Ethereum (ETH), Solana (SOL), etc.
Args:
symbol: Cryptocurrency symbol (e.g., "BTC", "ETH", "SOL")
start_date: Start date in YYYY-mm-dd format
end_date: End date in YYYY-mm-dd format
market: Market currency (default "USD", can also be "EUR", "GBP")
Returns:
CSV string with columns: time,open,high,low,close,volume
Example:
get_crypto_data("BTC", "2025-01-01", "2025-01-31", "USD")
"""
return route_to_vendor("get_crypto_data", symbol, start_date, end_date, market)

View File

@ -24,18 +24,25 @@ def get_news(
def get_global_news(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
look_back_days: Annotated[int, "Number of days to look back"] = 7,
limit: Annotated[int, "Maximum number of articles to return"] = 5,
limit: Annotated[int, "Number of articles (leave unset to use configured default, typically 15-20)"] = None,
) -> str:
"""
Retrieve global news data.
Uses the configured news_data vendor.
DO NOT specify limit parameter - it will use the system-configured optimal value.
Only override if you have a specific reason to fetch more/fewer articles.
Args:
curr_date (str): Current date in yyyy-mm-dd format
look_back_days (int): Number of days to look back (default 7)
limit (int): Maximum number of articles to return (default 5)
limit (int): OPTIONAL - Number of articles (omit to use configured default)
Returns:
str: A formatted string containing global news data
"""
# Use config default if not specified
if limit is None:
from tradingagents.dataflows.config import get_config
limit = get_config().get("global_news_limit", 15)
return route_to_vendor("get_global_news", curr_date, look_back_days, limit)
@tool
@ -58,6 +65,26 @@ def get_commodity_news(
"""
return route_to_vendor("get_commodity_news", commodity, start_date, end_date)
@tool
def get_crypto_news(
crypto: Annotated[str, "Crypto symbol like BTC, ETH, SOL"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
) -> str:
"""
Retrieve news data for a cryptocurrency (Bitcoin, Ethereum, etc.).
Uses topic-based search since crypto doesn't have traditional stock tickers.
Searches news by blockchain/technology topics and filters for the specific cryptocurrency.
Args:
crypto (str): Cryptocurrency symbol (e.g., "BTC", "ETH", "SOL")
start_date (str): Start date in yyyy-mm-dd format
end_date (str): End date in yyyy-mm-dd format
Returns:
str: A formatted string containing crypto-related news data
"""
return route_to_vendor("get_crypto_news", crypto, start_date, end_date)
@tool
def get_insider_sentiment(
ticker: Annotated[str, "ticker symbol for the company"],

View File

@ -0,0 +1,132 @@
"""
Unified tools for market data and news that work across all asset classes.
These tools automatically route to the correct vendor implementation based on asset class.
"""
from langchain_core.tools import tool
from typing import Annotated, Optional
from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_market_data(
symbol: Annotated[str, "Asset symbol (AAPL for stocks, BRENT for commodities, BTC for crypto)"],
start_date: Annotated[str, "Start date in YYYY-mm-dd format"],
end_date: Annotated[str, "End date in YYYY-mm-dd format"],
asset_class: Annotated[str, "Asset class: equity, commodity, or crypto"] = "equity",
interval: Annotated[str, "Data interval: daily, weekly, or monthly (for commodities)"] = "daily",
market: Annotated[str, "Market currency for crypto (USD, EUR, etc.)"] = "USD",
) -> str:
"""
Retrieve price data for any asset: stocks, commodities, or cryptocurrencies.
Automatically routes to the appropriate data source based on asset class.
For stocks: Returns OHLCV data (interval is ignored for stocks)
For commodities: Returns price data from Alpha Vantage commodity API
For crypto: Returns OHLCV data with specified market currency
Args:
symbol: Ticker or symbol (e.g., "AAPL", "BRENT", "BTC")
start_date: Start date in YYYY-mm-dd format
end_date: End date in YYYY-mm-dd format
asset_class: The type of asset (equity, commodity, crypto)
interval: Data interval (daily, weekly, monthly) - only used for commodities
market: Market currency for crypto pairs (default: USD) - only used for crypto
Returns:
CSV-formatted price data with appropriate columns for the asset type
"""
if asset_class == "crypto":
return route_to_vendor("get_crypto_data", symbol, start_date, end_date, market)
elif asset_class == "commodity":
return route_to_vendor("get_commodity_data", symbol, start_date, end_date, interval)
else: # equity
# Stock data functions only take 3 params (no interval)
return route_to_vendor("get_stock_data", symbol, start_date, end_date)
@tool
def get_indicators(
symbol: Annotated[str, "Stock ticker symbol"],
start_date: Annotated[str, "Start date in YYYY-mm-dd format"],
end_date: Annotated[str, "End date in YYYY-mm-dd format"],
indicators: Annotated[str, "Comma-separated list of indicators (sma_20, rsi, macd, etc.)"],
) -> str:
"""
Calculate technical indicators for stock data.
Note: Currently only supported for equities.
Supported indicators: sma_X, ema_X, rsi, macd, boll (Bollinger Bands), adx, cci, stoch
Args:
symbol: Stock ticker symbol
start_date: Start date in YYYY-mm-dd format
end_date: End date in YYYY-mm-dd format
indicators: Comma-separated list of indicator names
Returns:
CSV-formatted data with requested technical indicators
"""
# Indicators are equity-specific for now
return route_to_vendor("get_indicators", symbol, start_date, end_date, indicators)
@tool
def get_asset_news(
symbol: Annotated[str, "Asset symbol (AAPL for stocks, BRENT for commodities, BTC for crypto)"],
start_date: Annotated[str, "Start date in YYYY-mm-dd format"],
end_date: Annotated[str, "End date in YYYY-mm-dd format"],
asset_class: Annotated[str, "Asset class: equity, commodity, or crypto"] = "equity",
) -> str:
"""
Retrieve news and sentiment data for any asset type.
Automatically routes to the appropriate news source based on asset class.
For stocks: Returns ticker-specific news from financial outlets
For commodities: Returns topic-based news (energy, metals, agriculture)
For crypto: Returns blockchain/technology news filtered for the specific cryptocurrency
Args:
symbol: Ticker or symbol
start_date: Start date in YYYY-mm-dd format
end_date: End date in YYYY-mm-dd format
asset_class: The type of asset (equity, commodity, crypto)
Returns:
JSON-formatted news articles with sentiment scores and metadata
"""
if asset_class == "crypto":
return route_to_vendor("get_crypto_news", symbol, start_date, end_date)
elif asset_class == "commodity":
return route_to_vendor("get_commodity_news", symbol, start_date, end_date)
else: # equity
return route_to_vendor("get_news", symbol, start_date, end_date)
@tool
def get_global_news(
curr_date: Annotated[str, "Current date in YYYY-mm-dd format"],
look_back_days: Annotated[int, "Number of days to look back"] = 7,
limit: Annotated[Optional[int], "Maximum number of articles (omit to use configured default)"] = None,
) -> str:
"""
Retrieve global macroeconomic and financial market news.
IMPORTANT: Do NOT specify the 'limit' parameter - omit it to use the configured default.
The system is configured with an optimal limit based on data source performance.
Args:
curr_date: Current date in YYYY-mm-dd format
look_back_days: Number of days to look back (default: 7)
limit: Leave this parameter unset to use the configured default
Returns:
JSON-formatted global news articles about macro trends, markets, and economy
"""
from tradingagents.dataflows.config import get_config
if limit is None:
limit = get_config().get("global_news_limit", 15)
return route_to_vendor("get_global_news", curr_date, look_back_days, limit)

View File

@ -2,5 +2,7 @@
from .alpha_vantage_stock import get_stock
from .alpha_vantage_indicator import get_indicator
from .alpha_vantage_fundamentals import get_fundamentals, get_balance_sheet, get_cashflow, get_income_statement
from .alpha_vantage_news import get_news, get_insider_transactions, get_commodity_news
from .alpha_vantage_commodity import get_commodity
from .alpha_vantage_news import get_news, get_insider_transactions, get_commodity_news, get_crypto_news
from .alpha_vantage_global_news import get_global_news_alpha_vantage
from .alpha_vantage_commodity import get_commodity
from .alpha_vantage_crypto import get_crypto

View File

@ -0,0 +1,215 @@
"""Alpha Vantage cryptocurrency data fetcher."""
from .alpha_vantage_common import _make_api_request
from datetime import datetime, timedelta
import json
import io
# Map crypto symbols to full names for better context
CRYPTO_NAMES = {
"BTC": "Bitcoin",
"ETH": "Ethereum",
"BNB": "Binance Coin",
"XRP": "Ripple",
"ADA": "Cardano",
"SOL": "Solana",
"DOGE": "Dogecoin",
"DOT": "Polkadot",
"MATIC": "Polygon",
"AVAX": "Avalanche",
"LINK": "Chainlink",
"UNI": "Uniswap",
"ATOM": "Cosmos",
"LTC": "Litecoin",
"BCH": "Bitcoin Cash",
"XLM": "Stellar",
"ALGO": "Algorand",
"VET": "VeChain",
"ICP": "Internet Computer",
"FIL": "Filecoin",
}
def get_crypto(
symbol: str,
start_date: str,
end_date: str,
market: str = "USD",
) -> str:
"""
Fetch cryptocurrency OHLCV data from Alpha Vantage.
Uses the DIGITAL_CURRENCY_DAILY function to get daily crypto prices.
Args:
symbol: Cryptocurrency symbol (e.g., "BTC", "ETH", "SOL")
start_date: Start date in YYYY-mm-dd format
end_date: End date in YYYY-mm-dd format
market: Market currency (default "USD", can also be "EUR", "GBP", etc.)
Returns:
CSV string with columns: time,open,high,low,close,volume
"""
symbol_upper = symbol.upper()
market_upper = market.upper()
# Alpha Vantage uses DIGITAL_CURRENCY_DAILY
params = {
"symbol": symbol_upper,
"market": market_upper,
}
raw = _make_api_request("DIGITAL_CURRENCY_DAILY", params)
# Parse the JSON response and convert to CSV
try:
payload = json.loads(raw)
# Alpha Vantage returns data in "Time Series (Digital Currency Daily)" key
time_series_key = "Time Series (Digital Currency Daily)"
if time_series_key not in payload:
# If key not found, return raw for debugging
return raw
time_series = payload[time_series_key]
# Parse date range
s_dt = datetime.strptime(start_date, "%Y-%m-%d")
e_dt = datetime.strptime(end_date, "%Y-%m-%d")
# If very narrow window, widen to get more context
if (e_dt - s_dt).days < 7:
s_dt = e_dt - timedelta(days=90) # 90 days for crypto (more volatile)
# DEBUG: Check what keys are available in the first data point
if time_series:
first_date = next(iter(time_series))
first_data = time_series[first_date]
print(f"DEBUG: Available keys in crypto data: {list(first_data.keys())}")
# Build rows within date range
rows = []
for date_str, values in time_series.items():
try:
d = datetime.strptime(date_str, "%Y-%m-%d")
if s_dt <= d <= e_dt:
# Alpha Vantage crypto keys - try multiple formats
# Format 1: "1a. open (USD)" - standard format
# Format 2: "1b. open (USD)" - alternate
# Format 3: "1. open" - fallback
def find_value(patterns):
"""Try multiple key patterns and return first match."""
for pattern in patterns:
if pattern in values:
return values[pattern]
return ""
open_price = find_value([
f"1a. open ({market_upper})",
f"1b. open ({market_upper})",
"1a. open",
"1. open"
])
high_price = find_value([
f"2a. high ({market_upper})",
f"2b. high ({market_upper})",
"2a. high",
"2. high"
])
low_price = find_value([
f"3a. low ({market_upper})",
f"3b. low ({market_upper})",
"3a. low",
"3. low"
])
close_price = find_value([
f"4a. close ({market_upper})",
f"4b. close ({market_upper})",
"4a. close",
"4. close"
])
volume = values.get("5. volume", "")
rows.append((
date_str,
open_price,
high_price,
low_price,
close_price,
volume
))
except ValueError:
# Skip invalid dates
continue
# Sort by date (oldest first)
rows.sort(key=lambda x: x[0])
# Convert to CSV
out = io.StringIO()
out.write("time,open,high,low,close,volume\n")
for row in rows:
out.write(f"{row[0]},{row[1]},{row[2]},{row[3]},{row[4]},{row[5]}\n")
csv_text = out.getvalue()
# If still empty after widening, return recent data without filtering
if csv_text.strip() == "time,open,high,low,close,volume":
out = io.StringIO()
out.write("time,open,high,low,close,volume\n")
# Get last 90 days
sorted_dates = sorted(time_series.keys(), reverse=True)[:90]
def find_value(vals, patterns):
"""Try multiple key patterns and return first match."""
for pattern in patterns:
if pattern in vals:
return vals[pattern]
return ""
for date_str in reversed(sorted_dates):
values = time_series[date_str]
open_price = find_value(values, [
f"1a. open ({market_upper})",
f"1b. open ({market_upper})",
"1a. open",
"1. open"
])
high_price = find_value(values, [
f"2a. high ({market_upper})",
f"2b. high ({market_upper})",
"2a. high",
"2. high"
])
low_price = find_value(values, [
f"3a. low ({market_upper})",
f"3b. low ({market_upper})",
"3a. low",
"3. low"
])
close_price = find_value(values, [
f"4a. close ({market_upper})",
f"4b. close ({market_upper})",
"4a. close",
"4. close"
])
out.write(
f"{date_str},"
f"{open_price},"
f"{high_price},"
f"{low_price},"
f"{close_price},"
f"{values.get('5. volume', '')}\n"
)
return out.getvalue()
return csv_text
except (json.JSONDecodeError, KeyError, Exception) as e:
# Fallback: return raw payload for debugging
return f"Error parsing crypto data: {str(e)}\n\nRaw response:\n{raw}"

View File

@ -0,0 +1,39 @@
"""Global news fetcher using Alpha Vantage NEWS_SENTIMENT with topics."""
from .alpha_vantage_common import _make_api_request, format_datetime_for_api
from datetime import datetime, timedelta
def get_global_news_alpha_vantage(curr_date: str, look_back_days: int = 7, limit: int = 15) -> str:
"""
Fetch global macroeconomic news using Alpha Vantage NEWS_SENTIMENT.
Uses broad topics to get general market and economic news.
Args:
curr_date: Current date in YYYY-mm-dd format
look_back_days: Number of days to look back (default 7)
limit: Number of articles to return (default 15)
Returns:
JSON string with news articles
"""
# Calculate start date
curr_dt = datetime.strptime(curr_date, "%Y-%m-%d")
start_dt = curr_dt - timedelta(days=look_back_days)
start_date = start_dt.strftime("%Y-%m-%d")
# Use broad topics for global news
# Combine multiple topics: economy_macro, finance, earnings, ipo, mergers_and_acquisitions
topics = "economy_macro,finance,earnings"
params = {
"topics": topics,
"time_from": format_datetime_for_api(start_date),
"time_to": format_datetime_for_api(curr_date),
"sort": "LATEST",
"limit": str(limit),
}
return _make_api_request("NEWS_SENTIMENT", params)

View File

@ -33,6 +33,55 @@ COMMODITY_KEYWORDS = {
"COFFEE": "coffee commodity",
}
# Map crypto symbols to Alpha Vantage NEWS_SENTIMENT topics
CRYPTO_TOPIC_MAP = {
# Major cryptocurrencies - use blockchain/technology topics
"BTC": "blockchain",
"ETH": "blockchain",
"BNB": "blockchain",
"XRP": "blockchain",
"ADA": "blockchain",
"SOL": "blockchain",
"DOGE": "blockchain",
"DOT": "blockchain",
"MATIC": "blockchain",
"AVAX": "blockchain",
"LINK": "blockchain",
"UNI": "blockchain",
"ATOM": "blockchain",
"LTC": "blockchain",
"BCH": "blockchain",
"XLM": "blockchain",
"ALGO": "blockchain",
"VET": "blockchain",
"ICP": "blockchain",
"FIL": "blockchain",
}
# Map crypto symbols to search keywords for better news filtering
CRYPTO_KEYWORDS = {
"BTC": "Bitcoin BTC cryptocurrency",
"ETH": "Ethereum ETH cryptocurrency",
"BNB": "Binance Coin BNB",
"XRP": "Ripple XRP",
"ADA": "Cardano ADA",
"SOL": "Solana SOL",
"DOGE": "Dogecoin DOGE",
"DOT": "Polkadot DOT",
"MATIC": "Polygon MATIC",
"AVAX": "Avalanche AVAX",
"LINK": "Chainlink LINK",
"UNI": "Uniswap UNI",
"ATOM": "Cosmos ATOM",
"LTC": "Litecoin LTC",
"BCH": "Bitcoin Cash BCH",
"XLM": "Stellar XLM",
"ALGO": "Algorand ALGO",
"VET": "VeChain VET",
"ICP": "Internet Computer ICP",
"FIL": "Filecoin FIL",
}
def get_news(ticker, start_date, end_date) -> dict[str, str] | str:
"""Returns live and historical market news & sentiment data from premier news outlets worldwide.
@ -78,12 +127,15 @@ def get_commodity_news(commodity: str, start_date: str, end_date: str) -> dict[s
keyword = COMMODITY_KEYWORDS.get(commodity_upper, commodity)
# Use topics parameter instead of tickers for commodities
from .config import get_config
limit = get_config().get("commodity_news_limit", 50)
params = {
"topics": topic,
"time_from": format_datetime_for_api(start_date),
"time_to": format_datetime_for_api(end_date),
"sort": "LATEST",
"limit": "50", # Get more results to filter for commodity-specific news
"limit": str(limit), # Get more results to filter for commodity-specific news
}
result = _make_api_request("NEWS_SENTIMENT", params)
@ -107,6 +159,56 @@ def get_commodity_news(commodity: str, start_date: str, end_date: str) -> dict[s
return result
def get_crypto_news(crypto: str, start_date: str, end_date: str) -> dict[str, str] | str:
"""Returns news data for cryptocurrencies using Alpha Vantage ticker format.
Alpha Vantage supports crypto-specific tickers in the format CRYPTO:BTC.
Args:
crypto: Cryptocurrency symbol (e.g., "BTC", "ETH", "SOL")
start_date: Start date for news search.
end_date: End date for news search.
Returns:
Dictionary containing news sentiment data or JSON string.
"""
crypto_upper = crypto.upper()
# Use CRYPTO:XXX ticker format
ticker = f"CRYPTO:{crypto_upper}"
from .config import get_config
limit = get_config().get("commodity_news_limit", 50)
params = {
"tickers": ticker,
"time_from": format_datetime_for_api(start_date),
"time_to": format_datetime_for_api(end_date),
"sort": "LATEST",
"limit": str(limit),
}
result = _make_api_request("NEWS_SENTIMENT", params)
# Add metadata to help the LLM understand this is crypto-filtered news
import json
try:
data = json.loads(result) if isinstance(result, str) else result
if isinstance(data, dict) and "feed" in data:
keyword = CRYPTO_KEYWORDS.get(crypto_upper, f"{crypto} cryptocurrency")
data["_crypto_context"] = {
"cryptocurrency": crypto,
"ticker_used": ticker,
"keyword_filter": keyword,
"note": f"News filtered by ticker '{ticker}'. Articles tagged with {ticker}."
}
return json.dumps(data)
except (json.JSONDecodeError, TypeError):
pass
return result
def get_insider_transactions(symbol: str) -> dict[str, str] | str:
"""Returns latest and historical insider transactions by key stakeholders.

View File

@ -15,7 +15,10 @@ from .alpha_vantage import (
get_insider_transactions as get_alpha_vantage_insider_transactions,
get_news as get_alpha_vantage_news,
get_commodity_news as get_alpha_vantage_commodity_news,
get_commodity as get_alpha_vantage_commodity
get_crypto_news as get_alpha_vantage_crypto_news,
get_global_news_alpha_vantage,
get_commodity as get_alpha_vantage_commodity,
get_crypto as get_alpha_vantage_crypto
)
from .alpha_vantage_common import AlphaVantageRateLimitError
@ -36,6 +39,12 @@ TOOLS_CATEGORIES = {
"get_commodity_data"
]
},
"crypto_data": {
"description": "Cryptocurrency price data",
"tools": [
"get_crypto_data"
]
},
"technical_indicators": {
"description": "Technical analysis indicators",
"tools": [
@ -55,6 +64,8 @@ TOOLS_CATEGORIES = {
"description": "News (public/insiders, original/processed)",
"tools": [
"get_news",
"get_commodity_news",
"get_crypto_news",
"get_global_news",
"get_insider_sentiment",
"get_insider_transactions",
@ -81,6 +92,10 @@ VENDOR_METHODS = {
"get_commodity_data": {
"alpha_vantage": get_alpha_vantage_commodity,
},
# crypto_data
"get_crypto_data": {
"alpha_vantage": get_alpha_vantage_crypto,
},
# technical_indicators
"get_indicators": {
"alpha_vantage": get_alpha_vantage_indicator,
@ -118,7 +133,12 @@ VENDOR_METHODS = {
"alpha_vantage": get_alpha_vantage_commodity_news,
"openai": get_stock_news_openai, # Fallback to OpenAI web search
},
"get_crypto_news": {
"alpha_vantage": get_alpha_vantage_crypto_news,
"openai": get_stock_news_openai, # Fallback to OpenAI web search
},
"get_global_news": {
"alpha_vantage": get_global_news_alpha_vantage,
"openai": get_global_news_openai,
"local": get_reddit_global_news
},

View File

@ -1,8 +1,83 @@
import os
import configparser
from pathlib import Path
DEFAULT_CONFIG = {
def _load_user_config():
"""Load configuration from config.ini if it exists."""
config_path = Path(__file__).parent.parent / "config.ini"
if not config_path.exists():
return {}
parser = configparser.ConfigParser()
parser.read(config_path)
user_config = {}
# LLM settings
if parser.has_section("llm"):
user_config["llm_provider"] = parser.get("llm", "provider", fallback=None)
user_config["backend_url"] = parser.get("llm", "backend_url", fallback=None)
user_config["deep_think_llm"] = parser.get("llm", "deep_think_llm", fallback=None)
user_config["quick_think_llm"] = parser.get("llm", "quick_think_llm", fallback=None)
# Analysis settings
if parser.has_section("analysis"):
depth = parser.get("analysis", "research_depth", fallback=None)
if depth:
user_config["max_debate_rounds"] = int(depth)
user_config["max_risk_discuss_rounds"] = int(depth)
analysts = parser.get("analysis", "default_analysts", fallback=None)
if analysts:
user_config["default_analysts"] = [a.strip() for a in analysts.split(",")]
# Data settings
if parser.has_section("data"):
limit = parser.get("data", "global_news_limit", fallback=None)
if limit:
user_config["global_news_limit"] = int(limit)
commodity_limit = parser.get("data", "commodity_news_limit", fallback=None)
if commodity_limit:
user_config["commodity_news_limit"] = int(commodity_limit)
# Vendor settings
if parser.has_section("vendors"):
vendors = {}
if parser.has_option("vendors", "stock"):
vendors["core_stock_apis"] = parser.get("vendors", "stock")
if parser.has_option("vendors", "indicators"):
vendors["technical_indicators"] = parser.get("vendors", "indicators")
if parser.has_option("vendors", "fundamentals"):
vendors["fundamental_data"] = parser.get("vendors", "fundamentals")
if parser.has_option("vendors", "news"):
vendors["news_data"] = parser.get("vendors", "news")
if parser.has_option("vendors", "commodity"):
vendors["commodity_data"] = parser.get("vendors", "commodity")
if parser.has_option("vendors", "crypto"):
vendors["crypto_data"] = parser.get("vendors", "crypto")
if vendors:
user_config["data_vendors"] = vendors
# Storage settings
if parser.has_section("storage"):
results = parser.get("storage", "results_dir", fallback=None)
if results:
user_config["results_dir"] = results
# Remove None values
return {k: v for k, v in user_config.items() if v is not None}
# Load user config from config.ini
_user_config = _load_user_config()
# Base defaults
_base_config = {
"project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
"results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR", "./results"),
"results_dir": "./results",
"data_dir": "/Users/yluo/Documents/Code/ScAI/FR1-data",
"data_cache_dir": os.path.join(
os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
@ -10,7 +85,7 @@ DEFAULT_CONFIG = {
),
# LLM settings
"llm_provider": "openai",
"deep_think_llm": "o4-mini",
"deep_think_llm": "o1-mini",
"quick_think_llm": "gpt-4o-mini",
"backend_url": "https://api.openai.com/v1",
# Asset class (equity | commodity)
@ -19,18 +94,43 @@ DEFAULT_CONFIG = {
"max_debate_rounds": 1,
"max_risk_discuss_rounds": 1,
"max_recur_limit": 100,
# News limits
"global_news_limit": 15,
"commodity_news_limit": 50,
# Data vendor configuration
# Category-level configuration (default for all tools in category)
"data_vendors": {
"core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local
"technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local
"fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local
"news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local
"commodity_data": "alpha_vantage", # Options: alpha_vantage
"core_stock_apis": "yfinance",
"technical_indicators": "yfinance",
"fundamental_data": "alpha_vantage",
"news_data": "alpha_vantage",
"commodity_data": "alpha_vantage",
"crypto_data": "alpha_vantage",
},
# Tool-level configuration (takes precedence over category-level)
"tool_vendors": {
# Example: "get_stock_data": "alpha_vantage", # Override category default
# Example: "get_news": "openai", # Override category default
},
"tool_vendors": {},
}
# Merge with environment variables
_env_overrides = {
"llm_provider": os.getenv("LLM_PROVIDER"),
"deep_think_llm": os.getenv("DEEP_THINK_LLM"),
"quick_think_llm": os.getenv("QUICK_THINK_LLM"),
"backend_url": os.getenv("LLM_BACKEND_URL"),
"results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR"),
"global_news_limit": os.getenv("GLOBAL_NEWS_LIMIT"),
"commodity_news_limit": os.getenv("COMMODITY_NEWS_LIMIT"),
}
_env_overrides = {k: v for k, v in _env_overrides.items() if v is not None}
# Convert string numbers to int
if "global_news_limit" in _env_overrides:
_env_overrides["global_news_limit"] = int(_env_overrides["global_news_limit"])
if "commodity_news_limit" in _env_overrides:
_env_overrides["commodity_news_limit"] = int(_env_overrides["commodity_news_limit"])
# Priority: env vars > config.ini > base defaults
DEFAULT_CONFIG = {**_base_config, **_user_config, **_env_overrides}
# Update data_vendors if user config has partial vendors
if "data_vendors" in _user_config:
DEFAULT_CONFIG["data_vendors"] = {**_base_config["data_vendors"], **_user_config["data_vendors"]}

View File

@ -22,21 +22,19 @@ from tradingagents.agents.utils.agent_states import (
)
from tradingagents.dataflows.config import set_config
# Import the new abstract tool methods from agent_utils
# Import unified tools from agent_utils
from tradingagents.agents.utils.agent_utils import (
get_stock_data,
get_market_data,
get_indicators,
get_asset_news,
get_global_news_unified as get_global_news,
get_fundamentals,
get_balance_sheet,
get_cashflow,
get_income_statement,
get_news,
get_commodity_news,
get_insider_sentiment,
get_insider_transactions,
get_global_news
)
from tradingagents.agents.utils.commodity_data_tools import get_commodity_data
from .conditional_logic import ConditionalLogic
from .setup import GraphSetup
@ -123,16 +121,21 @@ class TradingAgentsGraph:
self.graph = self.graph_setup.setup_graph(selected_analysts)
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
"""Create tool nodes for different data sources using centralized config."""
from tradingagents.agents.config import get_analyst_config
analyst_config = get_analyst_config(self.config.get("asset_class", "equity"))
"""Create tool nodes using unified tools that work across all asset classes."""
# Unified tools automatically route based on asset_class context
# No more conditional logic needed here!
return {
"market": ToolNode(analyst_config.get_tools_for_analyst("market")),
"social": ToolNode(analyst_config.get_tools_for_analyst("social")),
"news": ToolNode(analyst_config.get_tools_for_analyst("news")),
"fundamentals": ToolNode(analyst_config.get_tools_for_analyst("fundamentals")),
"market": ToolNode([get_market_data, get_indicators]),
"social": ToolNode([get_asset_news, get_global_news]),
"news": ToolNode([get_asset_news, get_global_news]),
"fundamentals": ToolNode([
get_fundamentals,
get_balance_sheet,
get_cashflow,
get_income_statement,
get_insider_sentiment,
get_insider_transactions,
]),
}
def propagate(self, company_name, trade_date):