From 6295eb2a3722a1da5c21622a58603a32202b5c6b Mon Sep 17 00:00:00 2001 From: Abdul Shaik Date: Tue, 18 Nov 2025 15:33:01 -0500 Subject: [PATCH] Add Stock Analysis Agent use case example This adds a new use case example demonstrating stock market analysis using NVIDIA Nemotron. Features: - Real-time stock price retrieval - Company information lookup - Interactive historical price charts with plotly - Investment simulation (What If scenarios) - Automatic detection of significant price movements The agent uses yfinance for market data and provides a Streamlit web interface for natural language queries. Signed-off-by: Abdul Shaik --- .../Stock Analysis Agent/.gitignore | 3 + .../Stock Analysis Agent/README.md | 59 +++++ use-case-examples/Stock Analysis Agent/app.py | 102 ++++++++ .../Stock Analysis Agent/requirements.txt | 5 + .../Stock Analysis Agent/screenshot.png | Bin 0 -> 76966 bytes .../Stock Analysis Agent/src/llm.py | 87 +++++++ .../Stock Analysis Agent/src/stock_agent.py | 209 ++++++++++++++++ .../src/tools/stock_tools.py | 225 ++++++++++++++++++ 8 files changed, 690 insertions(+) create mode 100644 use-case-examples/Stock Analysis Agent/.gitignore create mode 100644 use-case-examples/Stock Analysis Agent/README.md create mode 100644 use-case-examples/Stock Analysis Agent/app.py create mode 100644 use-case-examples/Stock Analysis Agent/requirements.txt create mode 100644 use-case-examples/Stock Analysis Agent/screenshot.png create mode 100644 use-case-examples/Stock Analysis Agent/src/llm.py create mode 100644 use-case-examples/Stock Analysis Agent/src/stock_agent.py create mode 100644 use-case-examples/Stock Analysis Agent/src/tools/stock_tools.py diff --git a/use-case-examples/Stock Analysis Agent/.gitignore b/use-case-examples/Stock Analysis Agent/.gitignore new file mode 100644 index 0000000..f3e2ea0 --- /dev/null +++ b/use-case-examples/Stock Analysis Agent/.gitignore @@ -0,0 +1,3 @@ +static/ +__pycache__/ +*.pyc diff --git a/use-case-examples/Stock Analysis Agent/README.md b/use-case-examples/Stock Analysis Agent/README.md new file mode 100644 index 0000000..bb82e78 --- /dev/null +++ b/use-case-examples/Stock Analysis Agent/README.md @@ -0,0 +1,59 @@ +# Stock Analysis Agent + +This agent demonstrates how to build an agentic application using Nemotron for financial data analysis. It uses **yfinance** to fetch real-time stock data and **Plotly** for interactive visualizations. + +**Key Features:** +- 💰 Get current stock prices +- 🏢 Retrieve company information (sector, industry, business summary) +- 📊 Plot interactive historical price charts with major price movement detection +- 🎯 Simulate "What If?" investment scenarios +- 🔍 Identify significant price spikes and dips automatically + +![Stock Analysis Agent Screenshot](screenshot.png) + +## Overview + +The agent uses the `yfinance` library to fetch market data and `Plotly` to generate charts. It leverages the **Nemotron-Nano-9B-v2** model (or other Nemotron variants) to understand user intent and call the appropriate tools. + +## Requirements + +- Python 3.10+ +- NVIDIA API Key (get one at [build.nvidia.com](https://build.nvidia.com/)) + +## Installation + +1. **Create and activate a virtual environment (Recommended):** + ```bash + python3 -m venv venv + source venv/bin/activate + ``` + +2. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +3. **Set your NVIDIA API Key:** + ```bash + export NVIDIA_API_KEY="nvapi-..." + ``` + +## Usage + +Run the Streamlit app: + +```bash +streamlit run app.py +``` + +## Example Queries + +Try these questions with the agent: + +- "What is the current price of NVDA?" +- "Tell me about Tesla." +- "Plot the 1-year history of Apple." +- "What if I invested $500 in NVDA 1 year ago?" +- "Simulate investing $1000 in Microsoft 2 years ago." +- "Show me the price movements of Google over 6 months." +- "Compare the prices of Google and Amazon." diff --git a/use-case-examples/Stock Analysis Agent/app.py b/use-case-examples/Stock Analysis Agent/app.py new file mode 100644 index 0000000..f632545 --- /dev/null +++ b/use-case-examples/Stock Analysis Agent/app.py @@ -0,0 +1,102 @@ +import streamlit as st +import os +from src.stock_agent import StockAgent + +st.set_page_config(page_title="Stock Analysis Agent", page_icon="📈", layout="wide") + +# Initialize session state +if "agent" not in st.session_state: + st.session_state.agent = StockAgent() +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Sidebar +with st.sidebar: + st.header("📈 Stock Agent") + st.markdown(""" + This agent uses **NVIDIA Nemotron** to analyze stock market data. + + **Capabilities:** + - 💰 Get current stock prices + - 🏢 Get company information + - 📊 Plot historical price charts + - 🎯 Simulate "What If?" investment scenarios + + **Example Queries:** + - "What is the price of NVDA?" + - "Tell me about Microsoft." + - "Plot the 1-year history of Apple." + - "What if I invested $500 in NVDA 1 year ago?" + - "Simulate investing $1000 in Tesla 2 years ago." + """) + + if st.button("🗑️ Clear Chat"): + st.session_state.messages = [] + st.session_state.agent.clear_history() + st.rerun() + + # API Key check + if not os.getenv("NVIDIA_API_KEY"): + st.error("⚠️ NVIDIA_API_KEY not found!") + st.info("Please set the environment variable before running.") + +# Main Chat Interface +st.title("💬 Stock Analysis Assistant") + +# Display messages +for message in st.session_state.messages: + with st.chat_message(message["role"]): + # Check for chart data in the content + content = message["content"] + chart_data = None + + if "CHART_DATA:" in content: + # Extract the chart data and clean the content + parts = content.split("CHART_DATA:") + content = parts[0].strip() + if len(parts) > 1: + chart_data = parts[1].strip() + + st.markdown(content) + + if chart_data: + try: + import plotly.graph_objects as go + import json + fig = go.Figure(json.loads(chart_data)) + st.plotly_chart(fig, use_container_width=True) + except Exception: + pass + +# User Input +if prompt := st.chat_input("Ask about stocks..."): + # Add user message + st.session_state.messages.append({"role": "user", "content": prompt}) + with st.chat_message("user"): + st.markdown(prompt) + + # Generate response + with st.chat_message("assistant"): + with st.spinner("Thinking..."): + response, chart_data = st.session_state.agent.chat(prompt) + + # Display the text response + st.markdown(response) + + # Display the chart if one was generated + if chart_data: + try: + import plotly.graph_objects as go + import json + # Parse the JSON and create figure + fig = go.Figure(json.loads(chart_data)) + st.plotly_chart(fig, use_container_width=True) + except Exception as e: + st.error(f"Error displaying chart: {e}") + + # Store in session (we'll store chart data separately) + content_to_store = response + if chart_data: + content_to_store += f"\n\nCHART_DATA:{chart_data}" + + st.session_state.messages.append({"role": "assistant", "content": content_to_store}) diff --git a/use-case-examples/Stock Analysis Agent/requirements.txt b/use-case-examples/Stock Analysis Agent/requirements.txt new file mode 100644 index 0000000..8fac13f --- /dev/null +++ b/use-case-examples/Stock Analysis Agent/requirements.txt @@ -0,0 +1,5 @@ +streamlit +yfinance +plotly +pandas +requests diff --git a/use-case-examples/Stock Analysis Agent/screenshot.png b/use-case-examples/Stock Analysis Agent/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ace05684bff620c4c946aa37de15eda673510153 GIT binary patch literal 76966 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%0DH=kvK7;|TtNf3H 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%0DX*o!^-OdH2@x$3)e$R;xtYer2MlKw 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 dict: + """Send messages to the AI and get a response.""" + + # Prepare the request + request_data = { + "model": self.model, + "messages": messages, + "temperature": self.temperature, + "max_tokens": self.max_tokens, + } + + # Add tools if provided + if tools: + request_data["tools"] = tools + request_data["tool_choice"] = "auto" + + # Set up headers + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + try: + # Make the request + response = self.session.post( + f"{self.base_url}/chat/completions", + headers=headers, + json=request_data, + timeout=(10, 300) # 10 seconds to connect, 300 seconds to read + ) + + # Check if request was successful + response.raise_for_status() + return response.json() + + except requests.exceptions.RequestException as e: + raise RuntimeError(f"Failed to communicate with AI: {e}") + +def create_client() -> LLMClient: + """Create a new LLM client.""" + return LLMClient() diff --git a/use-case-examples/Stock Analysis Agent/src/stock_agent.py b/use-case-examples/Stock Analysis Agent/src/stock_agent.py new file mode 100644 index 0000000..95529d8 --- /dev/null +++ b/use-case-examples/Stock Analysis Agent/src/stock_agent.py @@ -0,0 +1,209 @@ +import json +from typing import List, Dict, Any +from src import llm +from src.tools import stock_tools + +class StockAgent: + """Agent for stock analysis using Nemotron.""" + + def __init__(self): + self.llm_client = llm.create_client() + self.conversation_history: List[Dict[str, str]] = [] + + # Tool definitions for the LLM + self.tool_definitions = [ + { + "type": "function", + "function": { + "name": "get_stock_price", + "description": "Get the current stock price for a given ticker symbol.", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The stock ticker symbol (e.g., NVDA, AAPL)" + } + }, + "required": ["ticker"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_company_info", + "description": "Get summary information about a company.", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The stock ticker symbol" + } + }, + "required": ["ticker"] + } + } + }, + { + "type": "function", + "function": { + "name": "plot_stock_history", + "description": "Plot the stock price history for a given period.", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The stock ticker symbol" + }, + "period": { + "type": "string", + "description": "The time period to plot (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)", + "enum": ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"] + } + }, + "required": ["ticker"] + } + } + }, + { + "type": "function", + "function": { + "name": "simulate_investment", + "description": "Simulate what would happen if you invested a certain amount of money in a stock over a time period. Shows profit/loss and portfolio value over time.", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The stock ticker symbol" + }, + "amount": { + "type": "number", + "description": "The investment amount in dollars (e.g., 500, 1000)" + }, + "period": { + "type": "string", + "description": "The time period to simulate (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)", + "enum": ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"] + } + }, + "required": ["ticker", "amount"] + } + } + } + ] + + # Map function names to actual functions + self.available_functions = { + "get_stock_price": stock_tools.get_stock_price, + "get_company_info": stock_tools.get_company_info, + "plot_stock_history": stock_tools.plot_stock_history, + "simulate_investment": stock_tools.simulate_investment + } + + self.system_message = """You are a helpful financial assistant powered by NVIDIA Nemotron. + You can provide stock prices, company information, historical price plots, and investment simulations. + + When asked to plot or show a chart, ALWAYS use the plot_stock_history tool. + + When asked "What if I invested..." or similar simulation questions, use the simulate_investment tool. + + CRITICAL INSTRUCTION FOR PLOTTING: + 1. CAREFULLY ANALYZE the user's request for any time period mentions (e.g., "6 months", "5 years", "1 month", "YTD"). + 2. Map these to the closest available period: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max. + 3. ONLY default to '1y' if the user did NOT specify a period. + 4. If the user asks for "over the years" without a number, use 'max' or '5y'. + + For investment simulations: + - Extract the dollar amount from phrases like "$500", "500 dollars", etc. + - Extract the time period (e.g., "1 year ago" = "1y", "6 months ago" = "6mo") + - If no period is specified, default to '1y' + + Be concise and professional in your responses.""" + + def chat(self, user_input: str) -> tuple[str, str | None]: + """Process user input and return a response. + + Returns: + tuple: (response_text, image_path or None) + """ + + # Add user message to history + self.conversation_history.append({"role": "user", "content": user_input}) + + # Prepare messages for API (System + History) + messages = [{"role": "system", "content": self.system_message}] + self.conversation_history + + # Track if we generated a plot + plot_path = None + + try: + # First call to LLM + response = self.llm_client.chat(messages, tools=self.tool_definitions) + response_message = response["choices"][0]["message"] + + # Check for tool calls + tool_calls = response_message.get("tool_calls") + + if tool_calls: + # Append assistant's message with tool calls to history + messages.append(response_message) + self.conversation_history.append(response_message) + + # Execute tool calls + for tool_call in tool_calls: + function_name = tool_call["function"]["name"] + function_args = json.loads(tool_call["function"]["arguments"]) + + function_to_call = self.available_functions.get(function_name) + if function_to_call: + function_response = function_to_call(**function_args) + + # Check if this was a plot function or simulation + if function_name in ["plot_stock_history", "simulate_investment"] and "PLOTLY_CHART:" in str(function_response): + # Extract the JSON + import re + match = re.search(r"PLOTLY_CHART:(.*?)(?:\n|$)", str(function_response), re.DOTALL) + if match: + plot_path = match.group(1).strip() + # Remove everything after the first newline or closing brace pattern + # The JSON ends when we hit ** + if "**" in plot_path: + plot_path = plot_path.split("**")[0].strip() + + # Append tool response to history + messages.append({ + "tool_call_id": tool_call["id"], + "role": "tool", + "name": function_name, + "content": str(function_response) + }) + # Also update local history + self.conversation_history.append({ + "tool_call_id": tool_call["id"], + "role": "tool", + "name": function_name, + "content": str(function_response) + }) + + # Second call to LLM to get final response + second_response = self.llm_client.chat(messages) + final_content = second_response["choices"][0]["message"]["content"] + + self.conversation_history.append({"role": "assistant", "content": final_content}) + return final_content, plot_path + + else: + # No tool calls, just return content + content = response_message["content"] + self.conversation_history.append({"role": "assistant", "content": content}) + return content, None + + except Exception as e: + return f"Error: {str(e)}", None + + def clear_history(self): + self.conversation_history = [] diff --git a/use-case-examples/Stock Analysis Agent/src/tools/stock_tools.py b/use-case-examples/Stock Analysis Agent/src/tools/stock_tools.py new file mode 100644 index 0000000..522164d --- /dev/null +++ b/use-case-examples/Stock Analysis Agent/src/tools/stock_tools.py @@ -0,0 +1,225 @@ +import yfinance as yf +import plotly.graph_objects as go +import pandas as pd +import os + +def get_stock_price(ticker: str) -> str: + """Get the current stock price for a given ticker.""" + try: + stock = yf.Ticker(ticker) + # Try to get fast info first, then fallback to history + if hasattr(stock, 'fast_info') and 'last_price' in stock.fast_info: + price = stock.fast_info['last_price'] + else: + history = stock.history(period="1d") + if history.empty: + return f"Could not find price data for {ticker}." + price = history['Close'].iloc[-1] + + return f"The current price of {ticker.upper()} is ${price:.2f}" + except Exception as e: + return f"Error fetching price for {ticker}: {str(e)}" + +def get_company_info(ticker: str) -> str: + """Get summary information about a company.""" + try: + stock = yf.Ticker(ticker) + info = stock.info + + name = info.get('longName', ticker.upper()) + sector = info.get('sector', 'Unknown') + industry = info.get('industry', 'Unknown') + summary = info.get('longBusinessSummary', 'No summary available.') + + # Truncate summary if too long + if len(summary) > 500: + summary = summary[:500] + "..." + + return f"**{name}**\nSector: {sector}\nIndustry: {industry}\n\n{summary}" + except Exception as e: + return f"Error fetching info for {ticker}: {str(e)}" + +def plot_stock_history(ticker: str, period: str = "1y") -> str: + """ + Plot the stock history for a given ticker and period. + Period options: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max + Returns JSON representation of the plotly figure and events summary. + """ + try: + stock = yf.Ticker(ticker) + hist = stock.history(period=period) + + if hist.empty: + return f"No historical data found for {ticker} with period {period}." + + # Calculate daily percentage changes + hist['Pct_Change'] = hist['Close'].pct_change() * 100 + + # Identify significant movements (>3% change) + significant_moves = hist[abs(hist['Pct_Change']) > 3].copy() + + # Create interactive plotly chart + fig = go.Figure() + + fig.add_trace(go.Scatter( + x=hist.index, + y=hist['Close'], + mode='lines', + name='Close Price', + line=dict(color='#00cc96', width=2), + hovertemplate='Date: %{x|%Y-%m-%d}
Price: $%{y:.2f}' + )) + + # Add markers for significant movements + if not significant_moves.empty: + # Separate spikes (positive) and dips (negative) + spikes = significant_moves[significant_moves['Pct_Change'] > 0] + dips = significant_moves[significant_moves['Pct_Change'] < 0] + + if not spikes.empty: + fig.add_trace(go.Scatter( + x=spikes.index, + y=spikes['Close'], + mode='markers', + name='Major Spikes', + marker=dict(color='lime', size=10, symbol='triangle-up'), + hovertemplate='Date: %{x|%Y-%m-%d}
Price: $%{y:.2f}
Change: +%{customdata:.1f}%', + customdata=spikes['Pct_Change'] + )) + + if not dips.empty: + fig.add_trace(go.Scatter( + x=dips.index, + y=dips['Close'], + mode='markers', + name='Major Dips', + marker=dict(color='red', size=10, symbol='triangle-down'), + hovertemplate='Date: %{x|%Y-%m-%d}
Price: $%{y:.2f}
Change: %{customdata:.1f}%', + customdata=dips['Pct_Change'] + )) + + fig.update_layout( + title=f"{ticker.upper()} Stock Price - {period}", + xaxis_title="Date", + yaxis_title="Price ($)", + hovermode='x unified', + template='plotly_dark', + height=500, + showlegend=True + ) + + # Save figure as JSON for transfer + import json + fig_json = fig.to_json() + + # Create summary of significant events + events_summary = "" + if not significant_moves.empty: + events_summary = "\n\n**Significant Price Movements Detected:**\n" + top_moves = significant_moves.nlargest(5, 'Pct_Change', keep='all') + bottom_moves = significant_moves.nsmallest(5, 'Pct_Change', keep='all') + all_moves = pd.concat([top_moves, bottom_moves]).sort_index(ascending=False).head(10) + + for date, row in all_moves.iterrows(): + change = row['Pct_Change'] + marker = "▲" if change > 0 else "▼" + sign = "+" if change > 0 else "" + events_summary += f"\n- **{date.strftime('%Y-%m-%d')}** {marker} {sign}{change:.1f}% (${row['Close']:.2f})" + + events_summary += "\n\n*Tip: Search news for these dates to understand what caused these movements.*" + + return f"PLOTLY_CHART:{fig_json}{events_summary}" + except Exception as e: + return f"Error plotting history for {ticker}: {str(e)}" + +def simulate_investment(ticker: str, amount: float, period: str = "1y") -> str: + """ + Simulate what would happen if you invested a certain amount in a stock. + + Args: + ticker: Stock ticker symbol + amount: Investment amount in dollars + period: Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max) + + Returns: + String with simulation results and chart path + """ + try: + stock = yf.Ticker(ticker) + hist = stock.history(period=period) + + if hist.empty: + return f"No historical data found for {ticker} with period {period}." + + # Get starting and ending prices + start_price = hist['Close'].iloc[0] + end_price = hist['Close'].iloc[-1] + + # Calculate shares and returns + shares = amount / start_price + end_value = shares * end_price + profit = end_value - amount + profit_pct = (profit / amount) * 100 + + # Create visualization + fig = go.Figure() + + # Calculate portfolio value over time + portfolio_value = (hist['Close'] / start_price) * amount + + fig.add_trace(go.Scatter( + x=hist.index, + y=portfolio_value, + mode='lines', + name='Portfolio Value', + line=dict(color='#00cc96', width=2), + fill='tozeroy', + fillcolor='rgba(0, 204, 150, 0.2)', + hovertemplate='Date: %{x|%Y-%m-%d}
Value: $%{y:.2f}' + )) + + # Add initial investment line + fig.add_hline( + y=amount, + line_dash="dash", + line_color="gray", + annotation_text=f"Initial: ${amount:.2f}", + annotation_position="right" + ) + + fig.update_layout( + title=f"What If: ${amount:.0f} invested in {ticker.upper()} - {period}", + xaxis_title="Date", + yaxis_title="Portfolio Value ($)", + hovermode='x unified', + template='plotly_dark', + height=500, + showlegend=True + ) + + # Save figure as JSON for transfer + import json + fig_json = fig.to_json() + + # Format response + profit_label = "Profit" if profit >= 0 else "Loss" + return_label = "Return" if profit >= 0 else "Return" + + result = f"""**Investment Simulation Results** + +**Initial Investment:** ${amount:.2f} +**Final Value:** ${end_value:.2f} +**{profit_label}:** ${abs(profit):.2f} +**{return_label}:** {'+' if profit >= 0 else ''}{profit_pct:.1f}% + +**Period:** {period} +**Shares Purchased:** {shares:.4f} +**Start Price:** ${start_price:.2f} +**End Price:** ${end_price:.2f} + +PLOTLY_CHART:{fig_json}""" + + return result + + except Exception as e: + return f"Error simulating investment for {ticker}: {str(e)}"