---
name: trench-model-5
description: Price Predict part 5, 15 Sep 2026 after trench-model-4 neared its cap. The 25-hour final horizon that had never fired, the lock-contention chain (BEGIN IMMEDIATE, the wallet rebuild, callbook's grip on the writer lock), the quote-type board split, the dead rat_traders box, and Ben's decision to halve the Solana sample. Read with trench-model-4.
sources: [cowork]
aliases: [trench model part 5, price predict 5, patch187, patch199, final horizon, keep_coin, lock contention]
---
Continues [[trench-model-4]]. Everything there still applies.

# THE BIGGEST FIND: THE 25-HOUR FINAL HORIZON HAD NEVER FIRED. ONCE. ON EITHER CHAIN. patch194
- Robinhood: 479 rows old enough, 133 tradeable with a return. `settled=2`: **0**
- Solana: 2,887 rows old enough, 2,234 tradeable with a return. `settled=2`: **0**

**Every outcome the book has ever reported came from stage 1, the 70-minute horizon.** `hold_6h` and the closing 25-hour figure were empty sets, so any number quoted off them was quoting nothing.

Cause: `settle.due()` ended `ORDER BY ts DESC LIMIT 1200`, newest first, with the comment "so a winner is corrected while it matters". Right for the hourly refresh, fatal for the final horizon, because **a row becomes stage-2 eligible by getting OLD**, and newest-first puts the old rows last. With 18,465 due and a batch of 1,200, the 39-hour rows sat 17,000 places down and could never be reached. Not rarely: never, because they only get older.

**Fix: three priorities.** 0 = final horizon due, OLDEST first (its kline tape is expiring); 1 = never settled, newest first (the original intent); 2 = the hourly refresh. Robinhood caught up to 138 of 141 within an hour.

**The pattern, third time in this project: a rule that is correct in one regime, applied where it can never be satisfied, failing silently because a zero looks like a measurement.** Same shape as the field-percentile bug and the rat_traders bug below.

# THE LOCK CONTENTION CHAIN. Four separate bugs, found in order. patches 190, 191, 195, 198
7,754 `database is locked` errors, settlement pricing **0 rows**, rescore skipping 26,929, and the paper book periodically dead.

## patch190: it was never a timeout
`busy_timeout` had been 60s since patch119. The cause was the KIND of transaction: sqlite3's default `isolation_level=""` issues a plain (DEFERRED) BEGIN, which takes no lock and then tries to UPGRADE at the first write. **SQLite returns SQLITE_BUSY IMMEDIATELY on that upgrade and deliberately does NOT call the busy handler**, because waiting could never resolve it. So a 60-second timeout sat there doing nothing for the one failure it was set to prevent.
`isolation_level="IMMEDIATE"` on every connection (store.py, and newborn.py which opens its own). Writers now QUEUE. Error rate 9.4/min to 0.35/min.

## patch195: the wallet rebuild held the lock for 40 seconds
Measured on the real settlement path: kline call 0.51s, run_policies 0.00s, **the UPDATE and commit 39.57s**. A one-row UPDATE by primary key is 11ms on an idle file.
`wallets.refresh()` wrote 17,159 rows via one `executemany`, then `DELETE FROM walpair`, then another `executemany`, with **one commit at the end**. Under BEGIN IMMEDIATE that is one lock hold for the whole rebuild. Chunked to 2,000 rows per commit with a 0.05s gap. 39.57s to **3.62s**.

## patch196: settlement batches its writes
patch151 made settlement commit per row so no transaction spans a network call. That rule is kept: rows are QUEUED and written at the top of a later iteration, so the kline call still runs with no transaction open. What changed is that 25 rows now share one lock acquisition instead of taking 25. Throughput 5 rows to 39-45 rows per 90s.

## patch198: callbook was sitting on the book's lock
`UNHEALTHY: robinhood paper entries, solana paper entries`. The book was not broken, it could not get `/tmp/trenchw.lock`: **callbook.sh held it for up to 560s on a 600s cron**, and paper.sh skips rather than queues. 169 of 327 lines in paper.log were that skip.
**Entries are the one thing that cannot be caught up later** (the book buys at 60-240s of a coin's life), so a batch job must never be able to take that window. callbook moved to its own lock. The shared writer lock was a workaround for the deferred-transaction problem that patch190 actually fixed.
Entries resumed immediately: 391 Robinhood, 908 Solana in the first pass.

## patch191: rescore rewrote rows that were already correct
26,929 Solana rows rewritten every 5 minutes whether or not the score changed. Now skips rows whose score AND sigs already match, plus a 0.2s yield every 500 writes. First run after: 12,735 rescored, **15,430 already correct and skipped**, 6 locked (was 26,929).

## patches 192, 193: settlement could not report
- patch192: patch157 wrapped the MAIN update in try/except so a lock costs one row. It wrapped ONE branch. The no-tape branch (dead coins, which are common) still had two bare writes, so one busy lock ended the whole process. That is the real "0 priced" number.
- patch193: `settle.sh` ran each leg under `timeout 260` while `settle.py` took BATCH=1200 rows at one network call each. **Every pass was killed before the print at the end of main(), so a pass that priced 400 rows and a pass that died on its first row wrote the same thing to the log: nothing.** Now has its own budget (`SETTLE_SECONDS`, default 210) and always reports whether it finished or ran out of clock.
- patch197: budgets matched to queues (paper 200s, paperx 40s) and the cron moved from every 10 min to every 5.

# THE BOARD IS CAPPED PER QUERY, NOT PER CHAIN. patch188
1,982 coins in four days were seen by the newborn rank feed and never reached `obs`, so the book could never buy them. 1,306 were pons_v2, which `hidden_sweep` already asks for BY NAME every ten seconds.

Three things checked directly rather than assumed:
- **`limit` is decorative.** 80, 150, 300 and 500 all return exactly **60 rows per column**. The board is hard-capped near 180 coins a query and the `limit=80` in the code had been meaningless since it was written.
- **The missing coins are genuinely absent.** Of 181 pons_v2 coins born in the last 40 minutes and never in obs, **zero** were on the board at the moment we asked, while `new_creation` was returning coins aged -4 to 275 seconds.
- **One call per coin cannot fix it.** `token_info` returns 37 fields and `token_security` 28, and NEITHER carries progress, fresh_wallet_rate, suspected_insider_hold_rate, s_qsafe, creator_created_count, net_buy_24h or rat_trader_amount_rate. Six of the ten boxes would be silent zeros. **Verified on a live coin, not inherited from newborn.py's docstring.**

**The 60-row cap is per QUERY, so change the query.** `quote_address_type` is already in the request body and we sent all five types at once. One at a time: type 20 alone returns 160 distinct, type 24 returns 59, type 0 returns 18, against 179 for all five together. Union 237, of which **165 were not in the all-five call**. The collector now sweeps one quote type per cycle, rotating, so each is revisited every 50s against a 275s window.

**CORRECTION I had to make to Ben: I said this would roughly double the board's reach based on that one-shot probe. Measured live over 22 minutes it did not.** Coins first reaching obs went 15.4/min to 20.2/min (+31%), and the pons_v2 miss went 62.7% to 59.3%. The probe compared one instant; in continuous polling the all-five call already picks up most of those over time. **A one-shot union overstates a continuous-polling gain.**

# rat_traders WAS A DEAD BOX. IT COULD NEVER FIRE. patch189
Fired on **0 of 863** rows. Arithmetic, not data volume: `rat_trader_amount_rate` is non-zero on 3.1% of coins, so its 90th percentile is exactly 0.000000, and patch181 asked for `ratrate >= rat90 > 0`. `0 > 0` is False. **Waiting for more data would never have fixed it, because more data does not move a 90th percentile sitting on a floor of zeros.**
Now presence (`ratrate > 0`), which fires on ~3%, the same shape as `insiders` (4.6%, 1.99x). `net_buying` keeps its percentile: non-zero on 32.1% with a p90 of 356.76, fires 13.1% live. Both still CARRIED, not counted.

# boxlift WAS MEASURING A SCORE THAT NO LONGER EXISTED. patch187
It still listed `gmgn_safe` as counted and never printed `strong_pad`, `net_buying`, `rat_traders` or `dev_under_5` at all, because patch183 swapped the boxes and boxlift kept its own hand-written copy of the list. **A stale measuring tool is worse than none: it reports confidently on a score that does not exist.**
Now reads the names straight out of `paper.signals`, so it is correct by construction after any future swap. The "strong list" is also derived from what actually had lift on the run instead of a list written once.

**With it fixed, `strong_pad` is the second best box on Solana at 3.07x** (behind `real_buying` 3.21x), which confirms the swap was right.

## Current Robinhood board (3,739 settled, base 8.48%)
insiders 1.99x · curve_top10 1.87x · real_buying 1.85x · eye_top10 1.78x · holders>6 1.67x · smart_3plus 1.60x · entrapment 1.58x · **strong_pad 1.35x** · fresh_wallets 1.22x · dev_under_5 1.21x (carried) · dev_under_100 1.21x · gmgn_safe 1.01x (carried) · site_and_x 0.99x · site_or_x 0.97x
Book B as it runs, 6 of 10: **2.27x**.

## Current Solana board (18,554 settled, base 9.63%)
real_buying 3.21x · **strong_pad 3.07x** · curve_top10 2.86x · entrapment 2.67x · holders>6 2.37x · smart_3plus 2.01x · insiders 1.98x · eye_top10 1.78x · dev_under_5 1.46x · fresh_wallets 1.45x · site_and_x 1.40x · dev_under_100 1.30x · site_or_x 1.11x · gmgn_safe 1.00x
Book B 6 of 10: **2.96x**. **The counted ten at 6 PLUS site_and_x is 3.28x on Solana**, worth testing as a counted box there (it is 2.00x on Robinhood, so it stays chain-specific).

**`dev_under_5` fires on 81.3% of coins**, which makes it a constant, not a signal. Still open.

# BEN'S DECISION: SOLANA KEEPS A RANDOM HALF. patch199
- [stated] Ben chose "keep a random half" over filtering to high scorers or buying more API speed, 15 Sep 2026.

The arithmetic: Solana intake 823 coins/hour against 436/hour of settlement capacity, queue 6,588 and growing 175/hour. **A row that is never settled has no peak, no return and no horizon: it is not data, it is a row.** Recording more of them does not make the sample bigger, it makes the measured fraction smaller and biases it toward whatever settlement reaches first.

**Why random and not "only the good ones", which was the option I pushed back on:** every number this project produces is a LIFT, hit rate divided by base rate, and the base rate can only be measured on a sample containing the losers in their true proportion. Filter the losers out and the base rate rises to meet the filtered hit rate, every box goes to 1.00x, and nothing can be compared to anything.

**Why a hash and not `random()`:** the decision must be STABLE (the early leg and the late leg must admit the same coins, or `paper` and `paperx` stop being two views of one population) and INDEPENDENT of outcome (arrival order correlates with how busy the chain is, which correlates with outcomes). `keep_coin()` hashes the address: fixed at creation, same answer in any process forever, no state.

`KEEP_RATE` = 0.5 on Solana, 1.0 on Robinhood, override with `TRENCH_KEEP`. Existing rows untouched.

**Verified before trusting it:** exactly 50.0% kept (10,417 / 10,416), stable across calls, and the kept and dropped halves have the same outcomes (over 2x: 9.60% vs 9.20%; over 5x: 3.16% vs 2.96%; medians identical at 1.000x). Solana queue turned within the hour: 6,473 to 6,293.

# NEVER TEST SOURCE TEXT. AGAIN. patch200
The gate check for "settlement runs one writer per database" grepped settle.sh for the literal `for TBL in paper paperx`, which patch197 replaced with two explicit lines. **Gate red on a correct file.** Same class as boxlift keeping its own box list. Rewritten to assert the SHAPE: leg backgrounded exactly twice, both SETTLE_TABLE lines inside leg(), neither backgrounded. This is the third time this rule has been re-learned (patch111, patch120, patch200).

# STATE AT HANDOVER, 15 Sep 2026 ~19:00 Bangkok
- **GATE GREEN, 95 checks**, both chains. ALL HEALTHY.
- Final horizon firing: Robinhood 138, Solana 366 and climbing from 0.
- Robinhood queues 10 and 2. Solana paper 6,293 and now draining.
- Lock errors under 1/min, from 9.4/min.
- Tech Score backfill 7,732 done, 7,149 left.
- **Open: `dev_under_5` fires on 81% and needs the margin treatment that `strong_pad` got.** `net_buying` and `rat_traders` need 24 hours before boxlift can judge them on the same sample.
- Note for any future script run with cwd=/opt/trench: something there shadows a stdlib import and breaks numpy (`inspect has no attribute cleandoc`). Run analysis scripts from /tmp.

# EXTERNAL REVIEW OF THE BRIEF, 15 Sep 2026 evening. patches 201-202
Ben had the v1 brief reviewed adversarially and pasted it back. Five claims fell.

## WITHDRAWN, and the two that matter
- **"A rule that cannot beat 2x cannot beat 40x" is FALSE.** Our reasoning, not a measurement. Counterexample: A hits 2x on 20/100 and 40x on 0; B hits 2x on 10 and 40x on 5. **The 2x proxy can rank rules in the wrong order**, which is exactly what section 12B was worried about and then answered wrongly.
- **The $1M edge was counted in CALLS, and one token can be called many times.** Counted as DISTINCT TOKENS:
  - Robinhood `smart`: 330 calls, **1 distinct $1M token**, CI 0.05-1.70%. Control 11,213 calls, 4 distinct. **The Robinhood $1M claim is withdrawn entirely.**
  - Solana `buying`: 1,539 calls, **24 distinct $1M tokens**, CI 1.05-2.31% against a control of 25 distinct in 23,356 calls, CI 0.07-0.16%. **Intervals do not overlap, so this one holds.**
  - The honest statement: **Solana has the $1M evidence, Robinhood does not, and the old table averaged them into one fake "7x to 14x".**
- "Waiting S1 to S2 costs 7%" divided the median of 100 stage-1 tokens by the median of a DIFFERENT 14 stage-2 tokens. Not the price change on the same tokens. Withdrawn until computed per token.
- The 46% conditional ($1M given $100k) is inflated because $1M winners are well counted and $100k-only failures are not. **Not usable as a licence to substitute the $100k bar for $1M**, which is what 12A proposed.
- 35.7% at stage 2 is **5 of 14**, CI 16-61%. The 24 threshold sweeps on overlapping tokens are ONE robustness check, not 24 replications. 0 of 540 is an upper bound near 0.55%, not proof of impossibility.

## CHECKED AND CLOSED, with receipts
- **The hourly percentile does NOT leak.** `field_at()` anchors on the bucket START and `field()` reads the hour ENDING there, so a coin decided at 14:01 is judged on 13:00-14:00. Already fixed as patch137.
- **The 5-minute cron does NOT cost coverage.** It replays the stored observation inside the window, not its own run time. Measured: RH n=4,313 min 60s median 68s max 238s; SOL n=21,340 min 60s median 69s max 240s. **ZERO outside 60-240s on either chain.** The reviewer's deeper point stands as an open risk: a replay proves the data existed, not that an order could have been placed. **We have never measured decision-to-order latency because there are no orders.**
- FEATS is 281 in code; the brief's block list was wrong by three.

## CONFIRMED AND FIXED
- **A "1m" snapshot can be 16 minutes old** (freezer accepts band..band+900). Measured, and much worse on Solana: RH 1m median 74s, p90 157s, max 938s, 46.1% over tolerance. **SOL 1m median 409s, p90 824s, 92.9% over: the Solana "1m" band is a SEVEN MINUTE band at the median.** Does not touch the paper book (see above), does touch the snapshot and model path. patch201 adds `feats.in_band(age, band_secs)`, proportional (age <= band x 1.25, 30s floor), so a row can say whether it is entitled to its label. Rows outside tolerance keep their data and lose only the minute-one claim.
- **The take-profit fills at the candle HIGH.** `settle.series()` injects each 1m candle's high as a reachable point before its close, so a candle that touched 2x and closed at 0.5x fills a 2x target. **We have silently assumed the favourable intra-candle order.** No decision rests on it yet (no exit rule in production) but it would flatter every exit rule. The exit experiment must report both orderings as a band.
- The gate rejecting any box under 1% fire rate is wrong for a 0.06% target: a 0.5% box could be the best one. Becomes a warning plus a sample-sufficiency rule.

## patch202: boxlift now prints 95% Wilson intervals and flags wide ones
Every box rate carries its interval, and anything wider than 8 points is tagged "too few to rank". **First run after the rat_traders fix (patch189) put `rat_traders` TOP of the Robinhood board at 2.28x on 82 rows** (CI 13.4-30.7, wide) and `net_buying` second at 1.96x. Both carried boxes are now earning their place.

## WHAT SURVIVED REVIEW UNCHANGED
The ten-box score. With integer hits and intervals: RH score 0 is 1.16% (CI 0.32-4.12) and score 9 is 32.38% (CI 24.19-41.82); SOL score 0 is 2.30% (1.94-2.71) and score 7 is 33.39% (29.67-37.33). **Non-overlapping on both chains.** The "27x spread" framing is retired: that compared best bin to worst bin. Against the base rate, RH score 9 is **3.58x**.

# ROUND TWO OF THE REVIEW. THE $1M RESULT IS MUCH SMALLER THAN WE SAID. `dedupe.py`
The reviewer asked the one question that mattered: does the $1M edge survive inside the trade Ben actually wants, entry $5,000-$30,000 at 60-240s? **Mostly it does not.**

## Two suspicions that measured CLEAN
- **The denominator was already one call per token.** The call book fires once per coin per rule by design and the data confirms it: **repeat factor 1.00x for every rule on both chains.** So 24/1,539 was a token rate after all. Closed with a measurement, not an argument.
- **Every call is already inside the window: 100.0% between 60 and 240 seconds**, every rule, both chains. The age filter costs nothing because there was nothing to filter.

## The binding filter is CAP, and it guts the result
Solana median entry by rule: **buying $48,445 · holders $38,755 · combo2 $37,287 · entrap $33,585** · eye $6,995 · control $3,038. Only **10% to 25%** of Solana calls land in $5k-$30k.

Restricted to the target trade (one decision per token, earliest call, chosen before the outcome):

| rule | tokens | $1M wins | rate | 95% CI |
|---|---|---|---|---|
| control | 2,667 | 6 | 0.22% | 0.10-0.49 |
| buying | 350 | **5** | 1.43% | 0.61-3.30 |
| entrap | 184 | **4** | 2.17% | 0.85-5.46 |
| combo2 | 183 | 3 | 1.64% | 0.56-4.71 |
| holders | 1,250 | 5 | 0.40% | 0.17-0.93 |
| eye | 1,138 | **0** | 0.00% | 0.00-0.34 |
| smart | 182 | 0 | 0.00% | 0.00-2.07 |

**24 winners becomes 5.** buying and entrap still clear the control on non-overlapping intervals (6x to 10x) but on FOUR AND FIVE EVENTS. A direction, not a result.

**Robinhood inside the target trade has nothing**: control 2 of 2,593, every rule also 2, smart 1 of 215.

## The winners are ONE SET, not several confirmations
buying caught 24, holders caught 24, **they share 24**. combo caught 23 of the same. The reviewer deduced this from the set sizes before seeing the data. **Counting the same successes under three rule names is not three results.**

## THE FIRST DIRECT EVIDENCE THAT THE 2x PROXY RANKS WRONG
`eye` is a strong short-horizon rule and has **ZERO** $1M winners in 1,138 target-range calls. `entrap` is mediocre at 2x and has **four** in 184. On four events it is not proof, but it is the first real sign that optimising at 2x picks different rules than optimising at $1M. W1 said the implication was false; this says the disagreement is observable.

## MY OWN FAILURE, WORTH KEEPING
In brief v2 I wrote that corrections were "applied in place throughout". **Seven sections still carried withdrawn claims** (the 7% waiting cost in 5 and 11, the 46% conditional in 1 and 12A, the comparability claim in 4, the freezer window in 7, the sub-1% gate in 9). The reviewer listed them by line. **Writing a corrections section is not the same as applying the corrections**, and I asserted the second while only doing the first. Fixed in v3.

## STANDING RULES THIS ROUND EARNED
- **Deduplicate BOTH sides, and say which unit the denominator is in.** I fixed the numerator in round one and left calls in the denominator, which is the same error one level down.
- **Measure the trade you actually want, not the trade the filter happens to allow.** A $50k cap filter is not a $5k-$30k entry, and the gap hid an eightfold shrink in the result.
- **When two rules report the same number of winners out of a small universe, check the overlap before calling them two results.**

# ROUND THREE: DOES IT MAKE MONEY? NO, AND THE SCORE MAKES IT WORSE. `pnl.py`, `pnl2.py`
The reviewer's question: every number this project publishes is a HIT RATE, and a hit rate is not a return. **We had never summed a P&L in the life of the project.**

We did not need to build one. **`paper.run_policies` has been computing the net return of every exit policy on every settled row since the beginning, and `settle.py` stores it in `paper.ret` as JSON with a `ripe` flag. Nobody ever added it up.**

## THE RESULT, hold_1h, $100 tickets, top 1% of the book removed
| score | RH mean | RH median | SOL mean | SOL median |
|---|---|---|---|---|
| 0 | $89.54 | $93.84 | $87.66 | $91.44 |
| 3 | $78.53 | $80.59 | $75.25 | $82.77 |
| 6 | $55.43 | $55.61 | $49.53 | $18.96 |
| 7 | $51.27 | $45.20 | $41.93 | **$3.73** |
| 8 | $42.11 | $36.17 | $36.82 | $4.84 |
| 9 | $39.20 | $26.30 | | |

**EVERY BAND LOSES, AND THE SCORE IS MONOTONE IN THE WRONG DIRECTION ON BOTH CHAINS.** Buy everything and hold an hour: ~$88. Buy only the best tenth: $39 (RH) / $37 (SOL).

**Both facts are true and not in conflict:** a high score really does mean far more likely to TOUCH 2x (31.68% at score 9 vs 1.16% at score 0), AND far more likely to be worth much less an hour later. **High-score coins spike and collapse. We were measuring the spike and calling it the result.**

## WHAT IT KILLS AND WHAT IT DOES NOT
- **Does NOT kill the score.** It is a real ENTRY signal attached to the WRONG EXIT. Holding an hour is the one thing you must not do with it.
- **Does kill "the ten-box score is the main result of the project"**, which is how briefs v1-v3 framed it. Finding the spike is the result; capturing it is undemonstrated.

## THE PROFITABLE-LOOKING ROWS ARE FLATTERED TWICE
tp2x shows ~$172 per $100 at score 6+ and tp3x ~$235. Unusable, for two separate reasons:
1. **The candle-high fill** (F2): a target counts filled whenever a 1m candle's HIGH touched it, even if that minute closed far below.
2. **A RIPENESS SELECTION BIAS we had not noticed.** `ripe[tp2x] = 1 if (hit or 24h elapsed)`. Most rows are younger than 24h, so **the ripe set is almost entirely winners**: tp2x is ripe on 536 of 4,045 RH rows (13.3%) against a measured touch-2x rate of 12.7%. The losers have not been let into the average yet. **This is the monotone early-settling error the project already documented once, in a new costume.**

## THE SOLANA TAIL IS PARTLY BAD DATA
Raw Solana mean is $300 per $100, but **ONE row is 22.1% of the entire book's return and the top ten are 65.5%**. Largest is a $100 ticket at 11,870x; one top-ten row carries a recorded peak of **$4,989,431,909,812**; several cluster at an entry cap near $58,000. Needs auditing before any Solana average is quoted. Robinhood does not have this problem (top row 1.1% of the book).

## STANDING RULES THIS EARNED
- **A hit rate is not a return. Sum the ledger before calling anything a result.** The ledger may already exist unsummed.
- **Only a policy that is ripe for EVERY row can be averaged.** A ripeness rule that settles winners early and losers late turns an average into a winners-only average.
- **Check what fraction of the total return one row carries** before quoting a mean.
- **An entry signal and an exit rule are different objects and a signal can be excellent at one while being fatal at the other.**

## THE PROJECT IS NOW AN EXIT PROBLEM
Next build: a small FROZEN set of exit rules, each priced under BOTH intra-candle orderings as a band, with a ripeness rule that cannot admit winners before losers.

# ROUND FOUR: I COMMITTED THE PROJECT'S SIGNATURE ERROR, AND THE AUDIT FOUND WORSE. `audit.py`, `peakchk.py`, `clean.py`
## My two errors, both caught by arithmetic
- **I trimmed the top 1% and then described the result as the whole book.** For a strategy whose entire thesis is rare large winners, removing the top 1% deletes exactly what it exists to capture. Same class as everything else in this file, committed by me, in the section that was supposed to be the honest one.
- **"$88 per $100 buying everything" was a score-0 row, not the book.** The untrimmed Robinhood whole book is **$74.39 per $100**.

## THE REAL FINDING: paper.peak IS BROKEN ON SOLANA
`paper.peak = entry_mcap x (candle high / p0)`. We have an independent witness: **the obs tape records market cap directly off the board rather than deriving it from a price ratio.**

| | 25 largest multiples | agree with tape | exceed it >10x |
|---|---|---|---|
| **Robinhood** | 25 | **25** | **0** |
| **Solana** | 25 | 4 | **21** |

Worst Solana row: peak **$4,989,431,909,812** against a highest observed market cap of **$121,948**. That is **40,914,553x**. Others understate: one records $239,931,142 against an observed $477,209,892.

**Only 80 of 21,644 Solana rows (0.37%) fail, and those 80 alone contribute $1,912,780 of "profit" to a 17,841-ticket book.** The bad rows ARE the result.

## THE MECHANISM: the migration problem in a new place
The affected rows **cluster at entry cap ~$58,000 with liquidity ~$22,800**. That is not a market, it is the **pump.fun graduation state**: every coin completing its bonding curve does so at the same cap with the same liquidity by construction. When it migrates, the kline changes pool/quote/supply basis and `entry_mcap x (high/p0)` stops meaning anything.
(Checked and ruled out first: these are NOT late=1, and their tape does not start before the recorded birth, so it is not the old pre-launch-entry leak. Age at entry is a normal 60-239s.)

## AFTER EXCLUDING ONLY THE IMPOSSIBLE ROWS, SOLANA STILL CANNOT BE MEASURED
Score 6+, by entry cap, hold_1h:

| band | n | mean/$100 | **median/$100** |
|---|---|---|---|
| under $30k | 359 | $73.67 | $36.74 |
| **$30k-$100k** | 1,021 | **$1,389.34** | **$0.04** |
| over $100k | 260 | $83.39 | $88.09 |

**The median ticket in the graduation band returns four cents while the mean says $1,389.** A distribution whose mean and median differ by 35,000x cannot be characterised by either.

## WHERE EACH CHAIN STANDS
- **ROBINHOOD: clean data, and it loses money.** $74.39 per $100 whole book, worse the higher the score. The 0.7 conclusion survives because Robinhood had nothing to audit.
- **SOLANA: every rate, lift, $1M count and P&L on this chain is computed from a broken peak field.** Including the 0.6 results that survived round two. **No Solana number is usable until peak is rebuilt.**

## NEXT JOB
**Rebuild `peak` from the obs tape** (a direct market-cap reading) and use the kline only where the two agree. Then re-run everything on Solana.

## STANDING RULES THIS EARNED
- **Exclude rows that fail a DATA-INTEGRITY test, never rows that are merely large.** "Top 1%" is not a filter, it is a deletion of the thesis.
- **Every derived outcome needs an independent witness.** A number computed from a ratio must be checkable against a number measured directly, or it cannot be trusted at the tail.
- **When mean and median disagree by orders of magnitude, report both and characterise neither.**
- **A tight cluster in a field that should vary continuously (entry cap, liquidity) is a mechanism, not a market.** Find out which mechanism before using those rows.

# ROUND FIVE: BEN STOPS THE BUILD AND SETS A BOUNDED TEST. 15 Sep 2026
- [stated] Ben's call: "give this one bounded test. Stop adding features until it produces trustworthy returns."
- [stated] Gate A: two working days after data access, repair the complete return calculation, preserve legitimate big winners, compare four specific exits, produce actual dollars invested and returned.
- [stated] Gate B: if a candidate survives, freeze its rules (chain, age route, entry rule, cap range, exit) and record the code version.
- [stated] Gate C: 14 days of NEW paper entries plus 24h to mature. Continuation hurdle **$110 back per $100 after costs**. No rule changes after seeing results.
- [stated] Fail or unmeasurable means pause active development, preserve the work, stop the open-ended build.
- [stated] The first-30-seconds experiment is separate from the existing minute-scale entries and must not be conflated with them.
- [stated] Ben believed he had not located the trading code or databases and called that the remaining prerequisite. He had: `/opt/trench` and `/opt/trench-sol` on kookwatch, with `trench.db` in each. Nothing needed finding.
- [stated] Ben saved an execution handoff at /Users/bk/Documents/Codex/2026-09-15/p/outputs/price-predict-latest.html

## CORRECTION TO ROUND FOUR: THE BROKEN SOLANA PEAK IS A CORRUPT `high` FIELD, NOT MIGRATION
Round four blamed the pump.fun graduation state changing the pool/quote/supply basis. The proximate cause is simpler and was found by printing the candles. GMGN's kline returns a `high` that is not inside its own candle. Receipt, `Eju51JV3nUmWE2bDPwBzzwYXAugE1JADneV79EZ3keU7`, candle 1789443960: open 0.00001925141, close 0.00046821155, low 0.00001925141, **high 0.36713502** - 784x its own close inside one minute, with the low sitting at the open. Another candle on the same token: open 0.0053912321, close 0.041493265, **high 647.19970148**.

`settle.series()` injects the high as a reachable point, so every take-profit fills against it and `peak` is written from it. That is the whole $4,989,431,909,812.

Measured on the cached tape: **Robinhood 0 impossible highs (>10x own body) in 6,662 candles. Solana 0.42% of candles, touching 3.1% of tokens.** The graduation clustering from round four is still consistent with that being where bad ticks land, but migration was never needed to explain the number.

**patch204**: a candle's high is accepted only when `high <= max(open, close) * HI_CAP`, HI_CAP 3.0, else the close is used. `SETTLE_HI_CAP` overrides. paper.py is unaffected: it builds its points from the obs tape, not the kline, so only settle.py needed the fix.

## THE TAPE EXPIRES IN 25 HOURS AND WE HAD NO COPY. patch203
`gmgn.kline(addr, chain, resolution, limit)` takes `limit` and NOTHING else - the docstring in gmgn.py already said from/to/end/before return empty. 1500 one-minute candles is a 25-hour window **ending now**. So a paper row older than 25 hours can never be re-priced, by anyone, ever. Every exit rule not yet invented is unmeasurable on it.

Both books only span two days anyway (RH from 2026-09-13 19:03 UTC, SOL from 2026-09-14 05:54 UTC), so there is no older history to mourn, but the rule bites continuously.

**Fix: `/opt/trench/kl_cache.py` + `klcache.sh` + `klrun.sh`.** Fetches each tradeable row's 1m tape once and stores it in a SEPARATE database (`/opt/trench/kl.db`, `/opt/trench-sol/kl.db`) so it can never take a write lock on the book. Runs both chains in parallel under `flock`, `gmgn.PRIORITY=False` and `RESERVE=5.0` so the live collectors keep priority, cron every 10 minutes plus a continuous `klrun.sh` loop. Oldest-entry-first, because those are the rows about to fall out of the window.
**This is what makes Gate A repeatable instead of one-shot.** Every future exit rule is now an offline replay costing zero API calls.

## THE OFFLINE REPLAY, AND IT WAS VALIDATED BEFORE IT WAS BELIEVED
`/tmp/exits2.py` prices Ben's four exits from the cache under three fill models: `close` (closes only, the floor), `clean` (highs kept only within HI_CAP of their own body), `hilo` (what settle.py did, the ceiling).
`/tmp/recon.py` checks it against the live book on the one policy both price the same way: **232 ripe hold_1h rows, median relative gap 0.0000, 95.3% within 5%.** The outliers are all rows where the replay reads HIGHER because the cached tape is longer than the one settlement saw.

## FIRST FOUR-EXIT NUMBERS. Robinhood, 427 positions, $17,118 deployed, UNTRIMMED
| exit | closes only | with candle highs | top1 row share |
|---|---|---|---|
| A hold 1h | **$63.75** | $67.87 | 6.8% |
| B sell all at 60s | **$82.92** | **$104.44** | 1.2% |
| C 2x or -50%, else 10m | **$78.14** | $95.01 | 1.4% |
| D half at 2x, rest at $500k | **$67.58** | $75.16 | 1.1% |

**B is the only exit that clears $100, and only on the optimistic fill.** It is not one lucky row: best single position is 1.2% of the total. The truth is between $82.92 and $104.44, because a real 60-second exit fills somewhere inside the minute.

## THE $100k / $500k / $1M HIT RATES AFTER ENTRY, CLOSES ONLY. `/tmp/bars.py`
Ben asked for these separately. A bar counts as reached only when a one-minute candle CLOSED at or above it.

| | RH (883 pos, median entry $5,149) | SOL (151 pos, median entry $2,927) |
|---|---|---|
| $100k | 17, **1.93%** (1.21-3.06), median 6m48s | 14, **9.27%** (5.60-14.96), median 3m07s |
| $500k | 2, 0.23% (0.06-0.82) | 6, **3.97%** (1.83-8.40), median 3m57s |
| $1M | 2, 0.23% (0.06-0.82) | 4, **2.65%** (1.03-6.61), median 3m00s |
| selling at $1M returns | $77.71 per $100 | **$102.14 per $100** |

**The only thing in the whole project that has ever cleared $100 on an untrimmed ledger is selling Solana at $1M**, and it rests on four events with a Wilson interval from 1.03% to 6.61%. A direction, not a result. Both Solana samples above are the OLDEST re-fetchable rows (the cache fills oldest-first), so the slice is not random and the numbers must be re-run on the full cache.

## STANDING RULES THIS ROUND EARNED
- **Print the raw records before theorising about a mechanism.** Round four built a migration story for a number that four lines of candle output explained outright.
- **Check whether the data source has a retention window before designing anything that depends on re-reading it.** An API that only answers "the last N" has a clock on every row you have not copied.
- **Cache the tape, not the conclusion.** A stored series can be re-priced after any bug; a stored return cannot.
- **Validate a replacement measurement against the old one on the one case where they must agree, before trusting it anywhere they differ.**

## ROUND FIVE CONTINUED: THE REPAIR NEEDED THREE FIXES, NOT ONE
The corrupt `high` field (patch204) was real but small. Two larger defects were behind it.

### FIX 2: THE SCALE WAS ANCHORED ON A LOOK-AHEAD CANDLE
A candle's `time` field is its OPEN. `settle.series()` takes `p0 = last close where t <= entry_ts`, so the candle CONTAINING the buy passes the test and its close is a price from up to 60 seconds AFTER we bought. On a coin that rugs inside that minute, p0 becomes the post-rug price and every other point is divided by it.
Receipt: `3zmVtrnPBeQDAtAQpReN9Cox4xizzpho2ipdoHKRZgGc`, entry $48,044.77, candle t=1789395000 close 3.6174884e-10 against 4.655298e-05 the minute before. Scale factor 1.3e14; a price of 4.6e-05 printed as $6,028,626,439.

**The right repair is not a better anchor, it is not to anchor at all.** Supply is fixed, so price and market cap differ by ONE constant for the life of the token. Fit that constant from the window where the kline and the obs tape overlap (obs reads market cap directly off the board). Scored on a HELD-OUT half of the overlap, Solana:
| scale | p10 | median | p90 | off >2x | off >10x |
|---|---|---|---|---|---|
| old (look-ahead anchor) | 0.9021 | 1.0000 | 2.2333 | 12.24% | 4.18% |
| new (strictly pre-entry anchor) | 0.5610 | 1.0000 | 1.3080 | 14.03% | 4.48% |
| **fit (from the obs overlap)** | **0.9200** | 1.0000 | **1.0947** | **7.16%** | **2.09%** |
Fitting the unit from post-entry observations is fine for a MEASUREMENT and would not be for a signal: it fixes what the series is denominated in, it says nothing about where the price goes.

### FIX 3: A PRICE WITH NO VOLUME BEHIND IT. THE BIGGEST OF THE THREE
`EaDE3kxa8o3c6fvTWdF6XdmthJ5Y2j9zykoEbicLjA3E`, entry $57,753.22:
```
i=16  close 7.23e-05    volume 312      real trading
i=17  close 0.16522301  volume 0.0101   x2,284 in one minute
i=18  close 1.11e-05    volume 0.0058   and straight back
```
A single dust trade of about a cent. The obs tape witnesses a maximum of $72,628 and a last reading of $5. The kline's "peak" was $166,204,072. **Two rows of this shape carried 91.2% of Solana's entire repaired one-hour return.**
Rule: a candle prices an exit only if its volume is at least VOL_FRAC (0.05) of that token's own MEDIAN positive candle volume. Unit-free, so it works on both chains without knowing the quote asset, and it asks the economically right question - you cannot sell $100 into a minute that traded a cent. It does not delete winners: a real run trades above its own median while it runs.

### I CORRECTED ROUND FOUR TOO FAST AND HAD TO CORRECT THE CORRECTION
Earlier this round I wrote that the pump.fun graduation/migration story was wrong and the corrupt `high` explained the impossible peaks. **It did not.** Round four was right about where the damage lives: the dust prints and the basis breaks cluster at the graduation state. The corrupt high is a third, smaller defect on top. **Standing rule: a new mechanism that explains SOME of a defect is not a replacement for the old one until it explains ALL of it. Check the residual before withdrawing the earlier finding.**

### A REPORTING ERROR IN MY OWN OUTPUT: "REACHED $1M" COUNTED COINS ALREADY ABOVE $1M
Solana printed "$1M reached by 3.12% of positions, median 28 seconds" against a median entry cap of $3,044 - a 328x in half a minute, which is not a thing. Listed one by one, **16 of those 18 positions were already above $1M when the book bought them** (entries of $55,852,162, $50,603,045, $14,438,027 and so on; "reached $1M" fired two seconds after entry because it had never left). The bar now excludes entries already above it from that bar's denominator.
Exactly one was the real thing: **`7EqFUJp8h6QsLfZg5d1KwSW17PoUxxqnZDHS2XaafhQ9`, entry $47,463, $1,159,706 at +79 seconds, obs witness maximum $6,511,648.**

### THE REPAIRED LEDGER, both chains, untrimmed, real tickets, real costs
Range is closes-only to candle-high fills.
| exit | Robinhood 1,812 pos / $70,927 | Solana 782 pos / $72,370 |
|---|---|---|
| A hold 1h | $66.47 - $69.29 | $79.06 - $79.87 |
| B sell all at 60s | **$83.77 - $103.48** | **$96.28 - $113.93** |
| C 2x or -50%, else 10m | $78.09 - $86.66 | $86.85 - $102.96 |
| D half at 2x, rest at $500k | $73.45 - $77.15 | $86.96 - $98.58 |
**Nothing clears Ben's $110 hurdle on the honest fill.** B and C clear $100 only on the optimistic one. B's top single row is 1.0% (RH) and 5.5% (SOL) of the total, so it is not one lucky position; C's is 0.3-0.6%, the most broadly based of the four.

Bars climbed to, closes only, entries already above a bar excluded:
| bar | Robinhood | Solana |
|---|---|---|
| $100k | 1.88% (1.35-2.62) | 4.79% (3.48-6.56) |
| $500k | 0.22% (0.09-0.57) | 0.79% (0.36-1.72) |
| **$1M** | **0.17%, 3 of 1,811** | **0.39%, 3 of 761** |
Selling at $1M returns $71.22 (RH) and $73.06 (SOL) per $100. **The winners are real and they do not pay for the losers.**

### patch205: A GATE THAT WENT RED ON A CORRECT SYSTEM
`check("the launchpad box still tells coins apart", 2.0 <= rate <= 70.0)` on a 90-minute window. strong_pad's fire rate is the share of the hour's launches from a QUALIFYING pad, so it moves with the launchpad mix and says nothing about the box.
What happened: `pons_v2` (about 85% of Robinhood launches) qualified a day ago on **183 resolved launches at 4.92% against a bar of 3.11%**, and was correctly retired as its sample grew to 12,888 and its rate settled at 2.79% against a bar of 3.60%. What is left is `pools_trade_instant`, genuinely strong at **24.93%, 8.7x the chain base**, and about 1% of launches. **The box got stricter because the evidence got better, and the gate called it a defect.**
The check now asserts the box is ALIVE (some pad qualifies) and DISCRIMINATING (it fires when a qualifying pad launches, and not above 70%), with the rate as a NOTE. Gate green at 96 checks.
**Open for Gate B, deliberately not applied during Gate A: PAD_MIN=40 is too low.** pons_v2 qualified on 9 wins in 183, whose 95% Wilson interval straddles the bar it was judged against. A pad should clear the bar with its LOWER Wilson bound, the same discipline boxlift got in patch202. **The entry rule must not move while a ledger is being measured on it.**

### TOOLING THAT NOW EXISTS (all on the droplet)
- `/opt/trench/kl_cache.py` + `klcache.sh` + `klrun.sh` - the tape cache, threaded (KL_THREADS=5; Solana kline latency is 1.98s against Robinhood's 0.61s for the same payload, so one sequential thread spent 70% of its life on a socket at 0.26 rows/sec; threads took it to 1.15). Separate `kl.db` per chain.
- `/tmp/pp.py` - the whole repaired analysis: fitted scale, three fill models, four exits, the bars. `/tmp/pp.py.prevol` and `.prebars` are the pre-fix versions.
- `/tmp/recon.py` - replay vs live book agreement check.
- `/tmp/anchor.py`, `/tmp/anchor2.py` - the held-out scale comparison.
- `/tmp/audit2.py`, `/tmp/baraudit.py` - dump the rows that carry a result, with the obs witness beside them. **Run these BEFORE quoting any Solana average.**
- `/tmp/peakchk2.py` - peak versus tape on the shared window.
