Multi-agent LLM system that, for every stock with an upcoming earnings report, builds the strongest bull case and bear case in parallel, then runs a synthesis agent that debates them into a single actionable outlook. Delivered as a daily digest and a dashboard.
Built as a personal exploration of multi-agent architectures — specifically the debate-then-synthesize pattern — grounded in real financial data (Finnhub, FRED, SEC EDGAR, yfinance) and using Gemini as the reasoning backbone.
The dashboard ranks upcoming earnings by conviction score, then surfaces the full bull-vs-bear breakdown per stock. The same agents produce different verdicts depending on what the data actually says — that's the point.
The batch job emails the top-N ranked stocks every morning so the analysis is where you already read the news, not on yet another dashboard tab.
┌─────────────────────────┐
│ Finnhub · earnings cal │
│ FRED · macro context │
│ SEC EDGAR · 10-K/10-Q │
│ yfinance · fundamentals│
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ analytics_pipeline │
│ (Go, batch/scheduled) │
└───────────┬─────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌────────────┐
│ Bull │ │ Bear │ │ Industry │
│ agent │ │ agent │ │ playbooks │
└────┬─────┘ └────┬─────┘ └─────┬──────┘
└───────┬──────┴───────────────┘
▼
┌───────────────┐
│ Synthesis │ → email digest
│ agent │ → dashboard
└───────────────┘
- Bull agent — role-prompted as a growth-fund manager building the "hidden upside" thesis.
- Bear agent — role-prompted as a short-seller stress-testing the consensus.
- Synthesis agent — receives both cases + industry rule playbooks, produces the final actionable outlook.
- Industry playbooks — hand-tuned prompt templates (bull, bear, synthesis) per sector so the agents ground their reasoning in industry-specific dynamics.
- Backend: Go 1.25 · sqlc-generated SQL · Chi/net-http services · Docker
- Data: Python 3.13 (yfinance + edgar for SEC filings) piped into Go — chose Python only for the two library ecosystems (yfinance and edgar-crawler) that don't have solid Go equivalents
- LLM: Gemini (via
google.golang.org/genai), configurable model - Data providers: Finnhub (earnings calendar), FRED (macro), SEC EDGAR (filings), yfinance (fundamentals + news)
- Frontend: React + TypeScript + Vite dashboard
- Auth: Google OAuth + JWT cookies
- Infra: Cloud Run + Cloud Build + Artifact Registry + Secret Manager
- Tests:
go testwith HTTP mocks + go-vcr-style cassettes for e2e coverage
Requires: Go 1.25+, Python 3.13+, Docker, and API keys for Finnhub, FRED, and Gemini.
# 1. Copy the env template and fill in your keys
cp .env.example .env
# edit .env with FINNHUB_API_KEY, GEMINI_API_KEY, FRED_API_KEY, DATABASE_URL
# 2. Install Python deps
pip install -r requirements.txt
# 3. Run the analytics batch job (dry-run over the next day of earnings)
go run ./batch_jobs/pre_earnings_analysis/main --earning-outlook-num-days=1 --gemini-model=gemini-2.5-flash
# 4. Run the dashboard backend
go run ./services/pre_earnings_dashboard
# 5. Run the frontend
cd frontend/pre-earnings-dashboard
npm install && npm run devOr bring the whole stack up in Docker:
docker compose up --buildcloudbuild.yaml is provided as a template. Set the substitutions to your own values:
_PROJECT_ID— your GCP project_LOCATION— your preferred region- URLs for
_FRONTEND_URL,_BACKEND_URL,_USER_MGMT_URL - All secrets should live in Secret Manager (never committed):
FINNHUB_API_KEY,GEMINI_API_KEY,FRED_API_KEY,DATABASE_URL,JWT_SECRET,GOOGLE_OAUTH_CLIENT_ID,GOOGLE_OAUTH_CLIENT_SECRET,EMAIL_PASSWORD
- Why debate-then-synthesize? A single-prompt "give me a stock analysis" agent tends to be milquetoast — bland, hedged, unhelpful. Forcing two agents to build the strongest opposing cases first, then reconciling, produces sharper reasoning.
- Why hand-tuned industry playbooks? LLMs are okay at generic financial reasoning but often miss sector-specific dynamics (e.g., what "growth" means for a REIT vs. a semiconductor). The playbooks are prompt scaffolding that pins the agents to sector-appropriate frameworks.
- Why Go for the pipeline? Concurrency for fan-out over the earnings calendar, and single-binary deploy. LLM latency dominates the wall-clock time, so goroutines pay off.
Personal side project — actively iterated on. Not investment advice.
MIT — see LICENSE.



