Spaces:
Sleeping
Sleeping
File size: 15,720 Bytes
5883272 bfc139e 665dde7 bfab9f9 a2cbcac 1ddda1a a2cbcac bfc139e a2cbcac bfab9f9 a2cbcac bfc139e a6f44ed bfc139e bfab9f9 bfc139e bfab9f9 bfc139e a2cbcac bfab9f9 1ddda1a a2cbcac bfc139e bfab9f9 e86acf4 bfc139e a2cbcac bfc139e a2cbcac bfab9f9 bfc139e a6f44ed bfab9f9 bfc139e d072f0c bfc139e bfab9f9 d072f0c bfab9f9 d072f0c e86acf4 bfc139e a2cbcac e86acf4 1ddda1a a2cbcac bfc139e a2cbcac 1ddda1a a2cbcac bfc139e a2cbcac 1ddda1a bfab9f9 bfc139e bfab9f9 d072f0c a2cbcac 1ddda1a a2cbcac 1ddda1a a2cbcac bfc139e bfab9f9 bfc139e bfab9f9 e86acf4 bfc139e bfab9f9 bfc139e bfab9f9 bfc139e bfab9f9 d072f0c bfc139e bfab9f9 bfc139e bfab9f9 bfc139e bfab9f9 bfc139e d072f0c bfab9f9 bfc139e bfab9f9 bfc139e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | ---
title: PrimoGreedy Agent
emoji: πΈ
colorFrom: green
colorTo: blue
sdk: docker
pinned: false
app_port: 7860
---
# PrimoGreedy Agent
**PrimoGreedy** is an automated, AI-driven financial analysis agent designed to hunt, filter, and evaluate Micro-Cap and Small-Cap stocks. It acts as a ruthless "Logic Firewall," aggressively rejecting high-debt, cash-burning, and overvalued companies before deploying a multi-agent LLM pipeline to write highly structured fundamental investment memos β and optionally execute paper trades via Alpaca.
---
## Core Architecture (The LangGraph Engine)
The system is built on **LangGraph** following modern best practices (partial state updates, `Command` routing, `Send` parallel fan-out, checkpointing, `RetryPolicy`, and multi-agent subgraphs).
### Hunter Pipeline (`src/agent.py` / `src/whale_hunter.py`)
```
START --> initial_routing --> [chat] --> END
\-> [scout] --> [gatekeeper] --Command--> [analyst] --> END
\--Command--> [scout] (retry)
```
1. **Scout Node** β Discovers candidates via yFinance screener + Brave Search trending, scores and ranks them, and pops the best unseen ticker.
2. **Gatekeeper Node** β Strict quantitative firewall using the `Command` pattern for routing:
- Market Cap: $5M -- $500M
- Share Price: under $30.00
- Zombie Filter: rejects unprofitable companies with < 6 months cash runway
- Routes directly to `analyst` (PASS / retries exhausted) or back to `scout` (FAIL) via `Command`.
3. **Analyst Node** β Two modes controlled by `USE_DEBATE` env var:
- **Single-LLM** (default): Senior Broker analysis via OpenRouter (6-model fallback chain) with structured `InvestmentVerdict` output.
- **Multi-Agent Debate** (`USE_DEBATE=true`): Three-agent Investment Committee subgraph (Pitcher β Skeptic β Judge) that produces a hallucination-resistant verdict.
Both modes fetch **SEC EDGAR** 10-K/10-Q filings (US equities), call Finnhub tools for deep fundamentals, and compute **Kelly Criterion position sizing**.
### Workflow Pipeline (`src/workflows/workflow.py`)
```
START --> [data_collection] --> [technical_analysis] --> [news_intelligence] --> [portfolio_manager] --> END
```
A linear 4-node pipeline for deep single-ticker analysis (used by `main.py` CLI).
### Multi-Agent Debate (`src/agents/debate.py`)
When `USE_DEBATE=true`, the analyst node runs a 3-agent LangGraph subgraph:
```
START --> [pitcher (Gemma)] --> [skeptic (Mistral)] --> [judge (Nemotron)] --> END
```
1. **The Pitcher** β Writes the strongest bullish thesis using only provided data.
2. **The Skeptic** β Challenges the bull case, flagging any fabricated claims.
3. **The Judge** β Synthesises the debate into a structured `InvestmentVerdict`, downgrading if fabrications were found.
Models are configurable via `DEBATE_PITCHER_MODEL`, `DEBATE_SKEPTIC_MODEL`, `DEBATE_JUDGE_MODEL` env vars.
### Parallel Region Orchestrator (`src/whale_hunter.py`)
The daily cron dispatches all 4 markets (USA, UK, Canada, Australia) **in parallel** via the LangGraph `Send` API:
```
START --> dispatch_regions --> [hunt_region: USA] \
\-> [hunt_region: UK] |-- region_results --> END
\-> [hunt_region: Canada] |
\-> [hunt_region: Australia] /
```
Each `hunt_region` invokes the full per-region subgraph (scout -> gatekeeper -> analyst -> email).
Supports **catalyst-triggered single-ticker mode** via `CATALYST_TICKER` env var (used by `repository_dispatch` from the VPS polling daemon).
---
## LangGraph Features Used
| Feature | Where | Purpose |
|---------|-------|---------|
| Partial state updates | All agent nodes | Nodes return `dict` with only changed keys |
| `Annotated` reducers | `src/core/state.py` | `candidates` and `candidate_scores` use `operator.add` |
| `Command` pattern | Gatekeeper nodes | Combines state update + routing in a single return |
| `Send` API | `whale_hunter.py` orchestrator | Parallel fan-out across 4 market regions |
| `InMemorySaver` | All 3 graphs | Checkpointing with `thread_id` for state persistence |
| `RetryPolicy` | All nodes | `max_attempts=3, initial_interval=2.0` for transient errors |
| `recursion_limit` | All `invoke()` calls | Set to 30 to prevent infinite loops |
| Structured output | Analyst nodes | `with_structured_output(InvestmentVerdict)` for validated verdicts |
| Subgraph | `src/agents/debate.py` | Pitcher/Skeptic/Judge multi-agent debate as nested graph |
| `@tool` decorator | `sec_edgar.py`, `finance_tools.py` | LangChain tool pattern for API integrations |
| `START` / `END` | All graphs | Modern entry-point API (no deprecated `set_entry_point`) |
---
## Key Modules
### The Interactive UI (`app.py`)
A Chainlit-powered chat interface.
- **`AUTO`** β Smart scan (yFinance screener + Brave trending)
- **`@Handle`** β Social scout from X/Twitter accounts
- **`PORTFOLIO`** β View the agent's paper trade track record
- **`BACKTEST`** β Run backtest on paper portfolio
- **Direct Ticker** β Type any ticker (e.g., `AAPL`) for an instant deep-dive
### The Morning Cron (`src/whale_hunter.py`)
Headless agent running as a **GitHub Action** daily cron. Hunts all 4 regions in parallel, evaluates candidates through the full pipeline, and emails HTML reports via Resend.
### Alpaca Paper Trading (`src/broker/alpaca.py`)
Optional **Alpaca Markets** integration for live paper trading of US equities:
- Automatically submits market orders for **BUY / STRONG BUY** verdicts on US tickers
- Calculates share quantity from Kelly position sizing and account equity
- Safety limits: minimum $1 order, maximum 25% of equity per position
- Records `order_id`, `fill_price`, and `broker_status` to VPS
- Dry-run mode when `ALPACA_ENABLED` is not set (logs but doesn't submit)
### VPS Data Layer (`vps/`)
Optional **FastAPI + DuckDB** backend deployed on a VPS (behind Tailscale) that replaces local JSON files for persistence:
- `seen_tickers` β Prevents re-analysing the same ticker (30 days for BUY/STRONG BUY, 14 days for AVOID/WATCH to allow re-evaluation)
- `paper_portfolio` β Records all paper trades with Kelly sizing, Alpaca order IDs, and fill prices
- `agent_runs` β Operational metrics for LangSmith correlation
- **Live Dashboard** (`GET /dashboard`) β Chart.js-powered portfolio dashboard with summary cards, verdict distribution donut, Kelly sizing bar chart, sortable trade table, and seen-ticker feed. Auto-refreshes every 5 minutes. Dark theme.
- `GET /portfolio/summary` β Lightweight aggregated stats endpoint (no yFinance calls)
The agent (`src/core/memory.py`, `src/portfolio_tracker.py`) auto-detects the VPS via `VPS_API_URL` env var and falls back to local JSON files when unavailable.
### Catalyst Polling Daemon (`vps/catalyst_poll.py`)
VPS-based systemd timer that polls every 15 minutes during US market hours for intraday triggers:
- **Volume spike** β Current volume > 3x average daily volume
- **Price move** β Intraday move > 10%
- **Insider filing** β New SEC Form 4 purchase for a tracked ticker
When triggered, fires a GitHub Actions `repository_dispatch` event to run the pipeline for that specific ticker.
### Grading Engine (`scripts/` + `src/core/online_eval.py`)
Automated quality assurance via LangSmith Evaluators, split into **offline** and **online** tiers:
**Offline Evaluators** (run on demand against golden dataset):
- `scripts/build_golden_dataset.py` β Curates 50 representative traces into a LangSmith Dataset
- `scripts/evaluators.py` β 5 custom evaluators:
- **Catalyst Grounding** (LLM-as-a-Judge) β Scores whether claims are backed by data
- **Company Identity** (LLM-as-a-Judge) β Catches "name-trap" hallucinations
- **Format** β Validates headers, no duplicates, Kelly present for BUY
- **Verdict Validity** β Ensures verdict is one of the 4 valid values
- **Kelly Math** β Checks allocation is within [1%, 25%] bounds
- `scripts/run_evals.py` β Runs all evaluators against the golden dataset
**Online Evaluators** (run inline during every cron):
- `src/core/online_eval.py` β After each analyst verdict, the cheap evaluators (`format_score`, `verdict_validity_score`) run automatically and post results as **LangSmith feedback** on the run. Zero extra LLM cost.
**Annotation Queue**:
- WATCH, AVOID, and fallback-path verdicts are automatically tagged with `needs_review=true` in LangSmith metadata, so the team can filter and review edge cases in the LangSmith UI.
**Prompt A/B Testing**:
- `src/prompts/senior_broker.py` supports a `PROMPT_VERSION` env var to pin to a specific LangSmith Hub commit. Deploy two cron runs with different versions and compare results in LangSmith Experiments.
### SEC EDGAR Ground Truth (`src/sec_edgar.py`)
Fetches the most recent 10-K or 10-Q filing from SEC EDGAR for US equities and extracts two investment-critical sections:
- **Item 7: Management's Discussion & Analysis (MD&A)** β Management's own view of operations
- **Item 1A: Risk Factors** β Legally mandated disclosure of what could go wrong
Uses the EDGAR EFTS full-text search API with BeautifulSoup HTML parsing and `RecursiveCharacterTextSplitter` for section truncation. Injected into the analyst prompt as `{sec_context}` for non-US equities this is skipped gracefully.
### Structured Verdicts (`src/models/verdict.py`)
Pydantic model enforcing one of 4 verdict types:
```
STRONG BUY | BUY | WATCH | AVOID
```
Used via `llm.with_structured_output(InvestmentVerdict)` with a graceful fallback to plain text LLM output.
### Kelly Criterion Position Sizing (`src/models/kelly.py`)
Computes optimal position size from historical portfolio performance using the **Kelly Criterion**:
- Calculates win rate, average win %, and average loss % from VPS or local trade history
- Applies the Kelly formula: `f* = (win_rate / avg_loss) - ((1 - win_rate) / avg_win)`
- Uses conservative **half-Kelly** with verdict-based scaling:
- `STRONG BUY` β 100% of half-Kelly
- `BUY` β 70% of half-Kelly
- `WATCH` β 30% of half-Kelly
- Clamped to **1% -- 25%** to prevent over-concentration
- Requires minimum 5 historical trades before activating (returns 0% otherwise)
Position sizing is computed **post-LLM** and injected into the `InvestmentVerdict` model, appearing in both the report output and the paper trade record.
---
## Quick Start Guide
### 1. Environment Setup
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 2. Configuration (`.env`)
```env
OPENROUTER_API_KEY=your_key # LLM Inference (6-model fallback chain)
FINNHUB_API_KEY=your_key # Deep Fundamentals & Insider Data
BRAVE_API_KEY=your_key # Web Search
RESEND_API_KEY_CISCO=your_key # Email Reporting (Cron only)
# Optional: VPS Data API
VPS_API_URL=http://your-vps:8080
VPS_API_KEY=your_vps_key
# Optional: Alpaca Paper Trading (US equities only)
ALPACA_API_KEY=your_key
ALPACA_SECRET_KEY=your_secret
ALPACA_ENABLED=true
# Optional: Multi-Agent Debate
USE_DEBATE=true # Enable pitcher/skeptic/judge pipeline
# Optional: LangSmith Observability
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=your_key
LANGCHAIN_PROJECT=primogreedy
# Optional: Prompt A/B Testing
PROMPT_VERSION=latest # Pin to a specific Hub commit hash for A/B tests
```
### 3. Launching the UI
```bash
chainlit run app.py -w
```
### 4. Running the Workflow CLI
```bash
python3 main.py
```
### 5. Deploying the VPS API (optional)
```bash
bash vps/deploy.sh
```
### 6. Running Evaluations (optional)
```bash
python scripts/build_golden_dataset.py # Build the golden dataset from LangSmith
python scripts/run_evals.py # Run all evaluators
```
---
## Project Structure
```
primogreedy/
βββ app.py # Chainlit web UI entry point
βββ main.py # Workflow CLI entry point
βββ requirements.txt # Python dependencies (LangChain 1.0 LTS)
βββ src/
β βββ agent.py # Interactive Chainlit pipeline (scout/gatekeeper/analyst)
β βββ whale_hunter.py # Daily cron pipeline + parallel Send orchestrator
β βββ llm.py # OpenRouter LLM with 6-model fallback + structured output
β βββ sec_edgar.py # SEC EDGAR 10-K/10-Q filing fetcher + parser (@tool)
β βββ finance_tools.py # Finnhub tools (@tool decorated)
β βββ portfolio_tracker.py # Paper trade recording + Alpaca execution
β βββ email_utils.py # Resend email dispatch
β βββ core/
β β βββ state.py # AgentState (TypedDict with Annotated reducers + debate fields)
β β βββ memory.py # Seen-tickers ledger (VPS or local JSON)
β β βββ search.py # Brave Search wrapper (with retry/backoff)
β β βββ ticker_utils.py # Ticker extraction, suffix resolution, noise filtering
β β βββ online_eval.py # Inline LangSmith evaluators + annotation queue
β β βββ logger.py # Logging config
β βββ models/
β β βββ verdict.py # InvestmentVerdict Pydantic model (with header-stripping)
β β βββ kelly.py # Kelly Criterion position sizing (with 10-min cache)
β βββ agents/
β β βββ debate.py # Multi-agent pitcher/skeptic/judge subgraph
β β βββ data_collection_agent.py
β β βββ technical_analysis_agent.py
β β βββ news_intelligence_agent.py
β β βββ portfolio_manager_agent.py
β βββ broker/
β β βββ alpaca.py # Alpaca Paper Trading order router + execution
β βββ workflows/
β β βββ workflow.py # 4-node linear workflow graph
β β βββ state.py # Workflow-specific AgentState
β βββ discovery/
β β βββ screener.py # yFinance micro-cap screener
β β βββ scoring.py # Quantitative candidate scoring
β β βββ insider_feed.py # SEC EDGAR / Finnhub insider data
β βββ prompts/
β βββ senior_broker.py # LangSmith Hub prompt template
βββ scripts/
β βββ build_golden_dataset.py # LangSmith golden dataset builder
β βββ evaluators.py # Custom LangSmith evaluators (5 scorers)
β βββ run_evals.py # Evaluation runner
βββ vps/
β βββ api.py # FastAPI + DuckDB data API (with broker fields + dashboard)
β βββ catalyst_poll.py # Intraday catalyst polling daemon
β βββ schema.sql # DuckDB table definitions
β βββ deploy.sh # VPS deployment script
β βββ requirements.txt # VPS-specific dependencies
βββ .github/workflows/
βββ hunter.yml # Daily cron + catalyst dispatch GitHub Action
```
---
## The Philosophy
PrimoGreedy does not try to predict the future. It relies on strict **Benjamin Graham** math (`Intrinsic Value = sqrt(22.5 * EPS * BookValue)`) to establish a baseline Margin of Safety, then applies **Peter Lynch's** logic to find the catalyst and **Charlie Munger's** inversion to find the catch. It is designed to say **AVOID** far more often than it says **BUY**.
|