mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-27 06:56:39 +03:00
refactor(agents): organise the agents package by what each module holds
- tools.py: the analysts' data tools, previously seven modules under utils - context.py (was agent_utils, without its tool re-exports), state.py, rating.py and structured.py sit beside schemas.py - every role package has an __init__
This commit is contained in:
@@ -76,7 +76,7 @@ class _FakeGraph:
|
||||
return {"messages": [], "company_of_interest": ticker}
|
||||
|
||||
def process_signal(self, text):
|
||||
from tradingagents.agents.utils.rating import parse_rating
|
||||
from tradingagents.agents.rating import parse_rating
|
||||
return parse_rating(text)
|
||||
|
||||
def record_decision(self, ticker, trade_date, final_state):
|
||||
|
||||
@@ -12,12 +12,12 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from tradingagents.agents.context import opponent_argument_or_opening
|
||||
from tradingagents.agents.researchers.bear_researcher import create_bear_researcher
|
||||
from tradingagents.agents.researchers.bull_researcher import create_bull_researcher
|
||||
from tradingagents.agents.risk_mgmt.aggressive_debator import create_aggressive_debator
|
||||
from tradingagents.agents.risk_mgmt.conservative_debator import create_conservative_debator
|
||||
from tradingagents.agents.risk_mgmt.neutral_debator import create_neutral_debator
|
||||
from tradingagents.agents.utils.agent_utils import opponent_argument_or_opening
|
||||
|
||||
_REPORTS = {
|
||||
"company_of_interest": "AAPL", "asset_type": "stock",
|
||||
|
||||
@@ -17,9 +17,8 @@ from langchain_core.outputs import ChatGeneration, ChatResult
|
||||
from langchain_core.runnables import RunnableLambda
|
||||
from pydantic import Field
|
||||
|
||||
from tradingagents.agents import schemas
|
||||
from tradingagents.agents import context, schemas
|
||||
from tradingagents.agents.analysts import sentiment_analyst
|
||||
from tradingagents.agents.utils import agent_utils
|
||||
from tradingagents.dataflows import router
|
||||
from tradingagents.dataflows.vendors.yahoo import market as yahoo_market, snapshot
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
@@ -108,7 +107,7 @@ def offline(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(sentiment_analyst, "fetch_stocktwits_messages", lambda *a, **k: "no posts")
|
||||
monkeypatch.setattr(sentiment_analyst, "fetch_reddit_posts", lambda *a, **k: "no posts")
|
||||
monkeypatch.setattr(yahoo_market.yf, "Ticker", lambda s: type("T", (), {"info": {"longName": "NVIDIA"}})())
|
||||
agent_utils.resolve_instrument_identity.cache_clear()
|
||||
context.resolve_instrument_identity.cache_clear()
|
||||
return called
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tradingagents.agents.utils.agent_utils import get_language_instruction
|
||||
from tradingagents.agents.context import get_language_instruction
|
||||
|
||||
_AGENTS_DIR = Path(__file__).resolve().parents[1] / "tradingagents" / "agents"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage
|
||||
|
||||
from tradingagents.agents.utils.agent_utils import (
|
||||
from tradingagents.agents.context import (
|
||||
build_instrument_context,
|
||||
create_msg_delete,
|
||||
get_instrument_context_from_state,
|
||||
|
||||
@@ -196,7 +196,7 @@ class TestTradingMemoryLogCore:
|
||||
def test_an_unreadable_decision_is_tagged_for_review(self, tmp_path):
|
||||
"""Not a Hold: a fabricated rating is quoted back to the next run as a
|
||||
call that was never made, and counted in the backtest figures."""
|
||||
from tradingagents.agents.utils.rating import RATING_REVIEW
|
||||
from tradingagents.agents.rating import RATING_REVIEW
|
||||
|
||||
log = make_log(tmp_path)
|
||||
log.store_decision("MSFT", "2026-01-12", DECISION_NO_RATING)
|
||||
|
||||
@@ -8,7 +8,7 @@ import inspect
|
||||
import pytest
|
||||
|
||||
import tradingagents.agents.analysts.news_analyst as na
|
||||
from tradingagents.agents.utils.news_data_tools import get_news
|
||||
from tradingagents.agents.tools import get_news
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
||||
@@ -13,7 +13,7 @@ import json
|
||||
|
||||
import pytest
|
||||
|
||||
from tradingagents.agents.utils.agent_utils import get_portfolio_context_from_state
|
||||
from tradingagents.agents.context import get_portfolio_context_from_state
|
||||
from tradingagents.portfolio import PortfolioContext, load_portfolio
|
||||
|
||||
HOLDING = {
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from tradingagents.agents.utils.rating import RATING_REVIEW, extract_rating, parse_rating
|
||||
from tradingagents.agents.rating import RATING_REVIEW, extract_rating, parse_rating
|
||||
|
||||
INVERTED = ("The aggressive analyst pushed hard for a Buy on the AI backlog, but the "
|
||||
"conservative case on margin compression carried the debate. "
|
||||
@@ -74,8 +74,8 @@ def test_the_memory_log_records_review_rather_than_a_tradeable_hold(tmp_path):
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_the_signal_and_the_log_agree_on_the_same_decision(tmp_path):
|
||||
from tradingagents.agents.rating import parse_rating
|
||||
from tradingagents.agents.utils.memory import TradingMemoryLog
|
||||
from tradingagents.agents.utils.rating import parse_rating
|
||||
|
||||
log = TradingMemoryLog({"memory_log_path": str(tmp_path / "m.md")})
|
||||
for text in (INVERTED, REFUSAL, "**Rating**: Buy\n\nAccumulate."):
|
||||
@@ -121,7 +121,7 @@ def test_the_cli_says_when_a_run_produced_no_usable_rating(monkeypatch, tmp_path
|
||||
pass
|
||||
|
||||
def process_signal(self, text):
|
||||
from tradingagents.agents.utils.rating import parse_rating
|
||||
from tradingagents.agents.rating import parse_rating
|
||||
return parse_rating(text)
|
||||
|
||||
def get_graph_args(self, callbacks=None):
|
||||
|
||||
@@ -6,12 +6,7 @@ header, so the rating is read deterministically; no second model call is made.
|
||||
|
||||
import pytest
|
||||
|
||||
from tradingagents.agents.utils.rating import (
|
||||
RATING_REVIEW,
|
||||
RATINGS_5_TIER,
|
||||
extract_rating,
|
||||
parse_rating,
|
||||
)
|
||||
from tradingagents.agents.rating import RATING_REVIEW, RATINGS_5_TIER, extract_rating, parse_rating
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Heuristic parser
|
||||
|
||||
@@ -18,8 +18,8 @@ import pytest
|
||||
import tradingagents.agents.analysts.sentiment_analyst as sentiment
|
||||
from tradingagents.agents.managers.portfolio_manager import create_portfolio_manager
|
||||
from tradingagents.agents.managers.research_manager import create_research_manager
|
||||
from tradingagents.agents.structured import NO_EXTERNAL_TOOLS
|
||||
from tradingagents.agents.trader.trader import create_trader
|
||||
from tradingagents.agents.utils.structured import NO_EXTERNAL_TOOLS
|
||||
|
||||
|
||||
def _capturing_llm(captured: dict, result):
|
||||
|
||||
@@ -197,7 +197,7 @@ def _structured_trader_llm(captured: dict, proposal: TraderProposal | None = Non
|
||||
def test_invoke_structured_falls_back_when_result_is_none():
|
||||
# A thinking model can answer in plain text, leaving the parser with None.
|
||||
# That must fall back to free text, not crash on render(None) (#1051).
|
||||
from tradingagents.agents.utils.structured import invoke_structured_or_freetext
|
||||
from tradingagents.agents.structured import invoke_structured_or_freetext
|
||||
|
||||
structured = MagicMock()
|
||||
structured.invoke.return_value = None
|
||||
|
||||
@@ -7,7 +7,7 @@ hit the right instrument instead of failing/mismatching.
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
import tradingagents.agents.utils.agent_utils as au
|
||||
import tradingagents.agents.context as au
|
||||
import tradingagents.dataflows.vendors.yahoo.market as yahoo_market
|
||||
import tradingagents.dataflows.vendors.yahoo.news as ynews
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
import pytest
|
||||
|
||||
from cli.utils import normalize_ticker_symbol
|
||||
from tradingagents.agents.utils.agent_utils import build_instrument_context
|
||||
from tradingagents.agents.context import build_instrument_context
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
||||
@@ -15,14 +15,7 @@ from langchain_core.messages import AIMessage
|
||||
from langgraph.graph import END, START, MessagesState, StateGraph
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
from tradingagents.agents.utils import (
|
||||
core_stock_tools,
|
||||
fundamental_data_tools,
|
||||
macro_data_tools,
|
||||
market_data_validation_tools,
|
||||
news_data_tools,
|
||||
technical_indicators_tools,
|
||||
)
|
||||
from tradingagents.agents import tools
|
||||
from tradingagents.dataflows.date_window import as_of, as_of_window
|
||||
|
||||
TRADE_DATE = "2026-08-14"
|
||||
@@ -56,16 +49,16 @@ def test_as_of_window(start, end, expected):
|
||||
|
||||
|
||||
DATED_TOOLS = [
|
||||
core_stock_tools.get_stock_data,
|
||||
fundamental_data_tools.get_fundamentals,
|
||||
fundamental_data_tools.get_balance_sheet,
|
||||
fundamental_data_tools.get_cashflow,
|
||||
fundamental_data_tools.get_income_statement,
|
||||
news_data_tools.get_news,
|
||||
news_data_tools.get_global_news,
|
||||
technical_indicators_tools.get_indicators,
|
||||
macro_data_tools.get_macro_indicators,
|
||||
market_data_validation_tools.get_verified_market_snapshot,
|
||||
tools.get_stock_data,
|
||||
tools.get_fundamentals,
|
||||
tools.get_balance_sheet,
|
||||
tools.get_cashflow,
|
||||
tools.get_income_statement,
|
||||
tools.get_news,
|
||||
tools.get_global_news,
|
||||
tools.get_indicators,
|
||||
tools.get_macro_indicators,
|
||||
tools.get_verified_market_snapshot,
|
||||
]
|
||||
|
||||
|
||||
@@ -95,28 +88,28 @@ def _run(tool, args, module):
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_statement_tool_with_omitted_date_uses_the_run_date():
|
||||
args = _run(fundamental_data_tools.get_balance_sheet, {"ticker": "AAPL"}, fundamental_data_tools)
|
||||
args = _run(tools.get_balance_sheet, {"ticker": "AAPL"}, tools)
|
||||
assert args[-1] == TRADE_DATE # #1331: an omitted date no longer means unfiltered
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_future_curr_date_from_the_model_is_clamped():
|
||||
args = _run(fundamental_data_tools.get_fundamentals,
|
||||
{"ticker": "AAPL", "curr_date": "2026-09-14"}, fundamental_data_tools)
|
||||
args = _run(tools.get_fundamentals,
|
||||
{"ticker": "AAPL", "curr_date": "2026-09-14"}, tools)
|
||||
assert args == ("get_fundamentals", "AAPL", TRADE_DATE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_future_window_from_the_model_is_clamped():
|
||||
args = _run(core_stock_tools.get_stock_data,
|
||||
{"symbol": "AAPL", "start_date": "2026-08-01", "end_date": "2026-09-14"}, core_stock_tools)
|
||||
args = _run(tools.get_stock_data,
|
||||
{"symbol": "AAPL", "start_date": "2026-08-01", "end_date": "2026-09-14"}, tools)
|
||||
assert args == ("get_stock_data", "AAPL", "2026-08-01", TRADE_DATE)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_direct_call_without_state_is_unchanged():
|
||||
with mock.patch.object(news_data_tools, "route_to_vendor", return_value="ok") as routed:
|
||||
news_data_tools.get_news.func("AAPL", "2026-09-01", "2026-09-08")
|
||||
with mock.patch.object(tools, "route_to_vendor", return_value="ok") as routed:
|
||||
tools.get_news.func("AAPL", "2026-09-01", "2026-09-08")
|
||||
assert routed.call_args.args == ("get_news", "AAPL", "2026-09-01", "2026-09-08")
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from unittest import mock
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from tradingagents.agents.utils import news_data_tools, prediction_markets_tools
|
||||
from tradingagents.agents import tools
|
||||
from tradingagents.dataflows.vendors import polymarket
|
||||
from tradingagents.dataflows.vendors.alpha_vantage import news as alpha_vantage_news
|
||||
from tradingagents.dataflows.vendors.yahoo import (
|
||||
@@ -82,8 +82,8 @@ def test_polymarket_serves_a_current_run():
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.parametrize("tool", [news_data_tools.get_insider_transactions,
|
||||
prediction_markets_tools.get_prediction_markets], ids=lambda t: t.name)
|
||||
@pytest.mark.parametrize("tool", [tools.get_insider_transactions,
|
||||
tools.get_prediction_markets], ids=lambda t: t.name)
|
||||
def test_trade_date_is_injected_not_model_visible(tool):
|
||||
assert "trade_date" in tool.func.__code__.co_varnames
|
||||
props = tool.tool_call_schema.model_json_schema()["properties"]
|
||||
@@ -98,7 +98,7 @@ def test_a_historical_run_is_told_the_identity_is_current(monkeypatch):
|
||||
They are usually right for a past date, but a company that renamed or was
|
||||
reclassified since would read wrong, and every agent is told to anchor to
|
||||
this identity, so the run has to know which date it describes."""
|
||||
from tradingagents.agents.utils.agent_utils import build_instrument_context
|
||||
from tradingagents.agents.context import build_instrument_context
|
||||
|
||||
identity = {"company_name": "Example Corp", "sector": "Technology",
|
||||
"industry": "Software", "exchange": "NMS"}
|
||||
@@ -110,7 +110,7 @@ def test_a_historical_run_is_told_the_identity_is_current(monkeypatch):
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_current_run_is_not_cluttered_with_a_vintage_note(monkeypatch):
|
||||
from tradingagents.agents.utils.agent_utils import build_instrument_context
|
||||
from tradingagents.agents.context import build_instrument_context
|
||||
from tradingagents.dataflows.date_window import get_current_date
|
||||
|
||||
today = build_instrument_context("EXMP", "stock", {"company_name": "Example Corp"},
|
||||
@@ -285,7 +285,7 @@ def _dates_after(text: str, cutoff: str) -> list[str]:
|
||||
def test_an_unavailable_notice_names_no_date_after_the_run():
|
||||
"""A notice explaining why data is missing named where the vendor's coverage
|
||||
starts or today's date, both after a historical run's date."""
|
||||
from tradingagents.agents.utils.agent_utils import build_instrument_context
|
||||
from tradingagents.agents.context import build_instrument_context
|
||||
from tradingagents.dataflows.date_window import (
|
||||
coverage_gap,
|
||||
get_current_date,
|
||||
|
||||
@@ -66,9 +66,7 @@ class TestVerifiedSnapshot:
|
||||
@pytest.mark.unit
|
||||
class TestTool:
|
||||
def test_tool_delegates_to_builder(self, monkeypatch):
|
||||
from tradingagents.agents.utils.market_data_validation_tools import (
|
||||
get_verified_market_snapshot,
|
||||
)
|
||||
from tradingagents.agents.tools import get_verified_market_snapshot
|
||||
monkeypatch.setattr(validator, "load_ohlcv", lambda s, d, fill_gaps=True: _sample_ohlcv())
|
||||
out = get_verified_market_snapshot.invoke(
|
||||
{"symbol": "COF", "curr_date": "2026-05-20"}
|
||||
|
||||
Reference in New Issue
Block a user