Thales
← research journal
Aug 4, 2026raw markdown ↗

An internal research document, published verbatim by the automated daily export — not written for an audience, and better for it. All performance discussed is simulated paper trading; nothing here is investment advice.

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 daypre-trade bookholds + buys = intended bookbook next sessionunexplained
2026-07-015342 + 8 = 5058+8
2026-08-035833 + 17 = 5062+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:

sessionn_positionsorders we sent that session
2026-08-03 (pre-trade snapshot)5866
2026-08-04620
2026-08-05630

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.