Skip to content

Add optional Adanos market sentiment tool - #1061

Open
alexander-schneider wants to merge 2 commits into
Lumiwealth:devfrom
adanos-software:codex/add-adanos-sentiment-tool
Open

Add optional Adanos market sentiment tool#1061
alexander-schneider wants to merge 2 commits into
Lumiwealth:devfrom
adanos-software:codex/add-adanos-sentiment-tool

Conversation

@alexander-schneider

@alexander-schneider alexander-schneider commented May 24, 2026

Copy link
Copy Markdown

Summary

  • Add an optional AdanosMarketSentiment helper available as strategy.sentiment
  • Expose BuiltinTools.sentiment.adanos_market_sentiment() for AI agents when ADANOS_API_KEY is configured
  • Document Adanos environment variables and built-in tool usage
  • Add unit coverage for client requests, missing credentials, tool binding, and AgentManager disabled-tool filtering

Design notes

  • The integration is optional and does not change broker or price-data behavior
  • Backtest calls default the Adanos date bound to the strategy datetime and pass it as the API to query parameter
  • The tool is omitted from agent runs when ADANOS_API_KEY is not set, matching the existing unavailable built-in-tool pattern

Checks

  • /tmp/lumibot-adanos-venv/bin/python -m pytest tests/test_adanos_sentiment.py tests/test_agent_alpaca_news_builtin.py tests/test_lazy_exports.py -q
  • /tmp/lumibot-adanos-venv/bin/python -m pytest tests/test_adanos_sentiment.py -q
  • /tmp/lumibot-adanos-venv/bin/python -m ruff check lumibot/sentiment/adanos.py tests/test_adanos_sentiment.py
  • /tmp/lumibot-adanos-venv/bin/python -m py_compile lumibot/sentiment/adanos.py lumibot/components/agents/builtins.py lumibot/components/agents/manager.py lumibot/strategies/_strategy.py tests/test_adanos_sentiment.py
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Optional Adanos Market Sentiment integration for AI agents (US-equity sentiment from Reddit, X/FinTwit, news, Polymarket).
    • New strategy.sentiment helper and adanos_market_sentiment agent tool (available when ADANOS_API_KEY is set). Tool supports per-stock or market modes and respects strategy/backtest dates.
  • Documentation

    • Docs updated with setup, ADANOS_API_KEY and ADANOS_API_BASE_URL config, and tool behavior.
  • Tests

    • Added tests covering client, tool behavior, and missing-key handling.

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b7d7528-a0ea-4794-a757-7b2e9198ed3a

📥 Commits

Reviewing files that changed from the base of the PR and between baab56d and 415d64a.

📒 Files selected for processing (3)
  • docsrc/environment_variables.rst
  • lumibot/sentiment/adanos.py
  • tests/test_adanos_sentiment.py
✅ Files skipped from review due to trivial changes (1)
  • docsrc/environment_variables.rst
🚧 Files skipped from review as they are similar to previous changes (2)
  • lumibot/sentiment/adanos.py
  • tests/test_adanos_sentiment.py

📝 Walkthrough

Walkthrough

This PR adds optional integration with Adanos Market Sentiment API to Lumibot strategies and agents. A new sentiment client library fetches US-equity sentiment from multiple sources (Reddit, X/FinTwit, news, Polymarket). Strategies access sentiment via self.sentiment helper; agents invoke it through the adanos_market_sentiment built-in tool. Both are gated by the ADANOS_API_KEY environment variable. Comprehensive tests validate the client, tool binding, and agent manager integration.

Changes

Adanos Market Sentiment Integration

Layer / File(s) Summary
Sentiment client library and module
lumibot/sentiment/adanos.py, lumibot/sentiment/__init__.py
New AdanosMarketSentiment HTTP client with constants for API base URL and allowed sources, date parsing and validation helpers, and public methods get_stock_sentiment() and get_market_sentiment() that fetch per-source sentiment and aggregate results/errors into structured responses. Module exports the client for lazy import.
Strategy initialization and package exposure
lumibot/strategies/_strategy.py, lumibot/__init__.py
Strategy __init__ instantiates self.sentiment with AdanosMarketSentiment; lumibot.__init__ adds "sentiment" to _SUBMODULE_EXPORTS for lazy submodule import.
Built-in agent tool definition and wiring
lumibot/components/agents/builtins.py, lumibot/components/agents/manager.py
Tool description constant, binder that checks ADANOS_API_KEY and conditionally disables tool with structured error payload, lazy client instantiation, _SentimentTools container, integration into _BuiltinTools.sentiment, inclusion in default tool list, and addition of adanos_market_sentiment to manager data-tool detection.
Comprehensive test suite
tests/test_adanos_sentiment.py
Test helpers for mocking strategy datetime and HTTP responses; tests covering client source validation, date-bounded requests, missing/available API key behavior, tool disabled/enabled flows, and AgentManager tool binding.
Documentation and changelog
CHANGELOG.md, docsrc/agents_builtin_tools.rst, docsrc/environment_variables.rst
Changelog entry for 4.5.32, agent tools documentation describing optional tool usage and backtest time-bounding, and environment variable docs for ADANOS_API_KEY and ADANOS_API_BASE_URL.

Sequence Diagram(s)

sequenceDiagram
  participant Strategy
  participant BuiltinTool as adanos_market_sentiment
  participant AdanosClient as AdanosMarketSentiment
  participant AdanosAPI as Adanos API

  Strategy->>BuiltinTool: call adanos_market_sentiment(mode, sources, days, end)
  BuiltinTool->>AdanosClient: delegate to get_market_sentiment / get_stock_sentiment
  AdanosClient->>AdanosAPI: GET /market or /stock with X-API-Key and params
  AdanosAPI-->>AdanosClient: per-source JSON responses
  AdanosClient-->>BuiltinTool: aggregated {results, errors, sources}
  BuiltinTool-->>Strategy: {ok: ..., results, errors}
Loading

Estimated code review effort:
🎯 3 (Moderate) | ⏱️ ~25 minutes

"A rabbit nibble, code in springtime bright,
I stitched sentiment into the night.
From Reddit, news, and tweets it brings,
A hop of data for trading things. 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: adding an optional Adanos market sentiment tool as a new feature for AI agents.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Pylint (4.0.5)
tests/test_adanos_sentiment.py

************* Module pylintrc
pylintrc:1:0: F0011: error while parsing the configuration: File contains no section headers.
file: 'pylintrc', line: 1
'known-third-party=lumibot' (config-parse-error)
[
{
"type": "convention",
"module": "tests.test_adanos_sentiment",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "tests/test_adanos_sentiment.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "error",
"module": "tests.test_adanos_sentiment",
"obj": "",
"line": 3,
"column": 0,
"endLine": 3,
"endColumn": 64,
"path": "tests/test_adanos_sentiment.py",
"symbol": "no-name-in-module",
"message": "No name 'AgentManager' in module 'lumibot.components.agents'",
"message-id": "E0611"
},
{
"type": "error

... [truncated 4770 characters] ...

"obj": "test_agent_manager_omits_unavailable_adanos_tool",
"line": 120,
"column": 0,
"endLine": 120,
"endColumn": 52,
"path": "tests/test_adanos_sentiment.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "tests.test_adanos_sentiment",
"obj": "test_agent_manager_omits_unavailable_adanos_tool",
"line": 126,
"column": 40,
"endLine": 126,
"endColumn": 66,
"path": "tests/test_adanos_sentiment.py",
"symbol": "protected-access",
"message": "Access to a protected member _ensure_bound_tools of a client class",
"message-id": "W0212"
}
]

lumibot/sentiment/adanos.py

************* Module pylintrc
pylintrc:1:0: F0011: error while parsing the configuration: File contains no section headers.
file: 'pylintrc', line: 1
'known-third-party=lumibot' (config-parse-error)
[
{
"type": "convention",
"module": "lumibot.sentiment.adanos",
"obj": "",
"line": 77,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "lumibot/sentiment/adanos.py",
"symbol": "line-too-long",
"message": "Line too long (110/100)",
"message-id": "C0301"
},
{
"type": "convention",
"module": "lumibot.sentiment.adanos",
"obj": "",
"line": 1,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "lumibot/sentiment/adanos.py",
"symbol": "missing-module-docstring",
"message": "Missing module docstring",
"message-id": "C0114"
},
{
"type": "warning",
"module": "lumibot.sentiment.

... [truncated 1241 characters] ...

le": "lumibot.sentiment.adanos",
"obj": "AdanosMarketSentiment.get_market_sentiment",
"line": 141,
"column": 4,
"endLine": 141,
"endColumn": 28,
"path": "lumibot/sentiment/adanos.py",
"symbol": "missing-function-docstring",
"message": "Missing function or method docstring",
"message-id": "C0116"
},
{
"type": "warning",
"module": "lumibot.sentiment.adanos",
"obj": "AdanosMarketSentiment.get_market_sentiment",
"line": 158,
"column": 19,
"endLine": 158,
"endColumn": 28,
"path": "lumibot/sentiment/adanos.py",
"symbol": "broad-exception-caught",
"message": "Catching too general exception Exception",
"message-id": "W0718"
}
]


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
tests/test_adanos_sentiment.py (4)

22-30: 💤 Low value

Add docstring to test helper class.

The _Response mock class lacks a docstring. A brief docstring clarifies its role as an HTTP response stub.

📝 Proposed docstring
 class _Response:
+    """Mock HTTP response for stubbing requests.get in tests."""
     def __init__(self, payload):
         self._payload = payload
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_adanos_sentiment.py` around lines 22 - 30, Add a concise docstring
to the test helper class _Response describing that it is a lightweight HTTP
response stub used in tests, what payload it stores, and that raise_for_status()
is a no-op while json() returns the stored payload; update the class _Response
(and optionally mention its methods __init__, raise_for_status, json in the
docstring) to include this brief explanation so future readers understand its
purpose.

44-53: ⚡ Quick win

Verify all HTTP calls when multiple sources are requested.

The test passes sources="reddit,news" (two sources) but only verifies calls[0]. If the client makes one HTTP call per source (as suggested by the /reddit/ path in the URL), the test should verify both calls or at least assert the total count to ensure no source is silently dropped.

🔍 Proposed assertion to verify call count
     assert result["sources"] == ["reddit", "news"]
     assert result["results"]["reddit"]["sentiment_score"] == 0.42
+    assert len(calls) == 2, "Expected one call per source"
     assert calls[0]["url"] == "https://adanos.test/reddit/stocks/v1/stock/AAPL"
     assert calls[0]["headers"] == {"X-API-Key": "test-key"}
     assert calls[0]["params"] == {"days": 5, "to": "2026-05-20"}
     assert calls[0]["timeout"] == 3
+    assert calls[1]["url"] == "https://adanos.test/news/stocks/v1/stock/AAPL"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_adanos_sentiment.py` around lines 44 - 53, The test currently only
inspects calls[0] after invoking client.get_stock_sentiment("aapl",
sources="reddit,news", days=5); update the assertions to ensure both HTTP
requests are made and validated: assert the length of calls (e.g., len(calls) ==
2) and add checks for calls[1] mirroring the expectations for the "news" source
(URL containing "/news/stocks/v1/stock/AAPL", same headers and params including
days/to, and timeout), keeping the existing assertions for calls[0] for the
"reddit" source so no source is dropped when get_stock_sentiment is called.

7-20: 💤 Low value

Add docstring to test helper class.

The _Strategy mock class lacks a docstring explaining its purpose as a test fixture. Adding a brief docstring improves test readability and maintainability.

📝 Proposed docstring
 class _Strategy:
+    """Mock strategy fixture for Adanos sentiment testing.
+    
+    Provides fixed datetime (2026-05-20 14:30 UTC) and captures log messages.
+    """
     is_backtesting = True
     broker = None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_adanos_sentiment.py` around lines 7 - 20, Add a concise docstring
to the _Strategy test helper class that states it is a mock/test fixture used by
tests to emulate a strategy object (providing attributes is_backtesting, broker,
log_messages and the AdanosMarketSentiment instance) and documents the purpose
of its helper methods get_datetime and log_message; update the class _Strategy
declaration (and optionally mention AdanosMarketSentiment, get_datetime, and
log_message by name) with a one- or two-line docstring describing its role in
tests.

1-109: ⚡ Quick win

Consider adding edge case tests.

The current test suite covers happy paths and missing-credential scenarios well. Consider adding tests for:

  • Invalid source names (e.g., sources="invalid_source")
  • Malformed API responses (missing expected fields, non-dict responses)
  • HTTP errors (non-200 status codes)
  • Timeout or network exceptions

These would increase robustness but are not essential for the initial implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_adanos_sentiment.py` around lines 1 - 109, Add unit tests covering
edge cases for AdanosMarketSentiment and the BuiltinTools adanos tool: add tests
that call AdanosMarketSentiment.get_stock_sentiment and
BuiltinTools.sentiment.adanos_market_sentiment().binder(...).function with
invalid source names (e.g., "invalid_source") and assert errors are recorded in
result["errors"]; add tests that monkeypatch
lumibot.sentiment.adanos.requests.get to return malformed JSON (e.g., non-dict
or missing expected keys) and assert the client handles it by returning empty
results and populating result["errors"]; add tests that simulate HTTP errors by
making _Response.raise_for_status raise an exception and verify exceptions are
caught and logged; and add tests that simulate timeouts/network exceptions by
having fake_get raise requests.Timeout (or a generic Exception) and assert
timeouts are handled gracefully. Reference AdanosMarketSentiment,
BuiltinTools.sentiment.adanos_market_sentiment, _Response, fake_get, and
AgentManager to locate where to add these tests.
docsrc/environment_variables.rst (1)

773-782: 💤 Low value

Consider relocating to a more appropriate section.

The ADANOS environment variables are documented under "SEC fundamentals and agent memory" (line 741), but Adanos provides market sentiment data rather than SEC fundamentals. This creates a categorization mismatch that may confuse users searching for sentiment-related configuration.

Consider either:

  • Creating a new "Market sentiment data" section for ADANOS variables
  • Renaming the section to "SEC fundamentals, market sentiment, and agent memory"
  • Moving ADANOS variables to a "Data source credentials" subsection

The documentation content itself is accurate and complete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docsrc/environment_variables.rst` around lines 773 - 782, The ADANOS_API_KEY
entry is placed under the "SEC fundamentals and agent memory" section but
belongs under a sentiment/data-sources area; please move the ADANOS_API_KEY
block (and any related ADANOS variables) out of the "SEC fundamentals and agent
memory" section and either create a new "Market sentiment data" section or a
"Data source credentials" subsection to house it, or alternatively rename the
parent section to "SEC fundamentals, market sentiment, and agent memory" so the
ADANOS_API_KEY entry logically matches surrounding content; update the section
title and TOC entries accordingly to keep the docs consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lumibot/sentiment/adanos.py`:
- Around line 44-55: The helper _source_list can silently return an empty list
(e.g., when sources=="" or an iterable of empty strings) which causes downstream
no-ops; update _source_list (the function handling ADANOS_SOURCES) to treat an
empty requested list as an error: after building requested (in all branches)
check if not requested and raise a ValueError like "No Adanos sources provided"
or similar, ensuring blank strings or empty iterables are rejected rather than
returning [].

---

Nitpick comments:
In `@docsrc/environment_variables.rst`:
- Around line 773-782: The ADANOS_API_KEY entry is placed under the "SEC
fundamentals and agent memory" section but belongs under a
sentiment/data-sources area; please move the ADANOS_API_KEY block (and any
related ADANOS variables) out of the "SEC fundamentals and agent memory" section
and either create a new "Market sentiment data" section or a "Data source
credentials" subsection to house it, or alternatively rename the parent section
to "SEC fundamentals, market sentiment, and agent memory" so the ADANOS_API_KEY
entry logically matches surrounding content; update the section title and TOC
entries accordingly to keep the docs consistent.

In `@tests/test_adanos_sentiment.py`:
- Around line 22-30: Add a concise docstring to the test helper class _Response
describing that it is a lightweight HTTP response stub used in tests, what
payload it stores, and that raise_for_status() is a no-op while json() returns
the stored payload; update the class _Response (and optionally mention its
methods __init__, raise_for_status, json in the docstring) to include this brief
explanation so future readers understand its purpose.
- Around line 44-53: The test currently only inspects calls[0] after invoking
client.get_stock_sentiment("aapl", sources="reddit,news", days=5); update the
assertions to ensure both HTTP requests are made and validated: assert the
length of calls (e.g., len(calls) == 2) and add checks for calls[1] mirroring
the expectations for the "news" source (URL containing
"/news/stocks/v1/stock/AAPL", same headers and params including days/to, and
timeout), keeping the existing assertions for calls[0] for the "reddit" source
so no source is dropped when get_stock_sentiment is called.
- Around line 7-20: Add a concise docstring to the _Strategy test helper class
that states it is a mock/test fixture used by tests to emulate a strategy object
(providing attributes is_backtesting, broker, log_messages and the
AdanosMarketSentiment instance) and documents the purpose of its helper methods
get_datetime and log_message; update the class _Strategy declaration (and
optionally mention AdanosMarketSentiment, get_datetime, and log_message by name)
with a one- or two-line docstring describing its role in tests.
- Around line 1-109: Add unit tests covering edge cases for
AdanosMarketSentiment and the BuiltinTools adanos tool: add tests that call
AdanosMarketSentiment.get_stock_sentiment and
BuiltinTools.sentiment.adanos_market_sentiment().binder(...).function with
invalid source names (e.g., "invalid_source") and assert errors are recorded in
result["errors"]; add tests that monkeypatch
lumibot.sentiment.adanos.requests.get to return malformed JSON (e.g., non-dict
or missing expected keys) and assert the client handles it by returning empty
results and populating result["errors"]; add tests that simulate HTTP errors by
making _Response.raise_for_status raise an exception and verify exceptions are
caught and logged; and add tests that simulate timeouts/network exceptions by
having fake_get raise requests.Timeout (or a generic Exception) and assert
timeouts are handled gracefully. Reference AdanosMarketSentiment,
BuiltinTools.sentiment.adanos_market_sentiment, _Response, fake_get, and
AgentManager to locate where to add these tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8527c3b-eb0c-40c0-945f-841249830147

📥 Commits

Reviewing files that changed from the base of the PR and between 1f91fa6 and baab56d.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docsrc/agents_builtin_tools.rst
  • docsrc/environment_variables.rst
  • lumibot/__init__.py
  • lumibot/components/agents/builtins.py
  • lumibot/components/agents/manager.py
  • lumibot/sentiment/__init__.py
  • lumibot/sentiment/adanos.py
  • lumibot/strategies/_strategy.py
  • tests/test_adanos_sentiment.py

Comment thread lumibot/sentiment/adanos.py
@alexander-schneider

Copy link
Copy Markdown
Author

Addressed the review feedback in commit 415d64a:\n\n- reject empty Adanos source lists with a clear ValueError\n- expanded the multi-source test to assert both outbound API calls\n- added helper fixture docstrings\n- aligned the environment variable docs heading with market sentiment\n\nVerification run locally:\n- /tmp/lumibot-adanos-venv/bin/python -m pytest tests/test_adanos_sentiment.py -q -> 6 passed\n- py_compile for lumibot/sentiment/adanos.py and tests/test_adanos_sentiment.py\n- git diff --check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant