This commit is contained in:
Alex Korbonits 2026-04-12 22:56:54 -07:00 committed by GitHub
commit 0daa97a4ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 596 additions and 1662 deletions

17
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Lint
on:
push:
branches: [main]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv python install 3.10
- run: uv pip install ruff
- run: uv run ruff check .
- run: uv run ruff format --check .

7
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.2
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

View File

@ -1,4 +1,5 @@
import getpass
import requests
from rich.console import Console
from rich.panel import Panel

View File

@ -1,34 +1,41 @@
from typing import Optional
import datetime
import typer
from pathlib import Path
import time
from collections import deque
from functools import wraps
from rich.console import Console
from pathlib import Path
import typer
from dotenv import load_dotenv
from rich import box
from rich.align import Align
from rich.console import Console
from rich.layout import Layout
from rich.live import Live
from rich.markdown import Markdown
from rich.panel import Panel
from rich.rule import Rule
from rich.spinner import Spinner
from rich.table import Table
from rich.text import Text
from cli.announcements import display_announcements, fetch_announcements
from cli.stats_handler import StatsCallbackHandler
from cli.utils import (
ask_anthropic_effort,
ask_gemini_thinking_config,
ask_openai_reasoning_effort,
ask_output_language,
select_analysts,
select_deep_thinking_agent,
select_llm_provider,
select_research_depth,
select_shallow_thinking_agent,
)
from tradingagents.default_config import DEFAULT_CONFIG
from tradingagents.graph.trading_graph import TradingAgentsGraph
# Load environment variables from .env file
load_dotenv()
from rich.panel import Panel
from rich.spinner import Spinner
from rich.live import Live
from rich.columns import Columns
from rich.markdown import Markdown
from rich.layout import Layout
from rich.text import Text
from rich.table import Table
from collections import deque
import time
from rich.tree import Tree
from rich import box
from rich.align import Align
from rich.rule import Rule
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG
from cli.models import AnalystType
from cli.utils import *
from cli.announcements import fetch_announcements, display_announcements
from cli.stats_handler import StatsCallbackHandler
console = Console()
@ -469,7 +476,9 @@ def get_user_selections():
welcome_content = f"{welcome_ascii}\n"
welcome_content += "[bold green]TradingAgents: Multi-Agents LLM Financial Trading Framework - CLI[/bold green]\n\n"
welcome_content += "[bold]Workflow Steps:[/bold]\n"
welcome_content += "I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management\n\n"
welcome_content += (
"I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management\n\n"
)
welcome_content += (
"[dim]Built by [Tauric Research](https://github.com/TauricResearch)[/dim]"
)
@ -502,7 +511,8 @@ def get_user_selections():
console.print(
create_question_box(
"Step 1: Ticker Symbol",
"Enter the exact ticker symbol to analyze, including exchange suffix when needed (examples: SPY, CNC.TO, 7203.T, 0700.HK)",
"Enter the exact ticker symbol to analyze, including exchange suffix when needed"
" (examples: SPY, CNC.TO, 7203.T, 0700.HK)",
"SPY",
)
)
@ -720,7 +730,8 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}")
# Write consolidated report
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
generated = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {generated}\n\n"
(save_path / "complete_report.md").write_text(header + "\n\n".join(sections))
return save_path / "complete_report.md"
@ -763,7 +774,10 @@ def display_complete_report(final_state):
# III. Trading Team
if final_state.get("trader_investment_plan"):
console.print(Panel("[bold]III. Trading Team Plan[/bold]", border_style="yellow"))
console.print(Panel(Markdown(final_state["trader_investment_plan"]), title="Trader", border_style="blue", padding=(1, 2)))
console.print(Panel(
Markdown(final_state["trader_investment_plan"]),
title="Trader", border_style="blue", padding=(1, 2),
))
# IV. Risk Management Team
if final_state.get("risk_debate_state"):
@ -783,7 +797,10 @@ def display_complete_report(final_state):
# V. Portfolio Manager Decision
if risk.get("judge_decision"):
console.print(Panel("[bold]V. Portfolio Manager Decision[/bold]", border_style="green"))
console.print(Panel(Markdown(risk["judge_decision"]), title="Portfolio Manager", border_style="blue", padding=(1, 2)))
console.print(Panel(
Markdown(risk["judge_decision"]),
title="Portfolio Manager", border_style="blue", padding=(1, 2),
))
def update_research_team_status(status):
@ -1015,7 +1032,7 @@ def run_analysis():
# Now start the display layout
layout = create_layout()
with Live(layout, refresh_per_second=4) as live:
with Live(layout, refresh_per_second=4):
# Initial display
update_display(layout, stats_handler=stats_handler, start_time=start_time)
@ -1154,9 +1171,7 @@ def run_analysis():
trace.append(chunk)
# Get final state and decision
final_state = trace[-1]
decision = graph.process_signal(final_state["final_trade_decision"])
# Update all agent statuses to completed
for agent in message_buffer.agent_status:

View File

@ -1,6 +1,4 @@
from enum import Enum
from typing import List, Optional, Dict
from pydantic import BaseModel
class AnalystType(str, Enum):

View File

@ -1,9 +1,9 @@
import threading
from typing import Any, Dict, List, Union
from typing import Any, Dict, List
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult
from langchain_core.messages import AIMessage
from langchain_core.outputs import LLMResult
class StatsCallbackHandler(BaseCallbackHandler):

View File

@ -1,6 +1,6 @@
import questionary
from typing import List, Optional, Tuple, Dict
from typing import List, Tuple
import questionary
from rich.console import Console
from cli.models import AnalystType
@ -83,7 +83,11 @@ def select_analysts() -> List[AnalystType]:
choices=[
questionary.Choice(display, value=value) for display, value in ANALYST_ORDER
],
instruction="\n- Press Space to select/unselect analysts\n- Press 'a' to select/unselect all\n- Press Enter when done",
instruction=(
"\n- Press Space to select/unselect analysts"
"\n- Press 'a' to select/unselect all"
"\n- Press Enter when done"
),
validate=lambda x: len(x) > 0 or "You must select at least one analyst.",
style=questionary.Style(
[

View File

@ -1,8 +1,8 @@
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG
from dotenv import load_dotenv
from tradingagents.default_config import DEFAULT_CONFIG
from tradingagents.graph.trading_graph import TradingAgentsGraph
# Load environment variables from .env file
load_dotenv()

View File

@ -40,3 +40,32 @@ include = ["tradingagents*", "cli*"]
[tool.setuptools.package-data]
cli = ["static/*"]
[tool.ruff]
line-length = 120
[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = ["E731"]
[tool.ruff.lint.isort]
combine-as-imports = true
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
# Agent files contain LLM system prompt strings that are intentionally long.
"tradingagents/agents/**/*.py" = ["E501"]
# These dataflow files contain long indicator/tool description strings, not logic code.
"tradingagents/dataflows/alpha_vantage_indicator.py" = ["E501"]
"tradingagents/dataflows/y_finance.py" = ["E501"]
# Reflection module contains a multi-line LLM prompt defined as a string constant.
"tradingagents/graph/reflection.py" = ["E501"]
[tool.uv]
[dependency-groups]
dev = [
"pytest>=8.3.0",
"ruff>=0.9.0",
]
# Install with: uv sync

View File

@ -1,5 +1,6 @@
import time
from tradingagents.dataflows.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 tradingagents.dataflows.y_finance import get_stock_stats_indicators_window
print("Testing optimized implementation with 30-day lookback:")
start_time = time.time()

View File

@ -1,2 +1,3 @@
import os
os.environ.setdefault("PYTHONUTF8", "1")

View File

@ -1,23 +1,18 @@
from .utils.agent_utils import create_msg_delete
from .utils.agent_states import AgentState, InvestDebateState, RiskDebateState
from .utils.memory import FinancialSituationMemory
from .analysts.fundamentals_analyst import create_fundamentals_analyst
from .analysts.market_analyst import create_market_analyst
from .analysts.news_analyst import create_news_analyst
from .analysts.social_media_analyst import create_social_media_analyst
from .managers.portfolio_manager import create_portfolio_manager
from .managers.research_manager import create_research_manager
from .researchers.bear_researcher import create_bear_researcher
from .researchers.bull_researcher import create_bull_researcher
from .risk_mgmt.aggressive_debator import create_aggressive_debator
from .risk_mgmt.conservative_debator import create_conservative_debator
from .risk_mgmt.neutral_debator import create_neutral_debator
from .managers.research_manager import create_research_manager
from .managers.portfolio_manager import create_portfolio_manager
from .trader.trader import create_trader
from .utils.agent_states import AgentState, InvestDebateState, RiskDebateState
from .utils.agent_utils import create_msg_delete
from .utils.memory import FinancialSituationMemory
__all__ = [
"FinancialSituationMemory",

View File

@ -1,14 +1,13 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from tradingagents.agents.utils.agent_utils import (
build_instrument_context,
get_balance_sheet,
get_cashflow,
get_fundamentals,
get_income_statement,
get_insider_transactions,
get_language_instruction,
)
from tradingagents.dataflows.config import get_config
def create_fundamentals_analyst(llm):

View File

@ -1,11 +1,11 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from tradingagents.agents.utils.agent_utils import (
build_instrument_context,
get_indicators,
get_language_instruction,
get_stock_data,
)
from tradingagents.dataflows.config import get_config
def create_market_analyst(llm):

View File

@ -1,11 +1,11 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from tradingagents.agents.utils.agent_utils import (
build_instrument_context,
get_global_news,
get_language_instruction,
get_news,
)
from tradingagents.dataflows.config import get_config
def create_news_analyst(llm):

View File

@ -1,6 +1,6 @@
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction, get_news
from tradingagents.dataflows.config import get_config
def create_social_media_analyst(llm):

View File

@ -1,6 +1,7 @@
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import MessagesState
from typing_extensions import TypedDict
# Researcher team state

View File

@ -1,23 +1,34 @@
from langchain_core.messages import HumanMessage, RemoveMessage
# Import tools from separate utility files
from tradingagents.agents.utils.core_stock_tools import (
get_stock_data
)
from tradingagents.agents.utils.technical_indicators_tools import (
get_indicators
)
# Re-export tool functions so agent modules can import from a single location.
from tradingagents.agents.utils.core_stock_tools import get_stock_data
from tradingagents.agents.utils.fundamental_data_tools import (
get_fundamentals,
get_balance_sheet,
get_cashflow,
get_income_statement
get_fundamentals,
get_income_statement,
)
from tradingagents.agents.utils.news_data_tools import (
get_news,
get_global_news,
get_insider_transactions,
get_global_news
get_news,
)
from tradingagents.agents.utils.technical_indicators_tools import get_indicators
__all__ = [
"get_stock_data",
"get_indicators",
"get_fundamentals",
"get_balance_sheet",
"get_cashflow",
"get_income_statement",
"get_news",
"get_insider_transactions",
"get_global_news",
"get_language_instruction",
"build_instrument_context",
"create_msg_delete",
]
def get_language_instruction() -> str:

View File

@ -1,5 +1,7 @@
from langchain_core.tools import tool
from typing import Annotated
from langchain_core.tools import tool
from tradingagents.dataflows.interface import route_to_vendor

View File

@ -1,5 +1,7 @@
from langchain_core.tools import tool
from typing import Annotated
from langchain_core.tools import tool
from tradingagents.dataflows.interface import route_to_vendor

View File

@ -4,9 +4,10 @@ Uses BM25 (Best Matching 25) algorithm for retrieval - no API calls,
no token limits, works offline with any LLM provider.
"""
from rank_bm25 import BM25Okapi
from typing import List, Tuple
import re
from typing import List, Tuple
from rank_bm25 import BM25Okapi
class FinancialSituationMemory:

View File

@ -1,7 +1,10 @@
from langchain_core.tools import tool
from typing import Annotated
from langchain_core.tools import tool
from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_news(
ticker: Annotated[str, "Ticker symbol"],

View File

@ -1,7 +1,10 @@
from langchain_core.tools import tool
from typing import Annotated
from langchain_core.tools import tool
from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_indicators(
symbol: Annotated[str, "ticker symbol of the company"],

View File

@ -1,5 +1,26 @@
# Import functions from specialized modules
from .alpha_vantage_stock import get_stock
from .alpha_vantage_fundamentals import (
get_balance_sheet,
get_cashflow,
get_fundamentals,
get_income_statement,
)
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_global_news, get_insider_transactions
from .alpha_vantage_news import (
get_global_news,
get_insider_transactions,
get_news,
)
from .alpha_vantage_stock import get_stock
__all__ = [
"get_stock",
"get_indicator",
"get_fundamentals",
"get_balance_sheet",
"get_cashflow",
"get_income_statement",
"get_news",
"get_global_news",
"get_insider_transactions",
]

View File

@ -1,10 +1,11 @@
import os
import requests
import pandas as pd
import json
import os
from datetime import datetime
from io import StringIO
import pandas as pd
import requests
API_BASE_URL = "https://www.alphavantage.co/query"
def get_api_key() -> str:

View File

@ -1,5 +1,6 @@
from .alpha_vantage_common import _make_api_request
def get_indicator(
symbol: str,
indicator: str,
@ -25,6 +26,7 @@ def get_indicator(
String containing indicator values and description
"""
from datetime import datetime
from dateutil.relativedelta import relativedelta
supported_indicators = {

View File

@ -1,5 +1,6 @@
from .alpha_vantage_common import _make_api_request, format_datetime_for_api
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.

View File

@ -1,5 +1,7 @@
from datetime import datetime
from .alpha_vantage_common import _make_api_request, _filter_csv_by_date_range
from .alpha_vantage_common import _filter_csv_by_date_range, _make_api_request
def get_stock(
symbol: str,

View File

@ -1,6 +1,7 @@
import tradingagents.default_config as default_config
from typing import Dict, Optional
import tradingagents.default_config as default_config
# Use default config but allow it to be overridden
_config: Optional[Dict] = None

View File

@ -1,31 +1,30 @@
from typing import Annotated
# 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 .alpha_vantage import (
get_stock as get_alpha_vantage_stock,
get_indicator as get_alpha_vantage_indicator,
get_fundamentals as get_alpha_vantage_fundamentals,
get_balance_sheet as get_alpha_vantage_balance_sheet,
get_cashflow as get_alpha_vantage_cashflow,
get_fundamentals as get_alpha_vantage_fundamentals,
get_global_news as get_alpha_vantage_global_news,
get_income_statement as get_alpha_vantage_income_statement,
get_indicator as get_alpha_vantage_indicator,
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,
get_stock as get_alpha_vantage_stock,
)
from .alpha_vantage_common import AlphaVantageRateLimitError
# Configuration and routing logic
from .config import get_config
from .y_finance import (
get_balance_sheet as get_yfinance_balance_sheet,
get_cashflow as get_yfinance_cashflow,
get_fundamentals as get_yfinance_fundamentals,
get_income_statement as get_yfinance_income_statement,
get_insider_transactions as get_yfinance_insider_transactions,
get_stock_stats_indicators_window,
get_YFin_data_online,
)
from .yfinance_news import get_global_news_yfinance, get_news_yfinance
# Tools organized by category
TOOLS_CATEGORIES = {

View File

@ -1,12 +1,13 @@
import time
import logging
import os
import time
from typing import Annotated
import pandas as pd
import yfinance as yf
from yfinance.exceptions import YFRateLimitError
from stockstats import wrap
from typing import Annotated
import os
from yfinance.exceptions import YFRateLimitError
from .config import get_config
logger = logging.getLogger(__name__)
@ -25,7 +26,10 @@ def yf_retry(func, max_retries=3, base_delay=2.0):
except YFRateLimitError:
if attempt < max_retries:
delay = base_delay * (2 ** attempt)
logger.warning(f"Yahoo Finance rate limited, retrying in {delay:.0f}s (attempt {attempt + 1}/{max_retries})")
logger.warning(
f"Yahoo Finance rate limited, retrying in {delay:.0f}s"
f" (attempt {attempt + 1}/{max_retries})"
)
time.sleep(delay)
else:
raise

View File

@ -1,9 +1,8 @@
import os
import json
import pandas as pd
from datetime import date, timedelta, datetime
from datetime import date, datetime, timedelta
from typing import Annotated
import pandas as pd
SavePathType = Annotated[str, "File path to save data. If None, data is not saved."]
def save_output(data: pd.DataFrame, tag: str, save_path: SavePathType = None) -> None:

View File

@ -1,10 +1,12 @@
from typing import Annotated
from datetime import datetime
from dateutil.relativedelta import relativedelta
from typing import Annotated
import pandas as pd
import yfinance as yf
import os
from .stockstats_utils import StockstatsUtils, _clean_dataframe, yf_retry, load_ohlcv, filter_financials_by_date
from dateutil.relativedelta import relativedelta
from .stockstats_utils import StockstatsUtils, filter_financials_by_date, load_ohlcv, yf_retry
def get_YFin_data_online(
symbol: Annotated[str, "ticker symbol of the company"],

View File

@ -1,7 +1,8 @@
"""yfinance-based news data fetching functions."""
import yfinance as yf
from datetime import datetime
import yfinance as yf
from dateutil.relativedelta import relativedelta
from .stockstats_utils import yf_retry
@ -171,7 +172,11 @@ def get_global_news_yfinance(
data = _extract_article_data(article)
# Skip articles published after curr_date (look-ahead guard)
if data.get("pub_date"):
pub_naive = data["pub_date"].replace(tzinfo=None) if hasattr(data["pub_date"], "replace") else data["pub_date"]
pub_naive = (
data["pub_date"].replace(tzinfo=None)
if hasattr(data["pub_date"], "replace")
else data["pub_date"]
)
if pub_naive > curr_dt + relativedelta(days=1):
continue
title = data["title"]

View File

@ -1,11 +1,11 @@
# TradingAgents/graph/__init__.py
from .trading_graph import TradingAgentsGraph
from .conditional_logic import ConditionalLogic
from .setup import GraphSetup
from .propagation import Propagator
from .reflection import Reflector
from .setup import GraphSetup
from .signal_processing import SignalProcessor
from .trading_graph import TradingAgentsGraph
__all__ = [
"TradingAgentsGraph",

View File

@ -1,8 +1,8 @@
# TradingAgents/graph/propagation.py
from typing import Dict, Any, List, Optional
from typing import Any, Dict, List, Optional
from tradingagents.agents.utils.agent_states import (
AgentState,
InvestDebateState,
RiskDebateState,
)

View File

@ -1,10 +1,25 @@
# TradingAgents/graph/setup.py
from typing import Any, Dict
from langgraph.graph import END, START, StateGraph
from langgraph.prebuilt import ToolNode
from tradingagents.agents import *
from tradingagents.agents import (
create_aggressive_debator,
create_bear_researcher,
create_bull_researcher,
create_conservative_debator,
create_fundamentals_analyst,
create_market_analyst,
create_msg_delete,
create_neutral_debator,
create_news_analyst,
create_portfolio_manager,
create_research_manager,
create_social_media_analyst,
create_trader,
)
from tradingagents.agents.utils.agent_states import AgentState
from .conditional_logic import ConditionalLogic

View File

@ -1,42 +1,33 @@
# TradingAgents/graph/trading_graph.py
import json
import os
from pathlib import Path
import json
from datetime import date
from typing import Dict, Any, Tuple, List, Optional
from typing import Any, Dict, List, Optional
from langgraph.prebuilt import ToolNode
from tradingagents.llm_clients import create_llm_client
from tradingagents.agents import *
from tradingagents.default_config import DEFAULT_CONFIG
from tradingagents.agents.utils.memory import FinancialSituationMemory
from tradingagents.agents.utils.agent_states import (
AgentState,
InvestDebateState,
RiskDebateState,
)
from tradingagents.dataflows.config import set_config
# Import the new abstract tool methods from agent_utils
from tradingagents.agents.utils.agent_utils import (
get_stock_data,
get_indicators,
get_fundamentals,
get_balance_sheet,
get_cashflow,
get_fundamentals,
get_global_news,
get_income_statement,
get_news,
get_indicators,
get_insider_transactions,
get_global_news
get_news,
get_stock_data,
)
from tradingagents.agents.utils.memory import FinancialSituationMemory
from tradingagents.dataflows.config import set_config
from tradingagents.default_config import DEFAULT_CONFIG
from tradingagents.llm_clients import create_llm_client
from .conditional_logic import ConditionalLogic
from .setup import GraphSetup
from .propagation import Propagator
from .reflection import Reflector
from .setup import GraphSetup
from .signal_processing import SignalProcessor

View File

@ -1,6 +1,6 @@
import warnings
from abc import ABC, abstractmethod
from typing import Any, Optional
import warnings
def normalize_content(response):

View File

@ -1,9 +1,9 @@
from typing import Optional
from .base_client import BaseLLMClient
from .openai_client import OpenAIClient
from .anthropic_client import AnthropicClient
from .base_client import BaseLLMClient
from .google_client import GoogleClient
from .openai_client import OpenAIClient
def create_llm_client(

View File

@ -2,7 +2,6 @@
from .model_catalog import get_known_models
VALID_MODELS = {
provider: models
for provider, models in get_known_models().items()

1836
uv.lock

File diff suppressed because it is too large Load Diff