Skip to main content

Command Palette

Search for a command to run...

Building an AI Trading Team on Your Mac: A Deep Dive into TradingAgents with Gemini 3

Updated
•16 min read•View as Markdown
Building an AI Trading Team on Your Mac: A Deep Dive into TradingAgents with Gemini 3
H
Father of two, tech lover. Building systems by day, raising curious minds by night.

Introduction

TradingAgents is an open-source multi-agent LLM framework that simulates a real-world trading firm. It deploys specialized agents — fundamental analysts, sentiment experts, technical analysts, researchers, traders, and risk managers — that collaboratively evaluate market conditions and debate optimal strategies before producing a final trading decision.

Built on LangGraph for stateful orchestration, the framework supports multiple LLM providers including Google Gemini, OpenAI, Anthropic, and more. This guide walks you through a complete production-grade setup on a Mac Mini: Gemini 3 as the inference backend, LangGraph Studio for visual debugging, uv for Python environment management, yfinance for market data, and a launchd scheduled job that runs the analysis twice daily and pushes the report to Telegram.

What you'll build:

  • A Python 3.11 virtual environment managed by uv
  • TradingAgents with Gemini 3 models
  • A centralized .env file — one place for every secret and setting
  • LangGraph Studio for step-by-step visual debugging
  • A launchd job running at 6:00 AM and 3:00 PM HKT
  • Automatic Telegram notifications after each run

Project Layout

Everything lives in a single repository with a predictable structure. All configuration is centralized in .env; the launchd job calls the wrapper script, which sources .env at runtime.

~/TradingAgents/
├── .env                     ← ALL configuration lives here
├── .venv/                   ← Python 3.11 virtual environment (uv-managed)
├── trade.py                 ← analysis runner
├── scripts/
│   ├── run_analysis.sh      ← wrapper: sources .env, runs trade.py, sends Telegram
│   └── send_telegram.py     ← Telegram notification script
└── trading_data/
    └── reports/             ← output: markdown report trees per ticker

Why this layout matters: You only edit .env when you rotate an API key or change a model, and you only touch the plist when you change the schedule. Everything else is automated. The wrapper script exists specifically because launchd does not expand $HOME in plists and freezes its EnvironmentVariables at load time — the wrapper resolves $HOME at runtime and re-sources .env on every run.

Architecture: How the Virtual Trading Firm Works

TradingAgents mirrors a real trading firm's organizational structure, implemented as a LangGraph StateGraph where each agent is a node and edges define information flow.

Four-layer workflow:

Layer Agents Role Think Level
Analyst Team Fundamentals, Sentiment, News, Technical Gather data, produce initial reports Quick Think
Researcher Debate Bull Researcher ↔ Bear Researcher Structured debate on analyst findings Deep Think
Trading Trader Agent Composes reports into a trading plan Deep Think
Risk Management Aggressive ↔ Conservative ↔ Neutral Analyst, Portfolio Manager Debates risk, approves/rejects the trade Deep Think

Each analyst runs a conditional tool-call loop: the agent calls a data tool (e.g., get_stock_data), receives the result, loops back, and advances when it has enough data. The debate phase uses bidirectional loops — Bull and Bear researchers alternate until the debate count reaches 2 × max_debate_rounds. The risk phase rotates three analysts until 3 × max_risk_discuss_rounds.

What runs where:

Layer Location Description
LangGraph orchestration Local (Mac Mini) State graph compilation, node routing, checkpoint persistence
TradingAgents framework Local Agent definitions, tool nodes, memory logs
yfinance data fetching Local (network) HTTP requests to Yahoo Finance APIs
Gemini inference Remote (Google Cloud) The actual LLM reasoning — each agent node sends a prompt to Gemini
Telegram delivery Remote (Telegram API) HTTP POST to api.telegram.org — no local server needed

When you call ta.propagate("NVDA", date), LangGraph compiles the graph locally, then streams execution node by node. Each agent node makes a remote API call to Gemini for reasoning, merges the response back into the shared AgentState, and LangGraph routes to the next node based on conditional logic. The final decision is written to disk as markdown and pushed to Telegram by a small wrapper script.

Why Gemini 3

Native Google client support. TradingAgents has a dedicated GoogleClient that handles Gemini's unique content format — Gemini 3 models return content as a list of typed blocks rather than plain strings, and the client normalizes this so downstream agents don't break.

Thinking level control. Gemini 3 models support thinking_level parameters (low, high, and minimal for Flash models), letting you control the reasoning budget per role.

Single-provider simplicity. Using Gemini for both Quick Think and Deep Think roles avoids cross-provider routing bugs that can cause API key mismatches or silent fallbacks.

Model selection for this project:

  • Deep Think — gemini-3.8-flash : Our most intelligent Flash model, engineered for long-horizon software engineering, autonomous agents, and complex enterprise workflows. Used for the Researcher debate, Trader, and Risk Management agents.
  • Quick Think — gemini-3.1-flash-lite : Frontier-class performance rivaling larger models at a fraction of the cost. Used for the four Analyst agents, which are high-throughput data gathering tasks.

Step 1: Install Homebrew

If you don't have Homebrew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Follow the printed instructions to add Homebrew to your PATH, then verify:

brew --version

Expected output:

Homebrew 4.x.x

Step 2: Install uv

brew install uv

Verify:

uv --version

Expected output:

uv 0.x.x

Step 3: Verify System Timezone is HKT

launchd's StartCalendarInterval uses the system timezone. Since we're scheduling for 6:00 AM and 3:00 PM HKT, the system timezone must be Asia/Hong_Kong.

sudo systemsetup -gettimezone

Expected output:

Time Zone: Asia/Hong_Kong

If it's not HKT, set it:

sudo systemsetup -settimezone Asia/Hong_Kong

Step 4: Clone TradingAgents and Create the Environment

git clone https://github.com/TauricResearch/TradingAgents.git
cd TradingAgents
uv venv --python 3.11
source .venv/bin/activate

Expected output:

Using CPython 3.11.x
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate

Python 3.11 is recommended for best performance and compatibility — TradingAgents supports 3.10–3.12, with 3.11 being the sweet spot. uv downloads Python 3.11 automatically if it's not already on your system.

Step 5: Install TradingAgents and LangGraph CLI

For the official LangGraph installation reference, see the LangGraph install documentation. For CLI and Studio setup, see the LangGraph CLI documentation and the Studio quickstart.

uv pip install -e .
uv pip install -U "langgraph-cli[inmem]"

Expected result: Both installations complete without errors. The -e flag installs TradingAgents in editable mode.

Verify:

python -c "from tradingagents.graph.trading_graph import TradingAgentsGraph; print('TradingAgents OK')"
python -c "from langgraph.graph import StateGraph; print('LangGraph OK')"

Expected output:

TradingAgents OK
LangGraph OK

Step 6: Create the Centralized .env File

This is your single source of truth. Every secret and configuration value lives here.

vim .env

Paste:

# ============================================================
# TradingAgents Centralized Configuration
# ============================================================

# --- LLM Provider ---
TRADINGAGENTS_LLM_PROVIDER=google
GOOGLE_API_KEY=your-google-api-key-here

# --- Models ---
TRADINGAGENTS_DEEP_THINK_LLM=gemini-3.8-flash
TRADINGAGENTS_QUICK_THINK_LLM=gemini-3.1-flash-lite

# --- Trading ---
TRADINGAGENTS_DATA_DIR=./trading_data
TICKER=NVDA
MAX_DEBATE_ROUNDS=2

# --- Telegram Notification ---
TELEGRAM_BOT_TOKEN=123456789:ABC-your-bot-token
TELEGRAM_CHAT_ID=987654321

# --- Logging ---
LOG_LEVEL=INFO

Save and exit: press Esc, type :wq, press Enter.

Replacing values: Edit with vim .env and replace the placeholders. Get your Google API key from Google AI Studio. Get your Telegram bot token from @BotFather and your chat ID from @userinfobot on Telegram.

TradingAgents automatically loads .env files on import — any TRADINGAGENTS_* variable overrides the matching key in DEFAULT_CONFIG.

Step 7: Create the Telegram Notification Script

Telegram is not integrated natively in the main TradingAgents repository. You do not need MLX or any local inference server — Telegram is just an HTTP API call.

mkdir -p scripts
vim scripts/send_telegram.py

Paste:

import os
import requests
from pathlib import Path
from datetime import date

BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
TICKER = os.environ.get("TICKER", "NVDA")

def send_telegram(text: str) -> None:
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    resp = requests.post(url, data={
        "chat_id": CHAT_ID,
        "text": text[:4000],
        "parse_mode": "Markdown",
        "disable_web_page_preview": True,
    }, timeout=30)
    resp.raise_for_status()
    print("Telegram report sent.")

report_dir = Path.home() / "TradingAgents" / "trading_data" / "reports" / TICKER / date.today().isoformat()
decision_file = report_dir / "5_portfolio" / "decision.md"

if decision_file.exists():
    decision = decision_file.read_text()
    send_telegram(f"*{TICKER} — {date.today().isoformat()}*\n\n{decision}")
else:
    send_telegram(f"{TICKER} analysis for {date.today().isoformat()} completed, but no decision file was found.")
    print("Decision file missing; sent fallback notice.")

Save and exit.

Step 8: Create the Wrapper Script

vim scripts/run_analysis.sh

Paste:

#!/bin/bash
set -euo pipefail

REPO="$HOME/TradingAgents"
VENV="$REPO/.venv/bin/python"
ENV_FILE="$REPO/.env"

# Load centralized .env into this process's environment
set -a
source "$ENV_FILE"
set +a

cd "$REPO"

# Step 1: Run the analysis
"$VENV" "$REPO/trade.py"

# Step 2: Send the Telegram report
"$VENV" "$REPO/scripts/send_telegram.py"

Make it executable:

chmod +x scripts/run_analysis.sh

Why the wrapper exists: launchd does not expand $HOME in plist strings, and its EnvironmentVariables dict is static at load time. The wrapper resolves $HOME at runtime and sources .env fresh on every run. This means you can rotate your Google API key or Telegram token by editing .env alone — no plist reload required.

Step 9: Create the trade.py Script

vim trade.py

Paste:

import os
from datetime import date
from dotenv import load_dotenv
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG

load_dotenv()

config = DEFAULT_CONFIG.copy()
config["llm_provider"] = os.getenv("TRADINGAGENTS_LLM_PROVIDER", "google")
config["quick_think_llm"] = os.getenv("TRADINGAGENTS_QUICK_THINK_LLM")
config["deep_think_llm"] = os.getenv("TRADINGAGENTS_DEEP_THINK_LLM")
config["data_vendors"] = {
    "core_stock_apis": "yfinance",
    "technical_indicators": "yfinance",
    "fundamental_data": "yfinance",
    "news_data": "yfinance"
}
config["max_debate_rounds"] = int(os.getenv("MAX_DEBATE_ROUNDS", "2"))

ta = TradingAgentsGraph(debug=True, config=config)

ticker = os.getenv("TICKER", "NVDA")
today = date.today().isoformat()
print(f"Starting Analysis for {ticker} ({today})...")

state, decision = ta.propagate(ticker, today)
ta.save_reports(state, ticker)

print("\n" + "="*60)
print("FINAL DECISION:")
print("="*60)
print(decision)

Save and exit.

Run it manually once to verify:

python trade.py

Expected result: The graph executes node by node. Reports are written to ./trading_data/reports/NVDA/<DATE>/, and the final decision prints to the terminal.

Step 10: Create the launchd Job

vim ~/Library/LaunchAgents/com.tradingagents.schedule.plist

Paste:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.tradingagents.schedule</string>

    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>"$HOME/TradingAgents/scripts/run_analysis.sh"</string>
    </array>

    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Hour</key>
            <integer>6</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>15</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
    </array>

    <key>StandardOutPath</key>
    <string>/tmp/tradingagents.out.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/tradingagents.err.log</string>

    <key>RunAtLoad</key>
    <false/>
</dict>
</plist>

Save and exit.

How the schedule works: StartCalendarInterval uses the system timezone — which you verified is HKT in Step 3. Hour 6 means 6:00 AM HKT, Hour 15 means 3:00 PM HKT. If the Mac is asleep at those times, launchd coalesces the missed intervals and fires once on wake.

Why RunAtLoad is false: Setting it to true would trigger a run immediately when the plist is loaded — including after every reboot. With false, the job only runs at the scheduled times.

Step 11: Load and Test the Job

launchctl load ~/Library/LaunchAgents/com.tradingagents.schedule.plist

Verify registration:

launchctl list | grep tradingagents

Expected output: A line showing com.tradingagents.schedule with a PID (or -) and an exit code.

Force a test run immediately without waiting for 6am:

launchctl start com.tradingagents.schedule

Watch the logs:

tail -f /tmp/tradingagents.out.log /tmp/tradingagents.err.log

Expected result: The analysis runs, reports are written to disk, and a Telegram message arrives in your chat.

To reload after editing the plist:

launchctl unload ~/Library/LaunchAgents/com.tradingagents.schedule.plist
launchctl load ~/Library/LaunchAgents/com.tradingagents.schedule.plist

Checkpoint/Resume

TradingAgents supports checkpointing to save state after each node to a per-ticker SQLite database. If a run crashes, it resumes from the last successful node instead of restarting.

How it works: Each ticker's run is stored at ~/.tradingagents/cache/checkpoints/<TICKER>.db. The thread_id is deterministic, based on the ticker, date, and a signature hash of graph-shape-affecting choices. If you change the configuration in a way that alters the graph's structure, a new checkpoint thread ID is generated, preventing resumption from an incompatible prior state.

Enable it in trade.py:

config["checkpoint_enabled"] = True

On resume, the framework prints Resuming from step N for <TICKER> on <date> instead of restarting from scratch. After successful completion, the checkpoint is automatically cleaned up.

CLI alternative: The TradingAgents CLI accepts --checkpoint as a command-line argument.

LangGraph Studio: Visual Debugging

LangGraph Studio gives you a live, interactive view of the graph as it executes. You can set breakpoints, inspect state at any node, view exact prompts sent to Gemini, and debug failures without re-running the entire pipeline. For the official reference, see the LangGraph Studio documentation.

Setup

Create langgraph.json at the repo root:

vim langgraph.json

Paste:

{
  "dependencies": ["."],
  "graphs": {
    "trading_agents": {
      "path": "tradingagents/graph/trading_graph.py:TradingAgentsGraph",
      "config": {
        "debug": true
      }
    }
  },
  "env": ".env"
}

Save and exit.

Start the dev server:

langgraph dev

Expected output:

Server started at http://localhost:2024

Open Studio in your browser. Safari and Brave block plain HTTP on localhost — if you're using either, add the --tunnel flag to get an HTTPS-accessible endpoint.

What Studio Gives You

Feature What It Does
Interactive graph visualization See all nodes and edges rendered as a live graph
Step-by-step execution Pause at any node, inspect input state, then resume
State inspection View the full AgentState at any point — messages, reports, debate counts
Breakpoints Set breakpoints on specific nodes to halt execution
Prompt & tool debugging View exact prompts sent to Gemini, tool arguments, token usage, latency
State editing & re-run Modify state at a breakpoint and re-run from that point
LangSmith integration Import traced runs from production for debugging

Debugging Workflow: A Practical Example

Step 1: Launch and load the graph. Open Studio. You'll see the full TradingAgents graph rendered — Market Analyst, Social Media Analyst, News Analyst, Fundamentals Analyst, Bull Researcher, Bear Researcher, Research Manager, Trader, and the Risk Analysts.

Step 2: Run a test input. In the input panel, provide:

{
  "company_of_interest": "NVDA",
  "trade_date": "2026-10-10",
  "messages": []
}

Click Run. Studio begins executing node by node, highlighting the current node.

Step 3: Inspect state at each node. When execution pauses at the Market Analyst node (if you set a breakpoint), click Inspect State. You'll see the AgentState with all fields: messages, company_of_interest, trade_date, market_report, sentiment_report, news_report, fundamentals_report, investment_debate_state, and risk_debate_state.

Step 4: Check tool calls and prompts. Click the Trace tab to see the exact prompt sent to Gemini for this node — system prompt, user message, tool definitions, and the model's response.

Step 5: Debug a failure. If the Bull Researcher node fails with a Gemini API error, Studio captures the exception with full context. You can inspect the input state, modify the state (e.g., adjust max_debate_rounds), and re-run from that point.

Step 6: Attach a line-level debugger (optional). For breakpoints and variable inspection:

uv pip install debugpy
langgraph dev --debug-port 5678

Then attach VS Code or PyCharm to port 5678.

Data Vendors: yfinance and Alternatives

TradingAgents ships with yfinance as the default data vendor. It requires no API keys and covers core stock APIs, technical indicators, fundamentals, and news.

Configuration:

config["data_vendors"] = {
    "core_stock_apis": "yfinance",
    "technical_indicators": "yfinance",
    "fundamental_data": "yfinance",
    "news_data": "yfinance"
}

Supported alternatives:

Vendor Category API Key Required Notes
alpha_vantage Core stock, fundamentals Yes Free tier: 25 requests/day
sec_edgar Fundamentals No SEC asks callers to identify themselves
fred Macro data Yes Federal Reserve economic data
polymarket Prediction markets No Optional enrichment category

To switch vendors, replace the value in the data_vendors dict:

config["data_vendors"]["core_stock_apis"] = "alpha_vantage"
config["data_vendors"]["fundamental_data"] = "sec_edgar,yfinance"

You can specify comma-separated fallback chains. If the first vendor is unavailable, the system automatically tries the next in the chain.

Output: What You Actually Get

When you call ta.propagate("NVDA", today), you get two things back:

  1. A tuple: (state, decision) — where state is the full AgentState dict and decision is the Portfolio Manager's final text.
  2. A 5-tier rating extracted deterministically from that decision text: Buy, Overweight, Hold, Underweight, or Sell.

The ta.save_reports(state, ticker) call writes a structured directory tree of markdown files:

trading_data/reports/NVDA/2026-10-10/
├── 1_analysts/
│   ├── market.md
│   ├── sentiment.md
│   ├── news.md
│   └── fundamentals.md
├── 2_research/
│   ├── bull.md
│   ├── bear.md
│   └── manager.md
├── 3_trading/
│   └── trader.md
├── 4_risk/
│   ├── aggressive.md
│   ├── conservative.md
│   └── neutral.md
├── 5_portfolio/
│   └── decision.md
└── complete_report.md

The Telegram script reads 5_portfolio/decision.md and pushes it to your chat.

Risks and Limitations

Risk Severity Mitigation
Gemini rate limits High Free tier is 15 RPM. A single trade run can exceed this. Enable billing or throttle with max_debate_rounds = 1
yfinance single point of failure Medium Web scraping tool, subject to Yahoo page changes. Use fallback chains
Context growth during debate Medium Set max_debate_rounds and max_risk_discuss_rounds conservatively
No broker execution — TradingAgents is a research framework. It does not place real orders — output is a decision, not a trade
launchd timezone Low Schedule uses system timezone. Verify with sudo systemsetup -gettimezone

Important disclaimer: TradingAgents is designed for research purposes. Trading performance may vary based on many factors, including the chosen backbone language models, model temperature, trading periods, and data quality. It is not intended as financial, investment, or trading advice.

Troubleshooting: When Things Fail

Symptom Likely Cause Fix
launchctl start runs but nothing happens Wrapper script not executable chmod +x ~/TradingAgents/scripts/run_analysis.sh
Telegram message not received TELEGRAM_CHAT_ID wrong Message @userinfobot on Telegram to get your correct ID
GOOGLE_API_KEY not set in logs .env not sourced Verify wrapper has source "$ENV_FILE" before running
Analysis runs but no report files save_reports not called Ensure ta.save_reports(state, ticker) is in trade.py
Job runs at wrong time System timezone not HKT sudo systemsetup -settimezone Asia/Hong_Kong
uv: command not found in launchd logs PATH not set Use the venv Python directly (absolute path)

Conclusion

You now have a fully operational multi-agent trading firm running on your Mac, scheduled twice daily, with Telegram delivery. The architecture is:

  • LangGraph runs locally, orchestrating the entire workflow
  • Gemini 3 provides remote inference — gemini-3.8-flash for deep reasoning, gemini-3.1-flash-lite for high-throughput analysts
  • yfinance fetches market data with no API keys required
  • LangGraph Studio gives you step-by-step visual debugging
  • launchd runs the analysis at 6:00 AM and 3:00 PM HKT
  • Telegram receives the final decision after each run
  • One .env file holds every secret — rotate keys without touching the plist

Next steps:

  • Experiment with different Gemini model combinations for Quick Think and Deep Think
  • Add fallback data vendors (SEC EDGAR for fundamentals, Alpha Vantage for stock data)
  • Explore broker integration forks if you want live execution — but understand they are separate codebases