mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-11-03 16:12:44 +02:00
- only an answered lookup is cached for the process, so one failed request no longer leaves every later run for that ticker without an identity
131 lines
5.4 KiB
Python
131 lines
5.4 KiB
Python
"""Tests for deterministic instrument-identity resolution (#814) and the
|
|
context-anchored message placeholder (#888)."""
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from tradingagents.agents.context import (
|
|
_identity,
|
|
build_instrument_context,
|
|
get_instrument_context_from_state,
|
|
resolve_instrument_identity,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class ResolveInstrumentIdentityTests(unittest.TestCase):
|
|
def setUp(self):
|
|
_identity.cache_clear()
|
|
|
|
def test_resolves_company_metadata_from_yfinance(self):
|
|
with patch("tradingagents.dataflows.vendors.yahoo.market.yf.Ticker") as mock:
|
|
mock.return_value.info = {
|
|
"longName": "TOTO LTD.",
|
|
"shortName": "TOTO",
|
|
"sector": "Industrials",
|
|
"industry": "Building Products & Equipment",
|
|
"exchange": "PNK",
|
|
"quoteType": "EQUITY",
|
|
}
|
|
identity = resolve_instrument_identity("totdy")
|
|
mock.assert_called_once_with("TOTDY")
|
|
self.assertEqual(identity["company_name"], "TOTO LTD.")
|
|
self.assertEqual(identity["sector"], "Industrials")
|
|
self.assertEqual(identity["industry"], "Building Products & Equipment")
|
|
self.assertEqual(identity["exchange"], "PNK")
|
|
|
|
def test_falls_back_to_short_name(self):
|
|
with patch("tradingagents.dataflows.vendors.yahoo.market.yf.Ticker") as mock:
|
|
mock.return_value.info = {"shortName": "TOTO", "sector": "Industrials"}
|
|
identity = resolve_instrument_identity("TOTDY")
|
|
self.assertEqual(identity["company_name"], "TOTO")
|
|
|
|
def test_skips_placeholder_values(self):
|
|
with patch("tradingagents.dataflows.vendors.yahoo.market.yf.Ticker") as mock:
|
|
mock.return_value.info = {"longName": " ", "sector": "None", "industry": "n/a"}
|
|
identity = resolve_instrument_identity("TOTDY")
|
|
self.assertEqual(identity, {})
|
|
|
|
def test_fails_open_on_exception(self):
|
|
with patch(
|
|
"tradingagents.dataflows.vendors.yahoo.market.yf.Ticker",
|
|
side_effect=RuntimeError("rate limited"),
|
|
):
|
|
self.assertEqual(resolve_instrument_identity("TOTDY"), {})
|
|
|
|
def test_a_failed_lookup_is_asked_again(self):
|
|
# A long process (a backtest) must not run every later cell without an
|
|
# identity because one request failed.
|
|
with patch("tradingagents.dataflows.vendors.yahoo.market.yf.Ticker") as mock:
|
|
type(mock.return_value).info = property(lambda self: (_ for _ in ()).throw(TimeoutError()))
|
|
self.assertEqual(resolve_instrument_identity("TOTDY"), {})
|
|
type(mock.return_value).info = {"longName": "TOTO LTD."}
|
|
self.assertEqual(resolve_instrument_identity("TOTDY")["company_name"], "TOTO LTD.")
|
|
|
|
def test_result_is_cached(self):
|
|
with patch("tradingagents.dataflows.vendors.yahoo.market.yf.Ticker") as mock:
|
|
mock.return_value.info = {"longName": "TOTO LTD."}
|
|
first = resolve_instrument_identity("TOTDY")
|
|
second = resolve_instrument_identity("TOTDY")
|
|
mock.assert_called_once() # second call served from cache
|
|
self.assertEqual(first, second)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class BuildInstrumentContextTests(unittest.TestCase):
|
|
def test_mentions_exact_symbol_without_identity(self):
|
|
context = build_instrument_context("7203.T")
|
|
self.assertIn("7203.T", context)
|
|
self.assertIn("exchange suffix", context)
|
|
self.assertNotIn("Resolved identity", context)
|
|
|
|
def test_injects_resolved_identity(self):
|
|
context = build_instrument_context(
|
|
"TOTDY", "stock",
|
|
{
|
|
"company_name": "TOTO LTD.",
|
|
"sector": "Industrials",
|
|
"industry": "Building Products & Equipment",
|
|
"exchange": "PNK",
|
|
},
|
|
)
|
|
self.assertIn("Company: TOTO LTD.", context)
|
|
self.assertIn("Industrials / Building Products & Equipment", context)
|
|
self.assertIn("Exchange: PNK", context)
|
|
self.assertIn("Do not substitute a different company", context)
|
|
|
|
def test_crypto_uses_name_label_and_keeps_hint(self):
|
|
context = build_instrument_context(
|
|
"BTC-USD", "crypto", {"company_name": "Bitcoin USD"}
|
|
)
|
|
self.assertIn("Name: Bitcoin USD", context)
|
|
self.assertIn("crypto asset rather than a company", context)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class GetInstrumentContextFromStateTests(unittest.TestCase):
|
|
def test_prefers_precomputed_context(self):
|
|
state = {"company_of_interest": "TOTDY", "instrument_context": "PRECOMPUTED"}
|
|
self.assertEqual(get_instrument_context_from_state(state), "PRECOMPUTED")
|
|
|
|
def test_fallback_is_network_free_ticker_only(self):
|
|
# No instrument_context and no yfinance call — must not hit the network.
|
|
with patch("tradingagents.dataflows.vendors.yahoo.market.yf.Ticker") as mock:
|
|
context = get_instrument_context_from_state(
|
|
{"company_of_interest": "NVDA", "asset_type": "stock"}
|
|
)
|
|
mock.assert_not_called()
|
|
self.assertIn("NVDA", context)
|
|
|
|
def test_fallback_respects_asset_type(self):
|
|
context = get_instrument_context_from_state(
|
|
{"company_of_interest": "BTC-USD", "asset_type": "crypto"}
|
|
)
|
|
self.assertIn("crypto asset", context)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|