Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ INVESTO_LOG_LEVEL=WARNING
# Rate limiting (optional)
INVESTO_RATE_MIN_INTERVAL=0.0 # min seconds between Yahoo calls (0 = off)
INVESTO_AV_DAILY_CAP=25 # Alpha Vantage daily request cap (free tier), then falls back to Yahoo

# India shareholding source: fetch NSE/BSE quarterly filings for the shareholding pattern.
# Set to false to force the Yahoo insider/institutional fallback (e.g. offline). Optional.
INVESTO_ENABLE_INDIA_HOLDINGS=true
INVESTO_INDIA_HOLDINGS_MIN_INTERVAL=1.0 # polite gap between NSE/BSE calls
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

**An AI investment-analysis agent you run from Claude or Cursor.**

> ⚠️ **Research and education only — not investment advice.**

Give Investo a company name — Indian (NSE/BSE) or global — and it gathers public financial
data and produces a full analysis: what the company does, its financials & ratios, a
competitor comparison, DCF intrinsic value, economic moat, risks, management, recent news,
Expand Down Expand Up @@ -38,6 +40,20 @@ For any company, Investo supplies the evidence for:
8. **Economic moat** — brand / network / cost / scale / switching-cost signals.
9. **Risk analysis** — debt, currency, concentration, regulation, tech obsolescence.
10. **Rating out of 100** — a balanced 11-bucket score with per-bucket rationale.
11. **Warren Buffett checklist** — a weighted 0–100 quality-fit score; each criterion (ROE, ROIC,
debt, owner earnings, margin of safety, management, moat) shows value vs threshold, a
pass/warn/fail with the *reason*, a confidence, and its multi-year trend.
12. **Relative to industry** — key metrics vs the peer-set median with favourable-side percentiles.
13. **Shareholding pattern** — promoter/FII/DII/public split + promoter pledge, with
quarter-over-quarter smart observations and an ownership signal (NSE/BSE filings; Yahoo fallback).
14. **5-year growth engine** — the primary engine plus ranked drivers (estimated contribution %,
per-driver risks), a catalyst timeline, and a blended growth band.
15. **Fundamentals trend, red-flags, and an investment thesis** — multi-year health at a glance,
automated deterioration warnings, and a synthesized pros/cons verdict.

Every section carries a **confidence score, provenance and reasoning** (the evidence layer), so an
AI agent — or you — can judge how far to trust each conclusion. A machine-readable `ai_signals`
digest and a self-contained **HTML one-pager** (`--html`) are available too.

### Rating buckets (out of 100)

Expand Down Expand Up @@ -74,6 +90,7 @@ investo analyze "Infosys"
investo analyze "Reliance Industries"
investo analyze "Tata Motors"
investo analyze AAPL
investo analyze "Reliance Industries" --html reliance.html # self-contained analyst one-pager
investo search "tata motors"
```

Expand Down Expand Up @@ -140,7 +157,15 @@ of Reliance?"*
| `moat_assessment` | Economic-moat signals + heuristic score |
| `risk_assessment` | Risk signals + heuristic score |
| `score_company` | 0–100 composite rating |
| `analyze_company` | Everything above bundled into one report |
| `buffett_checklist` | Warren-Buffett quality checklist: weighted 0–100 fit, per-criterion pass/warn/fail + reason, confidence & multi-year trend |
| `relative_metrics` | Key metrics vs the peer-set median (industry proxy) with favourable-side percentiles |
| `shareholding_pattern` | Promoter/FII/DII/public split + pledge, QoQ smart observations & ownership signal (NSE/BSE filings, Yahoo fallback) |
| `growth_outlook` | 5-year growth engine: ranked drivers (contribution %, risks), catalyst timeline, blended growth band |
| `fundamental_trend` | Multi-year revenue/profit/margin/EPS/ROE with per-year direction & health grade |
| `red_flags` | Automated deterioration warnings + overall risk level |
| `investment_thesis` | Synthesized pros/cons, quality grade, valuation stance & one-line verdict |
| `ai_signals` | Compact machine-readable digest (thesis, quality, confidence, ownership/growth signals, risk, valuation) |
| `analyze_company` | Everything above bundled into one report (with a confidence/provenance evidence layer) |
| `get_sec_facts` | SEC EDGAR cross-check (US/ADR only) |

---
Expand All @@ -156,6 +181,7 @@ All optional — set as environment variables (or in `.env`; see `.env.example`)
| `INVESTO_RATE_MIN_INTERVAL` | Min seconds between Yahoo calls | `0.0` |
| `INVESTO_AV_DAILY_CAP` | Alpha Vantage daily cap before Yahoo fallback | `25` |
| `INVESTO_SEC_CONTACT` | Contact for the SEC EDGAR User-Agent | repo URL |
| `INVESTO_ENABLE_INDIA_HOLDINGS` | Fetch NSE/BSE shareholding filings (else Yahoo fallback) | `true` |
| `INVESTO_DEFAULT_MARKET` | `IN` or `US` | `IN` |
| `INVESTO_DCF_*` | DCF discount / terminal / years overrides | see `.env.example` |

Expand Down
8 changes: 8 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
{ "name": "moat_assessment", "description": "Economic-moat signals + score" },
{ "name": "risk_assessment", "description": "Risk signals + safety score" },
{ "name": "score_company", "description": "0-100 composite rating" },
{ "name": "buffett_checklist", "description": "Weighted Buffett quality checklist (0-100) with reasons, confidence & trend" },
{ "name": "relative_metrics", "description": "Metrics vs peer-set median + percentiles" },
{ "name": "shareholding_pattern", "description": "Promoter/FII/DII/public split + pledge, QoQ observations & signal" },
{ "name": "growth_outlook", "description": "5-year growth engine: ranked drivers, catalysts, blended band" },
{ "name": "fundamental_trend", "description": "Multi-year revenue/profit/margin/EPS/ROE trend + health" },
{ "name": "red_flags", "description": "Automated deterioration warnings + risk level" },
{ "name": "investment_thesis", "description": "Pros/cons, quality grade, valuation stance & verdict" },
{ "name": "ai_signals", "description": "Compact machine-readable analysis digest" },
{ "name": "get_sec_facts", "description": "SEC EDGAR cross-check (US/ADR)" },
{ "name": "provider_status", "description": "Active data providers + disclosure" }
],
Expand Down
103 changes: 95 additions & 8 deletions src/investo/analysis/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,55 @@
from ..resolve import resolve
from ..sources import data
from ..sources.news import get_news
from . import evidence as ev
from .buffett import buffett_checklist
from .dcf import compute_dcf
from .growth import growth_outlook
from .industry import get_industry_intelligence, industry_outlook
from .management import get_management
from .moat import moat_assessment
from .ownership import shareholding_pattern
from .peers import compare_peers
from .ratios import compute_ratios
from .redflags import detect_red_flags
from .relative import relative_comparison
from .risk import risk_assessment
from .scoring import compute_score
from .thesis import build_ai_signals, build_thesis
from .trends import fundamental_trend

_log = logging.getLogger("investo.analysis.report")

_LLM_GUIDANCE = (
"You are Investo. Using ONLY the structured evidence in this report (do not invent "
"numbers), write: (1) what the company does and its sector/sub-domains; (2) a competitor "
"comparison from `peers`; (3) a SWOT built from `swot_seeds`; (4) advantages and "
"disadvantages from `signals`; (5) growth drivers from `growth_driver_hints`; (6) key "
"risks from `risk`; then (7) present the rating `score.total`/100 with its bucket table "
"and the DCF. Close with a one-line reminder that this is research, not investment advice."
"You are Investo. Produce a PROFESSIONAL, ANALYST-GRADE report in clean, well-formatted "
"Markdown using ONLY the structured evidence here — never invent numbers. Use headed "
"sections, tables, and ✓/⚠/✗ markers; keep it scannable. Order:\n"
"1. HEADER: name, ticker, price, market cap, 52w range.\n"
"2. INVESTMENT THESIS (lead with `thesis`): the one-line `verdict`, `summary`, then a "
"Pros vs Cons table from `thesis.pros`/`thesis.cons`. Show `thesis.quality` and "
"`thesis.valuation_stance`.\n"
"3. RATING: `score.total`/100 (`score.verdict`) with the bucket table.\n"
"4. RELATIVE TO INDUSTRY (`relative`): a table of company vs industry(median) + the "
"percentile band for each metric.\n"
"5. WARREN BUFFETT CHECKLIST (`buffett`): the weighted `weighted_score`/100 and `verdict`, "
"then a table of each criterion — status (✓ pass / ⚠ warn / ✗ fail / — unknown), the "
"`reason`, the `confidence.tier`, and the `trend_verdict` where present.\n"
"6. SHAREHOLDING (`shareholding`): the latest promoter/FII/DII/public split and pledge, the "
"quarter-over-quarter `observations`, and the `ownership_signal`; note the source (exchange "
"filing vs Yahoo snapshot).\n"
"7. GROWTH ENGINE — NEXT 5 YEARS (`growth_outlook`): the `primary_engine`, a ranked table of "
"`drivers` (name, ~contribution %, confidence, key risks), the `catalysts` timeline "
"(year → event), the blended 5y band and `growth_signal`.\n"
"8. FUNDAMENTALS TREND (`fundamental_trend`): a compact table per metric with the ⬆/➡/⬇ "
"`directions` and `health` grade, and the `overall_health`.\n"
"9. RED FLAGS (`red_flags`): the `risk_level` and each flag with its severity; say so "
"explicitly if none.\n"
"10. WHAT IT DOES, competitor comparison (`peers`), SWOT (`swot_seeds`), key risks (`risk`), "
"and the DCF (respect any low-confidence note).\n"
"11. ANALYSIS QUALITY FOOTER (`evidence`): overall confidence (score + tier), data coverage, "
"source count, latest data date (`as_of`), and any `missing_fields`.\n"
"Surface confidence and provenance wherever the evidence provides them so the reader can "
"judge reliability. Close with one line: research/education only, not investment advice."
)


Expand Down Expand Up @@ -86,6 +117,22 @@ def _build_growth_hints(ratios, industry, news) -> list[str]:
return hints


def _growth_hints_from_outlook(growth, ratios, industry, news) -> list[str]:
"""Prefer the ranked growth-engine drivers; fall back to the legacy hint builder."""
hints: list[str] = []
if growth is not None and growth.primary_engine:
hints.append(f"Primary engine: {growth.primary_engine}")
if growth is not None and growth.drivers:
for d in growth.drivers[:4]:
share = f" (~{d.contribution_pct:.0%})" if d.contribution_pct is not None else ""
hints.append(f"{d.name}{share}")
# Always include the news/CAGR-derived hints so nothing is lost.
for h in _build_growth_hints(ratios, industry, news):
if h not in hints:
hints.append(h)
return hints


ProgressFn = Callable[[int, int, str], None]


Expand Down Expand Up @@ -115,15 +162,17 @@ def analyze(query: str, market: str = "IN", progress: ProgressFn | None = None)
report_progress(1, 5, f"Fetching financials, peers & news for {symbol}")

# Run the independent, network-bound fetches concurrently (peers is itself parallel).
with ThreadPoolExecutor(max_workers=4) as pool:
with ThreadPoolExecutor(max_workers=5) as pool:
f_financials = pool.submit(data.get_financials, symbol)
f_peers = pool.submit(compare_peers, symbol)
f_news = pool.submit(get_news, symbol, profile.name)
f_esg = pool.submit(data.get_esg_score, symbol)
f_shareholding = pool.submit(shareholding_pattern, symbol, info=info)
financials = f_financials.result()
peers = f_peers.result()
news = f_news.result()
esg = f_esg.result()
shareholding = f_shareholding.result()

report_progress(2, 5, "Computing ratios, DCF, moat & risk")
ratios = compute_ratios(symbol, info=info, financials=financials)
Expand All @@ -145,6 +194,35 @@ def analyze(query: str, market: str = "IN", progress: ProgressFn | None = None)
product_news=product_news, esg_total=esg,
)

report_progress(4, 5, "Buffett checklist, relative metrics, red flags & thesis")
# Analyst-grade evidence layer. Each reuses data already fetched above (no extra network),
# and each degrades gracefully to a mostly-empty result rather than raising.
relative = relative_comparison(symbol, peers, ratios)
buffett = buffett_checklist(
symbol, ratios=ratios, dcf=dcf, moat=moat, management=management,
financials=financials, info=info, sector=profile.sector,
)
growth = growth_outlook(
symbol, ratios=ratios, info=info, industry=industry, sector=profile.sector,
payout_ratio=management.dividend_payout_ratio,
)
trend = fundamental_trend(symbol, financials=financials)
red_flags = detect_red_flags(
symbol, ratios=ratios, financials=financials, info=info, shareholding=shareholding,
)
thesis = build_thesis(
symbol, score=score, ratios=ratios, buffett=buffett, red_flags=red_flags,
relative=relative, dcf=dcf, shareholding=shareholding, growth=growth,
)
ai_signals = build_ai_signals(
symbol, thesis=thesis, red_flags=red_flags, shareholding=shareholding, growth=growth,
)
overall_evidence = ev.aggregate(
[relative.evidence, buffett.evidence, growth.evidence, trend.evidence,
shareholding.evidence, red_flags.evidence, thesis.evidence],
notes=["Overall analysis quality blended across modules."],
)

signals = _build_signals(score)
report.profile = profile
report.ratios = ratios
Expand All @@ -156,9 +234,18 @@ def analyze(query: str, market: str = "IN", progress: ProgressFn | None = None)
report.moat = moat
report.risk = risk
report.score = score
report.relative = relative
report.buffett = buffett
report.growth_outlook = growth
report.fundamental_trend = trend
report.shareholding = shareholding
report.red_flags = red_flags
report.thesis = thesis
report.ai_signals = ai_signals
report.evidence = overall_evidence
report.signals = signals
report.swot_seeds = _build_swot(signals, industry, risk)
report.growth_driver_hints = _build_growth_hints(ratios, industry, news)
report.growth_driver_hints = _growth_hints_from_outlook(growth, ratios, industry, news)
report.llm_guidance = _LLM_GUIDANCE

# Degraded-mode: the source returned essentially nothing (rate-limited / delisted / unsupported).
Expand Down
Loading
Loading