---
name: rhx-index
description: RETIRED 28 Sep 2026 on Ben's instruction, stopped and removed from the droplet with the regime tracker. RHX, the Robinhood Chain index suite he commissioned 15 Sep 2026 - design, sectors, HEAT gauge, backtest, traps. Historical record only; do not treat /opt/rhx as live.
sources: [cowork]
aliases: [RHX, rhx, rh index, robinhood chain index, chain index, heat, crowd profit, rotation gap]
---

# The ask, 15 Sep 2026
- [stated] Ben: there is no chart with an index for Robinhood Chain. Other chains' charts and TOTAL3 do not reflect what happens here. He wants an index, a dashboard, sectors (meme stocks, long.xyz, infrastructure, top plays by market cap), hourly candles with lower timeframes available.
- [stated] The purpose in his words: "identify when the market will pull back in the high timeframe and then, when the lows are nearby, to reallocate again. I'm basically looking for risk-off signals and then risk-on signals."
- [stated] He asked whether the index should be rotating, and proposed a top 20, noting the chain is so dynamic that today's top 20 will not be next month's.
- [stated] Same session he pasted a long research brief and said to fold it in. Three things from it changed the design: pick the comparison group BEFORE the rally rather than replacing losers with winners; measure executable sell depth rather than market cap or TVL; measure memestocks against their PAIRED asset (MOO/MU), not only against dollars. He also asked whether a fear and greed index would be useful and whether "how much people are in profit" is evidence-backed.

# THE ANSWER ON ROTATING, and it is the design
**Yes rotating, but a rotating index alone is useless for his question, because it swaps losers out and winners in and therefore can never show him the damage he is trying to avoid.** So three legs run off the same 20 names:
| leg | what it is | what it answers |
|---|---|---|
| **RHX** | rotating top 20, cap-weighted, 15% cap per name | how is the typical big coin |
| **RHXU** | same 20, NO cap | what is this chain actually worth |
| **RHXF** | the basket from the FIRST rebalance, never swapped | what a buyer of "the market" that day really got |
| **RHXEW** | same 20, equal weight | is it broad or is it one coin |
The SPREAD between them is the signal, not any single line.

## CORRECTION TO THE ASSUMPTION THE BUILD STARTED FROM
I expected the rotating index to flatter the chain and the frozen basket to lag. **Measured, it is the opposite here.** The 10 Aug frozen basket is at 2.65x against RHX 1.42x, because **PONS went 14.4x from that rebalance at a 13.1% starting weight** and the 15% cap trims it back every Monday while the basket lets it ride. On a chain where one name carries the month, **rebalancing is the drag, not the flattery.** Both lines are kept and RHXU was added because of this.

# CONSTRUCTION, and why each number
- Universe 283 native RH tokens from `/opt/rhtop/universe.csv` (class NATIVE, STOCK/MAJOR/BRIDGED/GT-ONLY excluded) plus the long.xyz cohort from `/opt/longboard/lb.db`.
- **Rebalance Monday 06:00 UTC**, the measured quiet hour on this chain (from [[regime-tracker]]: volume peaks 12:00-15:00 UTC, bottoms 04:00-07:00).
- **20/30 rank buffer**, max 25% turnover per rebalance, **7-day minimum age**, **$250k market-cap floor**, $15k liquidity, $5k 24h volume.
- Weights drift between rebalances (buy and hold), so the index is what a basket did, not what a rebalancing strategy did.
- Sectors are **MEME** (plain memecoins, ETH/USDG quoted), **MEMESTOCK** (quotes against a tokenised equity, detected mechanically off the quote token), **INFRA** (DefiLlama protocol symbols on Robinhood plus a curated list; launchpads fold in here), **LONG** (venue overlay, anything in lb.db).
- **Sector indices are EQUAL WEIGHTED.** Cap-weighting a six-name sector just prints its biggest coin, and Ben's question is how the sector is doing, which is the typical coin in it.
- **VENUE and SECTOR are separate axes.** The first run merged them and put every long.xyz token into MEME, leaving LONG with 15 rows.

## MEASURED FACTS ABOUT THE CHAIN, found while building
- **long.xyz on Robinhood Chain is a micro-cap lane.** 37 of its launches are priced on DexScreener; exactly ONE (BOXY, $3.4M) is over $100k. Everything else is sub-$100k dust. A long.xyz index is therefore a bottom-of-the-chain risk-appetite gauge, not a sector index, and runs on a $20k floor.
- **Memory Cow MOO is NOT a long.xyz launch.** It is not in lb.db. There is a different, unrelated MOO in there. Do not assume the "Long cohort" framing from outside research maps onto the venue.
- **The launchpad sector is five tokens** (PONS, PRM, HOOKR, CME, PAIR) and two of them are under a week old, so there is no honest standalone launchpad index. PAD stays a tag on the token and indexes inside INFRA.
- **`lb.db.mcap` is only populated on enriched rows.** "9 long.xyz tokens over $100k" off that column is an enrichment gap, not a fact.
- INFRA's 7.6x since 17 Aug is real, not a data error: HOOKR 13.5x, DELTA 4.0x, NET 3.3x, INJOH 3.1x in the single week of 17-24 Aug, then 3.17x again the following week.

# THE BACKTEST, and it is what sets the signal's sign
Train on the first 60% of the history, test on the last 40%. **36 days, 133 overlapping observations, 15 feature-horizon tests.**
| feature | 24h train / test | reading |
|---|---|---|
| **price / 14d VWAP (mvrv)** | **-0.52 / -0.47** | strongest single term, MEAN REVERTS |
| share in profit | -0.35 / -0.44 | same sign |
| share at weekly highs | -0.30 / -0.40 | same sign |
| **breadth (equal wt vs cap wt, 7d)** | **+0.13 / +0.36** (+0.87 / +0.70 at 168h) | PERSISTS |
| rotation gap | flipped at every horizon | unusable, shown but never traded |
- **The profit axis mean reverts and the breadth axis trends.** That is the shape Ben described from instinct ("it will peak on some day and then we might get a significant decline") and it matches [[regime-tracker]]'s finding that a hot tape does not predict a hot tomorrow on this chain.
- **The first composite that shipped was BACKWARDS** and was replaced, not patched: its RISK ON days averaged +3.2% over the next 72h against +11.7% on NEUTRAL days.
- Caveat that travels with every number: six survivors out of fifteen tests is more than chance would hand you, but 36 days of overlapping windows is not enough to size on.

# HEAT, the answer to his fear-and-greed question
- [stated] Ben asked whether a fear and greed index would help and whether "how much people are in profit" is evidence-backed.
- Sentiment fear-and-greed is a survey plus volatility and carries nothing. **The profit version is the real one and it is computable here.** HEAT 0-100 = where today sits in this chain's own history on three terms: median member price / its own 14-day VWAP, share of members above their VWAP, and share within 2% of their weekly high. That is a supply-in-profit measure (the MVRV/NUPL family) built from candles rather than from a cost-basis reconstruction, which RH chain's 0.10s block time makes hopeless.
- **The mechanism, and it is why it works:** when almost everybody can sell at a gain, that is the supply that hits the next rally. When almost nobody can, the sellers left are taking losses, which is what exhaustion looks like.
- 15 Sep 2026 first read: **HEAT 18, breadth +2.07%, verdict BUY.** 30% of the 20 above their own 14-day cost, median member at 0.81x cost.

## THE ACTION GRID, two axes, four boxes, one plain instruction each
| | narrowing | broadening |
|---|---|---|
| **hot** (HEAT >= 75) | **TAKE PROFIT** | **TRIM** |
| **mid** | HOLD | HOLD |
| **cold** (HEAT <= 25) | **WAIT** (cheap is not done) | **BUY** (the reload) |
The word is computed on the droplet and READ by both the dashboard and the Telegram push, so the page and the message can never disagree.

# WHAT IS BUILT
- **`/opt/rhx/rhx.py`** on kookwatch. Subcommands `universe / backfill / tick / rebalance / index / heat / macro / signals / publish / all / daily`. SQLite `/opt/rhx/rhx.db` (uni, px, memb, idx, sig, depth, macro, meta).
- **`/opt/rhx/rhx_notify.py`** `daily / flip / watch`, reusing the linkscan bot token and chat id.
- **`/opt/rhx/rhx_test.py`** the train/test backtest. Re-run it as history accumulates.
- **`/opt/rhx/rhx_import.py`** imports a container-side candle pull.
- **Dashboard: http://159.223.88.168/rhx.html**, served by the existing lwserve static server out of `/opt/launchwatch/pub`, reading `rhx.json` beside it, self-refreshing every 2 minutes. Timeframes 1H / 4H / 12H / 1D resampled client side.
- **cron**: `7 * * * *` rhx.py all (43s per run), `25 5 * * *` universe + macro, `35 * * * *` notify watch, `40 * * * *` notify flip, `30 10 * * *` notify daily (17:30 Bangkok, same cut as [[regime-tracker]]).
- Data: DexScreener for price/cap/liquidity (pools within 25% of the top-VOLUME pool only), GeckoTerminal hourly candles for history anchored to the DexScreener price, DefiLlama free endpoints for stablecoins and DEX volume.

# TRAPS HIT, all of them the same shape as the ones already in this project
1. **THE INDEX WAS REWRITING ITS OWN HISTORY.** Weights used today's token supply, which DexScreener revises, so recomputing a past rebalance could swap a member and change a level that had already printed. RHXEW moved **12.7% on a recompute with no new information**. Fixed: past rebalances are frozen, base prices are stored in `memb.base_px`, and only a new rebalance is ever computed. `RHX_FORCE=1` to rebuild deliberately. **An index's history must be immutable.**
2. **A $35k COIN IN AN INDEX.** ICOIN joined MEMESTOCK at an 18.3% weight on 31 Aug with a $35k cap and then genuinely did **153x**, printing a sector index of 160,000 from a base of 1,000. The move was real; the coin had no business being indexed. Fixed with the market-cap floor, equal-weighted sectors, a 6-name minimum, and a 20x per-member winsor inside a rebalance window.
3. Member prices are smoothed with a **rolling median of three** before they can move an index, the same fix [[regime-tracker]] needed after one corrupted poll wrote a peak across six tokens.
4. `rebalance()` used `insert or replace` and left stale membership rows from an earlier ruleset alive, so a filter change appeared to do nothing. Recomputed tables get deleted, not merged.
5. Stale membership plus a lower floor is how you get a sector index that is up 160x and looks like a bug in the chart code.

# TRANSFER PLUMBING, reusable and worth keeping
- **The Cowork container has no outbound route to the droplet** (per [[data-access]]). The working path this session, faster and cheaper than writing through the Mac: upload from the container to **litterbox** (`curl -F reqtype=fileupload -F time=24h -F fileToUpload=@file https://litterbox.catbox.moe/resources/internals/api.php`) which returns a direct URL, then `curl` it on the droplet. Costs zero tokens per transfer.
- **tmpfiles.org does NOT work** for this: `/dl/` serves an HTML viewer page, not the file. catbox.moe's main endpoint returns "Invalid uploader"; litterbox works.
- **GeckoTerminal from the container is roughly 8x faster than from the droplet.** The one-time 283-token hourly candle pull (median 508 candles, max 1000, ~57 days of coverage, 159k rows) took about 21 minutes in the container at a 2.1-2.6s gap; the droplet was doing 18s per token. Do bulk GT pulls container-side and import.
- Desktop Commander device calls **time out at 60 seconds**, so every droplet job over a minute runs as `(setsid nohup bash -c "..." > log 2>&1 < /dev/null &)` and gets polled. A plain `& disown` inside the ssh command does NOT survive.
- A `for` loop with `${var%%:*}` inside an ssh double-quoted command silently does nothing. Use separate explicit commands.

# THE HORIZON THIS SERVES, stated 15 Sep 2026
- [stated] Ben trades this on a **couple of months**, flexible: faster if the trend develops faster, longer if it stays strong. No minimum hold, no commitment to ride a whole cycle.
- [stated] He wants to spot a trend early, ride the main part of it, and exit on stated evidence. Catching the exact bottom or top is explicitly NOT the goal.
- [stated] A 2x or 3x is an **ambition, not a required exit**. A smaller move can still be a good trade.
- [stated] He treats "another year to eighteen months of possible bull market" as a planning scenario, not a forecast.
- Consequence for this build: the zone is a market description, sector/token selection is a separate question, and neither sets position size. Exposure stays UNDEFINED until a policy is written down in advance and tested.

# THE TIMING TRAPS, found 15 Sep 2026 by outside review and they are the deep ones
6. **THE ZONE-CHANGE COUNTER COUNTED INITIALISATION.** `flips + (1 if n else 0)` turned one transition into two, and I published an argument built on the inflated number. The first reading is initialisation, never a flip. Also collapse rows to one per stamp BEFORE counting, or a rebuild at the same hour becomes a second hour.
7. **THE DOCUMENT DESCRIBED AN ALERT RULE THE CODE DID NOT HAVE.** The spec claimed a two-reading confirmation since revision 3; `rhx_notify.flip` pushed on ONE reading. Nothing contradicted it because no output showed the rule. **This is the most dangerous defect class in the project: correct-looking prose over absent code.** Check the code whenever the document asserts a behaviour.
8. **"SETTLED" WAS AN IMPOSSIBLE LABEL.** The member-price smoother is a CENTRED median of three, so the value stamped t needs t+1. A settled zone for the newest hour needs an hour that has not happened. Real-time rules must count **AS PUBLISHED** readings, available at their own stamp.
9. **EVERY POINT SETTLES ONE HOUR LATE, not just the newest.** Measured on 20 members: **44.3% of member-hours are revised once the next hour lands, median 1.15%, max 17.45%.** The index level barely moves (16:00 went 1462.333 to 1461.375, -0.066%) because member revisions are independent and cancel.
10. **THE BACKTEST BORROWS THAT HOUR.** `rhx_test.py` reads `idx.lvl` at a stamp, which is the settled value containing the next hour's price. Against the next actually-tradeable level, 845 samples: mean **+0.068%**, median +0.007%, **90th percentile of |difference| 2.46%**. Centre near zero so the correlations are probably not inflated; the tail is real. **Nothing in the backtest section is a tradeable result until features and entry levels are recomputed on the as-published series.** Fix stated, not yet applied.
11. **MAKE THE STORED RECORD THE STATE.** The notifier now derives its streak from journal rows (`min(jid) group by ts`, the first write for an hour) instead of incrementing a counter, so re-running cannot advance anything and the page and the alert cannot disagree. Prefer derived-from-stored-rows over any tally.
12. `/opt/rhx/rhx_trace.py` prints the availability audit: stamp, jid, publish time, zone, build, whether it votes, the replayed streak, and the settle/look-ahead measurements. Run it whenever a timing claim is made.

# THE DASHBOARD SHAPE HE ASKED FOR, 15 Sep 2026
- [stated] Ben: keep the main page minimal, attractive, easy to scan. Lead with sell/hold/buy, then sector performance, then actions for his holdings and possible new buys. Everything else folded away.
- [stated] **Sector weakness is NOT an exclusion criterion.** A strong individual coin stays eligible even when its sector lags. Sector strength supports a coin's case, it never vetoes it.
- [stated] His holdings as of 15 Sep 2026: Tibbir, MOO, Marscoin, DELTA, SCHIFFY, Premium/PRM, PAIREX, GASOLINU. He gave no quantities, entry prices, sizes or exit rules.
- Consequence built into the page: with no entry and no invalidation on file, the dashboard CANNOT print HOLD or SELL per coin, because HOLD means "your plan is still valid" and there is no plan. Held rows describe what the coin is doing and ask the question worth asking. Ask him for entry + invalidation per coin to turn these into real states.
- Resolved to one contract: MOO, DELTA, PRM, GASOLINU (already in the feed), plus SCHIFFY `0x42aFA2124ca5a2B83898E46B2dA9a190995b1E18` and PAIREX `0xB8Cf4ad387cfd607C66a207CFFFC46498aACd9d6` added as active=2 reference rows.
- **ALL EIGHT RESOLVED 15 Sep 2026 from pool links Ben sent.** TIBBIR = Ribbita by Virtuals on **Base** `0xA4A2E2ca3fBfE21aed83471D28b6f65A233C6e00` (quoted VIRTUAL). MARSCOIN on **BNB Chain** `0xFe189E97832DA1573e4e4Ff034F4fFC3a15c7777` (quoted USDT), matching the standing research. PAIREX `0xB8Cf4ad387cfd607C66a207CFFFC46498aACd9d6` (USDG) and SCHIFFY `0x42aFA2124ca5a2B83898E46B2dA9a190995b1E18` (GLD) confirmed on Robinhood. **Asking for the pool link is the fast, safe way to settle a ticker collision: it took one message and removed all guessing.**
- `tick_offchain()` polls the non-Robinhood holdings on their own chains via DexScreener `tokens/v1/{chain}/{addrs}` and stores them in px like any other row, so returns/staleness/states work with no second code path. They are `active=2` so they can NEVER enter the index or the discovery list.

# THE VERIFICATION TRAP, 15 Sep 2026, and it is a standing rule now
13. **"RENDERS CLEAN" IS NOT A TEST AND I SHIPPED IT AS ONE.** I checked the console and a screenshot, called a new dashboard verified, and an outside review then found three live defects in code I had just written. **A clean console proves nothing threw. It proves nothing about whether a control works, whether an element has width, or whether a state is right. CLICK every control, MEASURE every element, FORCE every branch.**
14. **A handler bound before its container's innerHTML is rewritten is a dead control that throws nothing.** render() bound the sector toggle, then replaced the container further down. Delegate on the container instead, bound once outside render.
15. **A flex bar with fixed-width siblings collapses to 0px on a phone**, and a desktop screenshot cannot show it. Measure with getBoundingClientRect in the page, do not eyeball.
16. **`null > 0` and `null < 0` are BOTH false**, so a comparison chain falls through to its last branch. A missing return rendered as RISING. Require Number.isFinite on every input before claiming a direction.
17. **`INSERT OR REPLACE` on px means an hour can be REWRITTEN.** Sectors were recomputed live from px while the index came from storage, so one file labelled 18:00 carried two price vintages (Memes ex-top read +1.47% in one publish, -0.03% in the next, same label). publish() now rebuilds the index when px is newer, and a `times` block publishes observation / index_built_to / px_latest / sectors_computed_at / published_at.

# THE SWING LAYER, built 15 Sep 2026
- [stated] Ben is a **swing trader**. The dashboard must recommend and explain holding, trimming, exiting, adding and switching over weeks to months, starting from his portfolio, comparing new candidates against what he already owns.
- [stated] He should not have to invent every technical invalidation himself. Research the charts and PROPOSE entry/hold/exit/trim conditions; he supplies sizes and risk limits and picks the plan. An old entry price is useful for P&L but is NOT needed to judge whether to hold from today onward.
- `/opt/rhx/rhx_swing.py`, versioned **swing-policy-0.1**, separate from the frozen rhx-3.2 index. Writes `/opt/rhx/plans.json`; publish() embeds it as `swing`. Cron 20 0 * * * (07:20 Bangkok, after the UTC daily close).
- Rules: COMPLETED UTC days only (running day dropped). Stop = last **confirmed** daily swing low minus 0.5 x ATR(14). A 2-2 pivot is not known until two days later and its available-at date is that later close, never the pivot date. Under 28 completed daily candles a coin gets the EARLY ROUTE and no invented daily level.
- **Measured 15 Sep: DELTA stop -37.0%, TIBBIR -34.4%, MARSCOIN -10.7%, all three reading "hold conditions intact".** The stop DISTANCE is the headline, not the state: three coins all showing HOLD are three completely different position sizes. Always show it.
- Backfill first, always: four holdings had 2-3 hours of history. GeckoTerminal gave 42 daily candles for DELTA/TIBBIR/MARSCOIN, and the rest are 3 to 17 days.
- **GT throttles the droplet far harder than the container.** First droplet run lost 4 of 8 series to 429s. Fix: candle cache on disk, reused on a failed pull; and prefer computing container-side then pushing plans.json. Also: a cache fallback placed inside a fetch loop is skipped by `continue` paths, so put it in its own loop after.
- **swing-policy-0.2, 15 Sep 2026: the five outputs are live** (exit, trim, keep holding, buy, switch), each with a reason, an exact trigger, a cancellation condition and a size. Daily decides hold/exit, four-hour decides entries/trims, completed bars only.
- Rules: EXIT = completed daily close at/below the stop. TRIM = 3 ATR extension AND a completed 4h close below the last 6 bars' low (extension alone never trims), 25% of the position. BUY = completed daily close above the previous 14 daily highs with a rising EMA20. SWITCH = B must pass its OWN entry first, then beat A over 7 and 21 days, costs out first.
- **SIZING WITHOUT HIS ACCOUNT, and I was wrong to say I needed it.** position value = (what he is willing to lose) / (distance to stop). So publish **coin per 1 of risk**, computed from the chart. Measured 15 Sep: MARSCOIN 9.35x (10.7% stop), TIBBIR 2.91x (34.4%), DELTA 2.71x (37.0%). He supplies ONE number, risk per coin, and every row is sized. Never ask for an account size again.
- **THE RISK GAP, and it is the most useful thing the layer found:** median stop on his holdings **-34.4%**, on the "coins going up" discovery list **-72.8%** (WOJAK -74.6%, FRONG -79.3%, HOODRAT -72.8%). For the same money risked he would own about half as much coin. **A 7-day return leaderboard hides this completely.** Always show stop distance beside any candidate.
- 15 Sep result: all three swing holdings KEEP HOLDING, no exits, no trims, and **zero rotations qualify** because not one candidate closed above its previous 14 daily highs.
- `/home/claude/rhx/swing2.py` (container) computes it; plans.json is pushed to the droplet and embedded by publish() as `swing`.
- NOT built and not claimed: fundamental thesis checks, the five-minute risk feed (the feed is hourly, so five-minute protection cannot be claimed from it), and a forward journal for swing states.

# SOURCE SELECTION, src-0.2, 15 Sep 2026, and it was the biggest single error
18. **THE POOL WAS PICKED BY 24-HOUR VOLUME, so the longest history was invisible.** MOO was called "too new to plan" on 14 days from its busy MOO/WETH pool while MOO/MU carried **55 completed days AND was the deeper pool** ($2.96M against $201k). Volume today says nothing about how far back you can see. **Select a price source on USABLE CONTINUOUS HISTORY; let reserve break ties only.**
19. **GeckoTerminal's hourly OHLCV caps at 1000 candles, about 42 days.** Everything older is silently missing. TIBBIR read 43 days when it has 177. **Page backward with `before_timestamp` until the source stops returning new rows.**
20. **The exotic-pair guard must distinguish a unit bug from a different asset.** Compare a pool's own DexScreener price to its own GeckoTerminal close: disagreement there is a units problem and rescaling is honest; agreement there while both differ from the token's main market means a different market, and the pool is REJECTED, never scaled into agreement. The old guard rescaled anything within 6 orders of magnitude.
21. **ATR ON HIGHS AND LOWS PRODUCES NONSENSE ON THIN POOLS.** A single bad swap prints a wick: MOO's lowest daily low in 55 days is **460x below its price**. That fed stops of **-0.0011 (PRM)** and **-0.00055 (GASOLINU)**, negative prices. **Build levels and volatility from completed daily CLOSES**, which is also what the trigger reads ("a completed daily close below X").
22. **AN EMA MAKES THE ANSWER DEPEND ON THE FETCH.** Same coin, same day, same rules: TIBBIR read TRIM on the droplet (43 days) and KEEP HOLDING in the container (177), because an EMA carries its seed and had not converged. **Use a simple moving average for anything a decision reads.** Verified identical at 44 / 63 / 177 days.
23. **A half-written cache is worse than no cache.** `json.load` threw, a bare `except` started from nothing, and a run silently used 49 days where 55 existed. **Write caches atomically (tmp + os.replace) and MERGE by key, never replace** — a 429 must not be able to shorten history the system already had.
24. **THE SAME SILENT-EXCEPT PATTERN APPEARED THREE TIMES IN ONE DAY.** Third instance: `computed_at` changed from epoch to ISO, `time.time()` minus a string raised, the handler swallowed it, and the page rendered "the swing layer has not published yet" over a perfectly good plan. **A broad except around a feature turns a type change into a missing feature. Log it or narrow it.**

# swing-policy-0.5, 15 Sep 2026
- **The blanket 28-day gate is gone.** It refused a risk plan to five coins Ben is exposed to right now. Each rule now reports evaluated, or which input it lacks with counts: volatility needs 6 daily candles, the 20-day average needs 20 (nothing shorter is substituted), a confirmed pivot needs 5, the breakout entry needs 15 plus the average.
- **The buffer under a stop is the coin's OWN median pullback that it later fully recovered**, with the sample count published beside it. Half an ATR was 9% under support for a coin whose recoveries run 5% to 55%. Measured buffers: MOO 49% (n=3), PRM 39% (n=1), GASOLINU 33% (n=3), DELTA 32% (n=6), TIBBIR 14% (n=5), MARSCOIN 13% (n=4), SCHIFFY 5% floor (n=4).
- **A 2-2 pivot's known-at date was one day early.** The confirming daily candle is stamped at its OPEN and does not finish until the next midnight. `known = stamp + one bar`.
- **Conditional buys must be sized at the PLANNED ENTRY, not today's price.** HOODRAT read 2.11x per 1 of risk at spot and **1.41x at its actual breakout entry**, a third less coin.
- **An empty rotation list is not a finding.** Publish a status code: NOT_EVALUATED, NO_ENTRY, COST_BLOCKED, NO_ELIGIBLE_REPLACEMENT, READY. All five forced and verified.
- **Stale protection must be computed once, upstream of every renderer.** The old table read the raw action while the banner said the page was not current. Forced with a 72-hour file: 18 No-call rows, zero instructions.
- **Plans persist to `/opt/rhx/plan_state.json`: a stop is never moved down and a trim never repeats on refresh.** Both fired 15 Sep (MOO held 0.0074217 against a recalculated 0.00617664).
- **Publish an EXIT with its margin.** MARSCOIN broke its stop by 0.8%, which is 0.05 of one ordinary day for that coin. A 0.8% break and a 30% break must not read the same.
- Files: `/opt/rhx/bf_hold2.py` (source discovery, weekly, 48 pools audited to `sources.json`), `/opt/rhx/swing3.py` (the policy), `/opt/rhx/rhx_swing2.py` (droplet runner, daily 20 0 * * *).

# THE FRIEND'S TOP INDICATORS, tested 15 Sep 2026
- [stated] Ben pasted a friend's 8 Sep 2026 Telegram note calling a top (DEX volume bands, alt OI excl BTC+ETH "topish", "Trump launches a coin = max liquidity = top", "too easy, no shakeout") and said he was "pretty much spot on". Asked to use the indicators only if they make sense.
- **The call is unscored, not proven.** BTC 78,456 on 8 Sep, 76,308 on 15 Sep, **-2.7% in seven days**. The part he got right is backward looking: entries 4 to 6 weeks earlier at ~63,900, now +20%. Calling a past entry is easy.
- **DEX volume bands REJECTED.** Raw >$90B/week looks strong (90d median -13.9% vs +6.2% baseline) but it is **8 episodes, 6 of them in 2025**. An absolute dollar threshold on a growing quantity only fires in the newest, worst-performing stretch. Detrended to a trailing-year percentile the edge swings from **-16.4 points (test from 2020) to -2.3 points (from 2021)** on a one-year shift of the start date. And the COLD end (bottom 20%) also predicts -7.8% at 90d: when both tails predict down you are measuring the sample's drift, not the indicator.
- **"Too easy, no shakeout" is BACKWARDS.** Coded as near 90d highs with no 10% pullback in 60 days: **90d median +23.1% vs +6.2% baseline, only 24% of those days lower three months on.** An advance that refuses to shake anyone out historically kept going. This is the intuition that feels most like wisdom and it is the one that measured wrong.
- **"Trump coin = top" is n=1.** Not codeable, not testable.
- **KEPT, pending data: alt open interest excl BTC and ETH crossing above BTC OI.** Rare (about six short episodes in 3.5 years, clustered near the Jan 2025 and late 2025 highs), a RATIO so it cannot go stale the way a dollar threshold did, and the only one that is cross-market. **Everything RHX reads is internal to Robinhood Chain**, so this is genuinely new information rather than a second opinion on extension. **Blocked: Coinalyze needs a free API key Ben has not registered.** Ask once, then wire as a market-wide risk flag beside the zone.
- **Trading terminal volume > $4B a week REJECTED, and it is the weakest of the four.** His own chart shades TWO clusters in 5.5 years (Dec 2024 to Feb 2025, and now), so "levels seen near previous local topS" rests on one previous top. Worse, **the basket changes underneath the line**: pulling the ten biggest terminals from DefiLlama, nine only start reporting mid 2026 (Axiom 110 days, Photon 133, Trojan 106, BullX 104 against GMGN's 1069), which lifts the combined series 24x between Sep 2025 and Sep 2026 with no behaviour change. A fixed $4B line compares a ten-member basket to a one-member basket.
- **THE UNIFYING RULE, and it is reusable on any pasted chart: every chart with a DIAL was tuned; the one with no dial survived.** The note opens "some thoughts and charts pulled with AI". Weekly DEX volume needs $60B/$90B picked, daily needs $10B/$20B/$25B, terminal needs $4B. Alt OI against BTC OI needs NOTHING picked, it is a crossover of two series. **Ask a model to shade where volume was high when you already know where the tops were, and the threshold you settle on is the one that makes the shading land on the tops.** No dishonesty required. When Ben pastes a chart with shaded bands, the first question is what number had to be chosen by hand.
- Terminal volume data does exist on DefiLlama per protocol: GMGN, Axiom, Photon, Trojan, BullX, Banana Gun, Bloom Trading Bot, BONKbot, moonshot.money, pump.fun Mobile App, all under `api.llama.fi/summary/dexs/{name}` (categories "Telegram Bot" and "Trading App"). History is short for everything except GMGN.
- Method note worth reusing: **count EPISODES, not days.** 246 consecutive shaded days is one event. Every band-style chart invites this error.
- Data that works from the container: DefiLlama `api.llama.fi/overview/dexs` (free, 4,594 daily points) and **`data-api.binance.vision`** for BTC klines. `api.binance.com` returns 451 from this region and CoinGecko throttles to 429.
- Write-up: artifact "The Too Easy Signal" https://claude.ai/artifact/YGCf4qpXACXo3oxXmgPXYN

# THE PAGE LAYOUT HE ACTUALLY WANTS, 16 Sep 2026
- [stated] "Format it properly. Use only very short texts and words. Talk to me like I'm 5. I want the graphic with the chart. Zone on top, then TWO COLUMNS of coins: one column I own with actions on the left, one column I don't own that are interesting on the right."
- Shipped: chart card moved ABOVE the coin list, then a `.twocol` grid, "You own these" left, "Worth watching" right, stacking to one column under 700px. Each row is four things only: ticker, action chip, four words, one level. The engine's full sentence became the `title` hover, not printed text.
- **THE DEFECT THAT FORCED IT: a wide table with prose in a cell overlaps its own number columns.** The seven-column decision table put "a completed daily close below 0.00763741, which is -55.5% away" straight through the TO STOP column, so two values sat on top of each other and neither could be read. **Long text and narrow numeric columns cannot share a row. Put the sentence in a tooltip or a card, never a table cell beside numbers.**
- Action words are now kid-words: Keep, Sell, Sell 25%, Buy, Wait, Too new, No data, No call. Symbols stay beside them.
- **A second answer on the same page is a defect, not clutter.** Section 3 still said "Why none of these says BUY, HOLD or SELL", which the new columns now do. Contradictory prose left standing is the same failure class as trap 7. It was folded into the collapsed detail section and reworded to "these rows are prices only".
- Verified before publish: 16 rows, 2 columns, 0 rows overflowing their box at 1400 / 900 / 390px, every control clicked, no console errors.
- **TRANSFER FALLBACK WHEN LITTERBOX IS DOWN (it 500'd all session 16 Sep, and 0x0.st / transfer.sh / bashupload are not on the proxy allowlist): ship a `diff -u` PATCH, not the file.** 67KB file became an 18KB patch, 6.3KB gzipped, 8.4KB base64, pasted into one Desktop Commander heredoc. Then `patch` on the droplet and **md5sum the result against the locally tested file** - identical md5 proves the droplet runs the exact bytes that were verified. Four times cheaper than base64-chunking the whole file and strictly more verifiable.
- Gotcha in that path: `cut -d" "` inside a single-quoted ssh command loses its quotes. Use `cut -d' '` carefully or run md5sum as its own call.

# THE TWO EVIDENCE GAUGES, 16 Sep 2026
- [stated] Ben: make recovery 0/3 bigger next to the zone word, and add the counterpart, "the evidence that the top is in", treated the same way visually.
- Shipped: two big side-by-side gauges under the zone word, **"Is the top in? 3 of 3"** and **"Is the bottom in? 0 of 3"**, each with three pips and one plain sentence. Computed server-side in rhx.py as `out["peak"]` beside `out["recovery"]`, same leg shape (value, threshold, op, passed, available), so the page and any future Telegram push read one number.
- **THE DESIGN QUESTION THAT MATTERED: do NOT make it the mirror of recovery.** The lazy version negates the recovery legs and is information-free by construction: recovery 0 of 3 forces top 3 of 3. So legs 1 and 2 ask whether the RISE ended, which is a different question from whether the FALL ended. Only leg 3 overlaps, and the page says so rather than claiming three independent facts.
- TOP legs: (1) extension reached 75+ somewhere in the last 30 days, (2) no new 30-day high for more than 7 days, (3) more than 10% below that high. Constants `PEAK_HOT_MIN=75, PEAK_WINDOW_D=30, PEAK_STALL_DAYS=7, PEAK_OFFHIGH_MIN=0.10`.
- **MEASURED AND IT CHANGES HOW LEG 1 IS WORDED: extension read 21 at the 07 Sep price peak. Its 30-day maximum of 88.6 came on 31 Aug, SEVEN DAYS EARLIER.** Extension did not mark this top in real time. So leg 1 claims only that a hot reading happened in the window, never that it happened AT the high, and the caveat is published in the JSON.
- **Extension history lives in `idx` under idxname='HEAT', NOT in the `sig` table.** `sig` is rewritten per run and held 31 rows, so "the hottest reading in 30 days" read off `sig` would have silently meant "in the last day". Always check a table's row count before writing a window query against it.
- **CSS CLASS COLLISION, and it is trap 14's sibling: `.gauge` already existed as the 9px risk-meter bar.** My new `.gauge` rule never set `height`, so `height:9px` kept applying and every box collapsed to 22px with its contents spilling across the chart. Nothing threw. **Grep for a class name before defining it; namespace new component classes (`.evg-*`).**
- The zone sentence from the server still said "Recovery evidence 0 of 3", which the gauge now shows far bigger, so the renderer strips that clause client-side. One fact, one place.
- Verified before publish: 2 gauges at 1400 / 900 / 390px, zero overflow, 3 pips lit, and both other branches forced (1 of 3 renders amber "part of the way there", 3 of 3 bottom renders green).

# THE HANDOVER DOCUMENT, revision 14, 16 Sep 2026
- [stated] Ben asked for "a handover document so that I can have it reviewed by ChatGPT, the entire thing."
- Built as **revision 14 of the existing methodology artifact**, not a second document, so there stays ONE link: https://claude.ai/artifact/Ciga2W6LcC4TBfXR1ymJ96 It carries a single "Copy the whole handover as Markdown" button (the ONE CLICK rule) so the entire thing pastes into another model in one action.
- **The shape that makes a handover reviewable, and it is reusable:** open with WHAT TO ATTACK, not with what was built. Revision 14 opens with the system in six lines, then **"The claims I am least sure of, ranked"** as a table with why each is weak, then the three measurements that changed the design, then current state, then what is NOT built. The methodology follows after.
- New sections: 5b source selection (`src-0.2`), 7b the two evidence counters, 8b the deployed swing layer, 13b the eleven defects of 15 to 16 Sep with the pattern they share, 13c the standing verification rule, 15 the side test of the four top-call indicators. Sections renumbered so "What changed" is 16 and "Questions" is 17.
- **Rewrote the reviewer questions to eight, ordered, with the first six named as the ones worth answering.** The best of them: is the top counter's leg 1 worth keeping given extension did not mark the top; is a one-sample median better or worse than a fixed percentage; what is the minimum forward log that would let a threshold be revised on evidence; should a stop beyond some distance be REFUSED rather than sized (a -95% stop prints as 1.05x per unit of risk with no statement that it is barely a stop).
- Gotchas hit while building it: the renderer auto-numbers h2s from a CSS counter, so a heading titled "## 14b." rendered in the nav as "b. A side test" (renumber instead of using letter suffixes); and **a bare URL in the version block had been overflowing the page by 12px on a phone since revision 12**, never caught because the doc had only ever been measured on desktop. Fixed with `article a{overflow-wrap:anywhere}`.
- Procedure that worked and is worth repeating: `action:"read"` the artifact, md5 its embedded markdown against the local backup to prove the new revision is built on the live one, then publish.

# WILLY WOO'S FISHER TRANSFORM, tested 18 Sep 2026
- [stated] Ben pasted https://x.com/willywoo/status/2100820107643760724 and asked "is this something that can help us?"
- His claim: Fisher Transform (John Ehlers, published 2002) picks turning points. "BTC bottoms: 3 for 3 without fake out. Latest cross is the 4th on record." Follow-up: "4 for 4 if we go back to the MtGox era."
- **REPLICATED MYSELF on BTC weekly, Binance 2017 to 2026, 475 bars, published parameters (period 10, median price, 0.33/0.67 and 0.5/0.5 smoothing). IT DOES NOT HOLD CLEANLY.**
  - 2018 bottom: **caught**, fired 24 Dec 2018 at $3,802 two weeks after the $3,229 low, +185% at 26 weeks.
  - 2020 Covid low: **missed entirely** at any selective threshold.
  - 2022 bottom: fired 18 Jul 2022 at $22,580, **four months early and 39% above** the real $16,280 low. Only +0.6% at 26 weeks.
  - 2021 mid-bull: a fire at $39,845 that was **-41% a year later**. That is a fakeout.
- **THE DIAL IS THE WHOLE STORY, and he never states it.** Episode count over the same 9 years by how deep the turn must come from: any = **34**, -0.5 = 20, -1.0 = 15, -1.25 = 9, -1.5 = **4**, -1.75 = 1, -2.0 = **0**. A 34x swing in signal count on a number the tweet does not mention. **Same disease as the friend's volume bands (see above): the threshold does the work.**
- On my weekly replication the current turn-up already happened **20 July 2026 at $65,400**, and BTC is $79,178, so the "latest cross" he posted on 18 Sep is either a different period, a different series (he uses Bitstamp/index back to MtGox, I used Binance) or a different timeframe. **I cannot see his chart, so this is a non-replication, not a refutation.**
- **WHAT IS ACTUALLY WORTH TAKING IS HIS SECOND POST, NOT THE INDICATOR.** Verbatim mechanism: tops form when investor buy pressure fades while speculative interest rises, so price keeps going on momentum with no cushion and prints many false turning points; bottoms form when price falls in a void of buy pressure, speculators disappear, and investors return cleanly. **Therefore bottoms are structurally easier to mark than tops.** This independently matches our own measurement: extension read 21 at the 07 Sep RHX top and peaked 88.6 seven days earlier. Our top gauge is the weak one and he explains the mechanism for why it must be.
- **Fisher's one real merit over the rejected indicators: it is NORMALISED (position inside a rolling min/max), so it cannot go stale like a dollar threshold, and it was published in 2002 so it was not fitted to this data.** Those are the same two properties that saved alt OI against BTC OI.
- Tested on our own coins, daily bars, 16 symbols with history: a deep turn-up (from <= -1.0) gave a 14-day median of +121.6% against a +12.5% all-days baseline. **n = 10 signals. That is not evidence and must not be quoted as one.**
- **RHX's own Fisher on 18 Sep reads -1.73 and is STILL FALLING** (0.20, -0.30, -0.78, -1.20, -1.48, -1.73). No turn. It agrees with the bottom gauge and would turn EARLIER than our legs 1 and 3, which need the index up on 3 days and +10% off the low.
- **SHIPPED 18 Sep as a WATCH LINE under the bottom gauge, not a fourth scored leg.** Changing a counter's definition on n=10 is the tune-on-one-reading mistake already in this file twice. Bottom only, never the top, per his own mechanism. `FISHER_N=10, FISHER_DEEP=-1.0`, computed in rhx.py on COMPLETED RHX daily bars and published as `out["fisher"]` with the non-replication as an attached caveat. On the page it gets no tick and no pip, sits in its own dashed band headed "WATCH, NOT A CHECK", and says ten test signals are behind it. All four branches forced (deep turn / shallow turn / still falling / block absent).
- **FIRED ON ITS FIRST DAY AND THE MARKET RIPPED.** Deep turn-up on the last completed day from -1.84, then RHX 1,330 to 1,844, **+39% in two days**. n=1.
- Fixed in the same pass: `.callmain{min-width:280px}` and `.callside{min-width:160px}` pushed the page past the screen below ~320px. Both `min-width:0` now.
- **TAB ICON, 18 Sep.** [stated] Ben wanted a tab logo, "green and red circle, half green half red", to find the dashboard among many tabs. TWO inline data URIs in the head (SVG plus 32px PNG fallback plus apple-touch-icon), so no extra file to serve. Page's own `--up #3ecf8e` left, `--dn #f2555a` right. Both decoded in a real browser and checked at 16px.
- Reusable code: `/home/claude/rhx/fisher.py` (Ehlers transform, cross detector, episode grouper). BTC weekly and daily high/low/close cached as `btc_weekly.json` and `btc_hlc.json` from `data-api.binance.vision`.

# NEXT, in order
0. **Recompute rhx_test.py features and entry levels on the as-published series** (trap 10). Until then no backtest number is quotable as tradeable.
0a. Get Ben's position sizes and per-coin max loss. Every stop level is a chart fact until then, not a sized decision.
0b. Get entry price and invalidation per holding from Ben, so the actions section can print real states instead of review prompts.
1. Executable sell depth (`depth_7d`) needs a week of ticks before it reads. It is the leg Ben's research brief cared most about and it is currently `unavailable`, which the panel says out loud rather than treating as good news.
2. Re-run `rhx_test.py` at 60 and 90 days. The bands (25 / 75) are currently quantiles of a 36-day history and should be refit from the outcome column, not guessed.
3. Add the macro panel from his brief: 2-year Treasury yield, 10-year real yield, dollar, high-yield spread, and whether RHX still reacts to them.
4. Add fresh-funded-buyer arrival per member (new wallets that bought and held), which his brief ranks first and which nothing here measures yet.
5. Consider a 30-day rolling frozen leg on the chart in addition to the fixed 10 Aug one.

# THE SIX CHECKS ARE NOW PRINTED, 17 Sep 2026
- [stated] Ben, looking at the gauges: "What are the confirmations that the top is in and the bottom is in? Now it says 1 of 3. You need to give a couple of words of detail: what is it?"

**The labels were already in `rhx.json`, but the page only put them in a pip `title` tooltip and a collapsed section, so the score could not be read without hunting.** Fixed: three short lines under each gauge, tick or cross, four words, and the number against its threshold.

## TOP legs, all three passing as of 17 Sep
| leg | reading | needs |
|---|---|---|
| Ran hot | extension 89 | >= 75 in the last 30 days |
| No new high | 10.5 days | > 7 days |
| Fell off the high | 37.7% below | > 10% |
Caveat that stays attached: **extension peaked at 88.6 on 31 Aug, seven days BEFORE the 07 Sep price high**, so leg 1 only claims a hot reading happened in the window, never that it marked the top.

## BOTTOM legs, 1 of 3
| leg | reading | needs | |
|---|---|---|---|
| Up vs 3 days ago | **+7.4%** | > 0 | **PASS** |
| Most coins green on the week | **exactly 50.0%** | **> 50%**, strictly | fail, one coin short |
| Up off the low AND not too deep | +127.4% off the low, but **-37.7% vs the peak** | off-low > 10% AND drawdown > -30% | fail on the drawdown half |
Constants: `REC_TREND_MIN=0.0, REC_PART_MIN=0.50, REC_OFFLOW_MIN=0.10, REC_MAXDD=-0.30`.

## A REAL DISPLAY DEFECT FOUND WHILE ANSWERING, and it is trap 7's family
**Leg 3 of the bottom gauge has TWO conditions but published only ONE value.** The page printed **+127.4% against a "needs > 10%" threshold and still showed a cross**, which reads as a broken counter rather than a failing check. The second condition (`second`: -37.7% vs -30%) was in the JSON and never rendered. Now both halves print, with "this is the one failing" pointing at the half that fails. **A multi-condition check that shows one number is worse than showing none.**

## SECONDARY
- The 30-day low that produces +127.4% sits on **19 Aug, before this fall even started**, so it is not this recovery. The `event-low anchor` research variant measures from the actual fall low (16 Sep, 21:00 Bangkok) and reads **+14.6%**, and it also scores 1 of 3. Worth promoting over the calendar-window version once there is a forward log to judge it on.
- "Fell off the high" was rendering as **+37.7%**, which reads as a rise. The stored value is the magnitude of the fall; the plus is now stripped on that leg only.
- New CSS namespaced `.evgl-*` after grepping for collisions first (the `.gauge` collision of 16 Sep). Verified at full width, 350px and 300px forced: gauges down to **145px wide with zero overflow, zero clipped names, zero zero-width rows**, and the phone media query hides the number column below 560px anyway.
- **Patch scripts are now idempotent by rule**: each checks for its own marker and asserts its anchor appears exactly once before writing. Written after the 14 Sep double-apply, when a dropped device-relay reply caused a retry and a block landed twice.
- **Still UTC on the page.** The footer reads "07 Sept, 06:00" and every stamp on the dashboard is UTC while Ben's standing rule is Bangkok. Flagged to him, not yet changed. 06:00 UTC = 13:00 Bangkok, 14:00 UTC = 21:00 Bangkok.

# CORRECTION AND THE FORMATTING FIX, 17 Sep 2026
**I TOLD BEN THE DASHBOARD WAS STILL ON UTC. IT IS NOT, AND IT NEVER WAS.** Every formatter in rhx.html (`F.t`, `F.d`, `F.tany`) already carries `timeZone:'Asia/Bangkok'`. Verified against the raw stamps: `event_peak_ts` is 06 Sep 23:00 UTC and the page prints "07 Sept, 06:00", which is Bangkok. I read "06:00" beside a known 06:00 UTC rebalance and assumed. **Check the formatter, never infer a timezone from a familiar-looking hour.**

## THE CHECK LINES, v2, after Ben said "fix the formatting"
v1 put the name and a mono `value > threshold` string in the same flex row inside a 215px box. The thresholds truncated to "10.…", the names wrapped to two lines while the number stayed on the first, and the rows read ragged. **`text-overflow:ellipsis` made it worse, not better: it turned a clip into a visible "…".** Two things fighting for one narrow row cannot be fixed with overflow rules, only by removing one of them.
1. **Shorter names** so each fits one line: Off the high, Up on 3 days, Off the low.
2. **The threshold only renders when the check FAILS**, on its own line underneath. On a pass the value alone is the detail; on a fail the bar is the whole point.
3. **A two-condition check now shows the half that DECIDES.** "Off the low" printed +127.4% (the passing half) next to a cross; it now prints -37.7% with "needs > -30.0% vs the peak". The number and the mark finally agree.
Measured at full width, 350px and 300px: **zero clipped elements, zero overflow, every name on one line down to 170px** (at 145px some wrap, nothing clips). Backups `rhx.html.bak-legs` and `.bak-legs2`.
