The Agent Development Kit (ADK) is an open-source framework from Google designed to simplify building, evaluating, and deploying AI agents. It allows developers to define agent logic, connect external tools, and orchestrate workflows directly in Python.
I wanted a slightly more interesting app than Hello World to exercise more parts of the platform, so figured I’d write a little stock trading agent that applies a sentiment momentum trading strategy. We’ll use ADK and Gemini API to execute trades on Alpaca, chosen for it’s REST API, and free paper trading account.
NOTE: I use this trading agent with paper trades only. It’s likely a terrible trading strategy, and also poorly executed, so I do not endorse it. If you build your own agent based on this prompt, I strongly advise you not to switch the API keys from paper trades to money trades. It makes for a fun demo though.
First, let’s setup a project with a python environment via venv:
mkdir -p trading-agent-project; cd trading-agent-project
python3 -m venv trading-env
source trading-env/bin/activate
pip install google-adk alpaca-py "google-cloud-aiplatform[agent_engines,adk]"Code language: JavaScript (javascript)
And create an empty folder for our agent populated with just a .env file with API keys for Gemini and Alpaca.
mkdir trading_agent
touch trading_agent/.env
Edit your .env and add a Google API key which you can get from AI Studio, and one from Alpaca from the API Keys box on the account overview.
# Google AI Studio / Vertex AI Key for the ADK Agent
GOOGLE_API_KEY="<your key>"
# Alpaca Paper Trading Keys
APCA_API_KEY_ID="<your key id>"
APCA_API_SECRET_KEY="<your key>"Code language: PHP (php)

Now, we can use the Gemini CLI (or your preferred coding agent) to generate the ADK agent. Here’s my prompt:
You are operating from within a venv project folder. Create an AI agent using ADK in python in the sub-folder "trading_agent". It already contains a .env with the required keys. The agent will be executed with `adk run trading_agent`.
The agent is a stock trading bot that will use Alpaca with paper trades (not real money). Each execution, it performs the following actions:
1/ News lookup. Discover the latest financial news affecting companies, primarily the S&P 500
2/ Analyze the news for positive or negative sentiments using Gemini 2.5 Pro and assign a sentiment rating between -1 and 1. Ignore Stock Analyst Rating changes; market news only. Format as Primary ticker affected, news summary, score.
3/ Query current position portfolio from Alpaca markets
4/ For any stock currently held, if the sentiment is negative, sell the stock. If no negative sentiment, do not sell. For all trades, introduce a 1s delay to rate-limit API calls.
5/ Rank the news in order of positivity, and create market BUY trades for $1000 worth of EACH of up to 5 stock with high confidence positive sentiments NOT currently held (fractional shares are OK). If no positive sentiments, do not buy.
6/ Produce a summary of all orders placed with a summary of the news that led to the order, along with a sentiment score. Additionally, provide a market sentiment overview of the top 10 positive, and top 10 negative news stories (include the ticker, summary, and score).Code language: plaintext (plaintext)
Note: If you run out of Gemini CLI quota, you can use the same API key in the .env file above to authenticate the Gemini CLI (run /auth in Gemini, and enter the key).
It’s best to let Gemini run the agent the first time to iron out any bugs. Just be aware that however it runs, the trades are executing.
I encourage you to generate your own agent code, but here’s what I got from the Gemini CLI:
agent.py
import os
import time
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
from dotenv import load_dotenv
from alpaca.data.historical import NewsClient, StockHistoricalDataClient
from alpaca.data.requests import NewsRequest, StockLatestQuoteRequest
from alpaca.trading.client import TradingClient
from alpaca.trading.requests import MarketOrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce
from google.adk.agents import Agent
from vertexai import agent_engines
# Load environment variables
env_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(env_path)
# API Keys
APCA_API_KEY_ID = os.getenv('APCA_API_KEY_ID')
APCA_API_SECRET_KEY = os.getenv('APCA_API_SECRET_KEY')
# Initialize Alpaca Clients
trading_client = TradingClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY, paper=True)
news_client = NewsClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
stock_client = StockHistoricalDataClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
# Full S&P 500 Tickers List (Retrieved April 2026)
SP500_TICKERS = [
"A", "AAPL", "ABBV", "ABT", "ACGL", "ACN", "ADBE", "ADI", "ADM", "ADP", "ADSK", "AEE", "AEP", "AES", "AFL", "AIG", "AIZ", "AJG", "AKAM", "ALB", "ALGN", "ALL", "ALLE", "AMAT", "AMCR", "AMD", "AME", "AMGN", "AMP", "AMT", "AMZN", "ANET", "AON", "AOS", "APA", "APD", "APH", "APO", "APP", "APTV", "ARE", "ATO", "AVB", "AVGO", "AVY", "AWK", "AXON", "AXP", "AZO", "BA", "BAC", "BALL", "BAX", "BBY", "BDX", "BEN", "BF.B", "BG", "BIIB", "BK", "BKNG", "BKR", "BLDR", "BLK", "BMY", "BR", "BRO", "BRK.B", "BSX", "BWA", "BX", "BXP", "C", "CAG", "CAH", "CARR", "CAT", "CB", "CBOE", "CBRE", "CCI", "CCL", "CDNS", "CDW", "CEG", "CF", "CFG", "CHD", "CHRW", "CHTR", "CI", "CINF", "CL", "CLX", "CMA", "CMCSA", "CME", "CMG", "CMI", "CMS", "CNC", "CNP", "COF", "COO", "COP", "COR", "COST", "CPAY", "CPB", "CPRT", "CPT", "CRL", "CRM", "CRWD", "CSCO", "CSGP", "CSX", "CTAS", "CTSH", "CTVA", "CVS", "CVX", "CZR", "D", "DAL", "DAY", "DD", "DE", "DECK", "DELL", "DFS", "DG", "DGX", "DHI", "DHR", "DIS", "DLR", "DLTR", "DOC", "DOV", "DOW", "DPZ", "DRI", "DTE", "DUK", "DVA", "DVN", "DXCM", "EA", "EBAY", "ECL", "ED", "EFX", "EG", "EIX", "EL", "ELV", "EMN", "EMR", "ENPH", "EOG", "EPAM", "EQT", "EQR", "ERIE", "ES", "ESS", "ETN", "ETR", "EVRG", "EW", "EXC", "EXPD", "EXPE", "EXR", "F", "FANG", "FAST", "FCX", "FDS", "FDX", "FE", "FFIV", "FI", "FICO", "FIS", "FITB", "FMC", "FOX", "FOXA", "FRT", "FSLR", "FTNT", "FTV", "GD", "GDDY", "GE", "GEHC", "GEN", "GEV", "GILD", "GIS", "GL", "GLW", "GM", "GNRC", "GOOG", "GOOGL", "GPC", "GPN", "GRMN", "GS", "GWW", "HAL", "HAS", "HBAN", "HCA", "HD", "HES", "HIG", "HII", "HLT", "HOLX", "HON", "HPE", "HPQ", "HRL", "HSIC", "HST", "HSY", "HUBB", "HUM", "HWM", "IBM", "ICE", "IDXX", "IEX", "IFF", "ILMN", "INCY", "INTC", "INTU", "INVH", "IP", "IPG", "IQV", "IR", "IRM", "ISRG", "IT", "ITW", "IVZ", "J", "JBHT", "JCI", "JKHY", "JNJ", "JPM", "K", "KDP", "KEY", "KEYS", "KHC", "KIM", "KLAC", "KMB", "KMI", "KMX", "KO", "KR", "KVUE", "L", "LDOS", "LEN", "LH", "LHX", "LII", "LIN", "LKQ", "LLY", "LMT", "LNT", "LOW", "LRCX", "LULU", "LUV", "LVS", "LW", "LYB", "LYV", "MA", "MAA", "MAR", "MAS", "MCD", "MCHP", "MCK", "MCO", "MDLZ", "MDT", "MET", "META", "MGM", "MHK", "MKC", "MLM", "MMC", "MMM", "MNST", "MO", "MOH", "MOS", "MPC", "MPWR", "MRK", "MRNA", "MS", "MSCI", "MSFT", "MSI", "MTB", "MTD", "MU", "NDAQ", "NDSN", "NEE", "NEM", "NFLX", "NI", "NKE", "NOC", "NOW", "NRG", "NSC", "NTAP", "NTRS", "NUE", "NVDA", "NVR", "NWS", "NWSA", "NXPI", "O", "ODFL", "OKE", "OMC", "ON", "ORCL", "ORLY", "OTIS", "OXY", "PANW", "PARA", "PAYC", "PAYX", "PCAR", "PCG", "PEAK", "PEG", "PEP", "PFE", "PFG", "PG", "PGR", "PH", "PHM", "PKG", "PLD", "PLTR", "PM", "PNC", "PNR", "PNW", "PODD", "POOL", "PPG", "PPL", "PRU", "PSA", "PSX", "PTC", "PWR", "PYPL", "QCOM", "QRVO", "RCL", "REG", "REGN", "RF", "RHI", "RJF", "RL", "RMD", "ROK", "ROL", "ROP", "ROST", "RSG", "RTX", "RVTY", "SBAC", "SBUX", "SCHW", "SHW", "SJM", "SLB", "SMCI", "SNA", "SNPS", "SO", "SOLV", "SPG", "SPGI", "SRE", "STE", "STLD", "STT", "STX", "STZ", "SWK", "SWKS", "SYF", "SYK", "SYY", "T", "TAP", "TDG", "TDY", "TECH", "TEL", "TER", "TFC", "TFX", "TGT", "TJX", "TKO", "TMUS", "TMO", "TPR", "TRGP", "TRMB", "TROW", "TRV", "TSCO", "TSLA", "TSN", "TT", "TTWO", "TXN", "TXT", "TYL", "UAL", "UBER", "UDR", "UHS", "ULTA", "UNH", "UNP", "UPS", "URI", "USB", "V", "VFC", "VICI", "VLO", "VLTO", "VMC", "VRSK", "VRSN", "VRTX", "VST", "VTR", "VTRS", "VZ", "WAB", "WAT", "WBA", "WBD", "WDC", "WEC", "WELL", "WFC", "WHR", "WM", "WMB", "WMT", "WRB", "WST", "WTW", "WY", "WYNN", "XEL", "XOM", "XYL", "YUM", "ZBH", "ZBRA", "ZION", "ZTS"
]
def get_financial_news() -> str:
"""
Fetches the latest financial news for ALL S&P 500 companies from the last 24 hours.
Splits into two batches to stay within URL length limits.
"""
start_time = datetime.now() - timedelta(days=1)
# Split tickers into two batches of approx 250 each
mid = len(SP500_TICKERS) // 2
batches = [SP500_TICKERS[:mid], SP500_TICKERS[mid:]]
all_formatted_news = []
for batch in batches:
request_params = NewsRequest(
symbols=",".join(batch),
start=start_time,
limit=50
)
time.sleep(1) # Rate-limit delay
try:
news_response = news_client.get_news(request_params)
for article in news_response.data['news']:
# Focus on news stories specific to 1-3 symbols
if 1 <= len(article.symbols) <= 3:
symbols_str = ", ".join(article.symbols)
all_formatted_news.append(
f"Ticker(s): {symbols_str}\n"
f"Headline: {article.headline}\n"
f"Summary: {article.summary}\n"
"---"
)
except Exception as e:
print(f"Error fetching news for batch: {str(e)}")
if not all_formatted_news:
return "No relevant single-stock news found for the S&P 500 in the last 24 hours."
return "\n".join(all_formatted_news)
def get_portfolio_status() -> Dict[str, Any]:
"""Queries Alpaca for account status and open positions."""
time.sleep(1)
account = trading_client.get_account()
time.sleep(1)
positions = trading_client.get_all_positions()
pos_list = []
for p in positions:
pos_list.append({
"symbol": p.symbol,
"qty": float(p.qty),
"market_value": float(p.market_value),
"current_price": float(p.current_price)
})
return {
"available_cash": float(account.cash),
"positions": pos_list
}
def get_latest_price(symbol: str) -> float:
"""Retrieves the most recent ask price for a specific ticker."""
request_params = StockLatestQuoteRequest(symbol_or_symbols=symbol)
time.sleep(1)
quote = stock_client.get_stock_latest_quote(request_params)
return float(quote[symbol].ask_price)
def place_trade_order(symbol: str, side: str, amount_usd: Optional[float] = None, qty: Optional[float] = None) -> str:
"""Places a market order (fractional shares supported for buys)."""
side = side.lower()
if side not in ['buy', 'sell']:
return "Error: Side must be 'buy' or 'sell'."
order_side = OrderSide.BUY if side == 'buy' else OrderSide.SELL
try:
if amount_usd is not None and side == 'buy':
order_request = MarketOrderRequest(
symbol=symbol,
notional=amount_usd,
side=order_side,
time_in_force=TimeInForce.DAY
)
elif qty is not None:
order_request = MarketOrderRequest(
symbol=symbol,
qty=qty,
side=order_side,
time_in_force=TimeInForce.GTC
)
else:
return "Error: Either amount_usd (for buy) or qty (for sell/buy) must be provided."
time.sleep(1)
order = trading_client.submit_order(order_data=order_request)
return f"Success: {side.upper()} order placed for {symbol}. Order ID: {order.id}"
except Exception as e:
return f"Failed to place {side} order for {symbol}: {str(e)}"
# Define the Agent
root_agent = Agent(
name="TradingAgent",
model="gemini-2.5-pro",
instruction="""
You are a sophisticated stock trading AI agent using Gemini 2.5 Pro. Your mission is to execute a daily trading cycle based on news sentiment for the entire S&P 500.
FOLLOW THESE STEPS IN ORDER:
1. NEWS LOOKUP: Call `get_financial_news()` to discover the latest news affecting any of the 500 S&P companies.
2. SENTIMENT ANALYSIS:
- Analyze the news for positive or negative sentiments using Gemini 2.5 Pro and assign a sentiment rating between -1.0 and 1.0.
- CRITICAL: Ignore Stock Analyst Rating changes; market news only.
- CRITICAL: Only consider news stories specific to 1-3 stocks.
- Format your tracking of each analyzed story as: Primary ticker affected, news summary, score.
3. PORTFOLIO QUERY: Call `get_portfolio_status()`.
4. SELL PHASE:
- For every stock held: If sentiment score is NEGATIVE (< 0), sell ALL shares.
- If no negative sentiment, do not sell.
5. BUY PHASE:
- Identify stocks NOT held that have **high confidence positive sentiments**.
- Rank by positivity.
- Create market BUY trades for EXACTLY $1000 of EACH of **up to 5** stocks with highest scores.
- Stop if available cash is less than $1000.
6. FINAL REPORT:
Produce a comprehensive summary:
- SUMMARY OF ORDERS: List all orders placed with ticker, action, score, and news summary.
- MARKET SENTIMENT OVERVIEW: Top 10 Positive and Top 10 Negative news stories (Ticker, Summary, and Score).
Execute the entire cycle once when prompted.
""",
tools=[get_financial_news, get_portfolio_status, get_latest_price, place_trade_order]
)
# Wrap in AdkApp for execution
app = agent_engines.AdkApp(agent=root_agent)
Code language: Python (python)To run manually at any time:
source trading-env/bin/activate
adk run trading_agent
You’ll need to prompt it like “go” to kick off the analysis. ADK agents can be quiet while they “think”, to get some feedback, tailing the logs lets you watch the tool-calling in real-time:
tail -F /tmp/agents_log/agent.latest.log
As requested, it made 5 paper trades on my account that will execute on open.
And here’s the summary (from 2026-04-21):
[TradingAgent]: ## FINAL REPORT
### SUMMARY OF ORDERS
| Ticker | Action | Sentiment Score | News Summary |
|---|---|---|---|
| ADBE | BUY | 0.9 | Adobe announced a large $25 billion stock buyback plan. |
| UNH | BUY | 0.9 | UnitedHealth reported a strong Q1 beat and raised its profit outlook, causing the stock to jump. |
| GTLB | BUY | 0.8 | GitLab shares are rising after announcing a collaboration with Amazon Web Services. |
| ISRG | BUY | 0.8 | Intuitive Surgical reported a strong Q1 with a double beat on earnings and revenue. |
| IBM | BUY | 0.7 | Positive preview for IBM's Q1 earnings, with expectations of a sixth consecutive double beat, driven by AI momentum. |
### MARKET SENTIMENT OVERVIEW
#### Top 10 Positive Stories
| Ticker | Summary | Score |
|---|---|---|
| ADBE | Adobe Announces $25B Buyback Plan Through April 2030 | 0.9 |
| UNH | UnitedHealth Stock Jumps After Q1 Beat — Here's What Execs Say Drove It | 0.9 |
| GTLB | GitLab Stock Jumps After The Close: Here's Why | 0.8 |
| ISRG | Intuitive Surgical Delivers Q1 Double Beat: ISRG Earnings Highlights | 0.8 |
| IBM | IBM Q1 Preview: Dow Giant Goes For Six Straight Double Beats As AI Momentum Builds | 0.7 |
| CB | Chubb Q1 Adj. EPS $6.82 Beats $6.60 Estimate, Sales $11.716B Beat $11.687B Estimate | 0.7 |
| TMUS | UPDATE: 'Deutsche Telekom Weighs Full Combination With T-Mobile' - Bloomberg | 0.7 |
| BA | 'Boeing 737 Max 7, 10 Models on Track for 2026 Certification, FAA Says'- Bloomberg | 0.6 |
| COIN | Kalshi, Polymarket Announce Plans To Launch Crypto Perpetual Futures: Report | 0.6 |
| META | Meta Platforms Breaking Ground On $1B First Data Center In Oklahoma | 0.6 |
#### Top 10 Negative Stories
| Ticker | Summary | Score |
|---|---|---|
| COIN | New York Attorney General Sues Coinbase Over Prediction Markets: COIN Slides 6% | -0.9 |
| UAL | United Airlines Holdings Lowers FY2026 Adj EPS Guidance from $12.00-$14.00 to $7.00-$11.00 vs $9.96 Est | -0.9 |
| COF | Capital One Misses Q1 Estimates — Stock Drops | -0.8 |
| PRU | 'Prudential's Japan operations suspected of fraud at Gibraltar Life unit'- Nikkei Asia | -0.8 |
| GM | 'GM suspends next-gen electric trucks amid pivot back to gas engines, hybrids'- Crain's Detroit Business | -0.7 |
| TSLA | Tesla California Sales Fall 24% In Q1 Despite Model Y Leading EV Sales | -0.7 |
| UAL | United Airlines Stock Slips As Softer 2026 Outlook Overshadows Q1 Earnings Beat | -0.7 |
| BX | Blackstone's Private Credit Fund Slashes Value Of ACI Group Holdings | -0.6 |
| COF | Capital One Financial Reveals March Domestic Credit Card Net Charge-Off Rate 5.09% | -0.5 |
| DHI | America's Largest Homebuilder D.R. Horton Says Affordability Is Hitting Housing Demand | -0.5 |Code language: plaintext (plaintext)
I built this after-hours, and the trades executed at open. Here’s where I stand at 1pm:

Alpaca allows for 3 paper trading accounts, so to “reset” you simply create a new account with the desired paper balance, get the new API keys and update your .env (after which you can delete the old paper account).
The next step is to schedule this agent to run periodically, which I will follow up in a separate post.