Skip to content

rgoerwit/ai-investment-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

105 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Multi-Agent International Equity Analysis System

This repository is a multi-agent international equity research system. It can analyze single tickers, run broader screening pipelines, and optionally reconcile saved results against an Interactive Brokers portfolio through either a CLI workflow or a local Flask dashboard.

You need Python 3.12+, Poetry, and at least one working LLM API key. A Gemini key is the minimum practical setup; optional data-provider keys improve coverage and reliability.

Python 3.12+ License: MIT LangGraph

What This Repo Covers

  • Multi-agent international equity analysis for individual tickers
  • Structured markdown reports and charts
  • Screening pipeline for broader exchange-wide discovery
  • Optional IBKR portfolio reconciliation and watchlist handling
  • Optional local Flask dashboard for portfolio and refresh monitoring

Architecture

This is not a single prompt wrapped in a CLI. The runtime fans out work across specialist agents, applies deterministic validation before debate, then routes the surviving analysis into valuation, risk, and portfolio decision stages.

graph TB
    Start(["User: Analyze TICKER"]) --> Dispatcher{"Parallel<br/>Dispatch"}

    Dispatcher --> MarketAnalyst["Market Analyst<br/>(Technical)"]
    Dispatcher --> SentimentAnalyst["Sentiment Analyst<br/>(Social)"]
    Dispatcher --> NewsAnalyst["News Analyst<br/>(Events)"]
    Dispatcher --> JuniorFund["Junior Fundamentals<br/>(API Data)"]
    Dispatcher --> ForeignLang["Foreign Language<br/>(Native Sources)"]
    Dispatcher --> LegalCounsel["Legal Counsel<br/>(Tax & Reg)"]
    Dispatcher --> ValueTrap["Value Trap Detector<br/>(Governance)"]
    Dispatcher -.-> Auditor["Forensic Auditor<br/>(Independent Check)<br/>Optional"]

    MarketAnalyst --> SyncCheck["Sync Check<br/>(Fan-In Barrier)"]
    SentimentAnalyst --> SyncCheck
    NewsAnalyst --> SyncCheck
    ValueTrap --> SyncCheck
    Auditor -.-> SyncCheck

    JuniorFund --> FundSync["Fundamentals<br/>Sync"]
    ForeignLang --> FundSync
    LegalCounsel --> FundSync
    FundSync --> SeniorFund["Senior Fundamentals<br/>(Scoring)"]
    SeniorFund --> Validator["Financial Validator<br/>(Red-Flag Detection)"]
    Validator --> SyncCheck

    SyncCheck -->|"REJECT"| PMFastFail["PM Fast-Fail<br/>(Skip Debate)"]
    SyncCheck -->|"PASS"| DebateR1{"Parallel<br/>Debate R1"}

    DebateR1 --> BullR1["Bull Researcher R1"]
    DebateR1 --> BearR1["Bear Researcher R1"]
    BullR1 --> DebateSyncR1["Debate Sync R1"]
    BearR1 --> DebateSyncR1

    DebateSyncR1 -->|"Normal"| DebateR2{"Parallel<br/>Debate R2"}
    DebateSyncR1 -->|"Quick"| DebateSyncFinal["Debate Sync Final"]

    DebateR2 --> BullR2["Bull Researcher R2"]
    DebateR2 --> BearR2["Bear Researcher R2"]
    BullR2 --> DebateSyncFinal
    BearR2 --> DebateSyncFinal

    DebateSyncFinal --> ResearchManager["Research Manager<br/>(Synthesis)"]
    ResearchManager --> ValuationCalc["Valuation Calculator"]
    ResearchManager -.-> Consultant["External Consultant<br/>(Cross-Validation)"]
    Auditor -.->|Independent Forensic Report| Consultant

    ValuationCalc --> Trader["Trader<br/>(Plan)"]
    Consultant -.-> Trader

    Trader --> RiskyAnalyst["Risky Analyst"]
    Trader --> SafeAnalyst["Safe Analyst"]
    Trader --> NeutralAnalyst["Neutral Analyst"]

    RiskyAnalyst --> PortfolioManager["Portfolio Manager<br/>(Verdict)"]
    SafeAnalyst --> PortfolioManager
    NeutralAnalyst --> PortfolioManager

    PMFastFail --> ChartGen["Chart Generator"]
    PortfolioManager --> ChartGen

    ChartGen --> Decision(["BUY / SELL / HOLD"])

    style Dispatcher fill:#ffeaa7,color:#333
    style SyncCheck fill:#e0e0e0,color:#333
    style Validator fill:#ffcccc,color:#333
    style Consultant fill:#e8daff,color:#333
    style Auditor fill:#e8daff,color:#333,stroke-dasharray: 5 5
    style PMFastFail fill:#ffcccc,color:#333
    style Decision fill:#55efc4,color:#333
Loading

At a high level:

  • Parallel analyst fan-out gathers market, news, sentiment, fundamentals, language, legal, and value-trap evidence.
  • Fundamentals are synthesized and then checked by deterministic red-flag rules before the debate path is allowed to continue.
  • Bull and bear researchers argue one or two rounds depending on --quick, and Research Manager consolidates the result.
  • Valuation, trader, and risk personas shape the portfolio decision before Portfolio Manager emits the final verdict.
  • Chart generation and report rendering run after the decision.
  • Memory and retrospective context are optional layers around the core analysis flow, not substitutes for it.

Start Here

Install

git clone https://github.com/rgoerwit/ai-investment-agent.git
cd ai-investment-agent

poetry install
cp .env.example .env

At minimum, set GOOGLE_API_KEY in .env. Optional keys such as Tavily, FMP, EODHD, and OpenAI improve coverage, search quality, or optional consultant paths.

Confirm the Core Path Works

poetry run python -m src.main --ticker 7203.T --quick --output results/7203.T.md

That command exercises the main runtime and writes a markdown report. Saved analysis JSONs in results/ are also what later power portfolio_manager.py and the dashboard.

Choose Your Workflow

  • Analyze one ticker: use python -m src.main --ticker ...
  • Screen a broader universe: use scripts/run_pipeline.sh with or without scripts/find_gems.py
  • Reconcile a portfolio: use scripts/portfolio_manager.py
  • Use the browser UI: run python -m src.web.ibkr_dashboard.app, and start the worker only if you want queued refresh jobs

Single-Ticker Analysis

This is the core engine. Use it first before touching portfolio workflows or the dashboard.

# Normal run
poetry run python -m src.main --ticker 0005.HK

# Save markdown output and charts
poetry run python -m src.main --ticker 0005.HK --output results/0005.HK.md

# Faster first pass
poetry run python -m src.main --ticker 0005.HK --quick --output results/0005.HK_quick.md

# Stateless run without Chroma-backed memory
poetry run python -m src.main --ticker 0005.HK --no-memory --output results/0005.HK.md

Practical notes:

  • --quick is usually the right first-pass setting for screening or broad review.
  • --output is the cleanest way to get markdown plus chart assets in a stable location.
  • Free-tier Gemini works, but it is slow for larger batches. Paid tiers mostly improve throughput and reduce retry friction.

Screening Pipeline

The screening pipeline is the shortest path from broad discovery to a shortlist of full reports.

# End-to-end path: scrape configured exchanges, filter, quick-screen, then run
# full analysis on BUY names only
./scripts/run_pipeline.sh

# Step-by-step alternative
poetry run python scripts/find_gems.py --output scratch/gems.txt
./scripts/run_pipeline.sh --skip-scrape scratch/gems.txt

Outputs land in scratch/. In practice you will see:

  • a source ticker list such as gems_YYYY-MM-DD.txt
  • quick-screen outputs
  • a buys_YYYY-MM-DD.txt list
  • full reports for BUY names

Resumption is built in:

  • Re-running the same command family skips completed outputs.
  • If Stage 2 was interrupted and you need to resume from an earlier day, point --buys-file at the original scratch/buys_YYYY-MM-DD.txt.
  • If you already have your own ticker list, skip scraping and feed it directly to the pipeline.

IBKR Portfolio Management

scripts/portfolio_manager.py sits on top of the saved analysis JSONs in results/. It bridges the evaluator output with live or offline portfolio context.

# Verify credentials and IBKR connectivity first
poetry run python scripts/portfolio_manager.py --test-auth

# Report only, using saved results with no IBKR connection
poetry run python scripts/portfolio_manager.py --read-only

# Reconcile against live IBKR positions
poetry run python scripts/portfolio_manager.py

# Add order-size recommendations
poetry run python scripts/portfolio_manager.py --recommend

# Re-run stale analyses, then reconcile
poetry run python scripts/portfolio_manager.py --refresh-stale --quick

# Evaluate a specific IBKR watchlist against existing analyses
poetry run python scripts/portfolio_manager.py --recommend --watchlist-name "my watchlist"

Notes:

  • --read-only is the safest way to understand the tool before you touch live broker data.
  • --recommend produces actionable suggestions and sizing guidance. Order execution is currently disabled, so the tool remains advisory.
  • Concentration warnings, stale-analysis flags, cash timing, and macro-demoted review items are part of the normal report output.

Local Flask Dashboard

The dashboard is a local browser view over the same recommendation and reconciliation stack. It is useful once you already have analysis JSONs in results/.

# App only
poetry run python -m src.web.ibkr_dashboard.app

# Worker, only needed for queued background refresh jobs
poetry run python -m src.web.ibkr_dashboard.worker

# Live broker mode with an explicit account and watchlist
poetry run python -m src.web.ibkr_dashboard.app \
  --account-id U20958465 \
  --watchlist-name "default watchlist"

# Offline/read-only mode for saved results only
poetry run python -m src.web.ibkr_dashboard.app --read-only

Open http://127.0.0.1:5050.

Convenience options:

# Start both processes together
./scripts/run_ibkr_dashboard.sh

# Start only the Flask app through the launcher
./scripts/run_ibkr_dashboard.sh --no-worker

# Pass startup flags through to the app
./scripts/run_ibkr_dashboard.sh -- --account-id U20958465 --watchlist-name "default watchlist"

If you have already run poetry install, the Poetry script shims also work:

poetry run ibkr-dashboard
poetry run ibkr-dashboard-worker

The dashboard includes:

  • Overview: NLV, cash, freshness, pending inflows, concentration, portfolio health, macro alert
  • Actions: stop breaches, sells, soft rejections, macro reviews, adds, trims, dip-watch candidates, holds
  • Watchlist: new buys, off-watchlist candidates, monitor, and remove buckets
  • Orders & Cash: live orders plus settlement timing
  • Refresh: freshness summary and explicit background refresh jobs
  • Settings: lightweight local preferences/stubs

Operational notes:

  • The dashboard is read-only for trading.
  • Live IBKR mode is the default. Use --read-only or IBKR_DASHBOARD_READ_ONLY=true when you want a saved-results-only snapshot.
  • Set the account explicitly with --account-id or IBKR_DASHBOARD_ACCOUNT_ID when the default IBKR account is not the one you want.
  • Set the watchlist explicitly with --watchlist-name or in the Settings tab. Startup flags win for that run even if saved dashboard preferences differ.
  • The page auto-loads a snapshot on first open. Refresh Snapshot is the manual force-reload control.
  • Live orders and live broker cash context only appear in live mode.
  • The dashboard process serves cached snapshot reads; the worker is the only process that executes queued refresh jobs.
  • The module entrypoints are the most robust launch path because they do not depend on Poetry having installed wrapper scripts into .venv/bin.
  • Saving settings only reloads the snapshot when the changed fields actually affect the bundle, such as account, watchlist, mode, or max-age.
  • A snapshot status like ready, read-only with Fresh count > 0 and No refresh jobs yet is normal in offline mode. It means the dashboard successfully loaded saved analyses from results/, found nothing stale enough to queue automatically, and has not been asked to run any manual background job yet.
  • If all analyses are fresh, the stale/due-soon refresh buttons stay disabled. Use a ticker list if you want to force a rerun of specific names.
  • While the Refresh tab is open, the UI polls /api/refresh/jobs every 5 seconds. In the Flask dev server logs that will look like repeated GET /api/refresh/jobs 200 lines; that is expected.

Default Investment Thesis

The built-in screen is looking for transitional value-to-growth or GARP-style opportunities, not momentum chasing.

Hard requirements:

  • Financial health score of at least 50%
  • Growth score of at least 50%
  • Liquidity of at least about $500k USD daily
  • Low enough analyst coverage to still be plausibly underfollowed

Soft factors that still matter:

  • value-trap and governance warnings
  • regulatory and jurisdiction risk
  • capital allocation quality
  • valuation stretch versus thesis quality
  • business mix and US revenue exposure where relevant

Deterministic red-flag logic can reject a name before the debate path continues. That is intentional.

Repo Layout

prompts/                     Versioned prompt JSON files
scripts/                     Screening, portfolio, and operator scripts
src/main.py                  Main CLI/runtime entrypoint
src/graph/                   Graph assembly, routing, barriers
src/agents/                  Node logic and shared agent state
src/tools/                   Tool implementations by domain
src/toolkit.py               Tool facade used by agent roles
src/data/                    Market and fundamental data fetching
src/validators/              Deterministic validation and red-flag screening
src/charts/                  Chart extraction and rendering
src/memory.py                Chroma-backed memory and macro-event support
src/ibkr/                    Portfolio, reconciliation, and broker integration
src/web/ibkr_dashboard/      Local Flask dashboard
tests/                       Unit and integration coverage

How the pieces connect:

  • src/main.py is the staged runtime entrypoint.
  • src/graph/ wires the workflow, src/agents/ owns the node logic, and src/tools/ plus src/toolkit.py provide the tool surface.
  • src/data/, src/validators/, src/memory.py, and src/charts/ are shared subsystems used by the main analysis path.
  • src/ibkr/ and src/web/ibkr_dashboard/ are adjacent operator workflows built on top of saved analysis outputs and, optionally, live broker context.

Testing

# Full suite
poetry run pytest tests/ -v

# IBKR-focused changes
poetry run pytest tests/ibkr -v

# Dashboard-focused changes
poetry run pytest tests/web -v

If you are changing core runtime behavior, run the full suite before you call it done.

Troubleshooting

Poetry or import issues

poetry env remove --all
poetry install

If poetry run ibkr-dashboard or poetry run ibkr-dashboard-worker warns that the entry point "isn't installed as a script", the commands were added to pyproject.toml after the virtualenv was created, or the project root was not reinstalled. poetry install fixes that. As a fallback, run:

poetry run python -m src.web.ibkr_dashboard.app
poetry run python -m src.web.ibkr_dashboard.worker

Python version mismatch

  • This repo expects Python 3.12.x.
  • Check with python --version and make sure Poetry is using the same interpreter.

API errors or quota issues

  • Check .env first.
  • Free-tier Gemini works, but rate limits and retries are normal.
  • If you have a paid tier, make sure the API key belongs to the right project and that your RPM settings in .env make sense.

portfolio_manager.py or analysis index rebuild is unexpectedly slow on macOS

Spotlight indexing on .venv/ or results/ can turn a normal index rebuild into a very slow one.

touch .venv/.metadata_never_index results/.metadata_never_index

Advanced Topics and References

These are real features, but they are not required to get started:

  • Container mode: the repo includes a Dockerfile and supports local bind-mounted runs. Prefer Podman if you want stronger workstation isolation.
  • Observability: Langfuse and LangSmith hooks exist for tracing and diagnostics. For sensitive deployments, LangSmith also supports LANGSMITH_HIDE_INPUTS and LANGSMITH_HIDE_OUTPUTS.
  • Inspection and tool audit hooks: see src/tooling/ if you want to inspect or audit untrusted external content before it reaches LLM context.
  • Deployment references: terraform/ contains reference infrastructure, not a turnkey hosted product.
  • Dependency note: yfinance 1.2.0 still pins curl-cffi <0.14 upstream. The repo tracks the current SSRF advisory and currently treats it as a constrained transitive risk because Yahoo data paths here are driven by ticker-like symbols, not attacker-controlled URLs.

Limitations

  • This is a research tool, not an automated trading system.
  • Data quality and coverage vary by provider, exchange, and ticker.
  • Forward catalysts and regime changes are harder than backward-looking financial analysis.
  • Broad screens can be slow on free-tier APIs.
  • Portfolio workflows depend on having saved analysis JSONs in results/.

Contributing

Contributions are welcome. Good targets include:

  • additional or higher-quality data sources
  • validator and data-pipeline hardening
  • IBKR and portfolio workflow improvements
  • Flask dashboard enhancements in src/web/ibkr_dashboard/, including drilldowns, settings, monitoring, and presentation
  • test coverage and documentation cleanup

For orientation, start with:

  1. AGENTS.md
  2. docs/CODEBASE_MEMORY.md
  3. this README

License & Disclaimer

License: MIT

Disclaimer: This system is for research and educational use. It is not financial advice.

Acknowledgments

  • LangGraph and the broader LangChain ecosystem for the orchestration substrate
  • Open-source data and infrastructure tools that make local-first experimentation practical