Pre-roll fills stay visible; holds survive restarts and judge fund amounts

Second review round (e496404, 9b2d677):

- Tradovate: past the 5 PM roll an old-dated cash record counts only what
  it gained since the roll (its realizedPnL as last seen while current is
  kept as the roll base), so tonight's fills booked onto it before
  Tradovate rolls the account stay visible — no re-trading a one-and-done
  account. A record this process never saw before the roll leaves the
  account waiting for the broker's roll (ROLL_PENDING, counted in play for
  stop-after-all). The 60s snapshot carries the roll base and can't undo a
  newer record the socket delivered.
- A wipe's hold is kept in account_meta, so a restart onto a failing Cash
  History still holds the account.
- A Cash History whose pre-request balance the last vouched-for baseline
  can't explain, and which booked nothing, holds the account instead of
  rebasing onto an unbooked payout.
- Holds compare fund transactions by date and amount (a second payout on a
  date that already has one), and an in-flight hold is judged against the
  rows from before its report.
- The saved baseline tracks the day's realized P&L and folds, so a restart
  judges against current figures; held refetches back off to an hour, and
  a balance hold no Cash History can check is released after an hour.
- TopstepX: a fetched account is promoted only by a sync whose
  reconcileFunds actually checked the books (not one skipped for an open
  position), and a direct fetch awaits that sync, so wipe routes report
  the real outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-24 18:45:45 -05:00
co-authored by Claude Opus 5.5
parent 9b2d677662
commit 088886ed73
4 changed files with 204 additions and 107 deletions
+5 -1
View File
@@ -381,6 +381,9 @@ function entryCommissionFrom(before: number, after: number, contracts: number):
/** The ineligible reason for an account whose history isn't loaded yet — still
* in play for stop-after-all, since it trades once the fetch lands. */
const HISTORY_PENDING = 'history not loaded yet — waiting for the fetch';
/** Likewise for an account whose broker hasn't rolled its cash record to the
* new trade date, when this process didn't see the old day end. */
const ROLL_PENDING = "waiting for the broker to roll the account's day";
interface AccountEvaluation {
eligible: boolean;
@@ -431,6 +434,7 @@ function evaluateAccountForTrade(client: any, acc: { id: number; name: string; a
// account) or that a payout has overtaken would plan the account as a
// fresh stage-1 first day: an oversized leg, or a re-opened finished cycle.
if (!client.historyReady(acc.id)) return { ...base, eligible: false, reason: HISTORY_PENDING };
if (cash.pendingRoll) return { ...base, eligible: false, reason: ROLL_PENDING };
// Obfuscation stops: a stop-out shows as realized P&L falling short of the
// legs fired today — the day is booked; the make-back ride is tomorrow.
@@ -1214,7 +1218,7 @@ function hasRemainingConfiguredAccounts(): boolean {
// during the session and before it opens alike (a restart between
// the 5 PM roll and the open loads histories right then). Only
// once the session has closed is a still-loading account let go.
if (ev?.reason === HISTORY_PENDING && !isAfterSessionClose()) return true;
if ((ev?.reason === HISTORY_PENDING || ev?.reason === ROLL_PENDING) && !isAfterSessionClose()) return true;
}
}