Implement all review feedback for Global Macro Scanner: fix exception handling, index fetching efficiency, conditional logic, CLI DRY, error prefixes, remove artifacts
Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
This commit is contained in:
parent
edfb49d2b1
commit
44d13b6924
|
|
@ -217,3 +217,10 @@ __marimo__/
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
**/data_cache/
|
**/data_cache/
|
||||||
|
|
||||||
|
# Scan results and execution plans (generated artifacts)
|
||||||
|
results/
|
||||||
|
plans/
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.backup
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ Multi-agent LLM trading framework using LangGraph for financial analysis and dec
|
||||||
|
|
||||||
## Development Environment
|
## Development Environment
|
||||||
|
|
||||||
**Conda Environment**: `trasingagetns`
|
**Conda Environment**: `tradingagents`
|
||||||
|
|
||||||
Before starting any development work, activate the conda environment:
|
Before starting any development work, activate the conda environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda activate trasingagetns
|
conda activate tradingagents
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
|
||||||
59
cli/main.py
59
cli/main.py
|
|
@ -1178,6 +1178,30 @@ def run_analysis():
|
||||||
display_complete_report(final_state)
|
display_complete_report(final_state)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_scanner_error(result: str) -> bool:
|
||||||
|
"""Return True when *result* indicates an error or missing data from a scanner tool."""
|
||||||
|
error_prefixes = (
|
||||||
|
"Error",
|
||||||
|
"No data",
|
||||||
|
"No quotes",
|
||||||
|
"No movers",
|
||||||
|
"No news",
|
||||||
|
"No industry",
|
||||||
|
"Invalid",
|
||||||
|
"Alpha Vantage",
|
||||||
|
)
|
||||||
|
return any(result.startswith(prefix) for prefix in error_prefixes)
|
||||||
|
|
||||||
|
|
||||||
|
def _invoke_and_save(tool, args: dict, save_dir: Path, filename: str, label: str) -> str:
|
||||||
|
"""Invoke a scanner tool, print a preview, and save the result if it is valid."""
|
||||||
|
result = tool.invoke(args)
|
||||||
|
if not _is_scanner_error(result):
|
||||||
|
(save_dir / filename).write_text(result)
|
||||||
|
console.print(result[:500] + "..." if len(result) > 500 else result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def run_scan():
|
def run_scan():
|
||||||
console.print(Panel("[bold green]Global Macro Scanner[/bold green]", border_style="green"))
|
console.print(Panel("[bold green]Global Macro Scanner[/bold green]", border_style="green"))
|
||||||
default_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
default_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
@ -1190,35 +1214,20 @@ def run_scan():
|
||||||
|
|
||||||
# Call scanner tools
|
# Call scanner tools
|
||||||
console.print("[bold]1. Market Movers[/bold]")
|
console.print("[bold]1. Market Movers[/bold]")
|
||||||
movers = get_market_movers.invoke({"category": "day_gainers"})
|
_invoke_and_save(get_market_movers, {"category": "day_gainers"}, save_dir, "market_movers.txt", "Market Movers")
|
||||||
if not (movers.startswith("Error") or movers.startswith("No data")):
|
|
||||||
(save_dir / "market_movers.txt").write_text(movers)
|
|
||||||
console.print(movers[:500] + "..." if len(movers) > 500 else movers)
|
|
||||||
|
|
||||||
console.print("[bold]2. Market Indices[/bold]")
|
console.print("[bold]2. Market Indices[/bold]")
|
||||||
indices = get_market_indices.invoke({})
|
_invoke_and_save(get_market_indices, {}, save_dir, "market_indices.txt", "Market Indices")
|
||||||
if not (indices.startswith("Error") or indices.startswith("No data")):
|
|
||||||
(save_dir / "market_indices.txt").write_text(indices)
|
|
||||||
console.print(indices[:500] + "..." if len(indices) > 500 else indices)
|
|
||||||
|
|
||||||
console.print("[bold]3. Sector Performance[/bold]")
|
console.print("[bold]3. Sector Performance[/bold]")
|
||||||
sectors = get_sector_performance.invoke({})
|
_invoke_and_save(get_sector_performance, {}, save_dir, "sector_performance.txt", "Sector Performance")
|
||||||
if not (sectors.startswith("Error") or sectors.startswith("No data")):
|
|
||||||
(save_dir / "sector_performance.txt").write_text(sectors)
|
|
||||||
console.print(sectors[:500] + "..." if len(sectors) > 500 else sectors)
|
|
||||||
|
|
||||||
console.print("[bold]4. Industry Performance (Technology)[/bold]")
|
console.print("[bold]4. Industry Performance (Technology)[/bold]")
|
||||||
industry = get_industry_performance.invoke({"sector_key": "technology"})
|
_invoke_and_save(get_industry_performance, {"sector_key": "technology"}, save_dir, "industry_performance.txt", "Industry Performance")
|
||||||
if not (industry.startswith("Error") or industry.startswith("No data")):
|
|
||||||
(save_dir / "industry_performance.txt").write_text(industry)
|
|
||||||
console.print(industry[:500] + "..." if len(industry) > 500 else industry)
|
|
||||||
|
|
||||||
console.print("[bold]5. Topic News (Market)[/bold]")
|
console.print("[bold]5. Topic News (Market)[/bold]")
|
||||||
news = get_topic_news.invoke({"topic": "market", "limit": 10})
|
_invoke_and_save(get_topic_news, {"topic": "market", "limit": 10}, save_dir, "topic_news.txt", "Topic News")
|
||||||
if not (news.startswith("Error") or news.startswith("No data")):
|
|
||||||
(save_dir / "topic_news.txt").write_text(news)
|
|
||||||
console.print(news[:500] + "..." if len(news) > 500 else news)
|
|
||||||
|
|
||||||
console.print(f"[green]Results saved to {save_dir}[/green]")
|
console.print(f"[green]Results saved to {save_dir}[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1236
cli/main.py.backup
1236
cli/main.py.backup
File diff suppressed because it is too large
Load Diff
|
|
@ -1,82 +0,0 @@
|
||||||
# Data Layer Fix and Test Plan for Global Macro Analyzer
|
|
||||||
|
|
||||||
## Current State Assessment
|
|
||||||
|
|
||||||
- ✅ pyproject.toml configured correctly
|
|
||||||
- ✅ Removed stray scanner_tools.py files outside tradingagents/
|
|
||||||
- ✅ yfinance_scanner.py implements all required functions
|
|
||||||
- ✅ alpha_vantage_scanner.py implements fallback get_market_movers_alpha_vantage correctly
|
|
||||||
- ✅ scanner_tools.py wrappers properly use route_to_vendor for all scanner methods
|
|
||||||
- ✅ default_config.py updated with scanner_data vendor configuration
|
|
||||||
- ✅ All scanner tools import successfully without runtime errors
|
|
||||||
|
|
||||||
## Outstanding Issues
|
|
||||||
|
|
||||||
- CLI scan command not yet implemented in cli/main.py
|
|
||||||
- Scanner graph components (MacroScannerGraph) not yet created
|
|
||||||
- No end-to-end testing of the data layer functionality
|
|
||||||
|
|
||||||
## Fix Plan
|
|
||||||
|
|
||||||
### 1. Implement Scanner Graph Components
|
|
||||||
|
|
||||||
Create the following files in tradingagents/graph/:
|
|
||||||
|
|
||||||
- scanner_setup.py: Graph setup logic for scanner components
|
|
||||||
- scanner_conditional_logic.py: Conditional logic for scanner graph flow
|
|
||||||
- scanner_graph.py: Main MacroScannerGraph class
|
|
||||||
|
|
||||||
### 2. Add Scan Command to CLI
|
|
||||||
|
|
||||||
Modify cli/main.py to include:
|
|
||||||
|
|
||||||
- @app.command() def scan(): entry point
|
|
||||||
- Date prompt (default: today)
|
|
||||||
- LLM provider config prompt (reuse existing helpers)
|
|
||||||
- MacroScannerGraph instantiation and scan() method call
|
|
||||||
- Rich panel display for results
|
|
||||||
- Report saving to results/macro_scan/{date}/ directory
|
|
||||||
|
|
||||||
### 3. Create MacroScannerGraph
|
|
||||||
|
|
||||||
Implement the scanner graph that:
|
|
||||||
|
|
||||||
- Runs parallel Phase 1 scanners (geopolitical, market movers, sectors)
|
|
||||||
- Coordinates Phase 2 industry deep dive
|
|
||||||
- Produces Phase 3 macro synthesis output
|
|
||||||
- Uses ScannerState for state management
|
|
||||||
|
|
||||||
### 4. End-to-End Testing
|
|
||||||
|
|
||||||
Execute the scan command and verify:
|
|
||||||
|
|
||||||
- Rich panels display correctly for each report section
|
|
||||||
- Top-10 stock watchlist is generated and displayed
|
|
||||||
- Reports are saved to results/macro_scan/{date}/ directory
|
|
||||||
- No import or runtime errors occur
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
1. [ ] Create scanner graph components (scanner_setup.py, scanner_conditional_logic.py, scanner_graph.py)
|
|
||||||
2. [ ] Add scan command to cli/main.py with proper argument handling
|
|
||||||
3. [ ] Implement MacroScannerGraph with proper node/edge connections
|
|
||||||
4. [ ] Test scan command functionality
|
|
||||||
5. [ ] Verify output formatting and file generation
|
|
||||||
6. [ ] Document test results and any issues found
|
|
||||||
|
|
||||||
## Verification Criteria
|
|
||||||
|
|
||||||
- ✅ All scanner tools can be imported and used
|
|
||||||
- ✅ CLI scan command executes without errors
|
|
||||||
- ✅ Rich panels display market movers, indices, sector performance, and news
|
|
||||||
- ✅ Top-10 stock watchlist is generated and displayed
|
|
||||||
- ✅ Reports saved to results/macro_scan/{date}/ directory
|
|
||||||
- ✅ No runtime exceptions or import errors
|
|
||||||
|
|
||||||
## Contingency
|
|
||||||
|
|
||||||
- If errors occur during scan execution, check:
|
|
||||||
- Vendor routing configuration in default_config.py
|
|
||||||
- Function implementations in yfinance_scanner.py and alpha_vantage_scanner.py
|
|
||||||
- Graph node/edge connections in scanner graph components
|
|
||||||
- Rich panel formatting and output generation logic
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
# Data Layer Fix and Test Plan
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Verify and test the data layer for the Global Macro Analyzer implementation.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Python environment with dependencies installed
|
|
||||||
- yfinance and alpha_vantage configured
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
|
|
||||||
1. Import and test scanner tools individually
|
|
||||||
2. Run CLI scan command
|
|
||||||
3. Validate output
|
|
||||||
4. Document results
|
|
||||||
|
|
||||||
## Testing Scanner Tools
|
|
||||||
|
|
||||||
- Test get_market_movers
|
|
||||||
- Test get_market_indices
|
|
||||||
- Test get_sector_performance
|
|
||||||
- Test get_industry_performance
|
|
||||||
- Test get_topic_news
|
|
||||||
|
|
||||||
## Running CLI Scan
|
|
||||||
|
|
||||||
- Command: python -m tradingagents scan --date 2026-03-14
|
|
||||||
- Expected output: Rich panels with market movers, indices, sector performance, news, and top-10 watchlist
|
|
||||||
|
|
||||||
## Expected Results
|
|
||||||
|
|
||||||
- No import errors
|
|
||||||
- Successful execution without exceptions
|
|
||||||
- Output files generated under results/
|
|
||||||
- Top-10 stock watchlist displayed
|
|
||||||
|
|
||||||
## Contingency
|
|
||||||
|
|
||||||
- If errors occur, check import paths and configuration
|
|
||||||
- Verify default_config.py scanner_data setting is correct
|
|
||||||
- Ensure vendor routing works correctly
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- Address any failures
|
|
||||||
- Refine output formatting
|
|
||||||
- Add additional test cases
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# Industry Performance: Technology
|
|
||||||
# Data retrieved on: 2026-03-15 11:17:42
|
|
||||||
|
|
||||||
| Company | Symbol | Industry | Market Cap | Change % |
|
|
||||||
|---------|--------|----------|------------|----------|
|
|
||||||
| NVIDIA Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| Apple Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Microsoft Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| Broadcom Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Micron Technology, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Oracle Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| Palantir Technologies Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Advanced Micro Devices, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Cisco Systems, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Applied Materials, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Lam Research Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| International Business Machine | N/A | N/A | N/A | N/A |
|
|
||||||
| Intel Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| KLA Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| Salesforce, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Texas Instruments Incorporated | N/A | N/A | N/A | N/A |
|
|
||||||
| Arista Networks, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Amphenol Corporation | N/A | N/A | N/A | N/A |
|
|
||||||
| Shopify Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
| Uber Technologies, Inc. | N/A | N/A | N/A | N/A |
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Major Market Indices
|
|
||||||
# Data retrieved on: 2026-03-15 11:17:38
|
|
||||||
|
|
||||||
| Index | Current Price | Change | Change % | 52W High | 52W Low |
|
|
||||||
|-------|---------------|--------|----------|----------|----------|
|
|
||||||
| S&P 500 | 6632.19 | -40.43 | -0.61% | 7002.28 | 4835.04 |
|
|
||||||
| Dow Jones | 46558.47 | -119.38 | -0.26% | 50512.79 | 36611.78 |
|
|
||||||
| NASDAQ | 22105.36 | -206.62 | -0.93% | 24019.99 | 14784.03 |
|
|
||||||
| VIX (Volatility Index) | 27.19 | -0.10 | -0.37% | 60.13 | 13.38 |
|
|
||||||
| Russell 2000 | 2480.05 | -8.94 | -0.36% | 2735.10 | 1732.99 |
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
# Market Movers: Day Gainers
|
|
||||||
# Data retrieved on: 2026-03-15 11:17:38
|
|
||||||
|
|
||||||
| Symbol | Name | Price | Change % | Volume | Market Cap |
|
|
||||||
|--------|------|-------|----------|--------|------------|
|
|
||||||
| NP | Neptune Insurance Holdings Inc | $21.87 | 20.23% | 924,853 | $3,021,417,984 |
|
|
||||||
| VEON | VEON Ltd. | $50.60 | 14.20% | 687,398 | $3,491,177,216 |
|
|
||||||
| KLAR | Klarna Group plc | $15.91 | 8.82% | 8,979,495 | $6,006,150,656 |
|
|
||||||
| KYIV | Kyivstar Group Ltd. | $11.07 | 8.53% | 2,498,383 | $2,555,660,032 |
|
|
||||||
| GLXY | Galaxy Digital Inc. | $22.35 | 8.34% | 7,046,744 | $8,730,140,672 |
|
|
||||||
| BLLN | BillionToOne, Inc. | $69.11 | 7.93% | 230,655 | $3,165,566,720 |
|
|
||||||
| IBRX | ImmunityBio, Inc. | $8.39 | 7.29% | 30,384,030 | $8,625,855,488 |
|
|
||||||
| SNDK | Sandisk Corporation | $661.62 | 6.92% | 18,684,442 | $97,655,758,848 |
|
|
||||||
| SSL | Sasol Ltd. | $11.31 | 6.70% | 5,267,106 | $7,210,551,296 |
|
|
||||||
| SEDG | SolarEdge Technologies, Inc. | $37.44 | 6.39% | 1,971,961 | $2,260,113,920 |
|
|
||||||
| MARA | MARA Holdings, Inc. | $9.32 | 6.39% | 73,011,343 | $3,543,786,496 |
|
|
||||||
| MUR | Murphy Oil Corporation | $36.81 | 6.02% | 5,770,011 | $5,257,585,664 |
|
|
||||||
| ADPT | Adaptive Biotechnologies Corpo | $13.17 | 5.78% | 3,892,105 | $2,027,937,280 |
|
|
||||||
| NIO | NIO Inc. | $5.86 | 5.59% | 57,679,174 | $14,817,943,552 |
|
|
||||||
| CRDO | Credo Technology Group Holding | $117.69 | 5.49% | 4,460,224 | $21,707,913,216 |
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Sector Performance Overview
|
|
||||||
# Data retrieved on: 2026-03-15 11:17:40
|
|
||||||
|
|
||||||
| Sector | 1-Day % | 1-Week % | 1-Month % | YTD % |
|
|
||||||
|--------|---------|----------|-----------|-------|
|
|
||||||
| Communication Services | N/A | N/A | N/A | N/A |
|
|
||||||
| Consumer Cyclical | N/A | N/A | N/A | N/A |
|
|
||||||
| Consumer Defensive | N/A | N/A | N/A | N/A |
|
|
||||||
| Energy | N/A | N/A | N/A | N/A |
|
|
||||||
| Financial Services | N/A | N/A | N/A | N/A |
|
|
||||||
| Healthcare | N/A | N/A | N/A | N/A |
|
|
||||||
| Industrials | N/A | N/A | N/A | N/A |
|
|
||||||
| Basic Materials | N/A | N/A | N/A | N/A |
|
|
||||||
| Real Estate | N/A | N/A | N/A | N/A |
|
|
||||||
| Technology | N/A | N/A | N/A | N/A |
|
|
||||||
| Utilities | N/A | N/A | N/A | N/A |
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# News for Topic: market
|
|
||||||
# Data retrieved on: 2026-03-15 11:17:42
|
|
||||||
|
|
||||||
### Opinion: A Stock Market Crash Is Much More Likely Now Than It Was 2 Months Ago (source: Motley Fool)
|
|
||||||
Link: https://finance.yahoo.com/m/f5bf5eda-ecb7-3918-9b7a-0b4ebce070cf/opinion%3A-a-stock-market-crash.html
|
|
||||||
|
|
||||||
### UBS: AI investment is ‘lone buffer’ for emerging markets as energy costs soar (source: Investing.com)
|
|
||||||
Link: https://finance.yahoo.com/news/ubs-ai-investment-lone-buffer-021705455.html
|
|
||||||
|
|
||||||
### The Stock Market May Be Shifting From Risky Tech Stocks to Safer Sectors. Here Are 3 Stocks to Buy Before They Soar. (source: Motley Fool)
|
|
||||||
Link: https://finance.yahoo.com/m/36037b15-c941-3d1a-8b65-3fef291ca4ad/the-stock-market-may-be.html
|
|
||||||
|
|
||||||
### BizTips: Boost your business by using the marketing funnel (source: Cape Cod Times)
|
|
||||||
Link: https://finance.yahoo.com/m/22db7f27-19ca-372f-9eb3-d6bfe6531a87/biztips%3A-boost-your-business.html
|
|
||||||
|
|
||||||
### Sandisk (SNDK) Rockets 25.5%, Investors Makes Use of Market Bloodbath for Gains (source: Insider Monkey)
|
|
||||||
Link: https://finance.yahoo.com/news/sandisk-sndk-rockets-25-5-094041196.html
|
|
||||||
|
|
||||||
### Goldman: AI PCs to buck 10% market slump as ‘edge computing’ demand accelerates (source: Investing.com)
|
|
||||||
Link: https://finance.yahoo.com/news/goldman-ai-pcs-buck-10-003503068.html
|
|
||||||
|
|
||||||
### Fed to weigh interest rates amid Iran war, potential price increases (source: USA TODAY)
|
|
||||||
Link: https://finance.yahoo.com/m/fd7d2f56-6374-324a-87a7-ead11eed5d62/fed-to-weigh-interest-rates.html
|
|
||||||
|
|
||||||
### Is Mobileye (MBLY) Now Offering Value After A 49% One Year Share Price Decline (source: Simply Wall St.)
|
|
||||||
Link: https://finance.yahoo.com/news/mobileye-mbly-now-offering-value-080612183.html
|
|
||||||
|
|
||||||
### Will the Trump Bull Market Come to an Abrupt End Due to the Iran War? History Offers Its Objective and Potentially Uncomfortable Take. (source: Motley Fool)
|
|
||||||
Link: https://finance.yahoo.com/m/1b1369ec-855e-3380-9ed4-274a58d0c2be/will-the-trump-bull-market.html
|
|
||||||
|
|
||||||
### 3 Magnificent High-Yield Dividend Stocks to Buy and Hold (source: Motley Fool)
|
|
||||||
Link: https://finance.yahoo.com/m/1f5939f6-20c4-3c84-ac7a-f41d6218897c/3-magnificent-high-yield.html
|
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ import pytest
|
||||||
|
|
||||||
# Set up the Python path to include the project root
|
# Set up the Python path to include the project root
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, '/Users/Ahmet/Repo/TradingAgents')
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
from tradingagents.agents.utils.scanner_tools import (
|
from tradingagents.agents.utils.scanner_tools import (
|
||||||
get_market_movers,
|
get_market_movers,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ def get_market_movers_alpha_vantage(
|
||||||
return f"Invalid category '{category}'. Must be one of: day_gainers, day_losers, most_actives"
|
return f"Invalid category '{category}'. Must be one of: day_gainers, day_losers, most_actives"
|
||||||
|
|
||||||
if category == 'most_actives':
|
if category == 'most_actives':
|
||||||
return "Alpha Vantage does not support 'most_actives' category. Please use yfinance instead."
|
return "Error: Alpha Vantage does not support 'most_actives'. Use yfinance (default vendor) for this category."
|
||||||
|
|
||||||
# Make API request for TOP_GAINERS_LOSERS endpoint
|
# Make API request for TOP_GAINERS_LOSERS endpoint
|
||||||
response = _make_api_request("TOP_GAINERS_LOSERS", {})
|
response = _make_api_request("TOP_GAINERS_LOSERS", {})
|
||||||
|
|
@ -38,7 +38,7 @@ def get_market_movers_alpha_vantage(
|
||||||
return f"Error from Alpha Vantage: {data['Error Message']}"
|
return f"Error from Alpha Vantage: {data['Error Message']}"
|
||||||
|
|
||||||
if "Note" in data:
|
if "Note" in data:
|
||||||
return f"Alpha Vantage API limit reached: {data['Note']}"
|
return f"Error: Alpha Vantage API limit reached: {data['Note']}"
|
||||||
|
|
||||||
# Map category to Alpha Vantage response key
|
# Map category to Alpha Vantage response key
|
||||||
if category == 'day_gainers':
|
if category == 'day_gainers':
|
||||||
|
|
@ -46,7 +46,7 @@ def get_market_movers_alpha_vantage(
|
||||||
elif category == 'day_losers':
|
elif category == 'day_losers':
|
||||||
key = 'top_losers'
|
key = 'top_losers'
|
||||||
else:
|
else:
|
||||||
return f"Unsupported category: {category}"
|
return f"Error: unsupported category '{category}'"
|
||||||
|
|
||||||
if key not in data:
|
if key not in data:
|
||||||
return f"No data found for {category}"
|
return f"No data found for {category}"
|
||||||
|
|
@ -74,7 +74,7 @@ def get_market_movers_alpha_vantage(
|
||||||
if isinstance(price, str):
|
if isinstance(price, str):
|
||||||
try:
|
try:
|
||||||
price = f"${float(price):.2f}"
|
price = f"${float(price):.2f}"
|
||||||
except:
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
if isinstance(change_pct, str):
|
if isinstance(change_pct, str):
|
||||||
change_pct = change_pct.rstrip('%') # Remove % if present
|
change_pct = change_pct.rstrip('%') # Remove % if present
|
||||||
|
|
@ -83,7 +83,7 @@ def get_market_movers_alpha_vantage(
|
||||||
if isinstance(volume, (int, str)):
|
if isinstance(volume, (int, str)):
|
||||||
try:
|
try:
|
||||||
volume = f"{int(volume):,}"
|
volume = f"{int(volume):,}"
|
||||||
except:
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
result_str += f"| {symbol} | {price} | {change_pct} | {volume} |\n"
|
result_str += f"| {symbol} | {price} | {change_pct} | {volume} |\n"
|
||||||
|
|
|
||||||
|
|
@ -191,8 +191,7 @@ def route_to_vendor(method: str, *args, **kwargs):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return impl_func(*args, **kwargs)
|
return impl_func(*args, **kwargs)
|
||||||
except Exception:
|
except AlphaVantageRateLimitError:
|
||||||
# Continue to next vendor on any exception
|
continue # Only rate limits trigger fallback
|
||||||
continue
|
|
||||||
|
|
||||||
raise RuntimeError(f"No available vendor for '{method}'")
|
raise RuntimeError(f"No available vendor for '{method}'")
|
||||||
|
|
@ -31,7 +31,7 @@ def get_market_movers_yfinance(
|
||||||
# Use yfinance screener module's screen function
|
# Use yfinance screener module's screen function
|
||||||
data = yf.screener.screen(screener_keys[category], count=25)
|
data = yf.screener.screen(screener_keys[category], count=25)
|
||||||
|
|
||||||
if not data or 'quotes' not in data:
|
if not data or not isinstance(data, dict) or 'quotes' not in data:
|
||||||
return f"No data found for {category}"
|
return f"No data found for {category}"
|
||||||
|
|
||||||
quotes = data['quotes']
|
quotes = data['quotes']
|
||||||
|
|
@ -97,29 +97,46 @@ def get_market_indices_yfinance() -> str:
|
||||||
result_str += "| Index | Current Price | Change | Change % | 52W High | 52W Low |\n"
|
result_str += "| Index | Current Price | Change | Change % | 52W High | 52W Low |\n"
|
||||||
result_str += "|-------|---------------|--------|----------|----------|----------|\n"
|
result_str += "|-------|---------------|--------|----------|----------|----------|\n"
|
||||||
|
|
||||||
|
# Batch-download 1-day history for all symbols in a single request
|
||||||
|
symbols = list(indices.keys())
|
||||||
|
indices_history = yf.download(symbols, period="2d", auto_adjust=True, progress=False, threads=True)
|
||||||
|
|
||||||
for symbol, name in indices.items():
|
for symbol, name in indices.items():
|
||||||
try:
|
try:
|
||||||
ticker = yf.Ticker(symbol)
|
ticker = yf.Ticker(symbol)
|
||||||
info = ticker.info
|
# fast_info is a lightweight cached property (no extra HTTP call)
|
||||||
hist = ticker.history(period="1d")
|
fast = ticker.fast_info
|
||||||
|
|
||||||
if hist.empty:
|
# Extract history for this symbol from the batch download
|
||||||
|
try:
|
||||||
|
if len(symbols) > 1:
|
||||||
|
closes = indices_history["Close"][symbol].dropna()
|
||||||
|
else:
|
||||||
|
closes = indices_history["Close"].dropna()
|
||||||
|
except KeyError:
|
||||||
|
closes = None
|
||||||
|
|
||||||
|
if closes is None or len(closes) == 0:
|
||||||
|
result_str += f"| {name} | N/A | - | - | - | - |\n"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
current_price = hist['Close'].iloc[-1]
|
current_price = closes.iloc[-1]
|
||||||
prev_close = info.get('previousClose', current_price)
|
prev_close = closes.iloc[-2] if len(closes) >= 2 else fast.previous_close
|
||||||
|
if prev_close is None or prev_close == 0:
|
||||||
|
prev_close = current_price
|
||||||
|
|
||||||
change = current_price - prev_close
|
change = current_price - prev_close
|
||||||
change_pct = (change / prev_close * 100) if prev_close else 0
|
change_pct = (change / prev_close * 100) if prev_close else 0
|
||||||
|
|
||||||
high_52w = info.get('fiftyTwoWeekHigh', 'N/A')
|
high_52w = fast.year_high
|
||||||
low_52w = info.get('fiftyTwoWeekLow', 'N/A')
|
low_52w = fast.year_low
|
||||||
|
|
||||||
# Format numbers
|
# Format numbers
|
||||||
current_str = f"{current_price:.2f}"
|
current_str = f"{current_price:.2f}"
|
||||||
change_str = f"{change:+.2f}"
|
change_str = f"{change:+.2f}"
|
||||||
change_pct_str = f"{change_pct:+.2f}%"
|
change_pct_str = f"{change_pct:+.2f}%"
|
||||||
high_str = f"{high_52w:.2f}" if isinstance(high_52w, (int, float)) else high_52w
|
high_str = f"{high_52w:.2f}" if isinstance(high_52w, (int, float)) else str(high_52w)
|
||||||
low_str = f"{low_52w:.2f}" if isinstance(low_52w, (int, float)) else low_52w
|
low_str = f"{low_52w:.2f}" if isinstance(low_52w, (int, float)) else str(low_52w)
|
||||||
|
|
||||||
result_str += f"| {name} | {current_str} | {change_str} | {change_pct_str} | {high_str} | {low_str} |\n"
|
result_str += f"| {name} | {current_str} | {change_str} | {change_pct_str} | {high_str} | {low_str} |\n"
|
||||||
|
|
||||||
|
|
@ -140,7 +157,9 @@ def get_sector_performance_yfinance() -> str:
|
||||||
Formatted string containing sector performance data
|
Formatted string containing sector performance data
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get all GICS sectors
|
# All 11 standard GICS (Global Industry Classification Standard) sectors.
|
||||||
|
# These keys are fixed by yfinance's Sector API and cannot be fetched
|
||||||
|
# dynamically; the GICS taxonomy is maintained by MSCI/S&P and is stable.
|
||||||
sector_keys = [
|
sector_keys = [
|
||||||
"communication-services",
|
"communication-services",
|
||||||
"consumer-cyclical",
|
"consumer-cyclical",
|
||||||
|
|
|
||||||
|
|
@ -3,57 +3,47 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from tradingagents.agents.utils.scanner_states import ScannerState
|
from tradingagents.agents.utils.scanner_states import ScannerState
|
||||||
|
|
||||||
|
_ERROR_PREFIXES = ("Error", "No data", "No quotes", "No movers", "No news", "No industry", "Invalid")
|
||||||
|
|
||||||
|
|
||||||
|
def _report_is_valid(report: str) -> bool:
|
||||||
|
"""Return True when *report* contains usable data (non-empty, non-error)."""
|
||||||
|
if not report or not report.strip():
|
||||||
|
return False
|
||||||
|
return not any(report.startswith(prefix) for prefix in _ERROR_PREFIXES)
|
||||||
|
|
||||||
|
|
||||||
class ScannerConditionalLogic:
|
class ScannerConditionalLogic:
|
||||||
"""Conditional logic for scanner graph flow control."""
|
"""Conditional logic for scanner graph flow control."""
|
||||||
|
|
||||||
def should_continue_geopolitical(self, state: ScannerState) -> bool:
|
def should_continue_geopolitical(self, state: ScannerState) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine if geopolitical scanning should continue.
|
Determine if geopolitical scanning should continue.
|
||||||
|
|
||||||
Args:
|
Returns True only when the geopolitical report contains usable data.
|
||||||
state: Current scanner state
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: Whether to continue geopolitical scanning
|
|
||||||
"""
|
"""
|
||||||
# Always continue for initial scan - no filtering logic implemented
|
return _report_is_valid(state.get("geopolitical_report", ""))
|
||||||
return True
|
|
||||||
|
|
||||||
def should_continue_movers(self, state: ScannerState) -> bool:
|
def should_continue_movers(self, state: ScannerState) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine if market movers scanning should continue.
|
Determine if market movers scanning should continue.
|
||||||
|
|
||||||
Args:
|
Returns True only when the market movers report contains usable data.
|
||||||
state: Current scanner state
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: Whether to continue market movers scanning
|
|
||||||
"""
|
"""
|
||||||
# Always continue for initial scan - no filtering logic implemented
|
return _report_is_valid(state.get("market_movers_report", ""))
|
||||||
return True
|
|
||||||
|
|
||||||
def should_continue_sector(self, state: ScannerState) -> bool:
|
def should_continue_sector(self, state: ScannerState) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine if sector scanning should continue.
|
Determine if sector scanning should continue.
|
||||||
|
|
||||||
Args:
|
Returns True only when the sector performance report contains usable data.
|
||||||
state: Current scanner state
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: Whether to continue sector scanning
|
|
||||||
"""
|
"""
|
||||||
# Always continue for initial scan - no filtering logic implemented
|
return _report_is_valid(state.get("sector_performance_report", ""))
|
||||||
return True
|
|
||||||
|
|
||||||
def should_continue_industry(self, state: ScannerState) -> bool:
|
def should_continue_industry(self, state: ScannerState) -> bool:
|
||||||
"""
|
"""
|
||||||
Determine if industry deep dive should continue.
|
Determine if industry deep dive should continue.
|
||||||
|
|
||||||
Args:
|
Returns True only when the industry deep dive report contains usable data.
|
||||||
state: Current scanner state
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: Whether to continue industry deep dive
|
|
||||||
"""
|
"""
|
||||||
# Always continue for initial scan - no filtering logic implemented
|
return _report_is_valid(state.get("industry_deep_dive_report", ""))
|
||||||
return True
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue