2026 · Ongoing · Personal investing project
Silly Prices Holdings
Built on the old Buffett and Graham idea that quality companies sometimes trade at silly prices. Keep a pre-vetted list of wonderful businesses, wait for a temporary dislocation, buy at a real margin of safety, and hold. The software's job is to make each of those steps mechanical, reproducible, and honest about when it doesn't know.
The idea
Most "buy the dip" systems buy things that are down. Most things that are down are down for a reason. The discipline here is to separate the two: only businesses that pass hard quality gates are eligible at all, "cheap" is measured against each company's own history rather than the market's, and every candidate has to survive a written diagnosis of whether the damage is temporary before anything is bought. Nothing is bought to flip.
The whole pipeline is standard-library Python over free data — SEC EDGAR's XBRL company facts and Yahoo prices — with a disk cache in front of both (24 hours for EDGAR, 12 for prices, about three requests a second to the SEC). The universe is the full S&P 500 plus three foreign ADRs (TSMC, ASML, Novo Nordisk): 507 names, re-rated daily.
A GitHub Actions job rebuilds and publishes the board every evening. A separate daily cloud routine has Claude work through the judgment queues, and commits its verdicts to the repo — so the board is just a re-render of committed data, and any verdict can be traced back to the exact prompt that produced it.
- EligibilityHard quality gates computed from primary SEC filings.
- ScreenDown meaningfully, and valued in the bottom of its own history.
- DiagnoseClaude: temporary dislocation or permanent decline?
- ValueA rule-based buy line, with a stricter cash-flow floor beneath it.
- AcquireA weekly ladder of up to three lots, each only at a lower price.
- HoldSell only on a broken thesis or egregious overvaluation.
01Quality gates from raw XBRL
Every gate is computed from the company's own filings, with current-period figures built as trailing twelve months from 10-Qs rather than stale annual numbers.
| Gate | Bar | Notes |
|---|---|---|
| Piotroski F-Score | ≥ 7 | A criterion that can't be computed counts as 0 — deliberately strict. Returns n/a if fewer than 5 of 9 are computable. |
| ROIC vs. WACC | > 9% | After-tax EBIT over invested capital, at the company's own tax rate. Fails on three consecutive declines. Returns n/a if only half the debt is tagged, rather than guessing. |
| Altman Z | > 2.99 | Skipped for ADRs, where it would mix a USD market cap with a local-currency balance sheet; distress disqualifier waived for utilities and REITs. |
| Beneish M | < −2.22 | Earnings-manipulation screen; missing inputs get neutral values and are listed. |
| Balance sheet & dilution | — | Debt/EBITDA < 3.0×, gross profit/assets > 15%, cash flow > net income, share count up ≤ 1% over five years, no two consecutive revenue declines. |
The F-Score bar earned its place in the backtest: without it, the system bought Citigroup in October 2007 and rode it most of the way down. A bar of 5 would have refused it.
02What "cheap" means
For each company the pipeline builds about ten fiscal years of EV/EBIT, P/E, and P/FCF, each priced at that year's fiscal year-end. The buy line is the 5th percentile of the company's own EV/EBIT history, applied to current EBIT — so the target rises as the business grows. It started at the 20th percentile; a backtest grid showed the deepest entries winning every sell-side comparison, and the line moved.
Each name gets one rating: BUY at or below the line, CLOSE within 8%, WATCH within 25%, RICH beyond — or one of the refusals: FLAGGED (cheap, but a value-trap disqualifier fired or the diagnosis wasn't "temporary"), GATED (failed the F-Score), NO-VALUE (history too short, too erratic, or median multiple above 40×), and AVOID. A strict cash-flow DCF (9% discount rate, growth at 70% of the company's own, capped at 3–8%) is shown as a deeper floor when it sits below the line.
One multiple doesn't fit every business, so a few names get a different lens:
- Price-to-book for financials. EV/EBIT is meaningless for a bank, where deposits read as debt.
- Normalized EV/EBIT for cyclicals. For erratic earnings — semiconductors, industrials — the company's own median margin is applied to current revenue. Nvidia and Tesla still come out NO-VALUE, which is the right answer.
- Home-currency ADRs. For foreign listings, EV/EBIT is rebuilt in the home currency (TWD, EUR, DKK) from ordinary shares, the ADR ratio, and the FX rate, then converted back to a USD ADR price.
- Hand targets, with an expiry. When the data genuinely can't support a mechanical target, a hand-set one is allowed — but it expires after 180 days and shows on the board as a dashed "hand" chip.
03The qualitative half, automated
Cheap isn't enough; the question that actually matters is why it's cheap. When a name enters the buy zone, Claude researches it with web search and returns a structured verdict:
classification temporary | permanent | uncertain
confidence low | medium | high
cause what happened
moat_status intact | eroding | unclear
five_questions q1–q5, all must be "yes" for temporary
disqualifiers_fired, key_risks, recommendation, summary
narrative with cited sources, shown on the board
Only temporary can proceed to a buy; uncertain fails safe. Each verdict
records the provider, the model, and a hash of the prompt. Verdicts are reused for a week (90 days
for "permanent") unless something happens first — a new SEC filing, a 12% price move, crossing the
buy line, or a change in the disqualifiers — so the model is re-asked when the facts change, not on
a timer. Two newer queues follow the same shape: an exit review for held names that deliberately
withholds cost basis and P&L from the model, and an informational "is this expensive price
earned?" check.
04Data quality is most of the work
XBRL is messy, and every mistake in it becomes a wrong rating. A selection of what the git log records fixing:
- Tag fallbacks. Each concept has an ordered list of us-gaap and IFRS tags; the one with the most recent data wins and the others fill gaps. Restated values win — except share counts and EPS, which keep their originals so split adjustment still works.
- Missing debt read as zero. A missing debt tag used to count as no debt. Fiserv read 0.19× leverage against a filed 4.30×; about 68 names had the same shape.
- Partial tags beating totals. Partial debt tags displaced real totals — Dominion's debt fell from $46B to $436M, and Corpay's from $10B to nothing.
- Mixed split bases. Yahoo mixed pre- and post-split bars around Monster's 2:1 split, producing a fake 53% drawdown (the real one was 7%). Splits are now applied only when the price step proves them; Yahoo also reports spin-offs as splits, which 46 of 508 names had.
- Feeds that stop moving. A detector for price series that go flat — how a merged company quietly shows up.
05Keeping score on itself
A backtest of about 400 configurations on point-in-time S&P 500 data from 1998 to 2026 gave the shape the system is built around: names the funnel would have bought beat the market by a median +3.9% over five years, the value traps it rejected lagged by −10.9%, and naive "down 30%" dip-buying lagged by −23.9%. Mechanical thesis-break selling lost in every configuration tested, so it isn't used.
The same work also produced the results that keep the claims modest. Out of sample, the configuration rankings barely held (a rank correlation of 0.24 between training and test; the training winner placed 16th of 24). And a blinded audit — could Claude diagnose anonymized filings without recognizing the company? — failed all three rounds, which means the verdicts can't be assumed free of hindsight. Going forward, a paper portfolio built on paper-broker tracks every buy, and a "graveyard" report checks whether the names the diagnosis rejected actually do worse than the ones it accepted.
06The board
The public board shows each name's rating, buy line, and verdict with its citations, with a slider to narrow the universe to the largest N companies and a what-if slider for the buy percentile. The header carries a market-regime label built from volatility, credit spreads, and unemployment data. The deploy has two independent guards — a per-file publish list and a final check that refuses to ship if a position table appears — so the paper holdings never reach the public page.
Stack
Python (standard library only) · SEC EDGAR XBRL · Yahoo Finance · GitHub Actions · GitHub Pages · Claude (daily cloud routine) · paper-broker
A personal, long-term investing project and my own methodology. Not investment advice.