Financebotresearch desk研究台

Discovery › proposal

Proposal portfolio agnostic site

Written for: the site owner, deciding what to expose.

What shipped 2026-09-27

§1 — Sleeve glosses no longer speak as the owner. Four of seven said "we hold" / "we buy" / "we wait". Reworded in English and Chinese; the three already-neutral ones were left alone.

sleeve before after
evergreen "Businesses we plan to hold for years." "Wide-moat compounders meant to be owned for years."
yield today "We hold these mainly for the cash they pay out." "Owned mainly for the cash it pays out."
yield tomorrow "We buy now for the larger income later." "Bought for the larger income later."
re-rating "We wait for the price to catch up." "The thesis is the price catching up to the value."

§2 — The trim level now reads as a valuation ceiling, not an instruction. Five reader-facing sites changed. trim presupposes the reader holds the name; the number is a level, so it now reads as one.

where before after
price-bar label $282.71 trim $282.71 full value
price-axis legend $282.71+ trim · 46× fwd $282.71+ full value · 46× fwd
bar tooltip · trim $282.71+ · full value $282.71+
desk section "Above the sell price / not yet sold" "Above fair value / trading over the valuation ceiling"
per-name verdict "Above the sell price" "Above fair value"

Chinese added for all three new strings (估值上限, 高于合理价值, 价格已超过估值上限) and the three dead keys (减仓, 高于卖出价, 尚未卖出) dropped. A missing key falls back to English silently, so this had to move in lockstep.

Regression guard. python .mcp/site.py --selftest now asserts that no string in SLEEVE_GLOSS or VERDICT contains owner voice (we|our|us|trim|not yet sold|sell price), and that every reader-facing string has a Chinese key. The pattern itself is asserted too, because this guard shipped inert for an hour with backspace bytes where the \b boundaries should have been — a mangled pattern passes everything and looks like success.

Verified against the old copy: the guard catches all four previous strings.

What did NOT get fixed, and why you should probably leave it

The word "trim" still appears ~1,250 times in authored content. The chrome is clean; the vocabulary is not.

where count what it is
report bodies 933 the analyst's own verdicts and reasoning
company pages 188 Layer 2 thesis prose (Trim 42x fwd)
kb notes 94 method notes about trim discipline
chrome pages 33 also thesis prose, rendered on desk cards — not label maps

Fixing this is a different order of job, and it would break things:

  1. TRIM is a frontmatter enum, wired into kb.py, verdict_check.py, tickers.py, alerts.py, due.py, CLAUDE.md, analysis_notes.md — and written into 391 report files. Renaming it is a schema migration.
  2. Trim 42x fwd in Layer 2 is the source the price parser reads (TRIM_MULT_RE → trim_from_multiple()). Rewording it breaks the live dollar level on every price bar unless the parser moves in the same commit.

The recommendation is to keep it. A report that says TRIM is stating one analyst's verdict, which is the product. The thing worth removing was copy that addressed the reader as though they held the position — that is done. Content describing the author's own conclusion is honest, and sanitising it would cost clarity for no privacy gain.


§3 — The call that is actually yours: the universe

This is not a code problem and cannot be fixed in site.py.

The published universe is 151 names, and its shape reflects your taste, not a neutral screen.

Knowledge/Themes/defensive-income-fields.md states it plainly: healthcare, energy, industrials, staples and telecom were swept three times (Mar 19, Mar 28, Jul 29) and "rejected by the user on business-model grounds rather than valuation… a preference constraint, not a valuation one, and it doesn't expire when prices move."

That constraint is working as designed — the 2026-09-26 scan ran the yield-tomorrow screen, got 20 names (XOM, CVX, MO, AFL, DOV, CL, BDX…) and nominated zero, correctly. But the result on the published site is:

Technology + Semis + Cybersecurity + Infrastructure + Fintech 51% Financial Services 4% Energy 4% REITs 2%

Yield-Today is 12 names. REITs is 3. An income-oriented reader gets a distorted map — and the absences are invisible. Nothing on the site says "energy was considered and declined." It simply looks as though energy does not exist.

Three options

(a) Label it — recommended, cheap. Publish the field-level rejections you already wrote. Knowledge/Themes/ holds the reasoning for every declined field; it is currently indexed but the absence is never connected to it. A short "Fields considered and declined" section turns an invisible gap into a stated editorial position. Mostly links to notes that already exist.

(b) Broaden the universe. Research names you have deliberately declined, permanently. Real recurring cost, and it would dilute your own signal. Not recommended.

(c) Reframe the site. State it as one investor's research desk rather than a neutral screen, and let the filtering be assumed. Cheapest of all, but it leaves a reader to discover the skew themselves.

(a) plus a one-paragraph "how to read this" is the honest combination: one analyst's conviction scores, no positions disclosed, a deliberately filtered universe with the filters published, valuation levels rather than instructions, and not investment advice.

Draft copy for a "How to read this" note

What this is. One investor's research desk, published. Every conviction score, entry band and full-value level is a single analyst's judgement, not a consensus and not advice.

What it does not contain. No positions, sizes, cost bases or account values — by construction. The build refuses to publish if any appear.

How the numbers work. Entry is the band at which the analysis considers a name worth buying. Full value is the valuation ceiling, expressed as an earnings multiple so it rises as earnings grow. A verdict of TRIM is a statement about price versus value, not an instruction to you.

What is deliberately missing. The universe is filtered. Several fields — most of defensive income, energy and staples — were screened and declined on business-model grounds rather than price. Those decisions are published under Fields considered and declined, so you can weigh them yourself rather than assume the field was never looked at.

Effort summary

item status cost
§1 sleeve glosses ✅ shipped ~1h
§2 trim → full value (chrome) ✅ shipped ~1h
§2b TRIM vocabulary in content ⛔ not recommended schema migration, 391 files
§3a publish declined fields ⏳ your call ~2-3h, mostly linking existing notes
§3 "how to read this" page ⏳ your call ~1h, draft copy above
§3b broaden the universe ⛔ not recommended permanent research cost