Classify Theses to Focus Evidence on Relevant Signals

First classify natural-language theses into one of 10 allowed claim types—controlled_downside, momentum_strength, low_risk, high_risk, valuation_attractive, valuation_expensive, business_quality, weak_business_quality, premium_justified, premium_not_justified—using a structured LLM prompt that returns JSON with claim_types array and short summary. Python validates against the allowed set to prevent hallucinations. This narrows evaluation: controlled_downside prioritizes drawdown and volatility; business_quality checks margins (>=25% operating, >=20% profit), ROA (>=10%), ROE (>=20%), growth (>0% YoY revenue/earnings), and revisions (>0 net EPS last 30d); valuation_attractive uses P/E<20 or forward P/E below trailing.

Rule-Based Evidence Sorting Builds Balanced Arguments

Feed classified thesis and signals (from Part 1: price metrics like ret_total, vol_annualized<30% for low_risk, max_drawdown>-15% for controlled_downside, trend>0+positive return for momentum_strength; fundamentals like beta<0.9/ >1.2, PE>30 for expensive) into build_evidence_blocks(). Hard-coded if-then rules sort into three buckets:

  • evidence_for: e.g., drawdown -10% supports controlled_downside; vol 25% supports low_risk; operating margin 30% + ROE 25% + revenue growth 15% YoY hit business_quality (tracks hits, flags if zero).
  • evidence_against: e.g., drawdown -20%; P/E 35 for attractive valuation; negative earnings growth.
  • missing_evidence: e.g., no drawdown data; insufficient quality metrics.

Beta always checked (>1.2 against, <0.9 for); flags if no ret_to_vol. Ensures explicit gaps prevent overconfidence, turning raw signals into thesis-specific pros/cons.

Verdict Engine Balances Counts with Claim Dependencies

decide_verdict() counts evidence_for (n_for), evidence_against (n_against), missing (n_missing). Caps verdicts by claim:

  • Quality/valuation claims (business_quality etc.) unresolved or partially_supported if missing>=1 and against>0.
  • Pure support (n_for>0, n_against=0, missing<2): "supported".
  • n_for > n_against: "partially_supported".
  • n_against >= n_for: "weakly_supported".
  • No evidence: "unresolved_due_to_missing_evidence".

Returns verdict + reason, e.g., "partially_supported: The available evidence supports the thesis, but important evidence is still missing." Forces humility on incomplete data.

Facts Builder Structures Inputs for Memo Generation

extract_company_context() pulls clean dict from fundamentals.General: name, code, exchange, sector, industry, country, market_cap, pe_ratio, beta, dividend_yield, description (skips None/empty). Combines with thesis, signals, evidence, verdict into single facts object as memo prompt context, avoiding scattered vars for reliable LLM outputs. Test in Jupyter: fetch AAPL prices/fundamentals 2026-01-01 to 04-01, compute signals, classify "Apple looks attractive because downside has been controlled and business quality remains high.", build evidence/verdict—outputs claim_types like "controlled_downside", "business_quality", balanced bullets, e.g., supported by low drawdown/high margins if data fits.