# 2026-08-04 — the live momentum book has been 24% larger than its design, silently

*Daily audit finding. Observability only — no trading logic, sizing, or config
value was touched.*

## What was observed

The 2026-08-04 fleet digest reported momentum holding **62 positions**. Momentum
is a 50-name strategy (`strategy.construction.momentum_sleeve_size: 50`). Nothing
in the digest, the health checks, or CI said anything about it.

## What the ledger says

The committed state makes the expectation exact. Every selection run writes a
`run_summary` with the book it intends to end up holding — `n_holds` (names kept)
plus `n_buys` (names added):

| selection day | pre-trade book | holds + buys = intended book | book next session | unexplained |
|---|---|---|---|---|
| 2026-07-01 | 53 | 42 + 8 = **50** | 58 | +8 |
| 2026-08-03 | 58 | 33 + 17 = **50** | 62 | +12 |

The pre-trade counts reconcile perfectly (`n_holds + n_sells` = the book the run
saw: 42+11 = 53 ✓, 33+25 = 58 ✓), so the strategy is seeing the real book and
classifying all of it. It is the *post-trade* side that does not close: names the
run intended to exit are still there the next day, and the residue compounds —
53 → 58 → 62 across three monthly rebalances.

Neither rebalance was throttled: 2026-08-03 turnover was 0.496 against a 1.1 cap,
so the turnover limiter did not defer those exits. `n_failed` was 0.

## What it is not

Two plausible readings were tested and rejected:

**"The same names keep getting sold, so exits are failing."** 15 of the 24 names
sold on 2026-07-01 were sold again on 2026-08-03 with zero orders in between —
suggestive, but the August sells were *larger* than the July ones (SNX $122.70
then $210.66; CMI $33.41 then $120.01). Those are trims of names that stayed in
the target set, i.e. ordinary rebalancing, not failed exits.

**"It's the same thing meanrev is doing."** It is not. meanrev runs a full daily
re-selection against a 50% two-sided turnover cap that binds exactly
(`weighted_turnover` 0.5000 = `turnover_cap` 0.5), so its intended exits are
throttled by design and its book sits at 2–3× its target set. That is structural
and expected. Momentum's cap had headroom and still did not clear.

The remaining candidate — fractional-share exit orders leaving a residual
quantity that keeps the position alive — is consistent with every number here but
**cannot be confirmed from committed state**, which records counts and not
per-name quantities. It needs broker truth (see the ask below).

## Why nobody was told

`health.py`'s position-count check compared the book against
`momentum_sleeve_size * 1.5`. That single static tolerance was wrong in both
directions at once:

- **Too loose for momentum.** 62 of a 50-name design is 124%, and 62 < 75, so it
  was silent — for two months and two rebalances.
- **Too tight for meanrev.** Its throttled book has exceeded 75 every trading day
  since 2026-07-20, firing the identical warning line in every digest. An alarm
  that cannot be wrong is an alarm nobody reads.

## What was changed

The check now compares the book against the expectation the system already
recorded, and uses the last selection's own turnover as the arbiter for whether
an excess is explained:

- excess within tolerance (2 names or 5% of target) → clean;
- excess with the turnover cap bound → **explained**, reported as a notch, not a
  warning (the treatment §4 of `ops/DAILY_AUDIT.md` gives a calendar-explained
  capture drop);
- excess with the cap having had room → **fails** the check;
- arbiter missing or incomplete → falls back to the old static tolerance and says
  the arbiter was absent. A missing arbiter never excuses an overhang.

`thales portfolio health` is the paper-trading workflow's last hard-fail gate,
placed after state persistence precisely so a book-sanity failure reds the run
and emails the owner without ever blocking a trade or a commit. **So momentum's
daily run will go red until the overhang is resolved or the finding is
reclassified.** That is the intended loudness, but it is the owner's call —
the change is a PR, not a push.

## The open ask

This audit has no broker credentials, so the mechanism is unproven. From a
machine that does:

```
thales portfolio positions            # per-name quantities for all 62
thales portfolio reconcile            # book vs internal state
```

If the extra ~12 names carry negligible quantities, this is fractional-exit
residue: cosmetic in P&L, but it corrupts position-count telemetry, wastes
~12 sell orders a month, and grows every rebalance. If they carry real
quantities, the book is holding unintended exposure the strategy believes it
sold, and that is a risk-control problem rather than a reporting one.

---

## Addendum — 2026-08-05 daily audit: the book grew on a day we sent nothing

The next session added a data point that the exit-residue hypothesis above
cannot accommodate, so it is recorded here rather than in a separate note.

**2026-08-05 was a vol-check day.** Momentum selects on the first trading day
of each month; 08-03 was that day. The 08-04 and 08-05 run summaries are
`vol_check_only: true, selection_completed: false`, and the last non-summary
line in `data/processed/order_log.jsonl` is dated 2026-08-03. **Zero orders
were submitted on either session.**

The broker's book moved anyway:

| session | n_positions | orders we sent that session |
|---|---|---|
| 2026-08-03 (pre-trade snapshot) | 58 | 66 |
| 2026-08-04 | 62 | 0 |
| 2026-08-05 | **63** | 0 |

`n_positions` is not internal bookkeeping — `daily.py:652` records
`len(broker.get_positions())`, read live from the broker inside
`_record_portfolio_snapshot`. So a name appeared in the broker's position list
on a session in which the system placed no order.

**Why this matters for the diagnosis.** A fractional-share exit that fails to
fully close leaves a position that was *already there*; it cannot create one.
Whatever produced 62 → 63 is a second mechanism, or the first mechanism is
mis-stated. Three candidates, none confirmable from committed state:

1. **A straggling fill from the 08-03 batch.** The last three submissions in
   the order log are buys — DAR, JBHT, PSX — logged at
   `OrderStatus.PENDING_NEW`. A buy that did not fill at the 08-04 open and
   filled at the 08-05 open produces exactly this.
2. **A corporate action** delivering a new ticker into the account.
3. **Broker-side settlement** of a fractional lot crossing a display threshold.

If (1) is what happened, it also undermines an assumption in the table above:
"book next session" was read as the settled post-trade book, but if fills
straggle across two sessions then the 08-03 row's +12 was measured before
settlement finished, and the true overhang is whatever the book converges to,
not the T+1 reading.

**New supporting evidence for the exit-residue half.** `tca_log.jsonl` shows
all 66 of the 08-03 orders were notional (fractional-share) orders — every one
has a non-integer share count. The smallest intended exits are dust-sized:

```
INTC  0.135735556 sh @  90.38 = $12.27
WDC   0.024674214 sh @ 525.97 = $12.98
MU    0.017742585 sh @ 822.97 = $14.60
STX   0.024221583 sh @ 825.00 = $19.98
```

A notional sell sized from a stale price against a position of 0.0177 shares
has essentially no room to land exactly flat. That is consistent with residue
on the exit side, and is the first quantitative support for it.

## The ask, refined

The two commands in the section above are still the right ones. Add a third,
and one specific question:

```
thales portfolio positions            # per-name quantities for all 63
thales portfolio reconcile            # book vs internal state
thales portfolio orders --days 7      # did any 08-03 order stay open into 08-05?
```

**Which symbol is the 63rd?** If it is DAR, JBHT or PSX, candidate (1) is
confirmed and the overhang is partly a fill-timing artifact rather than pure
residue. If it is a name the strategy has never selected, look for a corporate
action. Either answer changes what the fix should be.
