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.mdgives 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:
- 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. - A corporate action delivering a new ticker into the account.
- 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.