# Prime Academy: correctness and robustness audit (arena synthesis, 2026-10-01)

Three independent audits (Opus, Fable, Sonnet), a cross-judge (Fable) that reran 16 seeds, and the
parent's own checks of the code. Base: candidate 3; grafts from candidates 1 and 2 (see
`SYNTHESIS.md`). Script paths are relative to `/tmp/arena-prime-audit/`.

**Bottom line.**

- **The model is correct.** Two auditors rebuilt the chains, the wallet and the published numbers
  independently, and both matched to the last digit.
- **The decision is fragile.**
  - The purchase gate sits at zero within its own noise.
  - Its sign depends on four modelling choices the paper does not list as open.
- **The code that would trade does not run the plan.** It also carries a clip defect that would stop
  the assessment from ever passing once a live spread is charged.

---

## 1. Findings, ranked

### 1. The live runner does not run the plan (critical; verified by reading, parent-checked)

- `run_live.py:74`: `FROZEN = dict(risk=0.04, ...)`. `set_account` is never called, so the live
  runner has:
  - no target clip and no floor clip;
  - no switch to the funded risk;
  - no payout clock.
- The only caller of `set_account` is the replay in `broker_map.py:63`. Neither `acceptance.py` nor
  the unit tests touch the account path.
- 4% risk breaks the 35% cap if the firm applies it in the assessment. Under the strict daily
  reading 4% passes 39.7%, against 44.6% for the plan (c1 `log_s06b.txt`).
- The window is shut (`PRIME_WINDOW=0-0`), so nothing trades today.
- **Fix.** Give the runner a stage flag and the two risks. Call `set_account` with the 7-trade
  clock. Add an acceptance replay of the account path pinned to `eval_chain`.

### 2. The target clip nets commission only: with a live spread the assessment cannot pass (critical once 1 is fixed; mechanism parent-checked, size from a re-implementation)

- `prime_academy_strategy.py:724-729` aims the target at `(T - eq) B + fee` with
  `fee = cost_per_lot x lots`. The replay passes commission only, and the bar broker charges no spread.
- **Live mechanics.**
  1. A clipped win lands short of +9% by the spread cost.
  2. The next clip then aims at that shortfall plus the commission.
  3. That target is smaller than the spread, so the win nets zero or less, and equity stalls under
     the target until a 3% stop.
- **Size.** On c3's lattice copy of the rule, pass falls from 45.3% to 0.0% for a shortfall of
  0.1 pip or more (c3 `scripts/e19`). This was not run through the engine, which has no
  spread-charging broker.
- **Fix.**
  - Net the quoted spread and a swap allowance in `cost_per_lot`.
  - Aim a few ticks past the target.
  - Confirm with one demo trade: the equity after a clipped win against +9%.

### 3. The purchase gate is zero within its noise (high; verified by execution, judge rerun)

- **The published +0.013 is one seed at 10,000 paths.** The judge reran all 16 candidate seeds
  digit for digit. Pooled over 80,000 paths the gate reads +0.0041 ± 0.0024 (t 1.7). With the
  published run included it is +0.0055 ± 0.0023 (judge `j01.log`).
- The gate rule has no noise margin (`decision.json` gate = `g_80 > 0`), while T and D in the
  front get a two-standard-error margin.
- **Known biases, each about the size of the level itself:**

| correction | effect on the gate | source |
|---|---|---|
| cost share for a variable stop is c·E[1/ℓ], not c/E[ℓ]: 2.97% against 2.87% for commission and spread | −0.009 to −0.010 | c1 `log_s04`, `s02`; c3 `e07`, `e14` |
| lot flooring: nominal 1.575% realises 1.504% | −0.008 | c3 `e14` |
| both together | −0.011, negative in all seeds | c3 `e14` |
| the open account and the last fee are valued at zero at the horizon | about +0.05 (inferred) | c1 F2; c3 `e06` (mean wallet $1,133 against $1,195 implied) |
| horizon | zero near 2.2 years | c3 `e05` |

- **Fix.**
  - Return the standard error from `wallet_mc` and require two standard errors.
  - Cost the stop by c·E[1/ℓ].
  - Size the funded risk so the floored lots realise the chain's risk.
  - Value the open account at the horizon, or gate on expected value with a margin (finding 6).

### 4. What a refused payout does decides the gate (high; verified by execution in two candidates)

- `wallet_mc` treats a refusal as "the equity leaves and nothing is paid" (`prime.py:679-683`).
  That reading is stated only inside the proof of Proposition 23.

| refusal reading | gate at 80% honoured | breakeven honour |
|---|---|---|
| equity withdrawn, nothing paid (coded) | about 0.00 | 79 to 80% |
| equity stays, request repeats | +0.16 to +0.19 | about 60% |
| account closed (Terms 23.1, clause 16) | −0.18 | about 89% |

  Sources: c2 `c2_refusal.out`, c3 `e06`.
- **Fix.** Add a `refusal` argument, print the gate under all three readings, and add the row to
  the open-questions table with "the firm" as the source of the answer.

### 5. The strict daily-loss reading costs pass rate and closes the gate (high; measured on the bars, small event counts)

- The strict reading counts open profit at the day boundary. A 3% stop-out after an overnight float
  above 0.63 of the stop breaches it.
- Measured on the cohort's hourly bars:
  - at nominal 3%: 0.76% of trades (c3) and 1.15 to 1.58% of losses (c1);
  - at the lot-floored 2.97%, the rate halves.
- **Effect.** Assessment pass falls 1.5 to 2 points. The gate moves by −0.002 at the floored rate
  and by −0.031 to −0.044 at nominal 3%.
- These rates rest on 8 to 35 events, so treat them as known to about ±40%.
- Paper line 1551 says the daily limit "never fires at one trade". That holds only under the
  lenient reading.
- **Fix.** Ask the firm, or read the demo account's equity at the rollover. If the reading is
  strict, 2.75% beats 3% (c1 `log_s06`).

### 6. On expected value the play is robustly worth buying (holds; verified by execution)

- At the deployed cell a purchase returns 1.61 times its fee if every payout is honoured. At 80%
  honoured that is 1.29 times, and the breakeven honour rate is 62%.
- Every rule reading tried keeps cash per fee at or above 1.43 (c3 `e20`):
  - the floor not on the tick;
  - the target not clipped;
  - the 35% rule read net, or absent;
  - the $100 minimum read before the split;
  - the clock from the assessment's opening, or 14 trades;
  - the assessment at 4%.
- The arithmetic cash rate stays positive until the cost share reaches 5.55% or honour falls below
  62% (c1 `log_s08`).
- The log gate is a thin proxy for this margin. Its knife edge comes from ruin, the horizon and
  terminal value, not from the expected cash.
- The step-asides close both views at the measured 1.33 a trade:
  - the expected-value view reopens below 0.75 asides a trade;
  - the log gate reopens only near zero asides at a 0.5-pip spread (finding 7).

### 7. The step-aside measurement cannot open the log gate alone (medium; inferred from verified roots)

- The largest aside rate a trade that keeps the log gate open depends on the spread:

| spread | largest aside rate |
|---|---|
| 0.5 pip | about 0.00 |
| 0.4 pip | 0.11 |
| 0.3 pip | 0.25 |
| 0.2 pip | 0.42 |
| 0.1 pip | 0.66 |
| 0 pip | 0.99 |

  Source: c2 [9]. The pooled root is a 0.52 to 0.53 pip spread (judge).
- A EUR+USD calendar holds 49% of the cohort's events (c2 `news_battery.json`), about 0.66 asides a
  trade. That opens the log gate only at a spread of about 0.1 pip, but it opens the expected-value
  gate.
- The live spread is the variable that decides it. The calendar alone does not.

### 8. The 40-pip pick is inside its inputs' error, and the two stop-rule settings differ (medium; verified)

- **The edge is small.** 40 pips beats 34.8 on cash by $2.8 a year, 4.4%. The lead flips when:
  - the 40-pip pace is 4.4% slower, or
  - its cost share is 0.08 points higher (c2 `c2_front`, c3 `e12`).
- **The paper's "the two settings give the same cell" (`prime_academy.tex` "From a mean stop to the rule") is false.**

| 40-pip rule | c·E[1/ℓ] | variable-stop cost uplift | cash a year |
|---|---|---|---|
| max(0.924σ, 30), multiple raised | 2.662% | +6.0% | $58.6 to $60.9, loses to 34.8 |
| max(0.75σ, 38.3), floor raised | 2.522% | +0.9% | $61.7 to $67.5, stays ahead |

  Sources: c1 `log_s04`, `log_s05`; c3 `e07`, `e12`.
- **Payout clock.** It rounds to 6 trades at 40 pips. The first trade ending on or after day 14 is
  the seventh (c1).
- **What holds.** At 40 pips the gate is open with room: +0.022 to +0.047 (c1), and +0.036 ± 0.011
  against +0.005 at 34.8 pips (c2). The days and the drawdown favour neither by much.
- **D is a multiple of the $38 fee.** Its bootstrap standard error is zero, so the drawdown margin
  in the dominance test does nothing (c3 F5). Use a mean or a smoothed quantile.
- **Reading.** Keep 40 pips, through the floor setting max(0.75σ, 38), on its gate margin. The
  "richest" label is not the reason.

### 9. The headline table mixes clocks (medium; verified)

- "Days to first payout 46" and "life 123 days" come from broker cells run on a 14-trade clock
  (`broker_map.py:34`, `make_numbers.py:980-983`).
- The plan runs 7 trades: a median first payout of 14 trades, about 28 days, and a mean life of
  about 95 days (c3 `e04`).
- Section 14's funded map also runs at 14 trades (`make_numbers.py:786`, no `wait`). Its levels sit
  about 2 points low, and its ordering stands.

### 10. The funded pick is the plateau's edge (low-medium; verified)

- The median first payout is 14 trades for every plateau risk from 1.35% to 1.575%. The
  "fewest trades" rule does not choose, so the notebook's tie-break (larger gross withdrawn) takes
  the upper edge (c2 [1], judge).
- The width of the plateau sets the pick:

| plateau width | pick | cash a year |
|---|---|---|
| 0.25 point | 1.45% | $55 |
| 1 point | 1.575% | $65 |
| 2 points | 1.675% | $73 |

  Source: c1 `log_s01`.
- On wallet growth the optimum is higher: +0.025 at 1.85%, where the chance of any payout is still
  49.8% (c3 `e21`).
- **Fix.** State the tie-break, or choose on G subject to a chance of any payout of at least 50%.

### 11. A second account depends on a correlation measured once (medium; verified by execution)

- Two accounts at 40 pips with commission and spread stay feasible up to a normal correlation of
  about 0.69, an outcome correlation of about 0.45 (c2 `c2_rho`).
- The 0.26 the front uses was measured at 07:00 and 16:00 UTC on a 30-pip stop. Hours inside the
  07:00 to 10:00 window correlate more.
- **Fix.** Run `fxcontrol.pair_corr` at (07,08), (07,09), (07,10) and (07,16) on the cached bars.
- Separately, `spread.family` gives every stream the same seed, so refusals coincide across
  accounts. The effect is small and conservative (c1 F10, c3 `e16`).

### 12. Live accounting at a payout (low until finding 1 is fixed; parent-checked)

- `prime_academy_strategy.py:344` sets `acct_start = broker.balance` on a payout. In a replay nothing
  is withdrawn, so that is right. Live, the operator's withdrawal lowers the balance, and the strategy
  then reads equity as minus the payout. Its floor clip would end the account early.
- **Fix.** On a live payout, take `acct_start` from the post-withdrawal balance, or subtract the
  withdrawn amount.

### 13. Smaller code faults (low; verified)

- `tooth_edges` (`prime.py:360-366`) uses n = wait for every j, but an even j first occurs at
  wait + 1. The chain's jump is at 1.276% and the formula's at 1.265%. The line search reads the
  chain, so no decision moves.
- `day_split_mc` checks the barriers once a day, not after each trade, which moves the k > 1 pass
  rates by up to 1.7 points. One trade a day stays the fastest (c1 `log_s09`).
- The strategy keeps the largest net win for the 35% rule, where the model reads it gross. That is
  lenient by the commission.

### 14. Paper text the code contradicts (cosmetic)

- Line 1548, "The target is not clipped ... not implemented": the plan and `set_account` clip it.
- Abstract: "fixes ... the risk at the daily-loss maximum". The plan risks 3%, at the 35% cap.
- Plan table, "a second only after a payout is honoured": no model buys that way.
- Line 136 states where the 14-day clock starts as fact, but `rules.json` marks it unverified.
- `rules.json` lacks the $300,000 cap, the news rule and the EA ban the paper cites from it.
- The README and `deploy/prime-academy.env.example` still say 4% and a fixed side.

## 2. What checked out

- **`eval_chain` and `funded_chain`** match an independent dynamic program to 8 digits (c3 `e02`).
- **`decision.json`** reproduces to 4 or 5 digits from the chains alone (c1, c2, c3), and
  `front.json` reproduces G to $0.1 (c2).
- **`wallet_mc`** matches the chains on cash per purchase.
- **`shared_wallet`** matches `wallet_mc` at one account.
- **Ruin, first-cash day and the purchase timing** have no off-by-one (c2, c3).
- **Clipped legs land exactly** in floating point (c2, c3).
- **The 14-day clock as 7 trades holds.** The empirical first exit at or after day 14 has a median
  of 7, and the gate keeps its sign from 6 to 8 trades. The clock change, not the horizon, flipped
  the gate from −0.035 (c1, c3).
- **The 3-day minimum and the allocation cap never bind** at the deployed cell.
- **The Wald mapping of pace to stop** holds within 3% on unclipped brackets (c3), and the
  walk-forward's measured paces corroborate it (c2).
- **The swap nights reproduce:** 0.729 against 0.743 a trade (c3).

## 3. Robustness table: where each decision flips

Deployed cell: random side with swap, 3% then 1.575%, 34.8 pips, 7-trade clock, $1,000 wallet,
80% honoured. Log gate: expected log growth over three years above zero. Expected-value view: 0.8
times cash per fee above 1.

| input (assumed) | log gate | expected-value view | funded pick | 40 pips, one account |
|---|---|---|---|---|
| Monte Carlo noise | +0.004 ± 0.0024 pooled: undecided | exact | exact | gate +0.02 to +0.05, robust |
| live spread (0.5 pip) | flips at 0.52 to 0.53 pip | flips at about 1.26 pip | 1.55% to 1.70% over 0.5 to 1.0 pip | a wider spread favours 40 |
| swap (+0.48% of risk) | flips about 0.06 points higher | flips at about 6 times | none | swap share scales near ℓ^1.5: 40 loses 0.06 points |
| step-asides (1.33 measured) | closed; opens near 0 at 0.5 pip | closed; opens below 0.75 | 1.6% to 1.7% | no cell feasible at 1.33 |
| honour (80%), refusal coded | flips at 79 to 80% | breakeven 62% | none | gate flips near 76% (inferred) |
| refusal reading | +0.17 stays, 0.00 coded, −0.18 closed | moves the same way | none | same ordering |
| strict daily reading | −0.002 (floored) to −0.044 (nominal 3%) | holds (cash per fee 1.55) | none | inferred −0.015 |
| variable-stop cost, lot floor | −0.011 together | holds | nominal about 1.65% to realise 1.575% | the multiple setting loses, the floor setting holds |
| terminal valuation | about +0.05 if valued (inferred) | n/a | none | none |
| horizon (3 years) | flips near 2.2 years | n/a | none | none |
| payout clock (7 trades) | holds from 6 to 8; −0.03 at 14 | holds | 1.575% from 5 to 12 trades | 6 against 7 at 40 is worth $2 to $3 |
| 35% cap in the assessment | not a flip | holds | none | 4% if the cap does not apply |
| floor on the tick | review reading opens it widely | holds | 1.65% | none |
| account correlation (0.26) | n/a | n/a | n/a | second account fails above about 0.45 outcome correlation |
| live clip netting (commission only) | pass goes to zero, so the gate closes | negative | n/a | all cells |

## 4. What each decision should read now

- **Purchase.** Undecided on the log gate, and robustly yes on expected value at 80% honoured
  without the step-asides. It needs three things:
  - the live spread at or below 0.5 pip;
  - the firm's reading of the daily rule and of a refused payout;
  - the step-aside rate.
  The log gate should carry a standard error and a terminal value, or be replaced by an
  expected-value gate with a stated margin.
- **Stop.** 40 pips through max(0.75σ, 38): its gate is open with room under every cost
  correction tried.
- **Funded risk.** Any plateau member is equivalent on the paper's own rule. State the tie-break,
  or choose on G with a chance of any payout of at least 50%. Size the lots so the realised risk
  matches.
- **Assessment risk.** 3% under the lenient daily reading. 2.75% if the firm reads the daily rule
  strictly.
- **Accounts.** One. A second only after `pair_corr` reads under about 0.45 at the second hour.
- **Live runner.** Not deployable until findings 1, 2 and 12 are fixed and an account-path
  acceptance replay passes.
