TradingAgents/tradingagents/nifty50_simple_recommender.py

365 lines
11 KiB
Python

"""
Simplified Nifty 50 Stock Recommendation System.
This module uses Claude Max subscription (via CLI) to analyze pre-fetched stock data
and provide recommendations. It bypasses the complex agent framework to work with
Claude Max subscription without API keys.
"""
import os
# Disable CUDA to avoid library issues with embeddings
os.environ["CUDA_VISIBLE_DEVICES"] = ""
# Remove API key to force Claude Max subscription auth
os.environ.pop("ANTHROPIC_API_KEY", None)
import json
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime, timedelta
from tradingagents.dataflows.markets import NIFTY_50_STOCKS, get_nifty_50_list
from tradingagents.claude_max_llm import ClaudeMaxLLM
# Import data fetching tools
from tradingagents.agents.utils.agent_utils import (
get_stock_data,
get_indicators,
get_fundamentals,
get_news,
)
def fetch_stock_data(symbol: str, trade_date: str) -> Dict[str, str]:
"""
Fetch all relevant data for a stock.
Args:
symbol: Stock symbol (e.g., 'TCS', 'RELIANCE')
trade_date: Date for analysis (YYYY-MM-DD)
Returns:
Dictionary with stock data, indicators, fundamentals, and news
"""
# Calculate date range (10 days before trade date)
end_date = trade_date
start_date = (datetime.strptime(trade_date, "%Y-%m-%d") - timedelta(days=30)).strftime("%Y-%m-%d")
data = {}
# Fetch stock price data
try:
data["price_data"] = get_stock_data.invoke({
"symbol": symbol,
"start_date": start_date,
"end_date": end_date
})
except Exception as e:
data["price_data"] = f"Error fetching price data: {e}"
# Fetch technical indicators
try:
data["indicators"] = get_indicators.invoke({
"symbol": symbol,
"start_date": start_date,
"end_date": end_date
})
except Exception as e:
data["indicators"] = f"Error fetching indicators: {e}"
# Fetch fundamentals
try:
data["fundamentals"] = get_fundamentals.invoke({
"symbol": symbol
})
except Exception as e:
data["fundamentals"] = f"Error fetching fundamentals: {e}"
# Fetch news
try:
company_name = NIFTY_50_STOCKS.get(symbol, symbol)
data["news"] = get_news.invoke({
"symbol": symbol,
"company_name": company_name
})
except Exception as e:
data["news"] = f"Error fetching news: {e}"
return data
def analyze_stock(symbol: str, data: Dict[str, str], llm: ClaudeMaxLLM) -> Dict[str, Any]:
"""
Use Claude to analyze a stock based on pre-fetched data.
Args:
symbol: Stock symbol
data: Pre-fetched stock data
llm: ClaudeMaxLLM instance
Returns:
Analysis result with decision and reasoning
"""
company_name = NIFTY_50_STOCKS.get(symbol, symbol)
prompt = f"""You are an expert stock analyst. Analyze the following data for {symbol} ({company_name}) and provide:
1. A trading decision: BUY, SELL, or HOLD
2. Confidence level: High, Medium, or Low
3. Key reasoning (2-3 sentences)
4. Risk assessment: High, Medium, or Low
## Price Data (Last 30 days)
{data.get('price_data', 'Not available')[:2000]}
## Technical Indicators
{data.get('indicators', 'Not available')[:2000]}
## Fundamentals
{data.get('fundamentals', 'Not available')[:2000]}
## Recent News
{data.get('news', 'Not available')[:1500]}
Provide your analysis in this exact format:
DECISION: [BUY/SELL/HOLD]
CONFIDENCE: [High/Medium/Low]
REASONING: [Your 2-3 sentence reasoning]
RISK: [High/Medium/Low]
TARGET: [Expected price movement in next 1-2 weeks, e.g., "+5%" or "-3%"]
"""
try:
response = llm.invoke(prompt)
analysis_text = response.content
# Parse the response
result = {
"symbol": symbol,
"company_name": company_name,
"raw_analysis": analysis_text,
"error": None
}
# Extract structured data (handle markdown formatting like **DECISION:**)
import re
text_upper = analysis_text.upper()
# Look for DECISION
decision_match = re.search(r'\*?\*?DECISION:?\*?\*?\s*([A-Z]+)', text_upper)
if decision_match:
result["decision"] = decision_match.group(1).strip()
# Look for CONFIDENCE
confidence_match = re.search(r'\*?\*?CONFIDENCE:?\*?\*?\s*([A-Z]+)', text_upper)
if confidence_match:
result["confidence"] = confidence_match.group(1).strip()
# Look for RISK
risk_match = re.search(r'\*?\*?RISK:?\*?\*?\s*([A-Z]+)', text_upper)
if risk_match:
result["risk"] = risk_match.group(1).strip()
return result
except Exception as e:
return {
"symbol": symbol,
"company_name": company_name,
"decision": None,
"error": str(e)
}
def analyze_all_stocks(
trade_date: str,
stock_subset: Optional[List[str]] = None,
on_progress: Optional[callable] = None
) -> Dict[str, Dict[str, Any]]:
"""
Analyze all Nifty 50 stocks (or a subset).
Args:
trade_date: Date for analysis
stock_subset: Optional list of symbols to analyze
on_progress: Optional callback(current, total, symbol, result)
Returns:
Dictionary of analysis results
"""
# Initialize Claude LLM
llm = ClaudeMaxLLM(model="sonnet")
stocks = stock_subset if stock_subset else get_nifty_50_list()
total = len(stocks)
results = {}
for i, symbol in enumerate(stocks, 1):
# Fetch data
data = fetch_stock_data(symbol, trade_date)
# Analyze with Claude
analysis = analyze_stock(symbol, data, llm)
results[symbol] = analysis
if on_progress:
on_progress(i, total, symbol, analysis)
return results
def rank_stocks(results: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
"""
Rank stocks by growth potential using Claude Opus.
Args:
results: Analysis results for all stocks
Returns:
Ranking with top picks
"""
llm = ClaudeMaxLLM(model="opus")
# Build summary of all analyses
summaries = []
for symbol, analysis in results.items():
if analysis.get("error"):
continue
summaries.append(f"""
{symbol} ({analysis.get('company_name', symbol)}):
- Decision: {analysis.get('decision', 'N/A')}
- Confidence: {analysis.get('confidence', 'N/A')}
- Risk: {analysis.get('risk', 'N/A')}
- Analysis: {analysis.get('raw_analysis', 'N/A')[:300]}
""")
if not summaries:
return {"error": "No valid analyses to rank", "ranking": None}
prompt = f"""You are an expert stock analyst. Based on the following analyses of Nifty 50 stocks,
identify the TOP 3 stocks with highest short-term growth potential (1-2 weeks).
## Stock Analyses
{''.join(summaries)}
Provide your ranking in this format:
## TOP 3 PICKS
### 1. [SYMBOL] - TOP PICK
**Decision:** [BUY/STRONG BUY]
**Reason:** [2-3 sentences explaining why this is the top pick]
**Risk Level:** [Low/Medium/High]
### 2. [SYMBOL] - SECOND PICK
**Decision:** [BUY]
**Reason:** [2-3 sentences]
**Risk Level:** [Low/Medium/High]
### 3. [SYMBOL] - THIRD PICK
**Decision:** [BUY]
**Reason:** [2-3 sentences]
**Risk Level:** [Low/Medium/High]
## STOCKS TO AVOID
List 2-3 stocks that should be avoided with brief reasons.
"""
try:
response = llm.invoke(prompt)
return {
"ranking": response.content,
"stocks_analyzed": len(summaries),
"timestamp": datetime.now().isoformat()
}
except Exception as e:
return {"error": str(e), "ranking": None}
def run_recommendation(
trade_date: str,
stock_subset: Optional[List[str]] = None,
save_results: bool = True,
results_dir: Optional[str] = None,
verbose: bool = True
) -> Tuple[Dict[str, Dict[str, Any]], Dict[str, Any]]:
"""
Main entry point for the recommendation system.
Args:
trade_date: Date for analysis
stock_subset: Optional list of symbols
save_results: Whether to save results to disk
results_dir: Directory for results
verbose: Print progress
Returns:
Tuple of (analysis results, ranking)
"""
def progress_callback(current, total, symbol, result):
if verbose:
status = "" if not result.get("error") else ""
decision = result.get("decision", "ERROR") if not result.get("error") else "ERROR"
print(f"[{current}/{total}] {symbol}: {status} {decision}")
if verbose:
print(f"\n{'='*60}")
print("NIFTY 50 STOCK RECOMMENDATION SYSTEM (Simple)")
print(f"{'='*60}")
print(f"Date: {trade_date}")
stocks = stock_subset if stock_subset else get_nifty_50_list()
print(f"Analyzing {len(stocks)} stocks...")
print(f"Using Claude Max subscription via CLI")
print(f"{'='*60}\n")
# Analyze all stocks
results = analyze_all_stocks(trade_date, stock_subset, progress_callback)
if verbose:
successful = sum(1 for r in results.values() if not r.get("error"))
print(f"\n{'='*60}")
print(f"Analysis Complete: {successful}/{len(results)} successful")
print(f"{'='*60}\n")
print("Ranking stocks with Claude Opus...")
# Rank stocks
ranking = rank_stocks(results)
if verbose:
print(f"\n{'='*60}")
print("RECOMMENDATION RESULTS")
print(f"{'='*60}\n")
if ranking.get("ranking"):
print(ranking["ranking"])
else:
print(f"Error: {ranking.get('error', 'Unknown error')}")
# Save results if requested
if save_results:
from tradingagents.default_config import DEFAULT_CONFIG
if results_dir is None:
results_dir = Path(DEFAULT_CONFIG["results_dir"]) / "nifty50_simple_recommendations"
else:
results_dir = Path(results_dir)
results_dir.mkdir(parents=True, exist_ok=True)
# Save analysis results
with open(results_dir / f"analysis_{trade_date}.json", "w") as f:
json.dump(results, f, indent=2, default=str)
# Save ranking
with open(results_dir / f"ranking_{trade_date}.json", "w") as f:
json.dump(ranking, f, indent=2, default=str)
# Save readable report
with open(results_dir / f"report_{trade_date}.md", "w") as f:
f.write(f"# Nifty 50 Stock Recommendation Report\n\n")
f.write(f"**Date:** {trade_date}\n\n")
f.write(f"**Stocks Analyzed:** {ranking.get('stocks_analyzed', 0)}\n\n")
f.write("---\n\n")
f.write(ranking.get("ranking", "No ranking available"))
if verbose:
print(f"\nResults saved to: {results_dir}")
return results, ranking