feat(scanner): Finviz smart money scanner + Golden Overlap strategy
## Summary - Adds `smart_money_scanner` as a new Phase 1b node that runs sequentially after `sector_scanner`, surfacing institutional footprints via Finviz screeners - Introduces the **Golden Overlap** strategy in `macro_synthesis`: stocks confirmed by both top-down macro themes and bottom-up Finviz signals are labelled high-conviction - Fixes model-name badge overflow in AgentGraph (long model IDs like OpenRouter paths were visually spilling into adjacent nodes) - Completes all documentation: ADR-014, dataflow, architecture, components, glossary, current-state ## Key Decisions (see ADR-014) - 3 zero-parameter tools (`get_insider_buying_stocks`, `get_unusual_volume_stocks`, `get_breakout_accumulation_stocks`) instead of 1 parameterised tool — prevents LLM hallucinations on string args - Sequential after `sector_scanner` (not parallel fan-out) — gives access to `sector_performance_report` context and avoids `MAX_TOOL_ROUNDS=5` truncation in market_movers_scanner - Graceful fallback: `_run_finviz_screen()` catches all exceptions and returns an error string — pipeline never hard-fails on web-scraper failure - `breakout_accumulation` (52-wk high + 2x vol = O'Neil CAN SLIM institutional signal) replaces `oversold_bounces` (RSI<30 = retail contrarian, not smart money) ## Test Plan - [x] 6 new mocked tests in `tests/unit/test_scanner_mocked.py` (happy path, empty DF, exception, sort order) - [x] Fixed `tests/unit/test_scanner_graph.py` — added `smart_money_scanner` mock to compilation test - [x] 2 pre-existing test failures excluded (verified baseline before changes) - [x] AgentGraph badge: visually verified truncation with long OpenRouter model identifiers 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
parent
6adf12eee6
commit
4c14080d73
|
|
@ -127,9 +127,15 @@ const AgentNode = ({ data }: NodeProps) => {
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{data.metrics?.model && data.metrics.model !== 'unknown' && (
|
{data.metrics?.model && data.metrics.model !== 'unknown' && (
|
||||||
<Badge variant="outline" fontSize="2xs" colorScheme="blue" alignSelf="flex-start">
|
<Tooltip label={data.metrics.model} placement="top" hasArrow openDelay={300}>
|
||||||
|
<Badge
|
||||||
|
variant="outline" fontSize="2xs" colorScheme="blue"
|
||||||
|
display="block" maxW="100%"
|
||||||
|
overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap"
|
||||||
|
>
|
||||||
{data.metrics.model}
|
{data.metrics.model}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Running shimmer */}
|
{/* Running shimmer */}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,26 @@
|
||||||
# Current Milestone
|
# Current Milestone
|
||||||
|
|
||||||
AgentOS visual observability layer shipped. Portfolio Manager fully implemented (Phases 1–10). All 725 tests passing (14 skipped). Stop-loss / take-profit fields added to trades.
|
Smart Money Scanner added to scanner pipeline (Phase 1b). `finvizfinance` integration with Golden Overlap strategy in macro_synthesis. 18 agent factories. All tests passing (2 pre-existing failures excluded).
|
||||||
|
|
||||||
# Recent Progress
|
# Recent Progress
|
||||||
|
|
||||||
- **Stop-loss & Take-profit on trades**: Added `stop_loss` and `take_profit` optional fields to the `Trade` model, SQL migration, PM agent prompt, trade executor, repository, API route, and frontend Trade History tab.
|
- **Smart Money Scanner (current branch)**: 4th scanner node added to macro pipeline
|
||||||
- **AgentOS (current PR)**: Full-stack visual observability layer for agent execution
|
- `tradingagents/agents/scanners/smart_money_scanner.py` — Phase 1b node, runs sequentially after sector_scanner
|
||||||
- `agent_os/backend/` — FastAPI backend (port 8088) with REST + WebSocket streaming
|
- `tradingagents/agents/utils/scanner_tools.py` — 3 zero-parameter Finviz tools: `get_insider_buying_stocks`, `get_unusual_volume_stocks`, `get_breakout_accumulation_stocks`
|
||||||
- `agent_os/frontend/` — React + Vite 8 + Chakra UI + ReactFlow dashboard
|
- `tradingagents/agents/utils/scanner_states.py` — Added `smart_money_report` field with `_last_value` reducer
|
||||||
- `agent_os/backend/services/langgraph_engine.py` — LangGraph event mapping engine (4 run types: scan, pipeline, portfolio, auto)
|
- `tradingagents/graph/scanner_setup.py` — Topology: sector_scanner → smart_money_scanner → industry_deep_dive
|
||||||
- `agent_os/backend/routes/websocket.py` — WebSocket streaming endpoint (`/ws/stream/{run_id}`)
|
- `tradingagents/graph/scanner_graph.py` — Instantiates smart_money_scanner with quick_llm
|
||||||
- `agent_os/backend/routes/runs.py` — REST run triggers (`POST /api/run/{type}`)
|
- `tradingagents/agents/scanners/macro_synthesis.py` — Golden Overlap instructions + smart_money_report in context
|
||||||
- `agent_os/backend/routes/portfolios.py` — Portfolio REST API with field mapping (backend models → frontend shape)
|
- `pyproject.toml` — Added `finvizfinance>=0.14.0` dependency
|
||||||
- `agent_os/frontend/src/Dashboard.tsx` — 2-page layout (dashboard + portfolio), agent graph + terminal + controls
|
- `docs/agent/decisions/014-finviz-smart-money-scanner.md` — ADR documenting all design decisions
|
||||||
- `agent_os/frontend/src/components/AgentGraph.tsx` — ReactFlow live graph visualization
|
- Tests: 6 new mocked tests in `tests/unit/test_scanner_mocked.py`, 1 fix in `tests/unit/test_scanner_graph.py`
|
||||||
- `agent_os/frontend/src/components/PortfolioViewer.tsx` — Holdings, trade history, summary views
|
- **AgentOS**: Full-stack visual observability layer (FastAPI + React + ReactFlow)
|
||||||
- `agent_os/frontend/src/components/MetricHeader.tsx` — Top-3 metrics (Sharpe, regime, drawdown)
|
- **Portfolio Manager**: Phases 1–10 fully implemented (models, agents, CLI integration, stop-loss/take-profit)
|
||||||
- `agent_os/frontend/src/hooks/useAgentStream.ts` — WebSocket hook with status tracking
|
- **PR #32 merged**: Portfolio Manager data foundation
|
||||||
- `tests/unit/test_langgraph_engine_extraction.py` — 14 tests for event mapping
|
|
||||||
- Pipeline recursion limit fix: passes `config={"recursion_limit": propagator.max_recur_limit}` to `astream_events()`
|
|
||||||
- Portfolio field mapping fix: shares→quantity, portfolio_id→id, cash→cash_balance, trade_date→executed_at
|
|
||||||
- **PR #32 merged**: Portfolio Manager data foundation — models, SQL schema, module scaffolding
|
|
||||||
- **Portfolio Manager Phases 2-5** (implemented): risk_evaluator, candidate_prioritizer, trade_executor, holding_reviewer, pm_decision_agent, portfolio_states, portfolio_setup, portfolio_graph
|
|
||||||
- **Portfolio CLI integration**: `portfolio`, `check-portfolio`, `auto` commands in `cli/main.py`
|
|
||||||
|
|
||||||
# In Progress
|
# In Progress
|
||||||
|
|
||||||
- None — PR ready for merge
|
- None — branch ready for PR
|
||||||
|
|
||||||
# Active Blockers
|
# Active Blockers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<!-- Last verified: 2026-03-23 -->
|
<!-- Last verified: 2026-03-24 -->
|
||||||
|
|
||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
TradingAgents v0.2.1 is a multi-agent LLM framework using LangGraph. It has 17 agent factory functions, 3 data vendors (yfinance, Alpha Vantage, Finnhub), and 6 LLM providers (OpenAI, Anthropic, Google, xAI, OpenRouter, Ollama).
|
TradingAgents v0.2.2 is a multi-agent LLM framework using LangGraph. It has 18 agent factory functions, 4 data vendors (yfinance, Alpha Vantage, Finnhub, Finviz), and 6 LLM providers (OpenAI, Anthropic, Google, xAI, OpenRouter, Ollama).
|
||||||
|
|
||||||
## 3-Tier LLM System
|
## 3-Tier LLM System
|
||||||
|
|
||||||
|
|
@ -36,6 +36,7 @@ Source: `tradingagents/llm_clients/`
|
||||||
| yfinance | Primary (free) | OHLCV, fundamentals, news, screener, sector/industry, indices |
|
| yfinance | Primary (free) | OHLCV, fundamentals, news, screener, sector/industry, indices |
|
||||||
| Alpha Vantage | Fallback | OHLCV, fundamentals, news, sector ETF proxies, market movers |
|
| Alpha Vantage | Fallback | OHLCV, fundamentals, news, sector ETF proxies, market movers |
|
||||||
| Finnhub | Specialized | Insider transactions (primary), earnings calendar, economic calendar |
|
| Finnhub | Specialized | Insider transactions (primary), earnings calendar, economic calendar |
|
||||||
|
| Finviz | Smart Money (best-effort) | Institutional screeners via `finvizfinance` web scraper — insider buys, unusual volume, breakout accumulation; graceful degradation on failure |
|
||||||
|
|
||||||
Routing: 2-level dispatch — category-level (`data_vendors` config) + tool-level (`tool_vendors` config). Fail-fast by default; only 5 methods in `FALLBACK_ALLOWED` get cross-vendor fallback (ADR 011).
|
Routing: 2-level dispatch — category-level (`data_vendors` config) + tool-level (`tool_vendors` config). Fail-fast by default; only 5 methods in `FALLBACK_ALLOWED` get cross-vendor fallback (ADR 011).
|
||||||
|
|
||||||
|
|
@ -65,12 +66,16 @@ Source: `tradingagents/graph/trading_graph.py`, `tradingagents/graph/setup.py`
|
||||||
## Scanner Pipeline
|
## Scanner Pipeline
|
||||||
|
|
||||||
```
|
```
|
||||||
START ──┬── Geopolitical Scanner (quick) ──┐
|
START ──┬── Geopolitical Scanner (quick) ──────────────────────┐
|
||||||
├── Market Movers Scanner (quick) ──┼── Industry Deep Dive (mid) ── Macro Synthesis (deep) ── END
|
├── Market Movers Scanner (quick) ─────────────────────┤
|
||||||
└── Sector Scanner (quick) ─────────┘
|
└── Sector Scanner (quick) ── Smart Money Scanner ─────┴── Industry Deep Dive (mid) ── Macro Synthesis (deep) ── END
|
||||||
|
(quick, Finviz)
|
||||||
```
|
```
|
||||||
|
|
||||||
Phase 1: 3 scanners run in parallel. Phase 2: Deep dive cross-references all outputs, calls `get_industry_performance` per sector. Phase 3: Macro synthesis produces top-10 watchlist as JSON.
|
- **Phase 1a** (parallel): geopolitical, market_movers, sector scanners
|
||||||
|
- **Phase 1b** (sequential after sector): smart_money_scanner — uses sector rotation context when running Finviz screeners (insider buys, unusual volume, breakout accumulation)
|
||||||
|
- **Phase 2**: Industry deep dive cross-references all 4 Phase 1 outputs
|
||||||
|
- **Phase 3**: Macro synthesis applies **Golden Overlap** — cross-references smart money tickers with top-down macro thesis to assign high/medium/low conviction; produces top 8-10 watchlist as JSON
|
||||||
|
|
||||||
Source: `tradingagents/graph/scanner_graph.py`, `tradingagents/graph/scanner_setup.py`
|
Source: `tradingagents/graph/scanner_graph.py`, `tradingagents/graph/scanner_setup.py`
|
||||||
|
|
||||||
|
|
@ -158,7 +163,7 @@ Full-stack web UI for monitoring and controlling agent execution in real-time.
|
||||||
|
|
||||||
| Type | REST Trigger | WebSocket Executor | Description |
|
| Type | REST Trigger | WebSocket Executor | Description |
|
||||||
|------|-------------|-------------------|-------------|
|
|------|-------------|-------------------|-------------|
|
||||||
| `scan` | `POST /api/run/scan` | `run_scan()` | 3-phase macro scanner |
|
| `scan` | `POST /api/run/scan` | `run_scan()` | 4-node macro scanner (3 parallel + smart money) |
|
||||||
| `pipeline` | `POST /api/run/pipeline` | `run_pipeline()` | Per-ticker trading analysis |
|
| `pipeline` | `POST /api/run/pipeline` | `run_pipeline()` | Per-ticker trading analysis |
|
||||||
| `portfolio` | `POST /api/run/portfolio` | `run_portfolio()` | Portfolio manager workflow |
|
| `portfolio` | `POST /api/run/portfolio` | `run_portfolio()` | Portfolio manager workflow |
|
||||||
| `auto` | `POST /api/run/auto` | `run_auto()` | Sequential: scan → pipeline → portfolio |
|
| `auto` | `POST /api/run/auto` | `run_auto()` | Sequential: scan → pipeline → portfolio |
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- Last verified: 2026-03-23 -->
|
<!-- Last verified: 2026-03-24 -->
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
|
|
||||||
|
|
@ -34,6 +34,7 @@ tradingagents/
|
||||||
│ │ ├── geopolitical_scanner.py # create_geopolitical_scanner(llm)
|
│ │ ├── geopolitical_scanner.py # create_geopolitical_scanner(llm)
|
||||||
│ │ ├── market_movers_scanner.py # create_market_movers_scanner(llm)
|
│ │ ├── market_movers_scanner.py # create_market_movers_scanner(llm)
|
||||||
│ │ ├── sector_scanner.py # create_sector_scanner(llm)
|
│ │ ├── sector_scanner.py # create_sector_scanner(llm)
|
||||||
|
│ │ ├── smart_money_scanner.py # create_smart_money_scanner(llm) ← NEW
|
||||||
│ │ ├── industry_deep_dive.py # create_industry_deep_dive(llm)
|
│ │ ├── industry_deep_dive.py # create_industry_deep_dive(llm)
|
||||||
│ │ └── macro_synthesis.py # create_macro_synthesis(llm)
|
│ │ └── macro_synthesis.py # create_macro_synthesis(llm)
|
||||||
│ ├── trader/
|
│ ├── trader/
|
||||||
|
|
@ -47,7 +48,7 @@ tradingagents/
|
||||||
│ ├── memory.py # FinancialSituationMemory
|
│ ├── memory.py # FinancialSituationMemory
|
||||||
│ ├── news_data_tools.py # get_news, get_global_news, get_insider_transactions
|
│ ├── news_data_tools.py # get_news, get_global_news, get_insider_transactions
|
||||||
│ ├── scanner_states.py # ScannerState, _last_value reducer
|
│ ├── scanner_states.py # ScannerState, _last_value reducer
|
||||||
│ ├── scanner_tools.py # Scanner @tool definitions (7 tools)
|
│ ├── scanner_tools.py # Scanner @tool definitions (10 tools: 7 routed + 3 Finviz direct)
|
||||||
│ ├── technical_indicators_tools.py
|
│ ├── technical_indicators_tools.py
|
||||||
│ └── tool_runner.py # run_tool_loop(), MAX_TOOL_ROUNDS, MIN_REPORT_LENGTH
|
│ └── tool_runner.py # run_tool_loop(), MAX_TOOL_ROUNDS, MIN_REPORT_LENGTH
|
||||||
├── dataflows/
|
├── dataflows/
|
||||||
|
|
@ -130,7 +131,7 @@ agent_os/
|
||||||
└── PortfolioViewer.tsx # Holdings table, trade history, snapshot view
|
└── PortfolioViewer.tsx # Holdings table, trade history, snapshot view
|
||||||
```
|
```
|
||||||
|
|
||||||
## Agent Factory Inventory (17 factories + 1 utility)
|
## Agent Factory Inventory (18 factories + 1 utility)
|
||||||
|
|
||||||
| Factory | File | LLM Tier | Extra Params |
|
| Factory | File | LLM Tier | Extra Params |
|
||||||
|---------|------|----------|-------------|
|
|---------|------|----------|-------------|
|
||||||
|
|
@ -149,6 +150,7 @@ agent_os/
|
||||||
| `create_geopolitical_scanner` | `agents/scanners/geopolitical_scanner.py` | quick | — |
|
| `create_geopolitical_scanner` | `agents/scanners/geopolitical_scanner.py` | quick | — |
|
||||||
| `create_market_movers_scanner` | `agents/scanners/market_movers_scanner.py` | quick | — |
|
| `create_market_movers_scanner` | `agents/scanners/market_movers_scanner.py` | quick | — |
|
||||||
| `create_sector_scanner` | `agents/scanners/sector_scanner.py` | quick | — |
|
| `create_sector_scanner` | `agents/scanners/sector_scanner.py` | quick | — |
|
||||||
|
| `create_smart_money_scanner` | `agents/scanners/smart_money_scanner.py` | quick | — |
|
||||||
| `create_industry_deep_dive` | `agents/scanners/industry_deep_dive.py` | mid | — |
|
| `create_industry_deep_dive` | `agents/scanners/industry_deep_dive.py` | mid | — |
|
||||||
| `create_macro_synthesis` | `agents/scanners/macro_synthesis.py` | deep | — |
|
| `create_macro_synthesis` | `agents/scanners/macro_synthesis.py` | deep | — |
|
||||||
| `create_msg_delete` | `agents/utils/agent_utils.py` | — | No LLM param |
|
| `create_msg_delete` | `agents/utils/agent_utils.py` | — | No LLM param |
|
||||||
|
|
@ -193,7 +195,7 @@ agent_os/
|
||||||
| Command | Function | Description |
|
| Command | Function | Description |
|
||||||
|---------|----------|-------------|
|
|---------|----------|-------------|
|
||||||
| `analyze` | `run_analysis()` | Interactive per-ticker multi-agent analysis with Rich live UI |
|
| `analyze` | `run_analysis()` | Interactive per-ticker multi-agent analysis with Rich live UI |
|
||||||
| `scan` | `run_scan(date)` | 3-phase macro scanner, saves 5 report files |
|
| `scan` | `run_scan(date)` | Macro scanner (4 Phase-1 nodes + deep dive + synthesis), saves 5 report files |
|
||||||
| `pipeline` | `run_pipeline()` | Full pipeline: scan JSON → filter by conviction → per-ticker deep dive |
|
| `pipeline` | `run_pipeline()` | Full pipeline: scan JSON → filter by conviction → per-ticker deep dive |
|
||||||
|
|
||||||
## AgentOS Frontend Components
|
## AgentOS Frontend Components
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- Last verified: 2026-03-23 -->
|
<!-- Last verified: 2026-03-24 -->
|
||||||
|
|
||||||
# Glossary
|
# Glossary
|
||||||
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
| Term | Definition | Source |
|
| Term | Definition | Source |
|
||||||
|------|-----------|--------|
|
|------|-----------|--------|
|
||||||
| Trading Graph | Full per-ticker analysis pipeline: analysts → debate → trader → risk → decision | `graph/trading_graph.py` |
|
| Trading Graph | Full per-ticker analysis pipeline: analysts → debate → trader → risk → decision | `graph/trading_graph.py` |
|
||||||
| Scanner Graph | 3-phase macro scanner: parallel scanners → deep dive → synthesis | `graph/scanner_graph.py` |
|
| Scanner Graph | 4-phase macro scanner: Phase 1a parallel scanners → Phase 1b smart money → deep dive → synthesis | `graph/scanner_graph.py` |
|
||||||
|
| Smart Money Scanner | Phase 1b scanner node, runs sequentially after sector_scanner. Runs 3 Finviz screeners (insider buying, unusual volume, breakout accumulation) to surface institutional footprints. Output feeds industry_deep_dive and macro_synthesis. | `agents/scanners/smart_money_scanner.py` |
|
||||||
|
| Golden Overlap | Cross-reference strategy in macro_synthesis: if a bottom-up Smart Money ticker also fits the top-down macro thesis, label it high-conviction. Provides dual evidence confirmation for stock selection. | `agents/scanners/macro_synthesis.py` |
|
||||||
| Agent Factory | Closure pattern `create_X(llm)` → returns `_node(state)` function | `agents/analysts/*.py`, `agents/scanners/*.py` |
|
| Agent Factory | Closure pattern `create_X(llm)` → returns `_node(state)` function | `agents/analysts/*.py`, `agents/scanners/*.py` |
|
||||||
| ToolNode | LangGraph-native tool executor — used in trading graph for analyst tools | `langgraph.prebuilt`, wired in `graph/setup.py` |
|
| ToolNode | LangGraph-native tool executor — used in trading graph for analyst tools | `langgraph.prebuilt`, wired in `graph/setup.py` |
|
||||||
| run_tool_loop | Inline tool executor for scanner agents — iterates up to `MAX_TOOL_ROUNDS` | `agents/utils/tool_runner.py` |
|
| run_tool_loop | Inline tool executor for scanner agents — iterates up to `MAX_TOOL_ROUNDS` | `agents/utils/tool_runner.py` |
|
||||||
|
|
@ -18,6 +20,11 @@
|
||||||
| Term | Definition | Source |
|
| Term | Definition | Source |
|
||||||
|------|-----------|--------|
|
|------|-----------|--------|
|
||||||
| route_to_vendor | Central dispatch: resolves vendor for a method, calls it, handles fallback for `FALLBACK_ALLOWED` methods | `dataflows/interface.py` |
|
| route_to_vendor | Central dispatch: resolves vendor for a method, calls it, handles fallback for `FALLBACK_ALLOWED` methods | `dataflows/interface.py` |
|
||||||
|
| Finviz / finvizfinance | Web scraper library (not official API) for Finviz screener data. Used only in Smart Money Scanner tools. Graceful fallback on any exception — returns error string, never raises. | `agents/utils/scanner_tools.py` |
|
||||||
|
| _run_finviz_screen | Shared private helper that runs a Finviz screener with hardcoded filters. Catches all exceptions, returns "Smart money scan unavailable" on failure. All 3 smart money tools delegate to this helper. | `agents/utils/scanner_tools.py` |
|
||||||
|
| Insider Buying Screen | Finviz filter: Mid+ market cap, InsiderPurchases > 0%, Volume > 1M. Surfaces stocks where corporate insiders are making open-market purchases. | `agents/utils/scanner_tools.py` |
|
||||||
|
| Unusual Volume Screen | Finviz filter: Relative Volume > 2x, Price > $10. Surfaces institutional accumulation/distribution footprints. | `agents/utils/scanner_tools.py` |
|
||||||
|
| Breakout Accumulation Screen | Finviz filter: 52-Week High, Relative Volume > 2x, Price > $10. O'Neil CAN SLIM institutional accumulation pattern. | `agents/utils/scanner_tools.py` |
|
||||||
| VENDOR_METHODS | Dict mapping method name → vendor → function reference. Direct function refs, not module paths. | `dataflows/interface.py` |
|
| VENDOR_METHODS | Dict mapping method name → vendor → function reference. Direct function refs, not module paths. | `dataflows/interface.py` |
|
||||||
| FALLBACK_ALLOWED | Set of 5 method names that get cross-vendor fallback: `get_stock_data`, `get_market_indices`, `get_sector_performance`, `get_market_movers`, `get_industry_performance` | `dataflows/interface.py` |
|
| FALLBACK_ALLOWED | Set of 5 method names that get cross-vendor fallback: `get_stock_data`, `get_market_indices`, `get_sector_performance`, `get_market_movers`, `get_industry_performance` | `dataflows/interface.py` |
|
||||||
| TOOLS_CATEGORIES | Dict mapping category name → `{"description": str, "tools": list}`. 6 categories: `core_stock_apis`, `technical_indicators`, `fundamental_data`, `news_data`, `scanner_data`, `calendar_data` | `dataflows/interface.py` |
|
| TOOLS_CATEGORIES | Dict mapping category name → `{"description": str, "tools": list}`. 6 categories: `core_stock_apis`, `technical_indicators`, `fundamental_data`, `news_data`, `scanner_data`, `calendar_data` | `dataflows/interface.py` |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# ADR 014: Finviz Smart Money Scanner — Phase 1b Bottom-Up Signal Layer
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The macro scanner pipeline produced top-down qualitative analysis (geopolitical events, market movers, sector rotation) but selected stocks entirely from macro reasoning. There was no bottom-up quantitative signal layer to cross-validate candidates. Adding institutional footprint detection (insider buying, unusual volume, breakout accumulation) via `finvizfinance` creates a "Golden Overlap" — stocks confirmed by both top-down macro themes and bottom-up institutional signals carry higher conviction.
|
||||||
|
|
||||||
|
Key constraints considered during design:
|
||||||
|
|
||||||
|
1. `run_tool_loop()` has `MAX_TOOL_ROUNDS=5`. The market_movers_scanner already uses ~4 rounds. Adding Finviz tools to it would silently truncate at round 5.
|
||||||
|
2. `finvizfinance` is a web scraper, not an official API — it can be blocked or rate-limited at any time.
|
||||||
|
3. LLMs can hallucinate string parameter values when calling parameterized tools.
|
||||||
|
4. Sector rotation context is available from sector_scanner output and should inform smart money interpretation.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### 1. Separate Phase 1b Node (not bolted onto market_movers_scanner)
|
||||||
|
|
||||||
|
A dedicated `smart_money_scanner` node avoids the `MAX_TOOL_ROUNDS=5` truncation risk entirely. It runs sequentially after `sector_scanner` (not in the Phase 1a parallel fan-out), giving it access to `sector_performance_report` in state. This context lets the LLM cross-reference institutional footprints against leading/lagging sectors.
|
||||||
|
|
||||||
|
Final topology:
|
||||||
|
```
|
||||||
|
Phase 1a (parallel): START → geopolitical_scanner
|
||||||
|
START → market_movers_scanner
|
||||||
|
START → sector_scanner
|
||||||
|
Phase 1b (sequential): sector_scanner → smart_money_scanner
|
||||||
|
Phase 2: geopolitical_scanner, market_movers_scanner, smart_money_scanner → industry_deep_dive
|
||||||
|
Phase 3: industry_deep_dive → macro_synthesis → END
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Three Zero-Parameter Tools (not one parameterized tool)
|
||||||
|
|
||||||
|
Original proposal: `get_smart_money_anomalies(scan_type: str)` with values like `"insider_buying"`.
|
||||||
|
|
||||||
|
Problem: LLMs hallucinate string parameter values. The LLM might call `get_smart_money_anomalies("insider_buys")` or `get_smart_money_anomalies("volume_spike")` — strings that have no corresponding filter set.
|
||||||
|
|
||||||
|
Solution: Three separate zero-parameter tools:
|
||||||
|
- `get_insider_buying_stocks()` — hardcoded insider purchase filters
|
||||||
|
- `get_unusual_volume_stocks()` — hardcoded volume anomaly filters
|
||||||
|
- `get_breakout_accumulation_stocks()` — hardcoded 52-week high + volume filters
|
||||||
|
|
||||||
|
With zero parameters, there is nothing to hallucinate. The LLM selects tools by name from its schema — unambiguous. All three share a `_run_finviz_screen(filters_dict, label)` private helper to keep the implementation DRY.
|
||||||
|
|
||||||
|
### 3. Graceful Degradation (never raise)
|
||||||
|
|
||||||
|
`finvizfinance` wraps a web scraper that can fail at any time (rate limiting, Finviz HTML changes, network errors). `_run_finviz_screen()` catches all exceptions and returns a string starting with `"Smart money scan unavailable (Finviz error): <message>"`. The pipeline never hard-fails due to Finviz unavailability. `macro_synthesis` is instructed to note the absence and proceed on remaining reports.
|
||||||
|
|
||||||
|
### 4. `breakout_accumulation` over `oversold_bounces`
|
||||||
|
|
||||||
|
Original proposal included an `oversold_bounces` scan (RSI < 30). This was rejected: RSI < 30 bounces are retail contrarian signals, not smart money signals. Institutions don't systematically buy at RSI < 30. Replaced with `breakout_accumulation` (52-week highs on 2x+ volume) — the O'Neil CAN SLIM institutional accumulation pattern, where institutional buying drives price to new highs on above-average volume.
|
||||||
|
|
||||||
|
### 5. Golden Overlap in macro_synthesis
|
||||||
|
|
||||||
|
`macro_synthesis` now receives `smart_money_report` alongside the 4 existing reports. The system prompt includes explicit Golden Overlap instructions: if a smart money ticker fits the top-down macro narrative (e.g., an energy stock with heavy insider buying during a supply shock), assign it `"high"` conviction. If no smart money tickers align, proceed on remaining reports. The JSON output schema is unchanged.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- **Pro**: Dual evidence layer — top-down macro + bottom-up institutional signals improve conviction quality
|
||||||
|
- **Pro**: Zero hallucination risk — no string parameters in any Finviz tool
|
||||||
|
- **Pro**: Pipeline never fails due to Finviz — graceful degradation preserves all other outputs
|
||||||
|
- **Pro**: Sector context injection — smart money interpretation is informed by rotation context from sector_scanner
|
||||||
|
- **Con**: `finvizfinance` is a web scraper — brittle to Finviz HTML changes; requires periodic maintenance
|
||||||
|
- **Con**: Finviz screener results lag real-time institutional data (data is end-of-day); not suitable for intraday signals
|
||||||
|
- **Con**: Adds ~620 tokens to scanner pipeline token budget (quick_llm tier, acceptable)
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
|
||||||
|
- `tradingagents/agents/scanners/smart_money_scanner.py` (new)
|
||||||
|
- `tradingagents/agents/utils/scanner_tools.py` (3 new tools + `_run_finviz_screen` helper)
|
||||||
|
- `tradingagents/agents/utils/scanner_states.py` (`smart_money_report` field)
|
||||||
|
- `tradingagents/graph/scanner_setup.py` (Phase 1b topology)
|
||||||
|
- `tradingagents/graph/scanner_graph.py` (agent instantiation)
|
||||||
|
- `tradingagents/agents/scanners/macro_synthesis.py` (Golden Overlap prompt)
|
||||||
|
- `pyproject.toml` (`finvizfinance>=0.14.0`)
|
||||||
|
- `tests/unit/test_scanner_mocked.py` (6 new tests for Finviz tools)
|
||||||
|
|
@ -30,8 +30,9 @@ used by every agent.
|
||||||
- [4.13 Geopolitical Scanner](#413-geopolitical-scanner)
|
- [4.13 Geopolitical Scanner](#413-geopolitical-scanner)
|
||||||
- [4.14 Market Movers Scanner](#414-market-movers-scanner)
|
- [4.14 Market Movers Scanner](#414-market-movers-scanner)
|
||||||
- [4.15 Sector Scanner](#415-sector-scanner)
|
- [4.15 Sector Scanner](#415-sector-scanner)
|
||||||
- [4.16 Industry Deep Dive](#416-industry-deep-dive)
|
- [4.16 Smart Money Scanner](#416-smart-money-scanner)
|
||||||
- [4.17 Macro Synthesis](#417-macro-synthesis)
|
- [4.17 Industry Deep Dive](#417-industry-deep-dive)
|
||||||
|
- [4.18 Macro Synthesis](#418-macro-synthesis)
|
||||||
5. [Tool → Data-Source Mapping](#5-tool--data-source-mapping)
|
5. [Tool → Data-Source Mapping](#5-tool--data-source-mapping)
|
||||||
6. [Memory System](#6-memory-system)
|
6. [Memory System](#6-memory-system)
|
||||||
7. [Tool Data Formats & Sizes](#7-tool-data-formats--sizes)
|
7. [Tool Data Formats & Sizes](#7-tool-data-formats--sizes)
|
||||||
|
|
@ -73,8 +74,9 @@ All are overridable via `TRADINGAGENTS_<KEY>` env vars.
|
||||||
| 13 | Geopolitical Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
| 13 | Geopolitical Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
||||||
| 14 | Market Movers Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
| 14 | Market Movers Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
||||||
| 15 | Sector Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
| 15 | Sector Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
||||||
| 16 | Industry Deep Dive | **Mid** | ✅ | — | `run_tool_loop()` |
|
| 16 | Smart Money Scanner | **Quick** | ✅ | — | `run_tool_loop()` |
|
||||||
| 17 | Macro Synthesis | **Deep** | — | — | — |
|
| 17 | Industry Deep Dive | **Mid** | ✅ | — | `run_tool_loop()` |
|
||||||
|
| 18 | Macro Synthesis | **Deep** | — | — | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -191,7 +193,7 @@ All are overridable via `TRADINGAGENTS_<KEY>` env vars.
|
||||||
│ (scan_date) │
|
│ (scan_date) │
|
||||||
└────────────┬─────────────┘
|
└────────────┬─────────────┘
|
||||||
│
|
│
|
||||||
┌────────────────────────────┼────────────────────────────┐
|
┌─────────────────────────────────┼──────────────────────────────────┐
|
||||||
▼ ▼ ▼
|
▼ ▼ ▼
|
||||||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||||
│ Geopolitical │ │ Market Movers │ │ Sector Scanner │
|
│ Geopolitical │ │ Market Movers │ │ Sector Scanner │
|
||||||
|
|
@ -205,17 +207,37 @@ All are overridable via `TRADINGAGENTS_<KEY>` env vars.
|
||||||
│ geopolitical_rpt │ │ indices │ │ Output: │
|
│ geopolitical_rpt │ │ indices │ │ Output: │
|
||||||
│ │ │ │ │ sector_perf_rpt │
|
│ │ │ │ │ sector_perf_rpt │
|
||||||
│ │ │ Output: │ │ │
|
│ │ │ Output: │ │ │
|
||||||
│ │ │ market_movers_rpt│ │ │
|
│ │ │ market_movers_rpt│ │ │ │
|
||||||
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
|
└────────┬─────────┘ └────────┬─────────┘ └────────┼─────────┘
|
||||||
│ │ │
|
│ │ │
|
||||||
└────────────────────────┼─────────────────────────┘
|
│ │ ┌───────────────┘
|
||||||
│ (Phase 1 → Phase 2)
|
│ │ ▼ (sector data available)
|
||||||
|
│ │ ┌──────────────────────────┐
|
||||||
|
│ │ │ Smart Money Scanner │
|
||||||
|
│ │ │ (quick_think) │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ Context: sector_perf_rpt │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ Tools (no params): │
|
||||||
|
│ │ │ • get_insider_buying_ │
|
||||||
|
│ │ │ stocks │
|
||||||
|
│ │ │ • get_unusual_volume_ │
|
||||||
|
│ │ │ stocks │
|
||||||
|
│ │ │ • get_breakout_ │
|
||||||
|
│ │ │ accumulation_stocks │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ Output: │
|
||||||
|
│ │ │ smart_money_report │
|
||||||
|
│ │ └──────────┬───────────────┘
|
||||||
|
│ │ │
|
||||||
|
└─────────────────────────────┼──────────────┘
|
||||||
|
│ (Phase 1 → Phase 2, all 4 reports)
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────┐
|
┌─────────────────────────────┐
|
||||||
│ Industry Deep Dive │
|
│ Industry Deep Dive │
|
||||||
│ (mid_think) │
|
│ (mid_think) │
|
||||||
│ │
|
│ │
|
||||||
│ Reads: all 3 Phase-1 reports │
|
│ Reads: all 4 Phase-1 reports │
|
||||||
│ Auto-extracts top 3 sectors │
|
│ Auto-extracts top 3 sectors │
|
||||||
│ │
|
│ │
|
||||||
│ Tools: │
|
│ Tools: │
|
||||||
|
|
@ -233,7 +255,10 @@ All are overridable via `TRADINGAGENTS_<KEY>` env vars.
|
||||||
│ Macro Synthesis │
|
│ Macro Synthesis │
|
||||||
│ (deep_think) │
|
│ (deep_think) │
|
||||||
│ │
|
│ │
|
||||||
│ Reads: all 4 prior reports │
|
│ Reads: all 5 prior reports │
|
||||||
|
│ Golden Overlap: cross-refs │
|
||||||
|
│ smart money tickers with │
|
||||||
|
│ top-down macro thesis │
|
||||||
│ No tools – pure LLM reasoning│
|
│ No tools – pure LLM reasoning│
|
||||||
│ │
|
│ │
|
||||||
│ Output: │
|
│ Output: │
|
||||||
|
|
@ -248,6 +273,12 @@ All are overridable via `TRADINGAGENTS_<KEY>` env vars.
|
||||||
└───────────────┘
|
└───────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Graph Topology Notes:**
|
||||||
|
- **Phase 1a** (parallel from START): geopolitical, market_movers, sector scanners
|
||||||
|
- **Phase 1b** (sequential after sector): smart_money_scanner — runs after sector data is available so it can use sector rotation context when interpreting Finviz signals
|
||||||
|
- **Phase 2** (fan-in from all 4 Phase 1 nodes): industry_deep_dive
|
||||||
|
- **Phase 3**: macro_synthesis with Golden Overlap strategy
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Per-Agent Data Flows
|
## 4. Per-Agent Data Flows
|
||||||
|
|
@ -1072,7 +1103,75 @@ Round 1 ≈ 17 KB (~4,250 tokens), Round 2 ≈ 31 KB (~7,800 tokens).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.16 Industry Deep Dive
|
### 4.16 Smart Money Scanner
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **File** | `agents/scanners/smart_money_scanner.py` |
|
||||||
|
| **Factory** | `create_smart_money_scanner(llm)` |
|
||||||
|
| **Thinking Modality** | **Quick** (`quick_think_llm`, default `gpt-5-mini`) |
|
||||||
|
| **Tool Execution** | `run_tool_loop()` |
|
||||||
|
| **Graph position** | Sequential after `sector_scanner` (Phase 1b) |
|
||||||
|
|
||||||
|
**Data Flow:**
|
||||||
|
|
||||||
|
```
|
||||||
|
State Input: scan_date
|
||||||
|
+ sector_performance_report ← injected from sector_scanner (available
|
||||||
|
because this node runs after it)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Tool calls via run_tool_loop():
|
||||||
|
(All three tools have NO parameters — filters are hardcoded.
|
||||||
|
The LLM calls each tool by name; nothing to hallucinate.)
|
||||||
|
|
||||||
|
1. get_insider_buying_stocks()
|
||||||
|
→ Mid/Large cap stocks with positive insider purchases, volume > 1M
|
||||||
|
→ Filters: InsiderPurchases=Positive, MarketCap=+Mid, Volume=Over 1M
|
||||||
|
Data source: Finviz screener (web scraper, graceful fallback on error)
|
||||||
|
|
||||||
|
2. get_unusual_volume_stocks()
|
||||||
|
→ Stocks trading at 2x+ normal volume today, price > $10
|
||||||
|
→ Filters: RelativeVolume=Over 2, Price=Over $10
|
||||||
|
Data source: Finviz screener
|
||||||
|
|
||||||
|
3. get_breakout_accumulation_stocks()
|
||||||
|
→ Stocks at 52-week highs on 2x+ volume (O'Neil CAN SLIM pattern)
|
||||||
|
→ Filters: Performance2=52-Week High, RelativeVolume=Over 2, Price=Over $10
|
||||||
|
Data source: Finviz screener
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
LLM Prompt (quick_think):
|
||||||
|
"Hunt for Smart Money institutional footprints. Call all three tools.
|
||||||
|
Use sector rotation context to prioritize tickers from leading sectors.
|
||||||
|
Flag signals that confirm or contradict sector trends.
|
||||||
|
Report: 5-8 tickers with scan source, sector, and anomaly explanation."
|
||||||
|
|
||||||
|
Context: sector_performance_report + 3 Finviz tool results
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Output: smart_money_report
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hallucination Safety:** Each Finviz tool is a zero-parameter `@tool`. Filters
|
||||||
|
are hardcoded inside the helper `_run_finviz_screen()`. If Finviz is unavailable
|
||||||
|
(rate-limited, scraped HTML changed), each tool returns
|
||||||
|
`"Smart money scan unavailable (Finviz error): <reason>"` — the pipeline never fails.
|
||||||
|
|
||||||
|
**Prompt Size Budget:**
|
||||||
|
|
||||||
|
| Component | Data Type | Format | Avg Size | Avg Tokens |
|
||||||
|
|-----------|-----------|--------|----------|------------|
|
||||||
|
| System prompt | Text | Instructions | ~0.7 KB | ~175 |
|
||||||
|
| Sector performance report (injected) | Markdown | Table (11 sectors) | ~0.9 KB | ~220 |
|
||||||
|
| `get_insider_buying_stocks` result | Markdown | 5-row ticker list | ~0.3 KB | ~75 |
|
||||||
|
| `get_unusual_volume_stocks` result | Markdown | 5-row ticker list | ~0.3 KB | ~75 |
|
||||||
|
| `get_breakout_accumulation_stocks` result | Markdown | 5-row ticker list | ~0.3 KB | ~75 |
|
||||||
|
| **Total prompt** | | | **~2.5 KB** | **~620** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.17 Industry Deep Dive
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -1087,9 +1186,10 @@ Round 1 ≈ 17 KB (~4,250 tokens), Round 2 ≈ 31 KB (~7,800 tokens).
|
||||||
┌─────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────┐
|
||||||
│ State Input: │
|
│ State Input: │
|
||||||
│ • scan_date │
|
│ • scan_date │
|
||||||
│ • geopolitical_report (Phase 1) │
|
│ • geopolitical_report (Phase 1a) │
|
||||||
│ • market_movers_report (Phase 1) │
|
│ • market_movers_report (Phase 1a) │
|
||||||
│ • sector_performance_report (Phase 1) │
|
│ • sector_performance_report (Phase 1a) │
|
||||||
|
│ • smart_money_report (Phase 1b) │
|
||||||
└────────────────────────┬────────────────────────────┘
|
└────────────────────────┬────────────────────────────┘
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
|
|
@ -1139,14 +1239,14 @@ Round 1 ≈ 17 KB (~4,250 tokens), Round 2 ≈ 31 KB (~7,800 tokens).
|
||||||
| Component | Data Type | Format | Avg Size | Avg Tokens |
|
| Component | Data Type | Format | Avg Size | Avg Tokens |
|
||||||
|-----------|-----------|--------|----------|------------|
|
|-----------|-----------|--------|----------|------------|
|
||||||
| System prompt | Text | Instructions + sector list | ~1 KB | ~250 |
|
| System prompt | Text | Instructions + sector list | ~1 KB | ~250 |
|
||||||
| Phase 1 context (3 reports) | Text | Concatenated Markdown | ~6 KB | ~1,500 |
|
| Phase 1 context (4 reports) | Text | Concatenated Markdown | ~8 KB | ~2,000 |
|
||||||
| `get_industry_performance` × 3 | Markdown | Tables (10–15 companies each) | ~7.5 KB | ~1,875 |
|
| `get_industry_performance` × 3 | Markdown | Tables (10–15 companies each) | ~7.5 KB | ~1,875 |
|
||||||
| `get_topic_news` × 2 | Markdown | Article lists (10 articles each) | ~5 KB | ~1,250 |
|
| `get_topic_news` × 2 | Markdown | Article lists (10 articles each) | ~5 KB | ~1,250 |
|
||||||
| **Total prompt** | | | **~20 KB** | **~4,875** |
|
| **Total prompt** | | | **~21.5 KB** | **~5,375** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.17 Macro Synthesis
|
### 4.18 Macro Synthesis
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -1160,15 +1260,20 @@ Round 1 ≈ 17 KB (~4,250 tokens), Round 2 ≈ 31 KB (~7,800 tokens).
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────┐
|
||||||
│ State Input: │
|
│ State Input: │
|
||||||
│ • geopolitical_report (Phase 1) │
|
│ • geopolitical_report (Phase 1a) │
|
||||||
│ • market_movers_report (Phase 1) │
|
│ • market_movers_report (Phase 1a) │
|
||||||
│ • sector_performance_report (Phase 1) │
|
│ • sector_performance_report (Phase 1a) │
|
||||||
|
│ • smart_money_report (Phase 1b) ← NEW │
|
||||||
│ • industry_deep_dive_report (Phase 2) │
|
│ • industry_deep_dive_report (Phase 2) │
|
||||||
└────────────────────────┬────────────────────────────┘
|
└────────────────────────┬────────────────────────────┘
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
LLM Prompt (deep_think):
|
LLM Prompt (deep_think):
|
||||||
"Synthesize all reports into final investment thesis.
|
"Synthesize all reports into final investment thesis.
|
||||||
|
GOLDEN OVERLAP: Cross-reference Smart Money tickers with macro thesis.
|
||||||
|
If a Smart Money ticker fits the top-down narrative (e.g., Energy stock
|
||||||
|
with heavy insider buying during an oil shortage) → label conviction 'high'.
|
||||||
|
If no Smart Money tickers fit → select best from other reports.
|
||||||
Output ONLY valid JSON (no markdown, no preamble).
|
Output ONLY valid JSON (no markdown, no preamble).
|
||||||
Structure:
|
Structure:
|
||||||
{
|
{
|
||||||
|
|
@ -1180,7 +1285,7 @@ Round 1 ≈ 17 KB (~4,250 tokens), Round 2 ≈ 31 KB (~7,800 tokens).
|
||||||
risk_factors
|
risk_factors
|
||||||
}"
|
}"
|
||||||
|
|
||||||
Context: all 4 prior reports concatenated
|
Context: all 5 prior reports concatenated
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
Post-processing (Python, no LLM):
|
Post-processing (Python, no LLM):
|
||||||
|
|
@ -1194,12 +1299,13 @@ Round 1 ≈ 17 KB (~4,250 tokens), Round 2 ≈ 31 KB (~7,800 tokens).
|
||||||
|
|
||||||
| Component | Data Type | Format | Avg Size | Avg Tokens |
|
| Component | Data Type | Format | Avg Size | Avg Tokens |
|
||||||
|-----------|-----------|--------|----------|------------|
|
|-----------|-----------|--------|----------|------------|
|
||||||
| System prompt | Text | Instructions + JSON schema | ~1.3 KB | ~325 |
|
| System prompt | Text | Instructions + JSON schema + Golden Overlap | ~1.5 KB | ~375 |
|
||||||
| Geopolitical report (Phase 1) | Text | Markdown report | ~3 KB | ~750 |
|
| Geopolitical report (Phase 1a) | Text | Markdown report | ~3 KB | ~750 |
|
||||||
| Market movers report (Phase 1) | Text | Markdown report | ~3 KB | ~750 |
|
| Market movers report (Phase 1a) | Text | Markdown report | ~3 KB | ~750 |
|
||||||
| Sector performance report (Phase 1) | Text | Markdown report | ~2 KB | ~500 |
|
| Sector performance report (Phase 1a) | Text | Markdown report | ~2 KB | ~500 |
|
||||||
|
| Smart money report (Phase 1b) | Text | Markdown report | ~2 KB | ~500 |
|
||||||
| Industry deep dive report (Phase 2) | Text | Markdown report | ~8 KB | ~2,000 |
|
| Industry deep dive report (Phase 2) | Text | Markdown report | ~8 KB | ~2,000 |
|
||||||
| **Total prompt** | | | **~17 KB** | **~4,325** |
|
| **Total prompt** | | | **~19.5 KB** | **~4,875** |
|
||||||
|
|
||||||
**Output:** Valid JSON (~3–5 KB, ~750–1,250 tokens).
|
**Output:** Valid JSON (~3–5 KB, ~750–1,250 tokens).
|
||||||
|
|
||||||
|
|
@ -1239,9 +1345,17 @@ dispatches to the configured vendor.
|
||||||
| `get_topic_news` | scanner_data | yfinance | — | Topic news |
|
| `get_topic_news` | scanner_data | yfinance | — | Topic news |
|
||||||
| `get_earnings_calendar` | calendar_data | **Finnhub** | — | Earnings cal. |
|
| `get_earnings_calendar` | calendar_data | **Finnhub** | — | Earnings cal. |
|
||||||
| `get_economic_calendar` | calendar_data | **Finnhub** | — | Econ cal. |
|
| `get_economic_calendar` | calendar_data | **Finnhub** | — | Econ cal. |
|
||||||
|
| `get_insider_buying_stocks` | *(Finviz direct)* | **Finviz** | graceful string | Insider buys |
|
||||||
|
| `get_unusual_volume_stocks` | *(Finviz direct)* | **Finviz** | graceful string | Vol anomalies |
|
||||||
|
| `get_breakout_accumulation_stocks` | *(Finviz direct)* | **Finviz** | graceful string | Breakouts |
|
||||||
|
|
||||||
> **Fallback rules** (ADR 011): Only 5 methods in `FALLBACK_ALLOWED` get
|
> **Fallback rules** (ADR 011): Only 5 methods in `FALLBACK_ALLOWED` get
|
||||||
> cross-vendor fallback. All others fail-fast on error.
|
> cross-vendor fallback. All others fail-fast on error.
|
||||||
|
>
|
||||||
|
> **Finviz tools** bypass `route_to_vendor()` — they call `finvizfinance` directly
|
||||||
|
> via the shared `_run_finviz_screen()` helper. Errors return a graceful string
|
||||||
|
> starting with `"Smart money scan unavailable"` so the pipeline never hard-fails.
|
||||||
|
> `finvizfinance` is a web scraper, not an official API — treat it as best-effort.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -1326,6 +1440,9 @@ typical size, and any truncation limits for each tool.
|
||||||
| `get_topic_news` | Markdown (article list) | ~2.5 KB | ~625 | 10 articles (default) | Configurable limit |
|
| `get_topic_news` | Markdown (article list) | ~2.5 KB | ~625 | 10 articles (default) | Configurable limit |
|
||||||
| `get_earnings_calendar` | Markdown (table) | ~3 KB | ~750 | 20–50+ events | All events in date range |
|
| `get_earnings_calendar` | Markdown (table) | ~3 KB | ~750 | 20–50+ events | All events in date range |
|
||||||
| `get_economic_calendar` | Markdown (table) | ~2.5 KB | ~625 | 5–15 events | All events in date range |
|
| `get_economic_calendar` | Markdown (table) | ~2.5 KB | ~625 | 5–15 events | All events in date range |
|
||||||
|
| `get_insider_buying_stocks` | Markdown (list) | ~0.3 KB | ~75 | Top 5 stocks | Hard limit: top 5 by volume; returns error string on Finviz failure |
|
||||||
|
| `get_unusual_volume_stocks` | Markdown (list) | ~0.3 KB | ~75 | Top 5 stocks | Hard limit: top 5 by volume; returns error string on Finviz failure |
|
||||||
|
| `get_breakout_accumulation_stocks` | Markdown (list) | ~0.3 KB | ~75 | Top 5 stocks | Hard limit: top 5 by volume; returns error string on Finviz failure |
|
||||||
|
|
||||||
### Non-Tool Data Injected into Prompts
|
### Non-Tool Data Injected into Prompts
|
||||||
|
|
||||||
|
|
@ -1377,8 +1494,9 @@ the context windows of popular models to identify potential overflow risks.
|
||||||
| 13 | Geopolitical Scanner | Quick | ~2,150 tok | ~3,000 tok | 2% | ✅ Safe |
|
| 13 | Geopolitical Scanner | Quick | ~2,150 tok | ~3,000 tok | 2% | ✅ Safe |
|
||||||
| 14 | Market Movers Scanner | Quick | ~1,525 tok | ~2,000 tok | 1–2% | ✅ Safe |
|
| 14 | Market Movers Scanner | Quick | ~1,525 tok | ~2,000 tok | 1–2% | ✅ Safe |
|
||||||
| 15 | Sector Scanner | Quick | ~345 tok | ~500 tok | <1% | ✅ Safe |
|
| 15 | Sector Scanner | Quick | ~345 tok | ~500 tok | <1% | ✅ Safe |
|
||||||
| 16 | Industry Deep Dive | Mid | ~4,875 tok | ~7,000 tok | 4–5% | ✅ Safe |
|
| 16 | Smart Money Scanner | Quick | ~620 tok | ~900 tok | <1% | ✅ Safe |
|
||||||
| 17 | Macro Synthesis | Deep | ~4,325 tok | ~6,500 tok | 3–5% | ✅ Safe |
|
| 17 | Industry Deep Dive | Mid | ~5,375 tok | ~7,500 tok | 4–6% | ✅ Safe |
|
||||||
|
| 18 | Macro Synthesis | Deep | ~4,875 tok | ~7,000 tok | 4–5% | ✅ Safe |
|
||||||
|
|
||||||
> **†Peak Prompt** = estimate with `max_debate_rounds=3` or maximum optional
|
> **†Peak Prompt** = estimate with `max_debate_rounds=3` or maximum optional
|
||||||
> tool calls. All agents are well within the 128K context window.
|
> tool calls. All agents are well within the 128K context window.
|
||||||
|
|
@ -1451,37 +1569,41 @@ TOTAL INPUT TOKENS (single company): ~98,400
|
||||||
```
|
```
|
||||||
Phase Calls Avg Tokens (per call) Subtotal
|
Phase Calls Avg Tokens (per call) Subtotal
|
||||||
─────────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────────
|
||||||
1. PHASE 1 SCANNERS (parallel)
|
1a. PHASE 1 SCANNERS (parallel from START)
|
||||||
Geopolitical Scanner 1 ~2,150 ~2,150
|
Geopolitical Scanner 1 ~2,150 ~2,150
|
||||||
Market Movers Scanner 1 ~1,525 ~1,525
|
Market Movers Scanner 1 ~1,525 ~1,525
|
||||||
Sector Scanner 1 ~345 ~345
|
Sector Scanner 1 ~345 ~345
|
||||||
Phase 1: ~4,020
|
Phase 1a: ~4,020
|
||||||
|
|
||||||
|
1b. SMART MONEY (sequential after Sector Scanner)
|
||||||
|
Smart Money Scanner 1 ~620 ~620
|
||||||
|
Phase 1b: ~620
|
||||||
|
|
||||||
2. PHASE 2
|
2. PHASE 2
|
||||||
Industry Deep Dive 1 ~4,875 ~4,875
|
Industry Deep Dive 1 ~5,375 ~5,375
|
||||||
Phase 2: ~4,875
|
Phase 2: ~5,375
|
||||||
|
|
||||||
3. PHASE 3
|
3. PHASE 3
|
||||||
Macro Synthesis 1 ~4,325 ~4,325
|
Macro Synthesis 1 ~4,875 ~4,875
|
||||||
Phase 3: ~4,325
|
Phase 3: ~4,875
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════
|
||||||
TOTAL INPUT TOKENS (market scan): ~13,220
|
TOTAL INPUT TOKENS (market scan): ~14,890
|
||||||
═══════════════════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════════════════
|
||||||
```
|
```
|
||||||
|
|
||||||
> Scanner output tokens ≈ 5,000–8,000 additional.
|
> Scanner output tokens ≈ 6,000–9,000 additional.
|
||||||
> **Grand total (input + output) ≈ 18,000–21,000 tokens per scan.**
|
> **Grand total (input + output) ≈ 21,000–24,000 tokens per scan.**
|
||||||
|
|
||||||
### Full Pipeline (Scan → Per-Ticker Deep Dives)
|
### Full Pipeline (Scan → Per-Ticker Deep Dives)
|
||||||
|
|
||||||
When running the `pipeline` command (scan + per-ticker analysis for top picks):
|
When running the `pipeline` command (scan + per-ticker analysis for top picks):
|
||||||
|
|
||||||
```
|
```
|
||||||
Scanner pipeline: ~13,220 input tokens
|
Scanner pipeline: ~14,890 input tokens
|
||||||
+ N company analyses (N = 8–10 picks): ~98,400 × N input tokens
|
+ N company analyses (N = 8–10 picks): ~98,400 × N input tokens
|
||||||
───────────────────────────────────────────────────────────────────
|
───────────────────────────────────────────────────────────────────
|
||||||
Example (10 companies): ~997,220 input tokens
|
Example (10 companies): ~998,890 input tokens
|
||||||
≈ 1.0M total tokens (input + output)
|
≈ 1.0M total tokens (input + output)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -1502,5 +1624,5 @@ Example (10 companies): ~997,220 input tokens
|
||||||
cannot accommodate debate agents beyond round 1. Use
|
cannot accommodate debate agents beyond round 1. Use
|
||||||
`max_debate_rounds=1` for such models.
|
`max_debate_rounds=1` for such models.
|
||||||
|
|
||||||
5. **Cost optimization**: The scanner pipeline uses ~13K tokens total —
|
5. **Cost optimization**: The scanner pipeline uses ~15K tokens total —
|
||||||
roughly 7× cheaper than a single company analysis.
|
roughly 6-7× cheaper than a single company analysis.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ dependencies = [
|
||||||
"tqdm>=4.67.1",
|
"tqdm>=4.67.1",
|
||||||
"typing-extensions>=4.14.0",
|
"typing-extensions>=4.14.0",
|
||||||
"yfinance>=0.2.63",
|
"yfinance>=0.2.63",
|
||||||
|
"finvizfinance>=0.14.0",
|
||||||
"psycopg2-binary>=2.9.11",
|
"psycopg2-binary>=2.9.11",
|
||||||
"fastapi>=0.115.9",
|
"fastapi>=0.115.9",
|
||||||
"uvicorn>=0.34.3",
|
"uvicorn>=0.34.3",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ def test_scanner_setup_compiles_graph():
|
||||||
"geopolitical_scanner": MagicMock(),
|
"geopolitical_scanner": MagicMock(),
|
||||||
"market_movers_scanner": MagicMock(),
|
"market_movers_scanner": MagicMock(),
|
||||||
"sector_scanner": MagicMock(),
|
"sector_scanner": MagicMock(),
|
||||||
|
"smart_money_scanner": MagicMock(),
|
||||||
"industry_deep_dive": MagicMock(),
|
"industry_deep_dive": MagicMock(),
|
||||||
"macro_synthesis": MagicMock(),
|
"macro_synthesis": MagicMock(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -727,3 +727,94 @@ class TestScannerRouting:
|
||||||
result = route_to_vendor("get_topic_news", "economy")
|
result = route_to_vendor("get_topic_news", "economy")
|
||||||
|
|
||||||
assert isinstance(result, str)
|
assert isinstance(result, str)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Finviz smart-money screener tools
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _make_finviz_df():
|
||||||
|
"""Minimal DataFrame matching what finvizfinance screener_view() returns."""
|
||||||
|
return pd.DataFrame([
|
||||||
|
{"Ticker": "NVDA", "Sector": "Technology", "Price": "620.00", "Volume": "45000000"},
|
||||||
|
{"Ticker": "AMD", "Sector": "Technology", "Price": "175.00", "Volume": "32000000"},
|
||||||
|
{"Ticker": "XOM", "Sector": "Energy", "Price": "115.00", "Volume": "18000000"},
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class TestFinvizSmartMoneyTools:
|
||||||
|
"""Mocked unit tests for Finviz screener tools — no network required."""
|
||||||
|
|
||||||
|
def _mock_overview(self, df):
|
||||||
|
"""Return a patched Overview instance whose screener_view() yields df."""
|
||||||
|
mock_inst = MagicMock()
|
||||||
|
mock_inst.screener_view.return_value = df
|
||||||
|
mock_cls = MagicMock(return_value=mock_inst)
|
||||||
|
return mock_cls
|
||||||
|
|
||||||
|
def test_get_insider_buying_stocks_returns_report(self):
|
||||||
|
from tradingagents.agents.utils.scanner_tools import get_insider_buying_stocks
|
||||||
|
|
||||||
|
with patch("tradingagents.agents.utils.scanner_tools._run_finviz_screen",
|
||||||
|
wraps=None) as _:
|
||||||
|
pass # use full stack — patch Overview only
|
||||||
|
|
||||||
|
mock_cls = self._mock_overview(_make_finviz_df())
|
||||||
|
with patch("finvizfinance.screener.overview.Overview", mock_cls):
|
||||||
|
result = get_insider_buying_stocks.invoke({})
|
||||||
|
|
||||||
|
assert "insider_buying" in result
|
||||||
|
assert "NVDA" in result or "AMD" in result or "XOM" in result
|
||||||
|
|
||||||
|
def test_get_unusual_volume_stocks_returns_report(self):
|
||||||
|
from tradingagents.agents.utils.scanner_tools import get_unusual_volume_stocks
|
||||||
|
|
||||||
|
mock_cls = self._mock_overview(_make_finviz_df())
|
||||||
|
with patch("finvizfinance.screener.overview.Overview", mock_cls):
|
||||||
|
result = get_unusual_volume_stocks.invoke({})
|
||||||
|
|
||||||
|
assert "unusual_volume" in result
|
||||||
|
|
||||||
|
def test_get_breakout_accumulation_stocks_returns_report(self):
|
||||||
|
from tradingagents.agents.utils.scanner_tools import get_breakout_accumulation_stocks
|
||||||
|
|
||||||
|
mock_cls = self._mock_overview(_make_finviz_df())
|
||||||
|
with patch("finvizfinance.screener.overview.Overview", mock_cls):
|
||||||
|
result = get_breakout_accumulation_stocks.invoke({})
|
||||||
|
|
||||||
|
assert "breakout_accumulation" in result
|
||||||
|
|
||||||
|
def test_empty_dataframe_returns_no_match_message(self):
|
||||||
|
from tradingagents.agents.utils.scanner_tools import get_insider_buying_stocks
|
||||||
|
|
||||||
|
mock_cls = self._mock_overview(pd.DataFrame())
|
||||||
|
with patch("finvizfinance.screener.overview.Overview", mock_cls):
|
||||||
|
result = get_insider_buying_stocks.invoke({})
|
||||||
|
|
||||||
|
assert "No stocks matched" in result
|
||||||
|
|
||||||
|
def test_exception_returns_graceful_unavailable_message(self):
|
||||||
|
from tradingagents.agents.utils.scanner_tools import get_breakout_accumulation_stocks
|
||||||
|
|
||||||
|
mock_inst = MagicMock()
|
||||||
|
mock_inst.screener_view.side_effect = ConnectionError("timeout")
|
||||||
|
mock_cls = MagicMock(return_value=mock_inst)
|
||||||
|
|
||||||
|
with patch("finvizfinance.screener.overview.Overview", mock_cls):
|
||||||
|
result = get_breakout_accumulation_stocks.invoke({})
|
||||||
|
|
||||||
|
assert "Smart money scan unavailable" in result
|
||||||
|
assert "timeout" in result
|
||||||
|
|
||||||
|
def test_all_three_tools_sort_by_volume(self):
|
||||||
|
"""Verify the top result is the highest-volume ticker."""
|
||||||
|
from tradingagents.agents.utils.scanner_tools import get_unusual_volume_stocks
|
||||||
|
|
||||||
|
# NVDA has highest volume (45M) — should appear first in report
|
||||||
|
mock_cls = self._mock_overview(_make_finviz_df())
|
||||||
|
with patch("finvizfinance.screener.overview.Overview", mock_cls):
|
||||||
|
result = get_unusual_volume_stocks.invoke({})
|
||||||
|
|
||||||
|
nvda_pos = result.find("NVDA")
|
||||||
|
amd_pos = result.find("AMD")
|
||||||
|
assert nvda_pos < amd_pos, "NVDA (higher volume) should appear before AMD"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from .geopolitical_scanner import create_geopolitical_scanner
|
from .geopolitical_scanner import create_geopolitical_scanner
|
||||||
from .market_movers_scanner import create_market_movers_scanner
|
from .market_movers_scanner import create_market_movers_scanner
|
||||||
from .sector_scanner import create_sector_scanner
|
from .sector_scanner import create_sector_scanner
|
||||||
|
from .smart_money_scanner import create_smart_money_scanner
|
||||||
from .industry_deep_dive import create_industry_deep_dive
|
from .industry_deep_dive import create_industry_deep_dive
|
||||||
from .macro_synthesis import create_macro_synthesis
|
from .macro_synthesis import create_macro_synthesis
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ def create_macro_synthesis(llm):
|
||||||
scan_date = state["scan_date"]
|
scan_date = state["scan_date"]
|
||||||
|
|
||||||
# Inject all previous reports for synthesis — no tools, pure LLM reasoning
|
# Inject all previous reports for synthesis — no tools, pure LLM reasoning
|
||||||
|
smart_money = state.get("smart_money_report", "") or "Not available"
|
||||||
all_reports_context = f"""## All Scanner and Research Reports
|
all_reports_context = f"""## All Scanner and Research Reports
|
||||||
|
|
||||||
### Geopolitical Report:
|
### Geopolitical Report:
|
||||||
|
|
@ -24,6 +25,9 @@ def create_macro_synthesis(llm):
|
||||||
### Sector Performance Report:
|
### Sector Performance Report:
|
||||||
{state.get("sector_performance_report", "Not available")}
|
{state.get("sector_performance_report", "Not available")}
|
||||||
|
|
||||||
|
### Smart Money Report (Finviz institutional screeners):
|
||||||
|
{smart_money}
|
||||||
|
|
||||||
### Industry Deep Dive Report:
|
### Industry Deep Dive Report:
|
||||||
{state.get("industry_deep_dive_report", "Not available")}
|
{state.get("industry_deep_dive_report", "Not available")}
|
||||||
"""
|
"""
|
||||||
|
|
@ -31,8 +35,13 @@ def create_macro_synthesis(llm):
|
||||||
system_message = (
|
system_message = (
|
||||||
"You are a macro strategist synthesizing all scanner and research reports into a final investment thesis. "
|
"You are a macro strategist synthesizing all scanner and research reports into a final investment thesis. "
|
||||||
"You have received: geopolitical analysis, market movers analysis, sector performance analysis, "
|
"You have received: geopolitical analysis, market movers analysis, sector performance analysis, "
|
||||||
"and industry deep dive analysis. "
|
"smart money institutional screener results, and industry deep dive analysis. "
|
||||||
"Synthesize these into a structured output with: "
|
"## THE GOLDEN OVERLAP (apply when Smart Money Report is available and not 'Not available'):\n"
|
||||||
|
"Cross-reference the Smart Money tickers with your macro regime thesis. "
|
||||||
|
"If a Smart Money ticker fits your top-down macro narrative (e.g., an Energy stock with heavy insider "
|
||||||
|
"buying during an oil shortage), prioritize it as a top candidate and label its conviction as 'high'. "
|
||||||
|
"If no Smart Money tickers fit the macro narrative, proceed with the best candidates from other reports.\n\n"
|
||||||
|
"Synthesize all reports into a structured output with: "
|
||||||
"(1) Executive summary of the macro environment, "
|
"(1) Executive summary of the macro environment, "
|
||||||
"(2) Top macro themes with conviction levels, "
|
"(2) Top macro themes with conviction levels, "
|
||||||
"(3) A list of 8-10 specific stocks worth investigating with ticker, name, sector, rationale, "
|
"(3) A list of 8-10 specific stocks worth investigating with ticker, name, sector, rationale, "
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
"""Smart Money Scanner — runs sequentially after sector_scanner.
|
||||||
|
|
||||||
|
Runs three Finviz screeners to find institutional footprints:
|
||||||
|
1. Insider buying (open-market purchases by insiders)
|
||||||
|
2. Unusual volume (2x+ normal, price > $10)
|
||||||
|
3. Breakout accumulation (52-week highs on 2x+ volume)
|
||||||
|
|
||||||
|
Positioned after sector_scanner so it can use sector rotation data as context
|
||||||
|
when interpreting and prioritizing Finviz signals. Each screener tool has no
|
||||||
|
parameters — filters are hardcoded to prevent LLM hallucinations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||||
|
|
||||||
|
from tradingagents.agents.utils.scanner_tools import (
|
||||||
|
get_breakout_accumulation_stocks,
|
||||||
|
get_insider_buying_stocks,
|
||||||
|
get_unusual_volume_stocks,
|
||||||
|
)
|
||||||
|
from tradingagents.agents.utils.tool_runner import run_tool_loop
|
||||||
|
|
||||||
|
|
||||||
|
def create_smart_money_scanner(llm):
|
||||||
|
def smart_money_scanner_node(state):
|
||||||
|
scan_date = state["scan_date"]
|
||||||
|
tools = [
|
||||||
|
get_insider_buying_stocks,
|
||||||
|
get_unusual_volume_stocks,
|
||||||
|
get_breakout_accumulation_stocks,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Inject sector rotation context — available because this node runs
|
||||||
|
# after sector_scanner completes.
|
||||||
|
sector_context = state.get("sector_performance_report", "")
|
||||||
|
sector_section = (
|
||||||
|
f"\n\nSector rotation context from the Sector Scanner:\n{sector_context}"
|
||||||
|
if sector_context
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
system_message = (
|
||||||
|
"You are a quantitative analyst hunting for 'Smart Money' institutional footprints in today's market. "
|
||||||
|
"You MUST call all three of these tools exactly once each:\n"
|
||||||
|
"1. `get_insider_buying_stocks` — insider open-market purchases\n"
|
||||||
|
"2. `get_unusual_volume_stocks` — stocks trading at 2x+ normal volume\n"
|
||||||
|
"3. `get_breakout_accumulation_stocks` — institutional breakout accumulation pattern\n\n"
|
||||||
|
"After running all three scans, write a concise report highlighting the best 5 to 8 specific tickers "
|
||||||
|
"you found. For each ticker, state: which scan flagged it, its sector, and why it is anomalous "
|
||||||
|
"(e.g., 'XYZ has heavy insider buying in a sector that is showing strong rotation momentum'). "
|
||||||
|
"Use the sector rotation context below to prioritize tickers from leading sectors and flag any "
|
||||||
|
"smart money signals that confirm or contradict the sector trend. "
|
||||||
|
"If any scan returned unavailable or empty, note it briefly and focus on the remaining results. "
|
||||||
|
"This report will be used by the Macro Strategist to identify high-conviction candidates via the "
|
||||||
|
"Golden Overlap (bottom-up smart money signals cross-referenced with top-down macro themes)."
|
||||||
|
f"{sector_section}"
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = ChatPromptTemplate.from_messages(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"You are a helpful AI assistant, collaborating with other assistants.\n{system_message}"
|
||||||
|
"\nFor your reference, the current date is {current_date}.",
|
||||||
|
),
|
||||||
|
MessagesPlaceholder(variable_name="messages"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
prompt = prompt.partial(system_message=system_message)
|
||||||
|
prompt = prompt.partial(current_date=scan_date)
|
||||||
|
|
||||||
|
chain = prompt | llm.bind_tools(tools)
|
||||||
|
result = run_tool_loop(chain, state["messages"], tools)
|
||||||
|
report = result.content or ""
|
||||||
|
|
||||||
|
return {
|
||||||
|
"messages": [result],
|
||||||
|
"smart_money_report": report,
|
||||||
|
"sender": "smart_money_scanner",
|
||||||
|
}
|
||||||
|
|
||||||
|
return smart_money_scanner_node
|
||||||
|
|
@ -31,6 +31,7 @@ class ScannerState(MessagesState):
|
||||||
geopolitical_report: Annotated[str, _last_value]
|
geopolitical_report: Annotated[str, _last_value]
|
||||||
market_movers_report: Annotated[str, _last_value]
|
market_movers_report: Annotated[str, _last_value]
|
||||||
sector_performance_report: Annotated[str, _last_value]
|
sector_performance_report: Annotated[str, _last_value]
|
||||||
|
smart_money_report: Annotated[str, _last_value]
|
||||||
|
|
||||||
# Phase 2: Deep dive output
|
# Phase 2: Deep dive output
|
||||||
industry_deep_dive_report: Annotated[str, _last_value]
|
industry_deep_dive_report: Annotated[str, _last_value]
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
"""Scanner tools for market-wide analysis."""
|
"""Scanner tools for market-wide analysis."""
|
||||||
|
|
||||||
from langchain_core.tools import tool
|
import logging
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
|
||||||
from tradingagents.dataflows.interface import route_to_vendor
|
from tradingagents.dataflows.interface import route_to_vendor
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_market_movers(
|
def get_market_movers(
|
||||||
|
|
@ -111,3 +116,87 @@ def get_economic_calendar(
|
||||||
Unique Finnhub capability not available in Alpha Vantage.
|
Unique Finnhub capability not available in Alpha Vantage.
|
||||||
"""
|
"""
|
||||||
return route_to_vendor("get_economic_calendar", from_date, to_date)
|
return route_to_vendor("get_economic_calendar", from_date, to_date)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Finviz smart-money screener tools
|
||||||
|
# Each tool has NO parameters — filters are hardcoded to prevent LLM
|
||||||
|
# hallucinating invalid Finviz filter strings.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _run_finviz_screen(filters_dict: dict, label: str) -> str:
|
||||||
|
"""Shared helper — runs a Finviz Overview screener with hardcoded filters."""
|
||||||
|
try:
|
||||||
|
from finvizfinance.screener.overview import Overview # lazy import
|
||||||
|
|
||||||
|
foverview = Overview()
|
||||||
|
foverview.set_filter(filters_dict=filters_dict)
|
||||||
|
df = foverview.screener_view()
|
||||||
|
|
||||||
|
if df is None or df.empty:
|
||||||
|
return f"No stocks matched the {label} criteria today."
|
||||||
|
|
||||||
|
if "Volume" in df.columns:
|
||||||
|
df = df.sort_values(by="Volume", ascending=False)
|
||||||
|
|
||||||
|
cols = [c for c in ["Ticker", "Sector", "Price", "Volume"] if c in df.columns]
|
||||||
|
top_results = df.head(5)[cols].to_dict("records")
|
||||||
|
|
||||||
|
report = f"Top 5 stocks for {label}:\n"
|
||||||
|
for row in top_results:
|
||||||
|
report += f"- {row.get('Ticker', 'N/A')} ({row.get('Sector', 'N/A')}) @ ${row.get('Price', 'N/A')}\n"
|
||||||
|
return report
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Finviz screener error (%s): %s", label, e)
|
||||||
|
return f"Smart money scan unavailable (Finviz error): {e}"
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def get_insider_buying_stocks() -> str:
|
||||||
|
"""
|
||||||
|
Finds Mid/Large cap stocks with positive insider purchases and volume > 1M today.
|
||||||
|
Insider open-market buys are a strong smart money signal — insiders know their
|
||||||
|
company's prospects better than the market.
|
||||||
|
"""
|
||||||
|
return _run_finviz_screen(
|
||||||
|
{
|
||||||
|
"InsiderPurchases": "Positive (>0%)",
|
||||||
|
"Market Cap.": "+Mid (over $2bln)",
|
||||||
|
"Current Volume": "Over 1M",
|
||||||
|
},
|
||||||
|
label="insider_buying",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def get_unusual_volume_stocks() -> str:
|
||||||
|
"""
|
||||||
|
Finds stocks trading at 2x+ their normal volume today, priced above $10.
|
||||||
|
Unusual volume is a footprint of institutional accumulation or distribution.
|
||||||
|
"""
|
||||||
|
return _run_finviz_screen(
|
||||||
|
{
|
||||||
|
"Relative Volume": "Over 2",
|
||||||
|
"Price": "Over $10",
|
||||||
|
},
|
||||||
|
label="unusual_volume",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def get_breakout_accumulation_stocks() -> str:
|
||||||
|
"""
|
||||||
|
Finds stocks hitting 52-week highs on 2x+ normal volume, priced above $10.
|
||||||
|
This is the classic institutional accumulation-before-breakout pattern
|
||||||
|
(O'Neil CAN SLIM). Price strength combined with volume confirms institutional buying.
|
||||||
|
"""
|
||||||
|
return _run_finviz_screen(
|
||||||
|
{
|
||||||
|
"Performance 2": "52-Week High",
|
||||||
|
"Relative Volume": "Over 2",
|
||||||
|
"Price": "Over $10",
|
||||||
|
},
|
||||||
|
label="breakout_accumulation",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Scanner graph — orchestrates the 3-phase macro scanner pipeline."""
|
"""Scanner graph — orchestrates the 4-phase macro scanner pipeline."""
|
||||||
|
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
|
|
@ -8,6 +8,7 @@ from tradingagents.agents.scanners import (
|
||||||
create_geopolitical_scanner,
|
create_geopolitical_scanner,
|
||||||
create_market_movers_scanner,
|
create_market_movers_scanner,
|
||||||
create_sector_scanner,
|
create_sector_scanner,
|
||||||
|
create_smart_money_scanner,
|
||||||
create_industry_deep_dive,
|
create_industry_deep_dive,
|
||||||
create_macro_synthesis,
|
create_macro_synthesis,
|
||||||
)
|
)
|
||||||
|
|
@ -15,10 +16,11 @@ from .scanner_setup import ScannerGraphSetup
|
||||||
|
|
||||||
|
|
||||||
class ScannerGraph:
|
class ScannerGraph:
|
||||||
"""Orchestrates the 3-phase macro scanner pipeline.
|
"""Orchestrates the macro scanner pipeline.
|
||||||
|
|
||||||
Phase 1 (parallel): geopolitical_scanner, market_movers_scanner, sector_scanner
|
Phase 1a (parallel): geopolitical_scanner, market_movers_scanner, sector_scanner
|
||||||
Phase 2: industry_deep_dive (fan-in from Phase 1)
|
Phase 1b (sequential after sector): smart_money_scanner
|
||||||
|
Phase 2: industry_deep_dive (fan-in from all Phase 1 nodes)
|
||||||
Phase 3: macro_synthesis -> END
|
Phase 3: macro_synthesis -> END
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -47,6 +49,7 @@ class ScannerGraph:
|
||||||
"geopolitical_scanner": create_geopolitical_scanner(quick_llm),
|
"geopolitical_scanner": create_geopolitical_scanner(quick_llm),
|
||||||
"market_movers_scanner": create_market_movers_scanner(quick_llm),
|
"market_movers_scanner": create_market_movers_scanner(quick_llm),
|
||||||
"sector_scanner": create_sector_scanner(quick_llm),
|
"sector_scanner": create_sector_scanner(quick_llm),
|
||||||
|
"smart_money_scanner": create_smart_money_scanner(quick_llm),
|
||||||
"industry_deep_dive": create_industry_deep_dive(mid_llm),
|
"industry_deep_dive": create_industry_deep_dive(mid_llm),
|
||||||
"macro_synthesis": create_macro_synthesis(deep_llm),
|
"macro_synthesis": create_macro_synthesis(deep_llm),
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +146,7 @@ class ScannerGraph:
|
||||||
"geopolitical_report": "",
|
"geopolitical_report": "",
|
||||||
"market_movers_report": "",
|
"market_movers_report": "",
|
||||||
"sector_performance_report": "",
|
"sector_performance_report": "",
|
||||||
|
"smart_money_report": "",
|
||||||
"industry_deep_dive_report": "",
|
"industry_deep_dive_report": "",
|
||||||
"macro_scan_summary": "",
|
"macro_scan_summary": "",
|
||||||
"sender": "",
|
"sender": "",
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,14 @@ from tradingagents.agents.utils.scanner_states import ScannerState
|
||||||
|
|
||||||
|
|
||||||
class ScannerGraphSetup:
|
class ScannerGraphSetup:
|
||||||
"""Sets up the 3-phase scanner graph with LLM agent nodes.
|
"""Sets up the scanner graph with LLM agent nodes.
|
||||||
|
|
||||||
Phase 1: geopolitical_scanner, market_movers_scanner, sector_scanner (parallel fan-out)
|
Phase 1a (parallel from START):
|
||||||
Phase 2: industry_deep_dive (fan-in from all three Phase 1 nodes)
|
geopolitical_scanner, market_movers_scanner, sector_scanner
|
||||||
|
Phase 1b (sequential after sector_scanner):
|
||||||
|
smart_money_scanner — runs after sector data is available so it can
|
||||||
|
use sector rotation context when interpreting Finviz signals
|
||||||
|
Phase 2: industry_deep_dive (fan-in from all Phase 1 nodes)
|
||||||
Phase 3: macro_synthesis -> END
|
Phase 3: macro_synthesis -> END
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -20,6 +24,7 @@ class ScannerGraphSetup:
|
||||||
- geopolitical_scanner
|
- geopolitical_scanner
|
||||||
- market_movers_scanner
|
- market_movers_scanner
|
||||||
- sector_scanner
|
- sector_scanner
|
||||||
|
- smart_money_scanner
|
||||||
- industry_deep_dive
|
- industry_deep_dive
|
||||||
- macro_synthesis
|
- macro_synthesis
|
||||||
"""
|
"""
|
||||||
|
|
@ -36,15 +41,18 @@ class ScannerGraphSetup:
|
||||||
for name, node_fn in self.agents.items():
|
for name, node_fn in self.agents.items():
|
||||||
workflow.add_node(name, node_fn)
|
workflow.add_node(name, node_fn)
|
||||||
|
|
||||||
# Phase 1: parallel fan-out from START
|
# Phase 1a: parallel fan-out from START
|
||||||
workflow.add_edge(START, "geopolitical_scanner")
|
workflow.add_edge(START, "geopolitical_scanner")
|
||||||
workflow.add_edge(START, "market_movers_scanner")
|
workflow.add_edge(START, "market_movers_scanner")
|
||||||
workflow.add_edge(START, "sector_scanner")
|
workflow.add_edge(START, "sector_scanner")
|
||||||
|
|
||||||
# Fan-in: all three Phase 1 nodes must complete before Phase 2
|
# Phase 1b: smart_money runs after sector (gets sector rotation context)
|
||||||
|
workflow.add_edge("sector_scanner", "smart_money_scanner")
|
||||||
|
|
||||||
|
# Fan-in: all Phase 1 nodes must complete before Phase 2
|
||||||
workflow.add_edge("geopolitical_scanner", "industry_deep_dive")
|
workflow.add_edge("geopolitical_scanner", "industry_deep_dive")
|
||||||
workflow.add_edge("market_movers_scanner", "industry_deep_dive")
|
workflow.add_edge("market_movers_scanner", "industry_deep_dive")
|
||||||
workflow.add_edge("sector_scanner", "industry_deep_dive")
|
workflow.add_edge("smart_money_scanner", "industry_deep_dive")
|
||||||
|
|
||||||
# Phase 2 -> Phase 3 -> END
|
# Phase 2 -> Phase 3 -> END
|
||||||
workflow.add_edge("industry_deep_dive", "macro_synthesis")
|
workflow.add_edge("industry_deep_dive", "macro_synthesis")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue