Indian Stock Market Analysis Agent
Build an AI agent that analyzes NSE stocks using GPT-4o-mini tool calling.
Introduction
30 Second Summary
When you want to know how a stock is performing, you usually open multiple tabs, hunt for price data, run calculations manually, and try to make sense of it all. What if you could just ask a question in plain English and get a complete analysis back?
In this project, you will build a Python-based AI agent that autonomously answers questions about Indian stocks. You give it a natural language question, and it decides which tools to call, fetches real market data, runs calculations, generates charts, and delivers a clear answer.
What You'll Build
An AI agent powered by GPT-4o-mini that can answer complex questions about Indian stock market data by autonomously calling custom analysis tools.
By the end of this project, you'll have:
- A complete agent loop that reasons about your question, calls the right tools, observes results, and repeats until it has a full answer.
- 4 custom tools for stock data retrieval, technical indicator calculation, stock comparison, and chart generation.
- Support for any NSE-listed stock using real market data fetched through yfinance.
- Secret Mission: A portfolio analysis tool that calculates weighted returns, Sharpe ratio, and a correlation matrix across multiple stocks.
Are there any prerequisites?
You should be comfortable writing Python and have a basic understanding of stock market data (open, high, low, close, volume). You'll also need an OpenAI API key ready to go.
Before We Start
To build your stock market analysis agent, you need a working Python environment with access to financial data and the OpenAI API.
In this step, you'll set up your project directory, install the required packages, and verify that you can pull live stock data from the National Stock Exchange.
In this step, get ready to:
- Verify your Python installation and create a virtual environment.
- Install the required packages and create project files.
- Set up your OpenAI API key and verify the connection to NSE stock data.
Check your Python version
The OpenAI Python SDK requires Python 3.9 or higher. Let's confirm you have the right version installed.
- Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal (macOS) or PowerShell (Windows), and pressing Enter.
- Check your Python version by running:
python3 --version
✔️ I see version 3.9 or higher
You're all set. Your Python version is compatible with this project.
ⓧ I see an older version
You need Python 3.9 or higher for this project.
- Visit python.org/downloads and download the latest Python 3 installer.
- Run the installer and make sure to check Add Python to PATH during installation.
- Close and reopen your terminal, then run python3 --version again to confirm.
macOS default Python
macOS may ship with an older system Python. After installing a newer version, you may need to use the full path or a version manager like pyenv to ensure the correct version is used.
ⓧ Command not found
Python is not installed on your machine yet.
- Visit python.org/downloads and download the latest Python 3 installer for your operating system.
- Run the installer and make sure to check Add Python to PATH during installation.
- Close and reopen your terminal, then run python3 --version to confirm Python is available.
Create your project directory and install packages
You'll create a dedicated folder for this project with its own virtual environment to keep dependencies isolated.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir stock-agent
cd stock-agent
- Create a virtual environment by running:
python3 -m venv venv
What is a virtual environment?
A virtual environment is an isolated Python installation inside your project folder.
Packages you install here won't affect your system Python or other projects. This keeps your dependencies clean and reproducible.
- Activate the virtual environment:
macOS
source venv/bin/activate
Windows
.\venv\Scripts\activate
You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.
Don't see (venv) in your prompt?
Make sure you ran the activation command from inside the stock-agent folder. Try running ls to confirm you can see the venv folder listed.
On Windows, if you see an execution policy error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned in PowerShell first, then try the activation command again.
Help me activate my Python virtual environment
- Create a requirements.txt file that lists all the packages your project needs. Create the file by running:
touch requirements.txt
- Open VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows), typing VS Code, and pressing Enter.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the stock-agent folder on your Desktop, then click Open.
- Open requirements.txt from the Explorer sidebar and paste the following contents:
openai>=2.0.0
yfinance>=1.0
pandas>=2.0.0
matplotlib>=3.7.0
python-dotenv>=1.0.0
What are these packages?
- openai is the official Python SDK for calling the OpenAI API, including tool calling with GPT-4o-mini.
- yfinance pulls stock market data from Yahoo Finance, including NSE-listed Indian stocks.
- pandas handles data manipulation and calculations for technical indicators.
- matplotlib generates charts and saves them as PNG files.
- python-dotenv loads environment variables (like your API key) from a .env file.
- Save requirements.txt.
- Back in your terminal (make sure you still see (venv) in the prompt), install all packages by running:
pip install -r requirements.txt
You should see pip downloading and installing each package. This may take a minute.
Seeing install errors?
Make sure your virtual environment is active (you should see (venv) in your prompt). If pip can't find a package, check that your requirements.txt has no typos.
Help me fix my pip install errors
✔️ Awesome, I've got everything!
Great. Double check you've saved requirements.txt and all packages installed successfully.
ⓧ I'd like to double check the full code
openai>=2.0.0
yfinance>=1.0
pandas>=2.0.0
matplotlib>=3.7.0
python-dotenv>=1.0.0
Set up your environment and verify the connection
Your agent needs an OpenAI API key to call GPT-4o-mini. You'll store it in a .env file so it stays out of your code.
- In VS Code, create a new file called .env in the root of your stock-agent folder.
- Paste the following into .env:
OPENAI_API_KEY=your-api-key-here
- Replace your-api-key-here with your actual OpenAI API key.
- Save .env.
Why a .env file?
Storing your API key in a .env file keeps it separate from your code. This prevents you from accidentally sharing your key if you push the project to GitHub.
Now let's verify that yfinance can successfully pull data from the National Stock Exchange.
- Test your setup by running this command in your terminal:
python3 -c "import yfinance as yf; data = yf.Ticker('RELIANCE.NS').history(period='5d'); print(data['Close'].tail())"
What does this command do?
This one-liner imports yfinance, fetches the last 5 days of closing prices for Reliance Industries from the NSE (using the .NS suffix), and prints them.
You should see a short table of dates and closing prices in INR. The exact numbers will depend on the current market data.
Seeing 'No data found' or an empty result?
This can happen if the stock market is closed and yfinance has a temporary connectivity issue. Try running the command again.
If you see an import error, make sure your virtual environment is active ((venv) in your prompt) and that pip install -r requirements.txt completed without errors.
Help me fix my yfinance test command
✔️ Awesome, I've got everything!
Great. Double check you've saved your .env file with your real API key.
ⓧ I'd like to double check the full code
OPENAI_API_KEY=your-api-key-here
Your environment is ready. Next up, you'll build the stock analysis tools that your AI agent will use to fetch and analyze market data.
Set Up Your Project Environment
Your AI agent needs a brain and a set of senses. The brain is OpenAI's GPT-4o-mini model, which decides what to do next. The senses are Python libraries that fetch market data, crunch numbers, and generate charts.
In this step, you'll create the main stock_agent.py file and wire up all the imports and connections your agent needs to function.
In this step, get ready to:
- Create the stock_agent.py file with all necessary imports.
- Initialize the OpenAI client to power your agent's reasoning.
- Verify the setup runs without errors.
Create the main agent file
Your project already has a requirements.txt with your dependencies installed and a .env file holding your API key. Now you need a single Python file that pulls everything together.
- Create a new file called stock_agent.py in your stock-agent folder by running this command:
touch stock_agent.py
- Confirm the file exists by running:
ls
You should see stock_agent.py listed alongside requirements.txt and .env.
- Open stock_agent.py in VS Code and paste the following code:
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
import yfinance as yf
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
# Load environment variables from .env
load_dotenv()
# Create the OpenAI client using your API key
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
What does this code do?
- os and json are built-in Python modules for reading environment variables and formatting data.
- openai connects to GPT-4o-mini for your agent's reasoning.
- yfinance fetches real stock data from Yahoo Finance using the .NS suffix for NSE stocks.
- pandas handles tabular data analysis. matplotlib generates charts as PNG files.
- matplotlib.use('Agg') sets the rendering backend to work without a GUI display window.
- load_dotenv() reads your .env file. Then OpenAI(api_key=...) creates the client your agent uses for every LLM call.
Why the Agg backend?
Matplotlib normally tries to open a GUI window to display charts. Your agent runs as a script, not a desktop app, so there is no GUI available. The Agg backend renders charts directly to image files instead.
- Save stock_agent.py.
Verify your setup
A successful run means all imports resolve and the OpenAI client initializes without errors.
- Run your file by executing this command:
python stock_agent.py
The script should complete silently with no output. If you see your terminal prompt return with no error messages, everything is working.
Seeing a ModuleNotFoundError?
Make sure your virtual environment is active. You should see the environment name in parentheses at the start of your terminal prompt.
If the error names a specific package like openai or yfinance, reinstall from your requirements file:
pip install -r requirements.txt
help me fix a ModuleNotFoundError when running stock_agent.py
Seeing an AuthenticationError?
This means the OpenAI client could not read your API key. Open your .env file and confirm it contains OPENAI_API_KEY= followed by your actual key with no spaces around the equals sign.
help me fix an OpenAI AuthenticationError in my Python script
✔️ Awesome, I've got everything!
Great. Double check you have saved stock_agent.py and move on to the next step.
ⓧ I'd like to double check the full code
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
import yfinance as yf
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
Your foundation is solid. Next up, you'll build the actual tools that let your agent fetch stock data, calculate indicators, and compare stocks.
Build the Stock Analysis Tools
Your environment is set up and connected to OpenAI. Now it's time to give your agent its actual capabilities.
An AI agent without tools is just a chatbot. Tools are Python functions that your agent can call autonomously to fetch real data and run calculations. In this step, you'll build three tools that let your agent pull live stock data from the NSE, compute technical indicators, and compare multiple stocks side by side.
In this step, get ready to:
- Create a stock data fetcher that pulls OHLCV data for any NSE-listed stock.
- Create a technical indicators calculator for moving averages, RSI, and volatility.
- Create a stock comparison tool that ranks multiple stocks by performance.
Create the fetch_stock_data tool
The first tool your agent needs is the ability to fetch real stock price data. This function takes a stock symbol like RELIANCE, appends the .NS suffix for NSE, and returns a JSON summary of price history.
- In stock_agent.py, find the line that says client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")).
- Below that line, add the following code to create your first tool function:
# --- Tool Functions ---
def fetch_stock_data(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df.index = df.index.strftime("%Y-%m-%d")
summary = {
"symbol": symbol,
"period": period,
"latest_close": round(df["Close"].iloc[-1], 2),
"period_high": round(df["High"].max(), 2),
"period_low": round(df["Low"].min(), 2),
"avg_volume": int(df["Volume"].mean()),
"total_return_pct": round(((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100, 2),
"data_points": len(df),
"start_date": df.index[0],
"end_date": df.index[-1],
}
return json.dumps(summary)
What does this code do?
This function is the agent's "eyes" into the stock market. Here's how each section works:
- f"{symbol}.NS" converts any NSE stock symbol to the yfinance ticker format. For example, RELIANCE becomes RELIANCE.NS.
- stock.history(period=period) fetches OHLCV (Open, High, Low, Close, Volume) data from Yahoo Finance for the specified time period.
- The error check (if df.empty) handles invalid symbols gracefully instead of crashing.
- The function returns a JSON string because the OpenAI API expects tool results as strings.
- Save stock_agent.py.
- Test the function by running this command in your terminal:
python -c "from stock_agent import fetch_stock_data; print(fetch_stock_data('RELIANCE'))"
You should see a JSON object with fields like latest_close, period_high, and total_return_pct populated with real RELIANCE stock data.
Seeing "No data found" or a connection error?
- Make sure you have an active internet connection. yfinance fetches live data from Yahoo Finance servers.
- Check that you're running the command from inside your stock-agent folder where stock_agent.py lives.
- Verify your virtual environment is still activated. You should see the environment name in your terminal prompt.
Help me debug why my fetch_stock_data function isn't returning data for NSE stocks.
Create the calculate_indicators tool
Raw price data is useful, but traders make decisions based on technical indicators. This next tool computes moving averages (SMA 20 and SMA 50), the Relative Strength Index (RSI), and volatility metrics from the same price data.
- In stock_agent.py, add the following function below your fetch_stock_data function:
def calculate_indicators(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df["SMA_20"] = df["Close"].rolling(window=20).mean()
df["SMA_50"] = df["Close"].rolling(window=50).mean()
df["Daily_Return"] = df["Close"].pct_change()
delta = df["Close"].diff()
gain = delta.where(delta > 0, 0).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df["RSI"] = 100 - (100 / (1 + rs))
latest = df.iloc[-1]
result = {
"symbol": symbol,
"latest_close": round(latest["Close"], 2),
"sma_20": round(latest["SMA_20"], 2) if pd.notna(latest["SMA_20"]) else None,
"sma_50": round(latest["SMA_50"], 2) if pd.notna(latest["SMA_50"]) else None,
"rsi": round(latest["RSI"], 2) if pd.notna(latest["RSI"]) else None,
"avg_daily_return_pct": round(df["Daily_Return"].mean() * 100, 4),
"volatility_pct": round(df["Daily_Return"].std() * 100, 4),
"sma_20_signal": "above" if latest["Close"] > latest["SMA_20"] else "below" if pd.notna(latest["SMA_20"]) else "insufficient data",
}
return json.dumps(result)
What does this code do?
This function turns raw price data into actionable trading signals:
- SMA_20 and SMA_50 are Simple Moving Averages over 20 and 50 days. When the price is above the SMA, it suggests an uptrend.
- RSI (Relative Strength Index) measures momentum on a 0-100 scale. Above 70 suggests overbought, below 30 suggests oversold.
- volatility_pct is the standard deviation of daily returns. Higher values mean more price swings.
- The pd.notna() checks handle cases where there isn't enough data to calculate an indicator (e.g., you need 50 days of data for SMA_50).
- Save stock_agent.py.
- Test the indicators function by running:
python -c "from stock_agent import calculate_indicators; print(calculate_indicators('TCS'))"
You should see a JSON object with fields like sma_20, rsi, and sma_20_signal showing either above or below.
Seeing None for all indicator values?
- This happens when the period is too short. The SMA_50 needs at least 50 data points. Make sure you're using the default 6mo period.
- If you changed the period to 1mo for testing, the SMA_50 will be None because there aren't enough data points. That's expected behavior.
Help me understand why my RSI or SMA values are showing as None.
Create the compare_stocks tool
Investors rarely look at a single stock in isolation. This third tool lets your agent compare multiple stocks at once, calculating return, volatility, and maximum drawdown for each.
- In stock_agent.py, add the following function below your calculate_indicators function:
def compare_stocks(symbols: list, period: str = "6mo") -> str:
results = []
for symbol in symbols:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
results.append({"symbol": symbol, "error": "No data found"})
continue
total_return = ((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100
daily_returns = df["Close"].pct_change().dropna()
results.append({
"symbol": symbol,
"total_return_pct": round(total_return, 2),
"volatility_pct": round(daily_returns.std() * 100, 4),
"latest_close": round(df["Close"].iloc[-1], 2),
"avg_volume": int(df["Volume"].mean()),
"max_drawdown_pct": round(((df["Close"] / df["Close"].cummax()) - 1).min() * 100, 2),
})
return json.dumps({"comparison": results, "period": period})
What does this code do?
This function loops through a list of stock symbols and computes performance metrics for each:
- total_return_pct measures how much the stock gained or lost over the period.
- volatility_pct shows how much the price swings day to day. Higher volatility means higher risk.
- max_drawdown_pct is the worst peak-to-trough drop during the period. It answers: "What's the most I could have lost if I bought at the worst time?"
- The continue statement skips stocks that return no data instead of crashing the whole comparison.
- Save stock_agent.py.
- Test the comparison tool by running:
python -c "from stock_agent import compare_stocks; print(compare_stocks(['RELIANCE', 'TCS', 'INFY']))"
You should see a JSON object with a comparison array containing performance data for all three stocks.
Getting a syntax error in the terminal command?
- Make sure you're using the correct quote style for your operating system. On Windows, you may need to swap the outer single quotes for double quotes and use escaped quotes inside.
- If you see an import error, check that you saved stock_agent.py and that your terminal is in the stock-agent directory.
Help me fix my compare_stocks test command.
Why return JSON strings instead of Python dictionaries?
The OpenAI API requires tool results to be strings. By returning json.dumps() output from each tool, the results plug directly into the API's message format without any extra conversion.
✔️ Awesome, I've got everything!
Great. Double check you've saved stock_agent.py with all three tool functions.
ⓧ I'd like to double check the full code
Here's the complete stock_agent.py file as it should look at the end of this step:
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
import yfinance as yf
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# --- Tool Functions ---
def fetch_stock_data(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df.index = df.index.strftime("%Y-%m-%d")
summary = {
"symbol": symbol,
"period": period,
"latest_close": round(df["Close"].iloc[-1], 2),
"period_high": round(df["High"].max(), 2),
"period_low": round(df["Low"].min(), 2),
"avg_volume": int(df["Volume"].mean()),
"total_return_pct": round(((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100, 2),
"data_points": len(df),
"start_date": df.index[0],
"end_date": df.index[-1],
}
return json.dumps(summary)
def calculate_indicators(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df["SMA_20"] = df["Close"].rolling(window=20).mean()
df["SMA_50"] = df["Close"].rolling(window=50).mean()
df["Daily_Return"] = df["Close"].pct_change()
delta = df["Close"].diff()
gain = delta.where(delta > 0, 0).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df["RSI"] = 100 - (100 / (1 + rs))
latest = df.iloc[-1]
result = {
"symbol": symbol,
"latest_close": round(latest["Close"], 2),
"sma_20": round(latest["SMA_20"], 2) if pd.notna(latest["SMA_20"]) else None,
"sma_50": round(latest["SMA_50"], 2) if pd.notna(latest["SMA_50"]) else None,
"rsi": round(latest["RSI"], 2) if pd.notna(latest["RSI"]) else None,
"avg_daily_return_pct": round(df["Daily_Return"].mean() * 100, 4),
"volatility_pct": round(df["Daily_Return"].std() * 100, 4),
"sma_20_signal": "above" if latest["Close"] > latest["SMA_20"] else "below" if pd.notna(latest["SMA_20"]) else "insufficient data",
}
return json.dumps(result)
def compare_stocks(symbols: list, period: str = "6mo") -> str:
results = []
for symbol in symbols:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
results.append({"symbol": symbol, "error": "No data found"})
continue
total_return = ((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100
daily_returns = df["Close"].pct_change().dropna()
results.append({
"symbol": symbol,
"total_return_pct": round(total_return, 2),
"volatility_pct": round(daily_returns.std() * 100, 4),
"latest_close": round(df["Close"].iloc[-1], 2),
"avg_volume": int(df["Volume"].mean()),
"max_drawdown_pct": round(((df["Close"] / df["Close"].cummax()) - 1).min() * 100, 2),
})
return json.dumps({"comparison": results, "period": period})
You now have three powerful tools that can fetch, analyze, and compare any NSE-listed stock. Next up, you'll build a chart generation tool so your agent can create visual output too.
Build the Chart Generation Tool
Your agent can now fetch stock data, calculate technical indicators, and compare multiple stocks. But numbers alone can be hard to interpret quickly.
In this step, you'll build a chart generation tool that turns raw price data into visual PNG charts. When the agent needs to show a trend or pattern, it will call this tool to create a matplotlib chart and return the file path in its response.
In this step, get ready to:
- Create the generate_chart tool that plots price data with moving average overlays.
- Save charts as PNG files with descriptive filenames.
- Return the file path back to the agent so it can reference the chart in its answer.
Create the generate_chart function
The chart tool follows the same pattern as your other tools. It fetches data using yfinance, creates a visualization, and returns a JSON string so the agent can describe the result.
- In stock_agent.py, find the end of your compare_stocks function (the last line is return json.dumps({"comparison": results, "period": period})).
- Add the following generate_chart function below it, leaving two blank lines between functions:
def generate_chart(symbol: str, period: str = "6mo", chart_type: str = "price") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
# Create a 12x6 inch figure for the chart
fig, ax = plt.subplots(figsize=(12, 6))
if chart_type == "price":
# Plot closing price as the main line
ax.plot(df.index, df["Close"], label="Close Price", linewidth=1.5)
# Add moving average overlays for trend context
sma_20 = df["Close"].rolling(window=20).mean()
sma_50 = df["Close"].rolling(window=50).mean()
ax.plot(df.index, sma_20, label="SMA 20", linestyle="--", alpha=0.7)
ax.plot(df.index, sma_50, label="SMA 50", linestyle="--", alpha=0.7)
ax.set_title(f"{symbol} - Price Chart with Moving Averages ({period})")
ax.set_ylabel("Price (INR)")
elif chart_type == "volume":
# Plot volume as a bar chart
ax.bar(df.index, df["Volume"], alpha=0.7, color="steelblue")
ax.set_title(f"{symbol} - Volume Chart ({period})")
ax.set_ylabel("Volume")
ax.set_xlabel("Date")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
# Save the chart to a file with a descriptive name
filename = f"charts/{symbol}_{chart_type}_{period}.png"
os.makedirs("charts", exist_ok=True)
plt.savefig(filename, dpi=100)
plt.close()
# Return the file path so the agent can reference it
return json.dumps({"chart_saved": filename, "symbol": symbol, "chart_type": chart_type})
What does this code do?
This function handles three responsibilities in one flow:
- It fetches historical price data the same way your other tools do (using the .NS suffix and yf.Ticker).
- For chart_type="price", it plots the closing price with 20-day and 50-day moving average overlays as dashed lines. For chart_type="volume", it draws a bar chart of daily trading volume.
- It saves the chart as a PNG to a charts/ directory (created automatically with os.makedirs) using a descriptive filename like RELIANCE_price_6mo.png.
- It returns JSON with the file path so the agent knows where the chart lives and can tell the user about it.
Why call plt.close() after saving?
Each time matplotlib creates a figure, it stays in memory until you close it. If the agent calls this tool multiple times in one session, figures would pile up and consume RAM. Calling plt.close() frees that memory immediately after the file is saved.
- Save stock_agent.py.
- Test the function by running this command in your terminal:
python -c "from stock_agent import generate_chart; print(generate_chart('RELIANCE'))"
You should see JSON output like {"chart_saved": "charts/RELIANCE_price_6mo.png", "symbol": "RELIANCE", "chart_type": "price"} and a new charts/ folder containing the PNG file.
- Confirm the chart file exists by running:
ls charts/
You should see RELIANCE_price_6mo.png in the output.
No chart file created?
- If you see a "No data found" error, check your internet connection. The function needs to fetch live data from Yahoo Finance.
- If you see an import error for matplotlib, make sure you installed all packages in Step 1 with pip install -r requirements.txt.
- Make sure matplotlib.use('Agg') appears in your imports before import matplotlib.pyplot as plt. Without the Agg backend, matplotlib tries to open a GUI window and may crash in script mode.
Help me debug why my generate_chart function isn't creating a PNG file.
✔️ Awesome, I've got everything!
Great. Double check you've saved stock_agent.py and you can see the PNG in your charts/ folder.
ⓧ I'd like to double check the full code
Here is the complete stock_agent.py so far. Compare it against yours to spot any differences.
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
import yfinance as yf
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# --- Tool Functions ---
def fetch_stock_data(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df.index = df.index.strftime("%Y-%m-%d")
summary = {
"symbol": symbol,
"period": period,
"latest_close": round(df["Close"].iloc[-1], 2),
"period_high": round(df["High"].max(), 2),
"period_low": round(df["Low"].min(), 2),
"avg_volume": int(df["Volume"].mean()),
"total_return_pct": round(((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100, 2),
"data_points": len(df),
"start_date": df.index[0],
"end_date": df.index[-1],
}
return json.dumps(summary)
def calculate_indicators(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df["SMA_20"] = df["Close"].rolling(window=20).mean()
df["SMA_50"] = df["Close"].rolling(window=50).mean()
df["Daily_Return"] = df["Close"].pct_change()
delta = df["Close"].diff()
gain = delta.where(delta > 0, 0).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df["RSI"] = 100 - (100 / (1 + rs))
latest = df.iloc[-1]
result = {
"symbol": symbol,
"latest_close": round(latest["Close"], 2),
"sma_20": round(latest["SMA_20"], 2) if pd.notna(latest["SMA_20"]) else None,
"sma_50": round(latest["SMA_50"], 2) if pd.notna(latest["SMA_50"]) else None,
"rsi": round(latest["RSI"], 2) if pd.notna(latest["RSI"]) else None,
"avg_daily_return_pct": round(df["Daily_Return"].mean() * 100, 4),
"volatility_pct": round(df["Daily_Return"].std() * 100, 4),
"sma_20_signal": "above" if latest["Close"] > latest["SMA_20"] else "below" if pd.notna(latest["SMA_20"]) else "insufficient data",
}
return json.dumps(result)
def compare_stocks(symbols: list, period: str = "6mo") -> str:
results = []
for symbol in symbols:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
results.append({"symbol": symbol, "error": "No data found"})
continue
total_return = ((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100
daily_returns = df["Close"].pct_change().dropna()
results.append({
"symbol": symbol,
"total_return_pct": round(total_return, 2),
"volatility_pct": round(daily_returns.std() * 100, 4),
"latest_close": round(df["Close"].iloc[-1], 2),
"avg_volume": int(df["Volume"].mean()),
"max_drawdown_pct": round(((df["Close"] / df["Close"].cummax()) - 1).min() * 100, 2),
})
return json.dumps({"comparison": results, "period": period})
def generate_chart(symbol: str, period: str = "6mo", chart_type: str = "price") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
fig, ax = plt.subplots(figsize=(12, 6))
if chart_type == "price":
ax.plot(df.index, df["Close"], label="Close Price", linewidth=1.5)
sma_20 = df["Close"].rolling(window=20).mean()
sma_50 = df["Close"].rolling(window=50).mean()
ax.plot(df.index, sma_20, label="SMA 20", linestyle="--", alpha=0.7)
ax.plot(df.index, sma_50, label="SMA 50", linestyle="--", alpha=0.7)
ax.set_title(f"{symbol} - Price Chart with Moving Averages ({period})")
ax.set_ylabel("Price (INR)")
elif chart_type == "volume":
ax.bar(df.index, df["Volume"], alpha=0.7, color="steelblue")
ax.set_title(f"{symbol} - Volume Chart ({period})")
ax.set_ylabel("Volume")
ax.set_xlabel("Date")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
filename = f"charts/{symbol}_{chart_type}_{period}.png"
os.makedirs("charts", exist_ok=True)
plt.savefig(filename, dpi=100)
plt.close()
return json.dumps({"chart_saved": filename, "symbol": symbol, "chart_type": chart_type})
You now have four complete tools for your agent: data fetching, technical indicators, stock comparison, and chart generation. Next up, you'll wire these tools into the agent loop so GPT-4o-mini can decide when and how to call them.
Build the Agent Loop
You now have four powerful tools that can fetch stock data, calculate indicators, compare stocks, and generate charts.
The missing piece is the brain that decides which tools to use and when. In this step, you'll build the agent loop that sends your question to GPT-4o-mini, lets it call your tools, feeds results back, and repeats until it has a final answer.
In this step, get ready to:
- Define JSON schemas that tell the OpenAI API about your tools.
- Build the agent loop that orchestrates multi-step reasoning.
- Add an interactive main loop so you can chat with your agent.
Define the tool schemas for the OpenAI API
For GPT-4o-mini to call your tools, it needs to know what each tool does, what parameters it accepts, and which parameters are required. You describe this using JSON schemas that follow the OpenAI function calling format.
- In stock_agent.py, scroll to the bottom of the file (after the generate_chart function) and add the tool schemas by pasting this code:
# --- Tool Schemas ---
TOOLS = [
{
"type": "function",
"function": {
"name": "fetch_stock_data",
"description": "Fetch OHLCV stock data and summary statistics for an NSE-listed Indian stock. Use this when you need price data, returns, or basic info about a stock.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "NSE stock symbol without the .NS suffix (e.g., RELIANCE, TCS, INFY, SBIN)"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for historical data"
}
},
"required": ["symbol"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "calculate_indicators",
"description": "Calculate technical indicators (SMA 20, SMA 50, RSI, volatility, daily returns) for an NSE stock. Use this when asked about technical analysis, momentum, or signals.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "NSE stock symbol without the .NS suffix"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for indicator calculation"
}
},
"required": ["symbol"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "compare_stocks",
"description": "Compare multiple NSE stocks on return, volatility, drawdown, and volume. Use this when asked to compare or rank multiple stocks.",
"parameters": {
"type": "object",
"properties": {
"symbols": {
"type": "array",
"items": {"type": "string"},
"description": "List of NSE stock symbols to compare (e.g., ['RELIANCE', 'TCS', 'INFY'])"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for comparison"
}
},
"required": ["symbols"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "generate_chart",
"description": "Generate a matplotlib chart (price with moving averages, or volume) for an NSE stock and save it as a PNG file. Use this when asked to visualize or plot stock data.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "NSE stock symbol without the .NS suffix"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for the chart"
},
"chart_type": {
"type": "string",
"enum": ["price", "volume"],
"description": "Type of chart to generate"
}
},
"required": ["symbol"],
"additionalProperties": False
}
}
}
]
What does this code do?
The TOOLS list describes your four functions in a format the OpenAI API understands. Each entry has a name, a natural language description (so the model knows when to use it), and a parameters JSON schema defining what inputs the tool accepts.
The descriptions are critical. GPT-4o-mini reads them to decide which tool fits the user's question. A vague description means the model picks the wrong tool.
What is function calling?
Function calling is how you give an LLM the ability to use external tools. You send the model a list of tool schemas alongside the conversation. When the model decides it needs data to answer, it responds with a structured tool_calls request instead of a text answer.
Your code then executes the requested function and sends the result back. The model reads the result and either calls another tool or generates a final text response.
- Save stock_agent.py.
Build the agent loop
The agent loop is the core pattern behind every AI agent system. It sends a message to the model, checks if the model wants to call a tool, executes that tool, feeds the result back, and repeats until the model is ready to answer.
- Below the TOOLS list, add the tool dispatcher by pasting this code:
# --- Tool Dispatcher ---
TOOL_MAP = {
"fetch_stock_data": lambda args: fetch_stock_data(**args),
"calculate_indicators": lambda args: calculate_indicators(**args),
"compare_stocks": lambda args: compare_stocks(**args),
"generate_chart": lambda args: generate_chart(**args),
}
What does this code do?
TOOL_MAP is a dictionary that maps tool name strings (like "fetch_stock_data") to the actual Python functions. When the model asks to call a tool by name, the agent loop looks up the function here and executes it.
Each lambda unpacks the arguments dictionary using **args so the function receives proper keyword arguments.
- Below the TOOL_MAP, add the agent loop function by pasting this code:
# --- Agent Loop ---
def run_agent(user_message: str, max_iterations: int = 10) -> str:
print(f"\n{'='*60}")
print(f"User: {user_message}")
print(f"{'='*60}")
messages = [
{
"role": "system",
"content": (
"You are an Indian stock market analysis agent. You help users analyze "
"NSE-listed stocks by fetching data, calculating technical indicators, "
"comparing stocks, and generating charts. Always use the tools available "
"to you to provide data-driven answers. When comparing stocks or analyzing "
"trends, call the appropriate tools first before answering. "
"Provide clear, actionable insights based on the data."
)
},
{"role": "user", "content": user_message}
]
for i in range(max_iterations):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
message = response.choices[0].message
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"\n [Tool Call] {fn_name}({fn_args})")
fn = TOOL_MAP.get(fn_name)
if fn:
result = fn(fn_args)
else:
result = json.dumps({"error": f"Unknown tool: {fn_name}"})
print(f" [Result] {result[:200]}...")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
else:
print(f"\nAgent: {message.content}")
return message.content
return "Max iterations reached. The agent could not complete the analysis."
What does this code do?
This is the heart of your agent. Here is how it works:
- It starts by building a messages list with a system prompt (telling the model its role) and the user's question.
- It calls client.chat.completions.create with the tools=TOOLS parameter so the model knows what tools are available.
- If the response contains tool_calls, the loop executes each tool, appends the results as role: "tool" messages, and loops again.
- If the response has no tool_calls, the model is done reasoning and returns its final text answer.
Why is there a max iterations cap?
The max_iterations=10 parameter prevents the agent from looping forever if the model keeps requesting tools without converging on an answer. This is a safety mechanism every production agent needs.
The "end turn" condition is the else branch. When the model responds without any tool_calls, it signals that it has enough information to give a final answer.
- Save stock_agent.py.
Seeing a syntax error?
Make sure the run_agent function is placed after the TOOL_MAP dictionary, not inside it. Check that all parentheses and braces are properly closed in the messages.append calls.
help me debug a syntax error in my run_agent function
Add the interactive main loop
The agent function is ready, but you need a way to interact with it. The main loop creates a simple REPL (read-eval-print loop) where you type questions and the agent responds.
- Below the run_agent function, add the main block by pasting this code:
# --- Main ---
if __name__ == "__main__":
print("Indian Stock Market Analysis Agent")
print("Type 'quit' to exit.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
continue
run_agent(user_input)
What does this code do?
The if __name__ == "__main__": block runs only when you execute the script directly (not when importing it as a module).
It creates an infinite loop that reads your input, passes it to run_agent, and prints the result. Typing quit, exit, or q breaks the loop and ends the program.
- Save stock_agent.py.
- Run a quick syntax check by running:
python stock_agent.py
You should see the prompt Indian Stock Market Analysis Agent followed by You: waiting for your input. Type quit to exit for now. You'll test it properly in the next step.
Seeing an import error or API error?
If you see a ModuleNotFoundError, make sure your virtual environment is activated and you installed all packages from requirements.txt.
If you see an authentication error, check that your .env file contains a valid OPENAI_API_KEY value.
help me fix an error when running stock_agent.py
✔️ Awesome, I've got everything!
Great. Double check you've saved stock_agent.py and that the agent prompt appeared when you ran it.
ⓧ I'd like to double check the full code
Here is the complete stock_agent.py file at this point.
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
import yfinance as yf
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# --- Tool Functions ---
def fetch_stock_data(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df.index = df.index.strftime("%Y-%m-%d")
summary = {
"symbol": symbol,
"period": period,
"latest_close": round(df["Close"].iloc[-1], 2),
"period_high": round(df["High"].max(), 2),
"period_low": round(df["Low"].min(), 2),
"avg_volume": int(df["Volume"].mean()),
"total_return_pct": round(((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100, 2),
"data_points": len(df),
"start_date": df.index[0],
"end_date": df.index[-1],
}
return json.dumps(summary)
def calculate_indicators(symbol: str, period: str = "6mo") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
df["SMA_20"] = df["Close"].rolling(window=20).mean()
df["SMA_50"] = df["Close"].rolling(window=50).mean()
df["Daily_Return"] = df["Close"].pct_change()
delta = df["Close"].diff()
gain = delta.where(delta > 0, 0).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df["RSI"] = 100 - (100 / (1 + rs))
latest = df.iloc[-1]
result = {
"symbol": symbol,
"latest_close": round(latest["Close"], 2),
"sma_20": round(latest["SMA_20"], 2) if pd.notna(latest["SMA_20"]) else None,
"sma_50": round(latest["SMA_50"], 2) if pd.notna(latest["SMA_50"]) else None,
"rsi": round(latest["RSI"], 2) if pd.notna(latest["RSI"]) else None,
"avg_daily_return_pct": round(df["Daily_Return"].mean() * 100, 4),
"volatility_pct": round(df["Daily_Return"].std() * 100, 4),
"sma_20_signal": "above" if latest["Close"] > latest["SMA_20"] else "below" if pd.notna(latest["SMA_20"]) else "insufficient data",
}
return json.dumps(result)
def compare_stocks(symbols: list, period: str = "6mo") -> str:
results = []
for symbol in symbols:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
results.append({"symbol": symbol, "error": "No data found"})
continue
total_return = ((df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0]) * 100
daily_returns = df["Close"].pct_change().dropna()
results.append({
"symbol": symbol,
"total_return_pct": round(total_return, 2),
"volatility_pct": round(daily_returns.std() * 100, 4),
"latest_close": round(df["Close"].iloc[-1], 2),
"avg_volume": int(df["Volume"].mean()),
"max_drawdown_pct": round(((df["Close"] / df["Close"].cummax()) - 1).min() * 100, 2),
})
return json.dumps({"comparison": results, "period": period})
def generate_chart(symbol: str, period: str = "6mo", chart_type: str = "price") -> str:
ticker = f"{symbol}.NS"
stock = yf.Ticker(ticker)
df = stock.history(period=period)
if df.empty:
return json.dumps({"error": f"No data found for {ticker}"})
fig, ax = plt.subplots(figsize=(12, 6))
if chart_type == "price":
ax.plot(df.index, df["Close"], label="Close Price", linewidth=1.5)
sma_20 = df["Close"].rolling(window=20).mean()
sma_50 = df["Close"].rolling(window=50).mean()
ax.plot(df.index, sma_20, label="SMA 20", linestyle="--", alpha=0.7)
ax.plot(df.index, sma_50, label="SMA 50", linestyle="--", alpha=0.7)
ax.set_title(f"{symbol} - Price Chart with Moving Averages ({period})")
ax.set_ylabel("Price (INR)")
elif chart_type == "volume":
ax.bar(df.index, df["Volume"], alpha=0.7, color="steelblue")
ax.set_title(f"{symbol} - Volume Chart ({period})")
ax.set_ylabel("Volume")
ax.set_xlabel("Date")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
filename = f"charts/{symbol}_{chart_type}_{period}.png"
os.makedirs("charts", exist_ok=True)
plt.savefig(filename, dpi=100)
plt.close()
return json.dumps({"chart_saved": filename, "symbol": symbol, "chart_type": chart_type})
# --- Tool Schemas ---
TOOLS = [
{
"type": "function",
"function": {
"name": "fetch_stock_data",
"description": "Fetch OHLCV stock data and summary statistics for an NSE-listed Indian stock. Use this when you need price data, returns, or basic info about a stock.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "NSE stock symbol without the .NS suffix (e.g., RELIANCE, TCS, INFY, SBIN)"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for historical data"
}
},
"required": ["symbol"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "calculate_indicators",
"description": "Calculate technical indicators (SMA 20, SMA 50, RSI, volatility, daily returns) for an NSE stock. Use this when asked about technical analysis, momentum, or signals.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "NSE stock symbol without the .NS suffix"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for indicator calculation"
}
},
"required": ["symbol"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "compare_stocks",
"description": "Compare multiple NSE stocks on return, volatility, drawdown, and volume. Use this when asked to compare or rank multiple stocks.",
"parameters": {
"type": "object",
"properties": {
"symbols": {
"type": "array",
"items": {"type": "string"},
"description": "List of NSE stock symbols to compare (e.g., ['RELIANCE', 'TCS', 'INFY'])"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for comparison"
}
},
"required": ["symbols"],
"additionalProperties": False
}
}
},
{
"type": "function",
"function": {
"name": "generate_chart",
"description": "Generate a matplotlib chart (price with moving averages, or volume) for an NSE stock and save it as a PNG file. Use this when asked to visualize or plot stock data.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "NSE stock symbol without the .NS suffix"
},
"period": {
"type": "string",
"enum": ["1mo", "3mo", "6mo", "1y", "2y"],
"description": "Time period for the chart"
},
"chart_type": {
"type": "string",
"enum": ["price", "volume"],
"description": "Type of chart to generate"
}
},
"required": ["symbol"],
"additionalProperties": False
}
}
}
]
# --- Tool Dispatcher ---
TOOL_MAP = {
"fetch_stock_data": lambda args: fetch_stock_data(**args),
"calculate_indicators": lambda args: calculate_indicators(**args),
"compare_stocks": lambda args: compare_stocks(**args),
"generate_chart": lambda args: generate_chart(**args),
}
# --- Agent Loop ---
def run_agent(user_message: str, max_iterations: int = 10) -> str:
print(f"\n{'='*60}")
print(f"User: {user_message}")
print(f"{'='*60}")
messages = [
{
"role": "system",
"content": (
"You are an Indian stock market analysis agent. You help users analyze "
"NSE-listed stocks by fetching data, calculating technical indicators, "
"comparing stocks, and generating charts. Always use the tools available "
"to you to provide data-driven answers. When comparing stocks or analyzing "
"trends, call the appropriate tools first before answering. "
"Provide clear, actionable insights based on the data."
)
},
{"role": "user", "content": user_message}
]
for i in range(max_iterations):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
message = response.choices[0].message
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f"\n [Tool Call] {fn_name}({fn_args})")
fn = TOOL_MAP.get(fn_name)
if fn:
result = fn(fn_args)
else:
result = json.dumps({"error": f"Unknown tool: {fn_name}"})
print(f" [Result] {result[:200]}...")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
else:
print(f"\nAgent: {message.content}")
return message.content
return "Max iterations reached. The agent could not complete the analysis."
# --- Main ---
if __name__ == "__main__":
print("Indian Stock Market Analysis Agent")
print("Type 'quit' to exit.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
continue
run_agent(user_input)
Your agent is fully wired up and ready to answer questions. Next up, you'll put it through its paces with real Indian stock market queries to see the agent loop in action.
Test Your Agent with Real Questions
In the previous step, you wired up the agent loop that sends messages to GPT-4o-mini, checks for tool calls, executes them, and feeds results back until the model delivers a final answer.
Now it is time to put the agent through its paces with real questions about Indian stocks. You will start simple and ramp up complexity, watching how the agent autonomously decides which tools to call and in what order.
In this step, get ready to:
- Ask the agent progressively harder questions about NSE stocks.
- Test multi-tool chaining where the agent calls several tools in sequence.
- Observe how the agent reasons about which tools to use and in what order.
Ask the agent progressively harder questions about Indian stocks
The best way to understand how an agent works is to watch it think. Each question you ask triggers a different reasoning path through your tools.
- Launch the agent by running this command in your terminal:
python stock_agent.py
You should see this prompt appear:
Indian Stock Market Analysis Agent
Type 'quit' to exit.
You:
- Start with a simple single-tool question. Type this at the You: prompt:
What is the current price of RELIANCE?
What should I see?
The agent prints a [Tool Call] line showing it called fetch_stock_data with symbol: RELIANCE. After the [Result] line with raw JSON, you get a natural language answer summarizing the stock's latest close price, period high/low, and total return.
- Now ask a question that triggers the calculate_indicators tool:
What is the RSI for TCS over the last 3 months?
This time the agent calls calculate_indicators instead of fetch_stock_data. The model read your question, matched it to the tool whose description mentions "RSI" and "technical analysis", and chose accordingly.
- Try a multi-stock comparison:
Compare RELIANCE, TCS, and INFY performance over the last 6 months
The agent calls compare_stocks with all three symbols in a single call. It returns a structured comparison of total return, volatility, and max drawdown for each stock.
Seeing a "No data found" error?
This usually means the ticker symbol is incorrect or yfinance temporarily cannot reach Yahoo Finance servers. Check that you are using valid NSE symbols (e.g., RELIANCE, TCS, INFY, SBIN) without the .NS suffix. Your code appends that automatically.
help me debug a "No data found" error from my stock agent
Test multi-tool chaining
Single-tool calls are straightforward. The real power of an agent is chaining multiple tools in sequence without you telling it exactly which ones to use.
- Ask a question that requires multiple tools to answer:
Fetch INFY data, calculate its RSI, and generate a price chart
What should I see?
Watch the terminal output carefully. You should see multiple [Tool Call] lines appear one after another. The agent calls fetch_stock_data, then calculate_indicators, then generate_chart.
After all three tools return, the agent writes a unified response summarizing the data, the RSI value, and confirming the chart file was saved.
- Verify the chart was saved by running ls charts/ in a separate terminal window.
You should see a file like charts/INFY_price_6mo.png listed. Open it in your file browser to see the price chart with SMA 20 and SMA 50 moving averages plotted.
- Try another multi-tool question that the agent has to reason about more carefully:
Which stock has better momentum right now, RELIANCE or SBIN? Show me a chart of the winner.
This question is more open-ended. The agent must decide on its own to calculate indicators for both stocks, compare the RSI or return values, pick the "winner", and then generate a chart for that stock.
Why does the agent sometimes call tools in parallel?
GPT-4o-mini can return multiple tool calls in a single response when it determines the calls are independent. For example, calculating indicators for RELIANCE and SBIN does not depend on the other's result, so the model may issue both calls at once.
Your agent loop handles this correctly because it iterates over all message.tool_calls in the response and appends each result before sending the next request.
Agent seems stuck or hits max iterations?
If you see the message "Max iterations reached", the agent is looping without producing a final text response. This can happen if a tool returns an error that confuses the model. Try rephrasing your question or using a simpler query to confirm the agent is working.
help me debug why my agent loop is hitting max iterations
Observe how the agent reasons about which tools to use
Every time the agent calls a tool, it reveals the core pattern behind AI agents: reason, act, observe, repeat. Look at the terminal output from your previous questions.
What is the agent loop pattern?
The [Tool Call] lines you see printed are the "act" phase. The model reads your question, reasons about which tool best fits (based on each tool's description field in the schema), and emits a structured function call.
The [Result] lines are the "observe" phase. Your code runs the tool locally, collects the JSON output, and feeds it back to the model as a tool role message.
This loop repeats until the model has enough data to produce a final text answer without requesting more tool calls.
- Try one final complex question to see the full reasoning chain:
Give me a full technical analysis of HDFCBANK over the last year with a chart
Notice how the agent decides to call both fetch_stock_data and calculate_indicators for the fundamental and technical data, plus generate_chart for the visual. You never specified which tools to use. The model figured it out from the tool descriptions alone.
- When you are done experimenting, type quit to exit the agent.
Tool descriptions drive tool selection
The quality of your tool description fields in the TOOLS list directly impacts how well the agent selects tools. Vague descriptions lead to wrong tool choices. Specific descriptions like "Use this when asked about technical analysis, momentum, or signals" give the model clear routing criteria.
Secret mission
Add Portfolio Analysis
Your agent can analyze individual stocks. But what if you want to evaluate how a mix of stocks performs together? Build a portfolio analysis tool that calculates weighted returns, the Sharpe ratio, and a correlation matrix.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine. The OpenAI API only charges when you actively run the agent, so there are no ongoing costs from leaving your files in place.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Project files: stock_agent.py, .env, requirements.txt, and the charts/ folder with generated PNG files.
- Python packages: openai, yfinance, pandas, matplotlib, python-dotenv.
Keep everything running
No action needed. Choose this if you want to keep experimenting with your stock analysis agent or plan to extend it with more tools.
- Your project files stay on your Desktop (or wherever you created the folder).
- The OpenAI API only charges when you run the script, so there is no idle cost.
- Your installed Python packages remain available for other projects.
Pause - I'll come back to this later
There are no running processes to shut down for this project. Your files sit idle until you run the script again.
- No action required. Since the agent only runs when you execute stock_agent.py, nothing consumes resources while idle.
- Your .env file keeps your API key ready for when you return.
Delete - I don't want to use this again
Remove all project files and uninstall the packages you installed.
- Delete your project folder by running this command (macOS/Linux):
rm -rf ~/Desktop/stock-market-agent
On Windows, run this instead:
rmdir /s /q %USERPROFILE%\Desktop\stock-market-agent
- Uninstall the Python packages by running:
pip uninstall openai yfinance pandas matplotlib python-dotenv -y
- If you no longer need your OpenAI API key, you can revoke it from the API keys page in your OpenAI dashboard at platform.openai.com.
That's a Wrap!
That's a Wrap!
Nice work! You've just built a fully functional AI agent that autonomously analyzes Indian stock market data using natural language.
You've learned how to:
- Built a complete agent loop from scratch using the OpenAI Chat Completions API.
- Created 4 custom tools for stock data retrieval, technical analysis, comparison, and chart generation.
- Learned how gpt-4o-mini decides which tools to call and chains them autonomously to answer complex questions.
- Extended the agent with a portfolio analysis tool that calculates Sharpe ratio and correlation matrix.
Ready to quiz yourself?