Financebotresearch desk研究台

Playbook › Playbook

A broker export older than a correctly-logged trade reports as drift, and --write would erase the trade

static 2026-09-24

Claim

The drift table is a comparison against a snapshot with a date on it. A row can drift because the file is wrong, or because the export is old — and the tool cannot tell you which.

What happened

/reconcile on 2026-09-24, against exports generated 2026-09-23 16:04:

=== ACCOUNT - N positions in export, 1 drifting === tkr tracked qty @ cost | actual qty @ cost what TICKER Q_tot P_blend | Q_old P_old qty + cost

Read at face value this is the exact signature of the failure [[pitfall-average-cost-derived-not-copied]] was written about: a round share quantity missing, and an average cost that moved — the tell of a specific-lot sale logged sloppily.

It was the opposite. Transactions.md held:

DATE | ACCOUNT | BUY | TICKER | Q_new | P_new | new capital, not existing cash

The arithmetic closes exactly:

Q_old @ P_old + Q_new @ P_new = Q_tot @ P_blended (snapshot agreed to the cent)

A portfolio-specific passage was removed from the public build.

Why this is dangerous and not merely noisy

  1. The failure signature is identical to the real bug. Quantity gap plus a moved average is precisely what an unlogged specific-lot sale looks like. The 2026-09-23 reconciliation found many such rows, so the prior is strongly toward "believe the drift".
  2. The printed remedy is destructive. intake.py and sync_positions.py both print the --write command directly beneath the drift table. --write treats the broker as authoritative — correctly, in general — and would have rewritten the position back to Q_old @ P_old, erasing a real purchase from the position file while leaving it in Transactions.md. The next ledger.py check would then report drift caused by the fix.
  3. It inverts the usual direction of error. The whole reconciliation discipline is built on "the broker is right, the file is derived". This is the one case where the file is ahead of the broker, and the discipline points the wrong way.

The rule

  1. Read the export's as-of date before reading the drift table. intake.py prints it in the first column. Any trade in Transactions.md dated after it is expected to drift.
  2. Before believing a drift row, grep the ledger for that ticker. If a logged trade explains the gap arithmetically — shares and weighted-average cost — the file is right and the export is old. Close the arithmetic; do not eyeball the share count.
  3. A drift row where tracked > actual is a buy-after-export until proven otherwise. The unlogged-sale case leaves tracked > actual too, so this is not decisive — but it is the cheaper hypothesis to test first, and testing it costs one grep.
  4. Never --write on the same day a trade was logged without re-downloading the export. The fix for a stale export is a fresh export, not a write.
  5. This is the mirror of the CLAUDE.md sequencing trap. That one warns a trade dated after the baseline double-counts; this one warns a trade dated after the export reads as missing. Both are the same root cause: three artifacts with three different as-of dates, and only one of them prints its date.

What would falsify this

A sync_positions.py that parses the export's as-of date and excludes ledger trades postdating it from the comparison — at which point the row would not appear at all. That is the right fix and it is not built. Until it is, the check is manual.

Related

[[pitfall-average-cost-derived-not-copied]] is the failure this one impersonates; reading them together is the point, because the second is the false positive of the first. [[pitfall-drift-report-read-as-inventory]] — same family: a generated report describes a comparison, not reality, and the comparison has assumptions the report does not restate.

History

  • 2026-09-23 — first reconciliation; 24 genuine drifting positions found, --write correctly applied. This established the prior that a drift row means a bookkeeping error.
  • 2026-09-24 — /reconcile re-run against the same 9/23 exports after a 9/24 buy. One drift row, entirely spurious. Caught by the dry-run-first discipline in the skill; note written. Nothing was written, no re-anchor was taken.