Spaces:
Sleeping
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)
Scout Node β Discovers candidates via yFinance screener + Brave Search trending, scores and ranks them, and pops the best unseen ticker.
Gatekeeper Node β Strict quantitative firewall using the
Commandpattern 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 toscout(FAIL) viaCommand.
Analyst Node β Two modes controlled by
USE_DEBATEenv var:- Single-LLM (default): Senior Broker analysis via OpenRouter (6-model fallback chain) with structured
InvestmentVerdictoutput. - 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.
- Single-LLM (default): Senior Broker analysis via OpenRouter (6-model fallback chain) with structured
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
- The Pitcher β Writes the strongest bullish thesis using only provided data.
- The Skeptic β Challenges the bull case, flagging any fabricated claims.
- 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 accountsPORTFOLIOβ View the agent's paper trade track recordBACKTESTβ 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, andbroker_statusto VPS - Dry-run mode when
ALPACA_ENABLEDis 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 pricesagent_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 Datasetscripts/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=truein LangSmith metadata, so the team can filter and review edge cases in the LangSmith UI.
Prompt A/B Testing:
src/prompts/senior_broker.pysupports aPROMPT_VERSIONenv 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-KellyBUYβ 70% of half-KellyWATCHβ 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
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
2. Configuration (.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
chainlit run app.py -w
4. Running the Workflow CLI
python3 main.py
5. Deploying the VPS API (optional)
bash vps/deploy.sh
6. Running Evaluations (optional)
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.