Stock Trader Agent
Zero to Hero: Building a Financial Sim AI Agent with Google ADK
Introduction
In this tutorial, we will dissect the Stock Trader Agent. This application was originally inspired by the academic "Stockagent" paper (which simulated how different AI personas interact with complex financial markets using deep, complex python loops). We have upgraded this concept using Google's Agent Development Kit (ADK).
By the end of this guide, you will understand every single file in the project and how they work seamlessly together to analyze stock markets and produce strict trading recommendations.
Project Structure
A robust AI agent is organized like a software project, not a single monolithic script. Here is our file map:
/stock_trader_agent/
βββ __init__.py # πͺ Entry Point: Tells ADK "I am here!"
βββ agent.py # π§ The Brain: Parallel Sub-Agents & Orchestrator
βββ tools.py # π οΈ The Hands: Functions to simulate financial data
βββ prompts.py # π The Personas: Distinct rules for different AI roles
βββ config.py # βοΈ The Rules: Constants & settings
βββ .env # π Keys: API credentials
βββ run_agent.sh # π Launcher: Script to start the Web UI
Step 2: Defining the Rules
Every app needs strict parameters to ensure the simulation stays on track. We keep ours in
config.py so we can change them easily without digging into the LLM logic.
# config.py
# Constants for setting bounds of our simulation properties
MIN_INITIAL_PROPERTY = 0.5
MAX_INITIAL_PROPERTY = 1.0
# Loan Definitions
LOAN_RATE = [0.03, 0.05, 0.07]
LOAN_TYPE_DATE = [22, 44, 66]
PERSONAS = ["Conservative", "Aggressive", "Balanced", "Growth-Oriented"]
Step 3: Creating the Personas
Instead of passing a massive 300-line prompt to a single LLM, we divide reasoning into specialized
analysts. In prompts.py, we define their roles. Finding bugs is much easier this way!
# prompts.py
# Focus strictly on market news and sentiment.
FORUM_ANALYST_PROMPT = """Act like an expert market sentiment analyst...
Determine the overall market sentiment on Stock A and Stock B (Bullish, Bearish, or Neutral).
"""
# Focus strictly on fundamentals and valuation.
MARKET_ANALYST_PROMPT = """Act like a fundamental financial analyst...
Are these stocks overvalued or undervalued? Consider their historical revenue and net profit.
"""
# Focus strictly on portfolio safety.
RISK_MANAGER_PROMPT = """Act like a risk management advisor...
Determine if the trader is over-leveraged based on cash reserves and current debt.
"""
# The Master Agent prompt enforces JSON output from the other 3 analysis reports.
CHIEF_TRADER_PROMPT = """Act as the Chief Trader. You will receive advice from 3 sub-agents...
Return ONLY a valid JSON object matching this schema...
"""
Step 4: Giving the Agent "Hands"
LLMs don't natively know live stock prices or daily news. We create Toolsβstandard Python functions that fetch live data which the ADK framework allows the LLM to trigger autonomously.
Tool 1: Market Data Fetcher
The Market Analyst uses this numerical data.
# tools.py
import random
def get_current_stock_prices(stock_symbol: str) -> dict:
"""
Simulates getting the current stock price from the market.
"""
if stock_symbol == 'A':
return {"status": "success", "stock": "A", "price": round(random.uniform(50.0, 150.0), 2)}
elif stock_symbol == 'B':
return {"status": "success", "stock": "B", "price": round(random.uniform(10.0, 50.0), 2)}
return {"status": "error", "message": "Not found"}
Tool 2: News Fetcher
The Forum Analyst uses this sentiment data.
# tools.py (continued)
def get_market_news() -> dict:
"""Fetches the latest mock financial news to inform trading decisions."""
news = ["Company A reports higher earnings.", "Market rallies on rate cuts."]
return {"status": "success", "breaking_news": random.choice(news)}
Step 5: The Brain (Orchestration)
This is where ADK truly shines. Rather than executing manual `while` loops and waiting 15 seconds per call, we use ADK's ParallelAgent to process all three analysts at the exact same moment.
# agent.py
from google.adk.agents import LlmAgent, ParallelAgent, SequentialAgent
from .tools import get_current_stock_prices, get_market_news
# --- THE WORKERS ---
forum_analyst = LlmAgent(name="forum_analyst", tools=[get_market_news], ...)
market_analyst = LlmAgent(name="market_analyst", tools=[get_current_stock_prices], ...)
risk_manager = LlmAgent(name="risk_manager", ...)
# --- PARALLEL EXECUTION ---
# All 3 market analyses will now run concurrently to save vast amounts of time!
analyst_floor = ParallelAgent(
name="analyst_floor",
sub_agents=[forum_analyst, market_analyst, risk_manager]
)
# --- THE SYNTHESIZER ---
chief_trader = LlmAgent(name="chief_trader", ...)
# --- THE ORCHESTRATOR (ROOT) ---
root_agent = SequentialAgent(
name="stock_trader_agent",
sub_agents=[analyst_floor, chief_trader]
)
Step 6: Discovery
This tiny file is crucial. It tells the ADK framework "Hey, the agent you are looking for is called
root_agent inside agent.py". Without this, you get "No agents found".
# __init__.py
from .agent import root_agent
Step 7: Launching It
Finally, we need to run it. The trick is that ADK needs to be run from the parent directory to see the agent package correctly.
#!/bin/bash
# run_agent.sh
# 1. CD to the script's directory
cd "$(dirname "$0")"
# 2. CD up one level to the PARENT folder
cd ..
# 3. Tell ADK to serve the agent folder
echo "Starting Web UI..."
adk web stock_trader_agent
Step 8: The Complete Script
If you prefer to bypass complex modular file setups or simply want to share the code with a friend quickly, we have combined all the Prompts, Tools, and Agents into a single, perfectly runnable file.
import os
import random
import asyncio
from dotenv import load_dotenv
# 1. Robustly Load Environment Variables
current_dir = os.path.dirname(os.path.abspath(__file__))
env_path = os.path.join(current_dir, ".env")
load_dotenv(dotenv_path=env_path)
from google.adk.agents import LlmAgent, ParallelAgent, SequentialAgent
# ==========================================
# 2. TOOLS
# ==========================================
def get_current_stock_prices(stock_symbol: str) -> dict:
if stock_symbol == 'A':
return {"status": "success", "stock": "A", "price": round(random.uniform(50.0, 150.0), 2)}
elif stock_symbol == 'B':
return {"status": "success", "stock": "B", "price": round(random.uniform(10.0, 50.0), 2)}
return {"status": "error", "message": f"Stock {stock_symbol} not found in our market."}
def get_market_news() -> dict:
news = [
"Company A reports higher than expected quarterly earnings.",
"Company B CEO investigated for fraud.",
"Market rallies on news of interest rate cuts.",
"Tech stocks (like Company B) suffer mass selloff today.",
"New legislation provides tax breaks for Company A's industry."
]
return {"status": "success", "breaking_news": random.choice(news)}
# ==========================================
# 3. PROMPTS
# ==========================================
FORUM_ANALYST_PROMPT = """
Act like an expert market sentiment analyst. Review the trading data provided by the user.
Task: Analyze the posts by other traders on the forum and general market news.
Focus: Determine the overall market sentiment on Stock A and Stock B (Bullish, Bearish, or Neutral).
Recommendation: Provide a brief summary of how the herd is behaving and what unexpected traps might exist based on the chatter.
"""
MARKET_ANALYST_PROMPT = """
Act like a fundamental financial analyst.
Task: Review the current prices and recent financial history of Stock A and Stock B provided by the user.
Focus: Are these stocks overvalued or undervalued? Consider their historical revenue and net profit.
Recommendation: State clearly whether each stock represents a BUY or a SELL purely based on fundamentals.
"""
RISK_MANAGER_PROMPT = """
Act like a risk management advisor for a portfolio.
Task: Look at the trader's current cash reserves, current stock holdings, and any outstanding loans (debt).
Focus: Determine if the trader is over-leveraged. Should they take out a loan? Should they hold a specific cash buffer?
Recommendation: Provide strict boundaries on how much money can be spent today, and whether taking a loan is permitted under the current constraints.
"""
CHIEF_TRADER_PROMPT = """
Act as the Chief Trader. You will receive advice from 3 sub-agents:
1. Forum Analyst (Sentiment)
2. Market Analyst (Fundamentals)
3. Risk Manager (Portfolio safety)
Task: Combine their insights with the user's provided input regarding current stock prices and portfolio holdings.
Your goal is to decide the final trading action for the day.
Return ONLY a valid JSON object matching this exact schema:
{{
"action_type": "buy" | "sell" | "no",
"stock": "A" | "B" | "none",
"amount": ,
"price": ,
"loan_required": "yes" | "no",
"reasoning": "A one sentence explanation of your integrated decision."
}}
---
### FORUM ANALYST REPORT
{forum_report}
---
### MARKET ANALYST REPORT
{market_report}
---
### RISK MANAGER REPORT
{risk_report}
"""
# ==========================================
# 4. WORKERS
# ==========================================
forum_analyst = LlmAgent(
name="forum_analyst",
model="gemini-2.5-flash",
description="Analyzes trader forum sentiment and breaking news.",
instruction=FORUM_ANALYST_PROMPT,
tools=[get_market_news],
output_key="forum_report"
)
market_analyst = LlmAgent(
name="market_analyst",
model="gemini-2.5-flash",
description="Analyzes fundamental stock performance and prices.",
instruction=MARKET_ANALYST_PROMPT,
tools=[get_current_stock_prices],
output_key="market_report"
)
risk_manager = LlmAgent(
name="risk_manager",
model="gemini-2.5-flash",
description="Calculates portfolio risk and leverage boundaries.",
instruction=RISK_MANAGER_PROMPT,
output_key="risk_report"
)
# ==========================================
# 5. PARALLEL PANEL
# ==========================================
analyst_floor = ParallelAgent(
name="analyst_floor",
description="Runs all market analyses concurrently to save time.",
sub_agents=[forum_analyst, market_analyst, risk_manager]
)
# ==========================================
# 6. MASTER TRADER
# ==========================================
chief_trader = LlmAgent(
name="chief_trader",
model="gemini-2.5-flash",
description="Makes the final trading JSON decision based on analyst reports.",
instruction=CHIEF_TRADER_PROMPT,
output_key="final_trade_decision"
)
# ==========================================
# 7. ROOT ORCHESTRATOR
# ==========================================
root_agent = SequentialAgent(
name="stock_trader_agent",
description="Simulates a complex stock trading decision pipeline.",
sub_agents=[analyst_floor, chief_trader]
)
if __name__ == "__main__":
from google.adk.cli.adk_run import serve_agent_cli
print("Launching Stock Trader Agent in Local Terminal Mode...")
asyncio.run(serve_agent_cli(root_agent))