""" Nifty 50 Stock Recommendation System. This module predicts all 50 Nifty stocks and selects the ones with highest short-term growth potential using Claude Opus 4.5 via Claude Max subscription. """ 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 from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.default_config import DEFAULT_CONFIG from tradingagents.dataflows.markets import NIFTY_50_STOCKS, get_nifty_50_list from tradingagents.claude_max_llm import ClaudeMaxLLM def verify_claude_cli() -> bool: """ Verify that Claude CLI is available and authenticated. Returns: True if Claude CLI is available Raises: RuntimeError: If Claude CLI is not available or not authenticated """ import subprocess try: result = subprocess.run( ["claude", "--version"], capture_output=True, text=True, timeout=10 ) if result.returncode == 0: return True except FileNotFoundError: pass raise RuntimeError( "Claude CLI is not available.\n\n" "To use this recommendation system with Claude Max subscription:\n" "1. Install Claude Code: npm install -g @anthropic-ai/claude-code\n" "2. Authenticate: claude auth login\n" ) def create_claude_config() -> Dict[str, Any]: """ Create a configuration dictionary for using Claude models. Returns: Configuration dictionary with Anthropic settings """ config = DEFAULT_CONFIG.copy() config["llm_provider"] = "anthropic" config["deep_think_llm"] = config["anthropic_config"]["deep_think_llm"] config["quick_think_llm"] = config["anthropic_config"]["quick_think_llm"] config["market"] = "india_nse" # Use jugaad_data for NSE stocks config["data_vendors"] = { "core_stock_apis": "jugaad_data", "technical_indicators": "jugaad_data", "fundamental_data": "yfinance", "news_data": "google", } return config def predict_stock( graph: TradingAgentsGraph, symbol: str, trade_date: str ) -> Dict[str, Any]: """ Run prediction for a single stock. Args: graph: The TradingAgentsGraph instance symbol: Stock symbol (e.g., 'RELIANCE', 'TCS') trade_date: Date for the prediction (YYYY-MM-DD format) Returns: Dictionary containing prediction results including decision, market report, fundamentals report, news report, investment plan, and final trade decision """ try: final_state, decision = graph.propagate(symbol, trade_date) return { "symbol": symbol, "company_name": NIFTY_50_STOCKS.get(symbol, symbol), "decision": decision, "market_report": final_state.get("market_report", ""), "fundamentals_report": final_state.get("fundamentals_report", ""), "news_report": final_state.get("news_report", ""), "sentiment_report": final_state.get("sentiment_report", ""), "investment_plan": final_state.get("investment_plan", ""), "final_trade_decision": final_state.get("final_trade_decision", ""), "investment_debate": { "bull_history": final_state.get("investment_debate_state", {}).get("bull_history", ""), "bear_history": final_state.get("investment_debate_state", {}).get("bear_history", ""), "judge_decision": final_state.get("investment_debate_state", {}).get("judge_decision", ""), }, "risk_debate": { "risky_history": final_state.get("risk_debate_state", {}).get("risky_history", ""), "safe_history": final_state.get("risk_debate_state", {}).get("safe_history", ""), "neutral_history": final_state.get("risk_debate_state", {}).get("neutral_history", ""), "judge_decision": final_state.get("risk_debate_state", {}).get("judge_decision", ""), }, "error": None, } except Exception as e: return { "symbol": symbol, "company_name": NIFTY_50_STOCKS.get(symbol, symbol), "decision": None, "error": str(e), } def predict_all_nifty50( trade_date: str, config: Optional[Dict[str, Any]] = None, stock_subset: Optional[List[str]] = None, on_progress: Optional[callable] = None ) -> Dict[str, Dict[str, Any]]: """ Run predictions for all 50 Nifty stocks (or a subset). Args: trade_date: Date for the predictions (YYYY-MM-DD format) config: Optional configuration dictionary. If None, uses Claude config stock_subset: Optional list of stock symbols to analyze. If None, analyzes all 50 on_progress: Optional callback function(current_index, total, symbol, result) Returns: Dictionary mapping stock symbols to their prediction results """ if config is None: config = create_claude_config() # Verify Claude CLI is available for Max subscription verify_claude_cli() # Initialize the graph graph = TradingAgentsGraph( selected_analysts=["market", "social", "news", "fundamentals"], debug=False, config=config ) # Get list of stocks to analyze stocks = stock_subset if stock_subset else get_nifty_50_list() total = len(stocks) predictions = {} for i, symbol in enumerate(stocks, 1): result = predict_stock(graph, symbol, trade_date) predictions[symbol] = result if on_progress: on_progress(i, total, symbol, result) return predictions def format_predictions_for_prompt(predictions: Dict[str, Dict[str, Any]]) -> str: """ Format all predictions into a comprehensive prompt for Claude. Args: predictions: Dictionary of prediction results Returns: Formatted string containing all predictions """ formatted_parts = [] for symbol, pred in predictions.items(): if pred.get("error"): formatted_parts.append(f""" === {symbol} ({pred.get('company_name', symbol)}) === ERROR: {pred['error']} """) continue formatted_parts.append(f""" === {symbol} ({pred.get('company_name', symbol)}) === DECISION: {pred.get('decision', 'N/A')} MARKET ANALYSIS: {pred.get('market_report', 'N/A')[:1000]} FUNDAMENTALS: {pred.get('fundamentals_report', 'N/A')[:1000]} NEWS & SENTIMENT: {pred.get('news_report', 'N/A')[:500]} {pred.get('sentiment_report', 'N/A')[:500]} INVESTMENT PLAN: {pred.get('investment_plan', 'N/A')[:500]} FINAL TRADE DECISION: {pred.get('final_trade_decision', 'N/A')[:500]} BULL/BEAR DEBATE SUMMARY: Bull: {pred.get('investment_debate', {}).get('bull_history', 'N/A')[:300]} Bear: {pred.get('investment_debate', {}).get('bear_history', 'N/A')[:300]} Judge: {pred.get('investment_debate', {}).get('judge_decision', 'N/A')[:300]} RISK ASSESSMENT: {pred.get('risk_debate', {}).get('judge_decision', 'N/A')[:300]} --- """) return "\n".join(formatted_parts) def parse_ranking_response(response_text: str) -> Dict[str, Any]: """ Parse the ranking response from Claude. Args: response_text: Raw response text from Claude Returns: Dictionary containing parsed ranking results """ return { "raw_response": response_text, "parsed": True, } def rank_stocks_for_growth( predictions: Dict[str, Dict[str, Any]], ) -> Dict[str, Any]: """ Use Claude Opus 4.5 to rank stocks by short-term growth potential. Args: predictions: Dictionary of prediction results for all stocks Returns: Dictionary containing ranking results with top picks and stocks to avoid """ # Initialize Claude Opus via Max subscription llm = ClaudeMaxLLM(model="opus") # Filter out stocks with errors valid_predictions = { k: v for k, v in predictions.items() if not v.get("error") } if not valid_predictions: return { "error": "No valid predictions to rank", "top_picks": [], "stocks_to_avoid": [], } # Format predictions for prompt formatted = format_predictions_for_prompt(valid_predictions) prompt = f"""You are an expert stock analyst specializing in the Indian equity market. Analyze the following predictions for Nifty 50 stocks and select the TOP 3 stocks with the highest short-term growth potential (1-2 weeks timeframe). For each stock, consider: 1. BUY/SELL/HOLD decision and the confidence level 2. Technical indicators and price momentum 3. Fundamental strength (earnings, revenue, valuations) 4. News sentiment and potential catalysts 5. Risk factors and volatility STOCK PREDICTIONS: {formatted} Based on your comprehensive analysis, provide your recommendations in the following format: ## TOP 3 PICKS FOR SHORT-TERM GROWTH ### 1. TOP PICK: [SYMBOL] **Company:** [Company Name] **Recommendation:** [BUY/STRONG BUY] **Target Upside:** [X%] **Reasoning:** [2-3 sentences explaining why this is the top pick, citing specific data points] **Key Catalysts:** [List 2-3 near-term catalysts] **Risk Level:** [Low/Medium/High] ### 2. SECOND PICK: [SYMBOL] **Company:** [Company Name] **Recommendation:** [BUY/STRONG BUY] **Target Upside:** [X%] **Reasoning:** [2-3 sentences] **Key Catalysts:** [List 2-3 near-term catalysts] **Risk Level:** [Low/Medium/High] ### 3. THIRD PICK: [SYMBOL] **Company:** [Company Name] **Recommendation:** [BUY/STRONG BUY] **Target Upside:** [X%] **Reasoning:** [2-3 sentences] **Key Catalysts:** [List 2-3 near-term catalysts] **Risk Level:** [Low/Medium/High] ## STOCKS TO AVOID List any stocks that show concerning signals and should be avoided: - [SYMBOL]: [Brief reason - e.g., "Bearish technical setup, negative news flow"] - [SYMBOL]: [Brief reason] ## MARKET CONTEXT Provide a brief (2-3 sentences) overview of the current market conditions affecting these recommendations. ## DISCLAIMER Include a brief investment disclaimer. """ # Use Claude Opus 4.5's large context window response = llm.invoke(prompt) return { "ranking_analysis": response.content, "total_stocks_analyzed": len(valid_predictions), "stocks_with_errors": len(predictions) - len(valid_predictions), "timestamp": datetime.now().isoformat(), } 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 running the Nifty 50 recommendation system. Args: trade_date: Date for the predictions (YYYY-MM-DD format) stock_subset: Optional list of stock symbols to analyze. If None, analyzes all 50 save_results: Whether to save results to disk results_dir: Directory to save results. If None, uses default verbose: Whether to print progress updates Returns: Tuple of (predictions dict, ranking results dict) """ 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(f"NIFTY 50 STOCK RECOMMENDATION SYSTEM") 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"{'='*60}\n") # Run predictions for all stocks predictions = predict_all_nifty50( trade_date=trade_date, stock_subset=stock_subset, on_progress=progress_callback ) if verbose: successful = sum(1 for p in predictions.values() if not p.get("error")) print(f"\n{'='*60}") print(f"Predictions Complete: {successful}/{len(predictions)} successful") print(f"{'='*60}\n") print("Ranking stocks with Claude Opus 4.5...") # Rank stocks using Claude Opus 4.5 ranking = rank_stocks_for_growth(predictions) if verbose: print(f"\n{'='*60}") print("RECOMMENDATION RESULTS") print(f"{'='*60}\n") print(ranking.get("ranking_analysis", "No ranking available")) # Save results if requested if save_results: if results_dir is None: results_dir = Path(DEFAULT_CONFIG["results_dir"]) / "nifty50_recommendations" else: results_dir = Path(results_dir) results_dir.mkdir(parents=True, exist_ok=True) # Save predictions predictions_file = results_dir / f"predictions_{trade_date}.json" with open(predictions_file, "w") as f: # Convert to serializable format serializable_predictions = {} for symbol, pred in predictions.items(): serializable_predictions[symbol] = { k: str(v) if not isinstance(v, (str, dict, list, type(None))) else v for k, v in pred.items() } json.dump(serializable_predictions, f, indent=2) # Save ranking ranking_file = results_dir / f"ranking_{trade_date}.json" with open(ranking_file, "w") as f: json.dump(ranking, f, indent=2) # Save readable report report_file = results_dir / f"report_{trade_date}.md" with open(report_file, "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('total_stocks_analyzed', 0)}\n\n") f.write(f"**Generated:** {ranking.get('timestamp', 'N/A')}\n\n") f.write("---\n\n") f.write(ranking.get("ranking_analysis", "No ranking available")) if verbose: print(f"\nResults saved to: {results_dir}") return predictions, ranking