mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-27 15:02:39 +03:00
- the Research Manager's plan and the Portfolio Manager's decision live only in investment_plan and final_trade_decision; the debate states no longer copy them as judge_decision - the saved state log writes the Trader's plan as trader_investment_plan, its state key - the report tree, the final display and the live CLI read the decisions from those fields
148 lines
5.5 KiB
Python
148 lines
5.5 KiB
Python
"""What the live display shows, and what the run log keeps.
|
|
|
|
The display drops a message it judges empty, and the state log is written for a
|
|
person to read afterwards. Both got that wrong in ways that hide real content.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import unittest
|
|
|
|
import pytest
|
|
|
|
from cli.display import (
|
|
AnalystWallTimeTracker,
|
|
extract_content_string,
|
|
sync_analyst_tracker_from_chunk,
|
|
)
|
|
from tradingagents.graph.analyst_execution import build_analyst_execution_plan
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.parametrize("text", ["0", "False", "None", "[]", "{}", "0.0"])
|
|
def test_a_message_that_reads_like_a_python_value_is_still_text(text):
|
|
"""These were parsed as Python and judged empty, so the message vanished."""
|
|
assert extract_content_string(text) == text
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.parametrize("value, expected", [
|
|
(" Hold ", "Hold"),
|
|
("", None),
|
|
(" ", None),
|
|
(None, None),
|
|
([], None),
|
|
({}, None),
|
|
({"text": "from a dict"}, "from a dict"),
|
|
([{"type": "text", "text": "part one"}, {"type": "text", "text": "part two"}], "part one part two"),
|
|
])
|
|
def test_the_other_shapes_are_unchanged(value, expected):
|
|
assert extract_content_string(value) == expected
|
|
|
|
|
|
def _state(ticker, final="评级: 买入"):
|
|
return {
|
|
"company_of_interest": ticker, "trade_date": "2026-09-01",
|
|
"market_report": "市场", "sentiment_report": "情绪", "news_report": "新闻",
|
|
"fundamentals_report": "基本面", "investment_plan": "计划",
|
|
"trader_investment_plan": "交易计划", "final_trade_decision": final, "final_rating": "REVIEW",
|
|
"investment_debate_state": {"bull_history": "", "bear_history": "", "history": "",
|
|
"current_response": "", "count": 0},
|
|
"risk_debate_state": {"aggressive_history": "", "conservative_history": "",
|
|
"neutral_history": "", "history": "",
|
|
"latest_speaker": "", "current_aggressive_response": "",
|
|
"current_conservative_response": "", "current_neutral_response": "",
|
|
"count": 0},
|
|
}
|
|
|
|
|
|
def _bare_graph(tmp_path):
|
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
|
|
|
graph = object.__new__(TradingAgentsGraph)
|
|
graph.config = {"results_dir": str(tmp_path)}
|
|
return graph
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_the_state_log_keeps_non_ascii_readable(tmp_path):
|
|
"""Reports can be in any language; the log is read by a person."""
|
|
_bare_graph(tmp_path)._log_state("2026-09-01", _state("600519.SS"))
|
|
|
|
written = next(tmp_path.rglob("full_states_log*.json")).read_text(encoding="utf-8")
|
|
assert "买入" in written
|
|
assert "\\u" not in written
|
|
assert json.loads(written) # still valid JSON
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_the_state_log_names_each_field_as_the_state_does(tmp_path):
|
|
"""One name per field: the Trader's plan under its state key, and no second
|
|
copy of the managers' decisions under the debate states."""
|
|
_bare_graph(tmp_path)._log_state("2026-09-01", _state("NVDA"))
|
|
|
|
logged = json.loads(next(tmp_path.rglob("full_states_log*.json")).read_text(encoding="utf-8"))
|
|
assert logged["trader_investment_plan"] == "交易计划"
|
|
assert logged["investment_plan"] == "计划"
|
|
assert "trader_investment_decision" not in logged
|
|
assert "judge_decision" not in json.dumps(logged)
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_the_live_display_does_not_scroll_the_terminal():
|
|
"""A layout taller than the window makes rich redraw by scrolling, which
|
|
reads as flicker; the alternate screen holds it in place (#784). The final
|
|
report prints after the live view ends, so nothing is lost when it closes."""
|
|
import inspect
|
|
|
|
import cli.main as m
|
|
|
|
assert "screen=True" in inspect.getsource(m.run_analysis)
|
|
|
|
|
|
class AnalystWallTimeTrackerTests(unittest.TestCase):
|
|
def test_records_wall_time_when_analyst_completes(self):
|
|
plan = build_analyst_execution_plan(["market", "news"])
|
|
tracker = AnalystWallTimeTracker(plan)
|
|
|
|
tracker.mark_started("market", started_at=10.0)
|
|
tracker.mark_completed("market", completed_at=13.5)
|
|
|
|
self.assertEqual(tracker.format_summary(), "Analyst wall time: Market 3.50s")
|
|
|
|
def test_formats_summary_in_plan_order(self):
|
|
plan = build_analyst_execution_plan(["news", "market"])
|
|
tracker = AnalystWallTimeTracker(plan)
|
|
|
|
tracker.mark_started("market", started_at=20.0)
|
|
tracker.mark_completed("market", completed_at=22.25)
|
|
tracker.mark_started("news", started_at=10.0)
|
|
tracker.mark_completed("news", completed_at=14.0)
|
|
|
|
self.assertEqual(
|
|
tracker.format_summary(),
|
|
"Analyst wall time: News 4.00s | Market 2.25s",
|
|
)
|
|
|
|
def test_syncs_wall_time_from_sequential_chunks(self):
|
|
plan = build_analyst_execution_plan(["market", "news"])
|
|
tracker = AnalystWallTimeTracker(plan)
|
|
|
|
sync_analyst_tracker_from_chunk(tracker, {}, now=10.0)
|
|
self.assertEqual(tracker.format_summary(), "Analyst wall time: pending")
|
|
|
|
sync_analyst_tracker_from_chunk(
|
|
tracker,
|
|
{"market_report": "done"},
|
|
now=13.0,
|
|
)
|
|
self.assertEqual(tracker.format_summary(), "Analyst wall time: Market 3.00s")
|
|
|
|
sync_analyst_tracker_from_chunk(
|
|
tracker,
|
|
{"market_report": "done", "news_report": "done"},
|
|
now=18.0,
|
|
)
|
|
self.assertEqual(tracker.format_summary(), "Analyst wall time: Market 3.00s | News 5.00s")
|