style: correções automáticas pre-commit
This commit is contained in:
parent
020f51a146
commit
cefe0a12b2
|
|
@ -0,0 +1,37 @@
|
||||||
|
name: Python CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v2
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: "uv.lock"
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version-file: ".python-version"
|
||||||
|
|
||||||
|
- name: Install the project
|
||||||
|
run: uv sync --all-extras --dev
|
||||||
|
|
||||||
|
- name: Format with Black
|
||||||
|
run: uv run black --check .
|
||||||
|
|
||||||
|
- name: Lint with Ruff
|
||||||
|
run: uv run ruff check .
|
||||||
|
|
||||||
|
# - name: Run tests (Uncomment when tests exist)
|
||||||
|
# run: uv run pytest
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 24.2.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
language_version: python3.13
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.3.0
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
args: [ --fix ]
|
||||||
303
cli/main.py
303
cli/main.py
|
|
@ -1,34 +1,39 @@
|
||||||
from typing import Optional
|
|
||||||
import datetime
|
import datetime
|
||||||
import typer
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from rich.console import Console
|
from collections import deque
|
||||||
|
|
||||||
|
import typer
|
||||||
from dotenv import load_dotenv
|
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 tradingagents.default_config import DEFAULT_CONFIG
|
||||||
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||||
|
from cli.announcements import display_announcements, fetch_announcements
|
||||||
|
from cli.stats_handler import StatsCallbackHandler
|
||||||
|
from cli.utils import (
|
||||||
|
ask_gemini_thinking_config,
|
||||||
|
ask_openai_reasoning_effort,
|
||||||
|
select_analysts,
|
||||||
|
select_deep_thinking_agent,
|
||||||
|
select_llm_provider,
|
||||||
|
select_research_depth,
|
||||||
|
select_shallow_thinking_agent,
|
||||||
|
)
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
load_dotenv()
|
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()
|
console = Console()
|
||||||
|
|
||||||
|
|
@ -45,7 +50,11 @@ class MessageBuffer:
|
||||||
FIXED_AGENTS = {
|
FIXED_AGENTS = {
|
||||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||||
"Trading Team": ["Trader"],
|
"Trading Team": ["Trader"],
|
||||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
"Risk Management": [
|
||||||
|
"Aggressive Analyst",
|
||||||
|
"Neutral Analyst",
|
||||||
|
"Conservative Analyst",
|
||||||
|
],
|
||||||
"Portfolio Management": ["Portfolio Manager"],
|
"Portfolio Management": ["Portfolio Manager"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +197,12 @@ class MessageBuffer:
|
||||||
report_parts = []
|
report_parts = []
|
||||||
|
|
||||||
# Analyst Team Reports - use .get() to handle missing sections
|
# Analyst Team Reports - use .get() to handle missing sections
|
||||||
analyst_sections = ["market_report", "sentiment_report", "news_report", "fundamentals_report"]
|
analyst_sections = [
|
||||||
|
"market_report",
|
||||||
|
"sentiment_report",
|
||||||
|
"news_report",
|
||||||
|
"fundamentals_report",
|
||||||
|
]
|
||||||
if any(self.report_sections.get(section) for section in analyst_sections):
|
if any(self.report_sections.get(section) for section in analyst_sections):
|
||||||
report_parts.append("## Analyst Team Reports")
|
report_parts.append("## Analyst Team Reports")
|
||||||
if self.report_sections.get("market_report"):
|
if self.report_sections.get("market_report"):
|
||||||
|
|
@ -289,7 +303,11 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non
|
||||||
],
|
],
|
||||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||||
"Trading Team": ["Trader"],
|
"Trading Team": ["Trader"],
|
||||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
"Risk Management": [
|
||||||
|
"Aggressive Analyst",
|
||||||
|
"Neutral Analyst",
|
||||||
|
"Conservative Analyst",
|
||||||
|
],
|
||||||
"Portfolio Management": ["Portfolio Manager"],
|
"Portfolio Management": ["Portfolio Manager"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -538,9 +556,7 @@ def get_user_selections():
|
||||||
|
|
||||||
# Step 5: OpenAI backend
|
# Step 5: OpenAI backend
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box("Step 5: OpenAI backend", "Select which service to talk to")
|
||||||
"Step 5: OpenAI backend", "Select which service to talk to"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
selected_llm_provider, backend_url = select_llm_provider()
|
selected_llm_provider, backend_url = select_llm_provider()
|
||||||
|
|
||||||
|
|
@ -561,16 +577,14 @@ def get_user_selections():
|
||||||
if provider_lower == "google":
|
if provider_lower == "google":
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 7: Thinking Mode",
|
"Step 7: Thinking Mode", "Configure Gemini thinking mode"
|
||||||
"Configure Gemini thinking mode"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
thinking_level = ask_gemini_thinking_config()
|
thinking_level = ask_gemini_thinking_config()
|
||||||
elif provider_lower == "openai":
|
elif provider_lower == "openai":
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 7: Reasoning Effort",
|
"Step 7: Reasoning Effort", "Configure OpenAI reasoning effort level"
|
||||||
"Configure OpenAI reasoning effort level"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reasoning_effort = ask_openai_reasoning_effort()
|
reasoning_effort = ask_openai_reasoning_effort()
|
||||||
|
|
@ -635,8 +649,12 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
analyst_parts.append(("News Analyst", final_state["news_report"]))
|
analyst_parts.append(("News Analyst", final_state["news_report"]))
|
||||||
if final_state.get("fundamentals_report"):
|
if final_state.get("fundamentals_report"):
|
||||||
analysts_dir.mkdir(exist_ok=True)
|
analysts_dir.mkdir(exist_ok=True)
|
||||||
(analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"])
|
(analysts_dir / "fundamentals.md").write_text(
|
||||||
analyst_parts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
final_state["fundamentals_report"]
|
||||||
|
)
|
||||||
|
analyst_parts.append(
|
||||||
|
("Fundamentals Analyst", final_state["fundamentals_report"])
|
||||||
|
)
|
||||||
if analyst_parts:
|
if analyst_parts:
|
||||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts)
|
content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts)
|
||||||
sections.append(f"## I. Analyst Team Reports\n\n{content}")
|
sections.append(f"## I. Analyst Team Reports\n\n{content}")
|
||||||
|
|
@ -659,7 +677,9 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
(research_dir / "manager.md").write_text(debate["judge_decision"])
|
(research_dir / "manager.md").write_text(debate["judge_decision"])
|
||||||
research_parts.append(("Research Manager", debate["judge_decision"]))
|
research_parts.append(("Research Manager", debate["judge_decision"]))
|
||||||
if research_parts:
|
if research_parts:
|
||||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in research_parts)
|
content = "\n\n".join(
|
||||||
|
f"### {name}\n{text}" for name, text in research_parts
|
||||||
|
)
|
||||||
sections.append(f"## II. Research Team Decision\n\n{content}")
|
sections.append(f"## II. Research Team Decision\n\n{content}")
|
||||||
|
|
||||||
# 3. Trading
|
# 3. Trading
|
||||||
|
|
@ -667,7 +687,9 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
trading_dir = save_path / "3_trading"
|
trading_dir = save_path / "3_trading"
|
||||||
trading_dir.mkdir(exist_ok=True)
|
trading_dir.mkdir(exist_ok=True)
|
||||||
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"])
|
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"])
|
||||||
sections.append(f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}")
|
sections.append(
|
||||||
|
f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}"
|
||||||
|
)
|
||||||
|
|
||||||
# 4. Risk Management
|
# 4. Risk Management
|
||||||
if final_state.get("risk_debate_state"):
|
if final_state.get("risk_debate_state"):
|
||||||
|
|
@ -695,7 +717,9 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
||||||
portfolio_dir = save_path / "5_portfolio"
|
portfolio_dir = save_path / "5_portfolio"
|
||||||
portfolio_dir.mkdir(exist_ok=True)
|
portfolio_dir.mkdir(exist_ok=True)
|
||||||
(portfolio_dir / "decision.md").write_text(risk["judge_decision"])
|
(portfolio_dir / "decision.md").write_text(risk["judge_decision"])
|
||||||
sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}")
|
sections.append(
|
||||||
|
f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}"
|
||||||
|
)
|
||||||
|
|
||||||
# Write consolidated report
|
# Write consolidated report
|
||||||
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
|
@ -719,9 +743,15 @@ def display_complete_report(final_state):
|
||||||
if final_state.get("fundamentals_report"):
|
if final_state.get("fundamentals_report"):
|
||||||
analysts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
analysts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
||||||
if analysts:
|
if analysts:
|
||||||
console.print(Panel("[bold]I. Analyst Team Reports[/bold]", border_style="cyan"))
|
console.print(
|
||||||
|
Panel("[bold]I. Analyst Team Reports[/bold]", border_style="cyan")
|
||||||
|
)
|
||||||
for title, content in analysts:
|
for title, content in analysts:
|
||||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(content), title=title, border_style="blue", padding=(1, 2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# II. Research Team Reports
|
# II. Research Team Reports
|
||||||
if final_state.get("investment_debate_state"):
|
if final_state.get("investment_debate_state"):
|
||||||
|
|
@ -734,14 +764,32 @@ def display_complete_report(final_state):
|
||||||
if debate.get("judge_decision"):
|
if debate.get("judge_decision"):
|
||||||
research.append(("Research Manager", debate["judge_decision"]))
|
research.append(("Research Manager", debate["judge_decision"]))
|
||||||
if research:
|
if research:
|
||||||
console.print(Panel("[bold]II. Research Team Decision[/bold]", border_style="magenta"))
|
console.print(
|
||||||
|
Panel("[bold]II. Research Team Decision[/bold]", border_style="magenta")
|
||||||
|
)
|
||||||
for title, content in research:
|
for title, content in research:
|
||||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(content),
|
||||||
|
title=title,
|
||||||
|
border_style="blue",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# III. Trading Team
|
# III. Trading Team
|
||||||
if final_state.get("trader_investment_plan"):
|
if final_state.get("trader_investment_plan"):
|
||||||
console.print(Panel("[bold]III. Trading Team Plan[/bold]", border_style="yellow"))
|
console.print(
|
||||||
console.print(Panel(Markdown(final_state["trader_investment_plan"]), title="Trader", border_style="blue", padding=(1, 2)))
|
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),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# IV. Risk Management Team
|
# IV. Risk Management Team
|
||||||
if final_state.get("risk_debate_state"):
|
if final_state.get("risk_debate_state"):
|
||||||
|
|
@ -754,14 +802,36 @@ def display_complete_report(final_state):
|
||||||
if risk.get("neutral_history"):
|
if risk.get("neutral_history"):
|
||||||
risk_reports.append(("Neutral Analyst", risk["neutral_history"]))
|
risk_reports.append(("Neutral Analyst", risk["neutral_history"]))
|
||||||
if risk_reports:
|
if risk_reports:
|
||||||
console.print(Panel("[bold]IV. Risk Management Team Decision[/bold]", border_style="red"))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
"[bold]IV. Risk Management Team Decision[/bold]", border_style="red"
|
||||||
|
)
|
||||||
|
)
|
||||||
for title, content in risk_reports:
|
for title, content in risk_reports:
|
||||||
console.print(Panel(Markdown(content), title=title, border_style="blue", padding=(1, 2)))
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(content),
|
||||||
|
title=title,
|
||||||
|
border_style="blue",
|
||||||
|
padding=(1, 2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# V. Portfolio Manager Decision
|
# V. Portfolio Manager Decision
|
||||||
if risk.get("judge_decision"):
|
if risk.get("judge_decision"):
|
||||||
console.print(Panel("[bold]V. Portfolio Manager Decision[/bold]", border_style="green"))
|
console.print(
|
||||||
console.print(Panel(Markdown(risk["judge_decision"]), title="Portfolio Manager", border_style="blue", padding=(1, 2)))
|
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),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_research_team_status(status):
|
def update_research_team_status(status):
|
||||||
|
|
@ -821,6 +891,7 @@ def update_analyst_statuses(message_buffer, chunk):
|
||||||
if message_buffer.agent_status.get("Bull Researcher") == "pending":
|
if message_buffer.agent_status.get("Bull Researcher") == "pending":
|
||||||
message_buffer.update_agent_status("Bull Researcher", "in_progress")
|
message_buffer.update_agent_status("Bull Researcher", "in_progress")
|
||||||
|
|
||||||
|
|
||||||
def extract_content_string(content):
|
def extract_content_string(content):
|
||||||
"""Extract string content from various message formats.
|
"""Extract string content from various message formats.
|
||||||
Returns None if no meaningful text content is found.
|
Returns None if no meaningful text content is found.
|
||||||
|
|
@ -829,7 +900,7 @@ def extract_content_string(content):
|
||||||
|
|
||||||
def is_empty(val):
|
def is_empty(val):
|
||||||
"""Check if value is empty using Python's truthiness."""
|
"""Check if value is empty using Python's truthiness."""
|
||||||
if val is None or val == '':
|
if val is None or val == "":
|
||||||
return True
|
return True
|
||||||
if isinstance(val, str):
|
if isinstance(val, str):
|
||||||
s = val.strip()
|
s = val.strip()
|
||||||
|
|
@ -848,16 +919,19 @@ def extract_content_string(content):
|
||||||
return content.strip()
|
return content.strip()
|
||||||
|
|
||||||
if isinstance(content, dict):
|
if isinstance(content, dict):
|
||||||
text = content.get('text', '')
|
text = content.get("text", "")
|
||||||
return text.strip() if not is_empty(text) else None
|
return text.strip() if not is_empty(text) else None
|
||||||
|
|
||||||
if isinstance(content, list):
|
if isinstance(content, list):
|
||||||
text_parts = [
|
text_parts = [
|
||||||
item.get('text', '').strip() if isinstance(item, dict) and item.get('type') == 'text'
|
(
|
||||||
else (item.strip() if isinstance(item, str) else '')
|
item.get("text", "").strip()
|
||||||
|
if isinstance(item, dict) and item.get("type") == "text"
|
||||||
|
else (item.strip() if isinstance(item, str) else "")
|
||||||
|
)
|
||||||
for item in content
|
for item in content
|
||||||
]
|
]
|
||||||
result = ' '.join(t for t in text_parts if t and not is_empty(t))
|
result = " ".join(t for t in text_parts if t and not is_empty(t))
|
||||||
return result if result else None
|
return result if result else None
|
||||||
|
|
||||||
return str(content).strip() if not is_empty(content) else None
|
return str(content).strip() if not is_empty(content) else None
|
||||||
|
|
@ -872,7 +946,7 @@ def classify_message_type(message) -> tuple[str, str | None]:
|
||||||
"""
|
"""
|
||||||
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
||||||
|
|
||||||
content = extract_content_string(getattr(message, 'content', None))
|
content = extract_content_string(getattr(message, "content", None))
|
||||||
|
|
||||||
if isinstance(message, HumanMessage):
|
if isinstance(message, HumanMessage):
|
||||||
if content and content.strip() == "Continue":
|
if content and content.strip() == "Continue":
|
||||||
|
|
@ -893,9 +967,10 @@ def format_tool_args(args, max_length=80) -> str:
|
||||||
"""Format tool arguments for terminal display."""
|
"""Format tool arguments for terminal display."""
|
||||||
result = str(args)
|
result = str(args)
|
||||||
if len(result) > max_length:
|
if len(result) > max_length:
|
||||||
return result[:max_length - 3] + "..."
|
return result[: max_length - 3] + "..."
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def run_analysis():
|
def run_analysis():
|
||||||
# First get all user selections
|
# First get all user selections
|
||||||
selections = get_user_selections()
|
selections = get_user_selections()
|
||||||
|
|
@ -934,7 +1009,9 @@ def run_analysis():
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
# Create result directory
|
# Create result directory
|
||||||
results_dir = Path(config["results_dir"]) / selections["ticker"] / selections["analysis_date"]
|
results_dir = (
|
||||||
|
Path(config["results_dir"]) / selections["ticker"] / selections["analysis_date"]
|
||||||
|
)
|
||||||
results_dir.mkdir(parents=True, exist_ok=True)
|
results_dir.mkdir(parents=True, exist_ok=True)
|
||||||
report_dir = results_dir / "reports"
|
report_dir = results_dir / "reports"
|
||||||
report_dir.mkdir(parents=True, exist_ok=True)
|
report_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -943,6 +1020,7 @@ def run_analysis():
|
||||||
|
|
||||||
def save_message_decorator(obj, func_name):
|
def save_message_decorator(obj, func_name):
|
||||||
func = getattr(obj, func_name)
|
func = getattr(obj, func_name)
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
|
|
@ -950,10 +1028,12 @@ def run_analysis():
|
||||||
content = content.replace("\n", " ") # Replace newlines with spaces
|
content = content.replace("\n", " ") # Replace newlines with spaces
|
||||||
with open(log_file, "a") as f:
|
with open(log_file, "a") as f:
|
||||||
f.write(f"{timestamp} [{message_type}] {content}\n")
|
f.write(f"{timestamp} [{message_type}] {content}\n")
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def save_tool_call_decorator(obj, func_name):
|
def save_tool_call_decorator(obj, func_name):
|
||||||
func = getattr(obj, func_name)
|
func = getattr(obj, func_name)
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
|
|
@ -961,29 +1041,39 @@ def run_analysis():
|
||||||
args_str = ", ".join(f"{k}={v}" for k, v in args.items())
|
args_str = ", ".join(f"{k}={v}" for k, v in args.items())
|
||||||
with open(log_file, "a") as f:
|
with open(log_file, "a") as f:
|
||||||
f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n")
|
f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n")
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def save_report_section_decorator(obj, func_name):
|
def save_report_section_decorator(obj, func_name):
|
||||||
func = getattr(obj, func_name)
|
func = getattr(obj, func_name)
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
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]
|
content = obj.report_sections[section_name]
|
||||||
if content:
|
if 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)
|
||||||
|
|
||||||
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")
|
||||||
message_buffer.add_tool_call = save_tool_call_decorator(message_buffer, "add_tool_call")
|
message_buffer.add_tool_call = save_tool_call_decorator(
|
||||||
message_buffer.update_report_section = save_report_section_decorator(message_buffer, "update_report_section")
|
message_buffer, "add_tool_call"
|
||||||
|
)
|
||||||
|
message_buffer.update_report_section = save_report_section_decorator(
|
||||||
|
message_buffer, "update_report_section"
|
||||||
|
)
|
||||||
|
|
||||||
# Now start the display layout
|
# Now start the display layout
|
||||||
layout = create_layout()
|
layout = create_layout()
|
||||||
|
|
||||||
with Live(layout, refresh_per_second=4) as live:
|
with Live(layout, refresh_per_second=4):
|
||||||
# Initial display
|
# Initial display
|
||||||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||||
|
|
||||||
|
|
@ -1007,7 +1097,9 @@ def run_analysis():
|
||||||
spinner_text = (
|
spinner_text = (
|
||||||
f"Analyzing {selections['ticker']} on {selections['analysis_date']}..."
|
f"Analyzing {selections['ticker']} on {selections['analysis_date']}..."
|
||||||
)
|
)
|
||||||
update_display(layout, spinner_text, stats_handler=stats_handler, start_time=start_time)
|
update_display(
|
||||||
|
layout, spinner_text, stats_handler=stats_handler, start_time=start_time
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize state and get graph args with callbacks
|
# Initialize state and get graph args with callbacks
|
||||||
init_agent_state = graph.propagator.create_initial_state(
|
init_agent_state = graph.propagator.create_initial_state(
|
||||||
|
|
@ -1041,7 +1133,9 @@ def run_analysis():
|
||||||
tool_call["name"], tool_call["args"]
|
tool_call["name"], tool_call["args"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
message_buffer.add_tool_call(tool_call.name, tool_call.args)
|
message_buffer.add_tool_call(
|
||||||
|
tool_call.name, tool_call.args
|
||||||
|
)
|
||||||
|
|
||||||
# Update analyst statuses based on report state (runs on every chunk)
|
# Update analyst statuses based on report state (runs on every chunk)
|
||||||
update_analyst_statuses(message_buffer, chunk)
|
update_analyst_statuses(message_buffer, chunk)
|
||||||
|
|
@ -1078,7 +1172,9 @@ def run_analysis():
|
||||||
)
|
)
|
||||||
if message_buffer.agent_status.get("Trader") != "completed":
|
if message_buffer.agent_status.get("Trader") != "completed":
|
||||||
message_buffer.update_agent_status("Trader", "completed")
|
message_buffer.update_agent_status("Trader", "completed")
|
||||||
message_buffer.update_agent_status("Aggressive Analyst", "in_progress")
|
message_buffer.update_agent_status(
|
||||||
|
"Aggressive Analyst", "in_progress"
|
||||||
|
)
|
||||||
|
|
||||||
# Risk Management Team - Handle Risk Debate State
|
# Risk Management Team - Handle Risk Debate State
|
||||||
if chunk.get("risk_debate_state"):
|
if chunk.get("risk_debate_state"):
|
||||||
|
|
@ -1089,33 +1185,65 @@ def run_analysis():
|
||||||
judge = risk_state.get("judge_decision", "").strip()
|
judge = risk_state.get("judge_decision", "").strip()
|
||||||
|
|
||||||
if agg_hist:
|
if agg_hist:
|
||||||
if message_buffer.agent_status.get("Aggressive Analyst") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Aggressive Analyst", "in_progress")
|
message_buffer.agent_status.get("Aggressive Analyst")
|
||||||
|
!= "completed"
|
||||||
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Aggressive Analyst", "in_progress"
|
||||||
|
)
|
||||||
message_buffer.update_report_section(
|
message_buffer.update_report_section(
|
||||||
"final_trade_decision", f"### Aggressive Analyst Analysis\n{agg_hist}"
|
"final_trade_decision",
|
||||||
|
f"### Aggressive Analyst Analysis\n{agg_hist}",
|
||||||
)
|
)
|
||||||
if con_hist:
|
if con_hist:
|
||||||
if message_buffer.agent_status.get("Conservative Analyst") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Conservative Analyst", "in_progress")
|
message_buffer.agent_status.get("Conservative Analyst")
|
||||||
|
!= "completed"
|
||||||
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Conservative Analyst", "in_progress"
|
||||||
|
)
|
||||||
message_buffer.update_report_section(
|
message_buffer.update_report_section(
|
||||||
"final_trade_decision", f"### Conservative Analyst Analysis\n{con_hist}"
|
"final_trade_decision",
|
||||||
|
f"### Conservative Analyst Analysis\n{con_hist}",
|
||||||
)
|
)
|
||||||
if neu_hist:
|
if neu_hist:
|
||||||
if message_buffer.agent_status.get("Neutral Analyst") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Neutral Analyst", "in_progress")
|
message_buffer.agent_status.get("Neutral Analyst")
|
||||||
|
!= "completed"
|
||||||
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Neutral Analyst", "in_progress"
|
||||||
|
)
|
||||||
message_buffer.update_report_section(
|
message_buffer.update_report_section(
|
||||||
"final_trade_decision", f"### Neutral Analyst Analysis\n{neu_hist}"
|
"final_trade_decision",
|
||||||
|
f"### Neutral Analyst Analysis\n{neu_hist}",
|
||||||
)
|
)
|
||||||
if judge:
|
if judge:
|
||||||
if message_buffer.agent_status.get("Portfolio Manager") != "completed":
|
if (
|
||||||
message_buffer.update_agent_status("Portfolio Manager", "in_progress")
|
message_buffer.agent_status.get("Portfolio Manager")
|
||||||
message_buffer.update_report_section(
|
!= "completed"
|
||||||
"final_trade_decision", f"### Portfolio Manager Decision\n{judge}"
|
):
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Portfolio Manager", "in_progress"
|
||||||
|
)
|
||||||
|
message_buffer.update_report_section(
|
||||||
|
"final_trade_decision",
|
||||||
|
f"### Portfolio Manager Decision\n{judge}",
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Aggressive Analyst", "completed"
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Conservative Analyst", "completed"
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Neutral Analyst", "completed"
|
||||||
|
)
|
||||||
|
message_buffer.update_agent_status(
|
||||||
|
"Portfolio Manager", "completed"
|
||||||
)
|
)
|
||||||
message_buffer.update_agent_status("Aggressive Analyst", "completed")
|
|
||||||
message_buffer.update_agent_status("Conservative Analyst", "completed")
|
|
||||||
message_buffer.update_agent_status("Neutral Analyst", "completed")
|
|
||||||
message_buffer.update_agent_status("Portfolio Manager", "completed")
|
|
||||||
|
|
||||||
# Update the display
|
# Update the display
|
||||||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||||
|
|
@ -1124,7 +1252,7 @@ def run_analysis():
|
||||||
|
|
||||||
# Get final state and decision
|
# Get final state and decision
|
||||||
final_state = trace[-1]
|
final_state = trace[-1]
|
||||||
decision = graph.process_signal(final_state["final_trade_decision"])
|
graph.process_signal(final_state["final_trade_decision"])
|
||||||
|
|
||||||
# Update all agent statuses to completed
|
# Update all agent statuses to completed
|
||||||
for agent in message_buffer.agent_status:
|
for agent in message_buffer.agent_status:
|
||||||
|
|
@ -1150,19 +1278,22 @@ def run_analysis():
|
||||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
default_path = Path.cwd() / "reports" / f"{selections['ticker']}_{timestamp}"
|
default_path = Path.cwd() / "reports" / f"{selections['ticker']}_{timestamp}"
|
||||||
save_path_str = typer.prompt(
|
save_path_str = typer.prompt(
|
||||||
"Save path (press Enter for default)",
|
"Save path (press Enter for default)", default=str(default_path)
|
||||||
default=str(default_path)
|
|
||||||
).strip()
|
).strip()
|
||||||
save_path = Path(save_path_str)
|
save_path = Path(save_path_str)
|
||||||
try:
|
try:
|
||||||
report_file = save_report_to_disk(final_state, selections["ticker"], save_path)
|
report_file = save_report_to_disk(
|
||||||
|
final_state, selections["ticker"], save_path
|
||||||
|
)
|
||||||
console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}")
|
console.print(f"\n[green]✓ Report saved to:[/green] {save_path.resolve()}")
|
||||||
console.print(f" [dim]Complete report:[/dim] {report_file.name}")
|
console.print(f" [dim]Complete report:[/dim] {report_file.name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error saving report: {e}[/red]")
|
console.print(f"[red]Error saving report: {e}[/red]")
|
||||||
|
|
||||||
# Prompt to display full report
|
# Prompt to display full report
|
||||||
display_choice = typer.prompt("\nDisplay full report on screen?", default="Y").strip().upper()
|
display_choice = (
|
||||||
|
typer.prompt("\nDisplay full report on screen?", default="Y").strip().upper()
|
||||||
|
)
|
||||||
if display_choice in ("Y", "YES", ""):
|
if display_choice in ("Y", "YES", ""):
|
||||||
display_complete_report(final_state)
|
display_complete_report(final_state)
|
||||||
|
|
||||||
|
|
|
||||||
71
cli/utils.py
71
cli/utils.py
|
|
@ -1,8 +1,10 @@
|
||||||
import questionary
|
import questionary
|
||||||
from typing import List, Optional, Tuple, Dict
|
from typing import List
|
||||||
|
from rich.console import Console
|
||||||
from cli.models import AnalystType
|
from cli.models import AnalystType
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
ANALYST_ORDER = [
|
ANALYST_ORDER = [
|
||||||
("Market Analyst", AnalystType.MARKET),
|
("Market Analyst", AnalystType.MARKET),
|
||||||
("Social Media Analyst", AnalystType.SOCIAL),
|
("Social Media Analyst", AnalystType.SOCIAL),
|
||||||
|
|
@ -146,13 +148,25 @@ def select_shallow_thinking_agent(provider) -> str:
|
||||||
("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"),
|
("Gemini 2.5 Flash Lite - Fast, low-cost", "gemini-2.5-flash-lite"),
|
||||||
],
|
],
|
||||||
"xai": [
|
"xai": [
|
||||||
("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"),
|
(
|
||||||
("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"),
|
"Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx",
|
||||||
("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"),
|
"grok-4-1-fast-non-reasoning",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Grok 4 Fast (Non-Reasoning) - Speed optimized",
|
||||||
|
"grok-4-fast-non-reasoning",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx",
|
||||||
|
"grok-4-1-fast-reasoning",
|
||||||
|
),
|
||||||
("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"),
|
("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"),
|
||||||
],
|
],
|
||||||
"openrouter": [
|
"openrouter": [
|
||||||
("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"),
|
(
|
||||||
|
"NVIDIA Nemotron 3 Nano 30B (free)",
|
||||||
|
"nvidia/nemotron-3-nano-30b-a3b:free",
|
||||||
|
),
|
||||||
("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"),
|
("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"),
|
||||||
],
|
],
|
||||||
"ollama": [
|
"ollama": [
|
||||||
|
|
@ -213,15 +227,27 @@ def select_deep_thinking_agent(provider) -> str:
|
||||||
("Gemini 2.5 Flash - Balanced, recommended", "gemini-2.5-flash"),
|
("Gemini 2.5 Flash - Balanced, recommended", "gemini-2.5-flash"),
|
||||||
],
|
],
|
||||||
"xai": [
|
"xai": [
|
||||||
("Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx", "grok-4-1-fast-reasoning"),
|
(
|
||||||
|
"Grok 4.1 Fast (Reasoning) - High-performance, 2M ctx",
|
||||||
|
"grok-4-1-fast-reasoning",
|
||||||
|
),
|
||||||
("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"),
|
("Grok 4 Fast (Reasoning) - High-performance", "grok-4-fast-reasoning"),
|
||||||
("Grok 4 - Flagship model", "grok-4-0709"),
|
("Grok 4 - Flagship model", "grok-4-0709"),
|
||||||
("Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx", "grok-4-1-fast-non-reasoning"),
|
(
|
||||||
("Grok 4 Fast (Non-Reasoning) - Speed optimized", "grok-4-fast-non-reasoning"),
|
"Grok 4.1 Fast (Non-Reasoning) - Speed optimized, 2M ctx",
|
||||||
|
"grok-4-1-fast-non-reasoning",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Grok 4 Fast (Non-Reasoning) - Speed optimized",
|
||||||
|
"grok-4-fast-non-reasoning",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
"openrouter": [
|
"openrouter": [
|
||||||
("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"),
|
("Z.AI GLM 4.5 Air (free)", "z-ai/glm-4.5-air:free"),
|
||||||
("NVIDIA Nemotron 3 Nano 30B (free)", "nvidia/nemotron-3-nano-30b-a3b:free"),
|
(
|
||||||
|
"NVIDIA Nemotron 3 Nano 30B (free)",
|
||||||
|
"nvidia/nemotron-3-nano-30b-a3b:free",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
"ollama": [
|
"ollama": [
|
||||||
("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"),
|
("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"),
|
||||||
|
|
@ -252,6 +278,7 @@ def select_deep_thinking_agent(provider) -> str:
|
||||||
|
|
||||||
return choice
|
return choice
|
||||||
|
|
||||||
|
|
||||||
def select_llm_provider() -> tuple[str, str]:
|
def select_llm_provider() -> tuple[str, str]:
|
||||||
"""Select the OpenAI api url using interactive selection."""
|
"""Select the OpenAI api url using interactive selection."""
|
||||||
# Define OpenAI api options with their corresponding endpoints
|
# Define OpenAI api options with their corresponding endpoints
|
||||||
|
|
@ -300,11 +327,13 @@ def ask_openai_reasoning_effort() -> str:
|
||||||
return questionary.select(
|
return questionary.select(
|
||||||
"Select Reasoning Effort:",
|
"Select Reasoning Effort:",
|
||||||
choices=choices,
|
choices=choices,
|
||||||
style=questionary.Style([
|
style=questionary.Style(
|
||||||
("selected", "fg:cyan noinherit"),
|
[
|
||||||
("highlighted", "fg:cyan noinherit"),
|
("selected", "fg:cyan noinherit"),
|
||||||
("pointer", "fg:cyan noinherit"),
|
("highlighted", "fg:cyan noinherit"),
|
||||||
]),
|
("pointer", "fg:cyan noinherit"),
|
||||||
|
]
|
||||||
|
),
|
||||||
).ask()
|
).ask()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -320,9 +349,11 @@ def ask_gemini_thinking_config() -> str | None:
|
||||||
questionary.Choice("Enable Thinking (recommended)", "high"),
|
questionary.Choice("Enable Thinking (recommended)", "high"),
|
||||||
questionary.Choice("Minimal/Disable Thinking", "minimal"),
|
questionary.Choice("Minimal/Disable Thinking", "minimal"),
|
||||||
],
|
],
|
||||||
style=questionary.Style([
|
style=questionary.Style(
|
||||||
("selected", "fg:green noinherit"),
|
[
|
||||||
("highlighted", "fg:green noinherit"),
|
("selected", "fg:green noinherit"),
|
||||||
("pointer", "fg:green noinherit"),
|
("highlighted", "fg:green noinherit"),
|
||||||
]),
|
("pointer", "fg:green noinherit"),
|
||||||
|
]
|
||||||
|
),
|
||||||
).ask()
|
).ask()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
from typing import Annotated, Sequence
|
from typing import Annotated
|
||||||
from datetime import date, timedelta, datetime
|
from typing_extensions import TypedDict
|
||||||
from typing_extensions import TypedDict, Optional
|
from langgraph.graph import MessagesState
|
||||||
from langchain_openai import ChatOpenAI
|
|
||||||
from tradingagents.agents import *
|
|
||||||
from langgraph.prebuilt import ToolNode
|
|
||||||
from langgraph.graph import END, StateGraph, START, MessagesState
|
|
||||||
|
|
||||||
|
|
||||||
# Researcher team state
|
# Researcher team state
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ from typing import Annotated
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
import yfinance as yf
|
import yfinance as yf
|
||||||
import os
|
|
||||||
from .stockstats_utils import StockstatsUtils
|
from .stockstats_utils import StockstatsUtils
|
||||||
|
|
||||||
|
|
||||||
def get_YFin_data_online(
|
def get_YFin_data_online(
|
||||||
symbol: Annotated[str, "ticker symbol of the company"],
|
symbol: Annotated[str, "ticker symbol of the company"],
|
||||||
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
|
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
|
||||||
|
|
@ -46,6 +46,7 @@ def get_YFin_data_online(
|
||||||
|
|
||||||
return header + csv_string
|
return header + csv_string
|
||||||
|
|
||||||
|
|
||||||
def get_stock_stats_indicators_window(
|
def get_stock_stats_indicators_window(
|
||||||
symbol: Annotated[str, "ticker symbol of the company"],
|
symbol: Annotated[str, "ticker symbol of the company"],
|
||||||
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
|
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
|
||||||
|
|
@ -146,7 +147,7 @@ def get_stock_stats_indicators_window(
|
||||||
date_values = []
|
date_values = []
|
||||||
|
|
||||||
while current_dt >= before:
|
while current_dt >= before:
|
||||||
date_str = current_dt.strftime('%Y-%m-%d')
|
date_str = current_dt.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
# Look up the indicator value for this date
|
# Look up the indicator value for this date
|
||||||
if date_str in indicator_data:
|
if date_str in indicator_data:
|
||||||
|
|
@ -187,7 +188,7 @@ def get_stock_stats_indicators_window(
|
||||||
def _get_stock_stats_bulk(
|
def _get_stock_stats_bulk(
|
||||||
symbol: Annotated[str, "ticker symbol of the company"],
|
symbol: Annotated[str, "ticker symbol of the company"],
|
||||||
indicator: Annotated[str, "technical indicator to calculate"],
|
indicator: Annotated[str, "technical indicator to calculate"],
|
||||||
curr_date: Annotated[str, "current date for reference"]
|
curr_date: Annotated[str, "current date for reference"],
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Optimized bulk calculation of stock stats indicators.
|
Optimized bulk calculation of stock stats indicators.
|
||||||
|
|
@ -195,9 +196,9 @@ def _get_stock_stats_bulk(
|
||||||
Returns dict mapping date strings to indicator values.
|
Returns dict mapping date strings to indicator values.
|
||||||
"""
|
"""
|
||||||
from .config import get_config
|
from .config import get_config
|
||||||
|
import os
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from stockstats import wrap
|
from stockstats import wrap
|
||||||
import os
|
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
online = config["data_vendors"]["technical_indicators"] != "local"
|
online = config["data_vendors"]["technical_indicators"] != "local"
|
||||||
|
|
@ -217,7 +218,7 @@ def _get_stock_stats_bulk(
|
||||||
else:
|
else:
|
||||||
# Online data fetching with caching
|
# Online data fetching with caching
|
||||||
today_date = pd.Timestamp.today()
|
today_date = pd.Timestamp.today()
|
||||||
curr_date_dt = pd.to_datetime(curr_date)
|
pd.to_datetime(curr_date)
|
||||||
|
|
||||||
end_date = today_date
|
end_date = today_date
|
||||||
start_date = today_date - pd.DateOffset(years=15)
|
start_date = today_date - pd.DateOffset(years=15)
|
||||||
|
|
@ -295,7 +296,7 @@ def get_stockstats_indicator(
|
||||||
|
|
||||||
def get_fundamentals(
|
def get_fundamentals(
|
||||||
ticker: Annotated[str, "ticker symbol of the company"],
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
|
curr_date: Annotated[str, "current date (not used for yfinance)"] = None,
|
||||||
):
|
):
|
||||||
"""Get company fundamentals overview from yfinance."""
|
"""Get company fundamentals overview from yfinance."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -342,7 +343,9 @@ def get_fundamentals(
|
||||||
lines.append(f"{label}: {value}")
|
lines.append(f"{label}: {value}")
|
||||||
|
|
||||||
header = f"# Company Fundamentals for {ticker.upper()}\n"
|
header = f"# Company Fundamentals for {ticker.upper()}\n"
|
||||||
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header += (
|
||||||
|
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
return header + "\n".join(lines)
|
return header + "\n".join(lines)
|
||||||
|
|
||||||
|
|
@ -353,7 +356,7 @@ def get_fundamentals(
|
||||||
def get_balance_sheet(
|
def get_balance_sheet(
|
||||||
ticker: Annotated[str, "ticker symbol of the company"],
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
||||||
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
|
curr_date: Annotated[str, "current date (not used for yfinance)"] = None,
|
||||||
):
|
):
|
||||||
"""Get balance sheet data from yfinance."""
|
"""Get balance sheet data from yfinance."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -372,7 +375,9 @@ def get_balance_sheet(
|
||||||
|
|
||||||
# Add header information
|
# Add header information
|
||||||
header = f"# Balance Sheet data for {ticker.upper()} ({freq})\n"
|
header = f"# Balance Sheet data for {ticker.upper()} ({freq})\n"
|
||||||
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header += (
|
||||||
|
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
return header + csv_string
|
return header + csv_string
|
||||||
|
|
||||||
|
|
@ -383,7 +388,7 @@ def get_balance_sheet(
|
||||||
def get_cashflow(
|
def get_cashflow(
|
||||||
ticker: Annotated[str, "ticker symbol of the company"],
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
||||||
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
|
curr_date: Annotated[str, "current date (not used for yfinance)"] = None,
|
||||||
):
|
):
|
||||||
"""Get cash flow data from yfinance."""
|
"""Get cash flow data from yfinance."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -402,7 +407,9 @@ def get_cashflow(
|
||||||
|
|
||||||
# Add header information
|
# Add header information
|
||||||
header = f"# Cash Flow data for {ticker.upper()} ({freq})\n"
|
header = f"# Cash Flow data for {ticker.upper()} ({freq})\n"
|
||||||
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header += (
|
||||||
|
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
return header + csv_string
|
return header + csv_string
|
||||||
|
|
||||||
|
|
@ -413,7 +420,7 @@ def get_cashflow(
|
||||||
def get_income_statement(
|
def get_income_statement(
|
||||||
ticker: Annotated[str, "ticker symbol of the company"],
|
ticker: Annotated[str, "ticker symbol of the company"],
|
||||||
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
|
||||||
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
|
curr_date: Annotated[str, "current date (not used for yfinance)"] = None,
|
||||||
):
|
):
|
||||||
"""Get income statement data from yfinance."""
|
"""Get income statement data from yfinance."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -432,7 +439,9 @@ def get_income_statement(
|
||||||
|
|
||||||
# Add header information
|
# Add header information
|
||||||
header = f"# Income Statement data for {ticker.upper()} ({freq})\n"
|
header = f"# Income Statement data for {ticker.upper()} ({freq})\n"
|
||||||
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header += (
|
||||||
|
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
return header + csv_string
|
return header + csv_string
|
||||||
|
|
||||||
|
|
@ -440,9 +449,7 @@ def get_income_statement(
|
||||||
return f"Error retrieving income statement for {ticker}: {str(e)}"
|
return f"Error retrieving income statement for {ticker}: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
def get_insider_transactions(
|
def get_insider_transactions(ticker: Annotated[str, "ticker symbol of the company"]):
|
||||||
ticker: Annotated[str, "ticker symbol of the company"]
|
|
||||||
):
|
|
||||||
"""Get insider transactions data from yfinance."""
|
"""Get insider transactions data from yfinance."""
|
||||||
try:
|
try:
|
||||||
ticker_obj = yf.Ticker(ticker.upper())
|
ticker_obj = yf.Ticker(ticker.upper())
|
||||||
|
|
@ -456,7 +463,9 @@ def get_insider_transactions(
|
||||||
|
|
||||||
# Add header information
|
# Add header information
|
||||||
header = f"# Insider Transactions data for {ticker.upper()}\n"
|
header = f"# Insider Transactions data for {ticker.upper()}\n"
|
||||||
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
header += (
|
||||||
|
f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
return header + csv_string
|
return header + csv_string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,25 @@
|
||||||
# TradingAgents/graph/setup.py
|
# TradingAgents/graph/setup.py
|
||||||
|
|
||||||
from typing import Dict, Any
|
from typing import Dict
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langgraph.graph import END, StateGraph, START
|
from langgraph.graph import END, StateGraph, START
|
||||||
from langgraph.prebuilt import ToolNode
|
from langgraph.prebuilt import ToolNode
|
||||||
|
|
||||||
from tradingagents.agents import *
|
from tradingagents.agents import (
|
||||||
|
create_msg_delete,
|
||||||
|
create_market_analyst,
|
||||||
|
create_social_media_analyst,
|
||||||
|
create_fundamentals_analyst,
|
||||||
|
create_news_analyst,
|
||||||
|
create_bull_researcher,
|
||||||
|
create_bear_researcher,
|
||||||
|
create_research_manager,
|
||||||
|
create_trader,
|
||||||
|
create_aggressive_debator,
|
||||||
|
create_conservative_debator,
|
||||||
|
create_neutral_debator,
|
||||||
|
create_risk_manager,
|
||||||
|
)
|
||||||
from tradingagents.agents.utils.agent_states import AgentState
|
from tradingagents.agents.utils.agent_states import AgentState
|
||||||
|
|
||||||
from .conditional_logic import ConditionalLogic
|
from .conditional_logic import ConditionalLogic
|
||||||
|
|
@ -58,9 +72,7 @@ class GraphSetup:
|
||||||
tool_nodes = {}
|
tool_nodes = {}
|
||||||
|
|
||||||
if "market" in selected_analysts:
|
if "market" in selected_analysts:
|
||||||
analyst_nodes["market"] = create_market_analyst(
|
analyst_nodes["market"] = create_market_analyst(self.quick_thinking_llm)
|
||||||
self.quick_thinking_llm
|
|
||||||
)
|
|
||||||
delete_nodes["market"] = create_msg_delete()
|
delete_nodes["market"] = create_msg_delete()
|
||||||
tool_nodes["market"] = self.tool_nodes["market"]
|
tool_nodes["market"] = self.tool_nodes["market"]
|
||||||
|
|
||||||
|
|
@ -72,9 +84,7 @@ class GraphSetup:
|
||||||
tool_nodes["social"] = self.tool_nodes["social"]
|
tool_nodes["social"] = self.tool_nodes["social"]
|
||||||
|
|
||||||
if "news" in selected_analysts:
|
if "news" in selected_analysts:
|
||||||
analyst_nodes["news"] = create_news_analyst(
|
analyst_nodes["news"] = create_news_analyst(self.quick_thinking_llm)
|
||||||
self.quick_thinking_llm
|
|
||||||
)
|
|
||||||
delete_nodes["news"] = create_msg_delete()
|
delete_nodes["news"] = create_msg_delete()
|
||||||
tool_nodes["news"] = self.tool_nodes["news"]
|
tool_nodes["news"] = self.tool_nodes["news"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,14 @@
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
import json
|
||||||
from datetime import date
|
from typing import Dict, Any, List, Optional
|
||||||
from typing import Dict, Any, Tuple, List, Optional
|
|
||||||
|
|
||||||
from langgraph.prebuilt import ToolNode
|
from langgraph.prebuilt import ToolNode
|
||||||
|
|
||||||
from tradingagents.llm_clients import create_llm_client
|
from tradingagents.llm_clients import create_llm_client
|
||||||
|
|
||||||
from tradingagents.agents import *
|
|
||||||
from tradingagents.default_config import DEFAULT_CONFIG
|
from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
from tradingagents.agents.utils.memory import FinancialSituationMemory
|
from tradingagents.agents.utils.memory import FinancialSituationMemory
|
||||||
from tradingagents.agents.utils.agent_states import (
|
|
||||||
AgentState,
|
|
||||||
InvestDebateState,
|
|
||||||
RiskDebateState,
|
|
||||||
)
|
|
||||||
from tradingagents.dataflows.config import set_config
|
from tradingagents.dataflows.config import set_config
|
||||||
|
|
||||||
# Import the new abstract tool methods from agent_utils
|
# Import the new abstract tool methods from agent_utils
|
||||||
|
|
@ -30,7 +23,7 @@ from tradingagents.agents.utils.agent_utils import (
|
||||||
get_income_statement,
|
get_income_statement,
|
||||||
get_news,
|
get_news,
|
||||||
get_insider_transactions,
|
get_insider_transactions,
|
||||||
get_global_news
|
get_global_news,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .conditional_logic import ConditionalLogic
|
from .conditional_logic import ConditionalLogic
|
||||||
|
|
@ -98,8 +91,12 @@ class TradingAgentsGraph:
|
||||||
self.bull_memory = FinancialSituationMemory("bull_memory", self.config)
|
self.bull_memory = FinancialSituationMemory("bull_memory", self.config)
|
||||||
self.bear_memory = FinancialSituationMemory("bear_memory", self.config)
|
self.bear_memory = FinancialSituationMemory("bear_memory", self.config)
|
||||||
self.trader_memory = FinancialSituationMemory("trader_memory", self.config)
|
self.trader_memory = FinancialSituationMemory("trader_memory", self.config)
|
||||||
self.invest_judge_memory = FinancialSituationMemory("invest_judge_memory", self.config)
|
self.invest_judge_memory = FinancialSituationMemory(
|
||||||
self.risk_manager_memory = FinancialSituationMemory("risk_manager_memory", self.config)
|
"invest_judge_memory", self.config
|
||||||
|
)
|
||||||
|
self.risk_manager_memory = FinancialSituationMemory(
|
||||||
|
"risk_manager_memory", self.config
|
||||||
|
)
|
||||||
|
|
||||||
# Create tool nodes
|
# Create tool nodes
|
||||||
self.tool_nodes = self._create_tool_nodes()
|
self.tool_nodes = self._create_tool_nodes()
|
||||||
|
|
@ -240,8 +237,12 @@ class TradingAgentsGraph:
|
||||||
},
|
},
|
||||||
"trader_investment_decision": final_state["trader_investment_plan"],
|
"trader_investment_decision": final_state["trader_investment_plan"],
|
||||||
"risk_debate_state": {
|
"risk_debate_state": {
|
||||||
"aggressive_history": final_state["risk_debate_state"]["aggressive_history"],
|
"aggressive_history": final_state["risk_debate_state"][
|
||||||
"conservative_history": final_state["risk_debate_state"]["conservative_history"],
|
"aggressive_history"
|
||||||
|
],
|
||||||
|
"conservative_history": final_state["risk_debate_state"][
|
||||||
|
"conservative_history"
|
||||||
|
],
|
||||||
"neutral_history": final_state["risk_debate_state"]["neutral_history"],
|
"neutral_history": final_state["risk_debate_state"]["neutral_history"],
|
||||||
"history": final_state["risk_debate_state"]["history"],
|
"history": final_state["risk_debate_state"]["history"],
|
||||||
"judge_decision": final_state["risk_debate_state"]["judge_decision"],
|
"judge_decision": final_state["risk_debate_state"]["judge_decision"],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue