Trade only on loaded history; catch early TopstepX fills; fix SI/M2K point values

- Both clients track which accounts this process has loaded history for
  (historyReady). An account restarted, rebuilt, wiped or newly listed is
  ineligible until its fetch lands, instead of trading as a fresh stage-1
  first day; a failed fetch with no cache no longer reads as empty history.
  Waiting accounts get a targeted fetch on the next sync.
- Tradovate holds an account whose start-of-day balance moves without a fill
  (a payout between hourly Cash History runs) until a Cash History books it,
  so a payout-pending account isn't re-traded on its ended cycle.
- Wipe routes forget history through the client and count accounts whose
  history didn't come back as failures.
- TopstepX sendOrder picks up a fill that arrived before the place response,
  and its REST backstop uses the documented Trade/search and Order/search.
- POINT_VALUES: SI 5000 (was 50), M2K 5 (was 10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-24 17:04:09 -05:00
co-authored by Claude Opus 5.5
parent 61384c9fa6
commit 77d8c2ecdb
7 changed files with 292 additions and 46 deletions
+11
View File
@@ -355,6 +355,10 @@ async function legInstrument(
// ── per-account trade evaluation (shared by runTrade / copyTrade / hasRemaining…) ──
/** 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';
interface AccountEvaluation {
eligible: boolean;
reason: string;
@@ -400,6 +404,10 @@ function evaluateAccountForTrade(client: any, acc: { id: number; name: string; a
// Another batch is placing this account's order right now; its position
// won't show until the fill comes back, so the check above can't see it.
if (runtime().accountsInFlight.has(acc.id)) return { ...base, eligible: false, reason: 'order in flight' };
// History this client hasn't loaded (a restart, a rebuild, a wipe, a new
// 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 };
// 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.
@@ -1166,6 +1174,9 @@ function hasRemainingConfiguredAccounts(): boolean {
const ev = evaluateAccountForTrade(client, acc, tradeDate);
if (ev?.eligible) return true;
// Waiting on its history, not done: it may still owe today's trade.
// Only while the session is open, like the unready firms above.
if (ev?.reason === HISTORY_PENDING && !isInNoTradeWindow()) return true;
}
}