Commit Graph
23 Commits
Author SHA1 Message Date
Brandon LiandClaude Opus 5.5 b5939a06a0 Vest: only a long closure holds entries back; the maintenance hour is ridden
The close gate held every Vest entry within four holds of any close,
which with a 2 h hold meant no weekday entry from about 9 AM to 5 PM ET
for the one-hour maintenance break. That hour reopens before the reset,
as CME's daily break does for futures legs, so only a weekend or holiday
closure gates an entry now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:51:00 -05:00
Brandon LiandClaude Opus 5.5 ef15200e5e Vest: report on Vest's day, and close a leg whose open came back unknown
The reporter and the dashboard take each firm's own trade date
(tradeDateFor), as the engine does: between 5 PM CT and Vest's 8 PM ET
reset the Central date counted the open Vest day as a prior day and as
today's P&L both, and could report a pass the master keeps for good. The
completion check's settle window now follows the client's own roll.

An open whose outcome came back unknown is marked the bot's once a read
shows the position it made, so the market-close guard closes it before a
long closure; an unmarked leg is no longer logged as a hand trade.

A symbol the close gate holds back stays on the candidate board for the
dashboard and the report, and fireLeg holds each account's leg to the
close at its own size, not just the batch lead's. check-vest-client.mjs
drives the guard and the board with every gateway call answered locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:41:20 -05:00
Brandon LiandClaude Opus 5.5 ec3e6ea388 Vest: no leg opens into a market close, and accounts plan on Vest's day
Market close: a Vest entry now needs its symbol's market to stay open
for RESET_CLEAR_HOLDS holds of the leg (clearsClose), the clearance the
8 PM ET reset already gets: at the target hold, and at the leg's sized
hold when a leverage cap stretches it. That covers the weekly close and
the daily maintenance hour as Vest's market-hours periods list them.
Before a closure that runs past an 8 PM ET reset (the weekend, a
holiday: longClosure), the bot's own legs still open in the last 5
minutes are closed at market, loudly; a position the bot didn't open is
named in the log and left alone.

Vest's day: Vest accounts are planned, and their legs and entries
counted, on Vest's day (20:00 ET) instead of the Central date
(tradeDateFor, PlatformClient.tradeDate). Between 5 PM CT and the reset
the Central date counted the open Vest day as a prior day and as today's
realized P&L both ($3,000 to go, $800 made: $2,050 left, not $1,900) and
started the leg count over. Other platforms keep the Central date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:12:27 -05:00
Brandon LiandClaude Opus 5.5 beddbdcf6c Vest: stops reserve 0.02% of the position for slippage, not 0.1%
A platform may now state its own stop slippage share
(PlatformClient.stopSlippageFraction); the daily-loss reserve is the
larger of 10% of the limit and that share of the position. Vest's 9
measured stop-outs filled 0.9 points past the trigger on average and
2.25 at worst, against the ~30 points 0.1% held back. On a $1,400 day
with ~$495k of NDX the stop goes from ~$880 to ~$1,230, and the leg
sizes up for the same hold. Sizing, the placed stop, the dashboard's
next stop and the reset guard all use it; other platforms keep 0.1%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:34:27 -05:00
Brandon LiandClaude Opus 5.5 f0c1569d96 Vest: a split day's next leg gives back the day's gain plus the limit
On a split day the next Vest leg's daily-loss room now adds the day's gain
still in the account to the bot's limit (floorRoom's `gain`), never past
Vest's own floor, and the leg sizes up for the same target hold (Dingo's
next eval leg: $455k -> $842k at 1.5 h, win 28% -> 56%). Other platforms
are unchanged: dllRoom still never widens on a green day.

The gain only widens a leg all but sure to close before Vest's 8 PM ET
reset (resetClearStop, held to the shortest leg the day may carve), and
none of a hand claim's profit counts. The last-stand margin is measured
against the limit, so a green day can't drop a leg's stop.

Around the reset: no new Vest position opens in the last 7 minutes, and
the guard re-fits every 15 s on fresh positions instead of one pass. An
account's positions share the next-day room (stops first, least at risk
first; locked-in profit counts), a hand-moved stop is fitted again, and a
close or profit-side stop that doesn't take is retried or closed.

The dashboard's next stop now reserves for the leg's notional, as fireLeg
does, so DXtrade labels read smaller (display only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:21:52 -05:00
Brandon LiandClaude Opus 5.5 6f6660fdcc Auto-claim without the scheduler, and no claim twice
A payout locks in profit and opens nothing, so stopping the trading no
longer stops it: a claim watcher runs the same claims once a minute while
the scheduler is off (its tick still claims while it runs), and the
dashboard's Auto-claim payouts box turns them off.

From two independent reviews of the claim path:
- A claim holds every further claim on the account until Vest's
  transactions show its WITHDRAW. A sync already out when the claim lands
  (by sync generation, not the clock) no longer writes the pre-claim
  balance back, and a fresh sync follows the claim.
- A withdraw that fails in transit (timeout, reset, 5xx) may have gone in:
  it is recorded as of unknown outcome and holds claims the same way,
  until the transactions show it, or an hour of them shows it didn't.
- claimProfit checks Vest's own account: funded (or instant), and no more
  than the balance above its starting capital.
- A claiming account is marked in flight; runClaims sends the amount it
  re-checked under the mutex.
- The watcher starts before the rest of the init can fail; settings are
  validated before any is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:45:35 -05:00
Brandon LiandClaude Opus 5.5 32081f3dcf Vest: claim the profit at the target, then trade again the same day
On Vest, a claim lowers the daily-loss floor by the amount claimed. Winning
$1,500 and claiming it leaves $3,000 of room the same day, the full
drawdown. The bot now uses that.

- Payout claims: a scheduler step (runClaims, under the lane mutex, before
  the flat gate) claims every payoutable account that reached its stage's
  target, via a platform that takes payouts by API. The claim is all
  profit above the starting balance. It then clears the account's legs and
  entry outcome for the day so it can trade its next stage. Failures retry
  after 5 minutes. The auto_claim setting 'off' disables it.
- VestClient.claimProfit: POST /v3/capital/withdraw to the login's main
  account (account_type 1 on /v3/accounts). The payout is booked at once:
  today's fund row and the cycle cut, with today's P&L counted from the
  post-claim balance. The history read later rebooks it from Vest's
  WITHDRAW row, and the books check counts a claim until that row shows.
- Same-day stages: an account type can pick its stage by the payouts since
  the firm's last daily reset (stagesSameDay, sameDayClaims). A stage can
  carry its own daily loss limit.
- That limit replaces the type's everywhere room, stops and last stands are
  worked out: the batch, the manual leg, copy trade, the dashboard and the
  report.
- Room from the platform's own floor: dailyLossFloor (Vest) makes the room
  min(stage limit, balance − Vest's floor). A same-day claim that lowers
  the floor therefore frees room, and the room is never more than Vest
  allows. VestClient.dayStartBalance is gone.
- Before the 8 PM ET reset: from 7:55 PM ET, each open Vest position on an
  account with a daily limit is fitted to the room the reset will leave
  (resetSafeStop). Its stop comes in via PUT/POST stop-loss, or the
  position closes when there is no room.
- Checks for same-day stages, the stage limit, floorRoom, the post-claim
  plan, the next reset across both time changes, and resetSafeStop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 15:56:00 -05:00
Brandon LiandClaude Opus 5.5 96fdd9ca23 Vest platform: a client for Vest Markets, traded on a session the extension relays
Vest (next.vestmarkets.com) is a perps prop firm: index, metal and energy
perps on CME hours, plus stocks, FX and crypto. It joins as a fourth
platform, 'vest', on the DXtrade engine paths. Those are now keyed by
isNotionalPlatform() instead of === 'dxtrade': the firm's own symbols,
percent fees, notional sizing from volatility, and a scheduler lane of its
own.

- Login is relayed, not stored. Vest signs people in with Google through
  Privy and renews the session with a device key the browser won't export.
  Extension 0.17.0's vest-relay.js reads the token the Vest app renews for
  itself (~15 min). It renews nothing, so the tab is never logged out. The
  background script posts that token to POST /api/vest/session, which holds
  it in memory only. With no Vest tab open on a machine that trades Vest,
  the extension opens one.
- The firm's credential is the Vest login's wallet address. The dashboard's
  form offers the relayed wallet with one click.
- VestClient (lib/vest-class.ts), mapped from the web app's traffic and code
  since Vest publishes no API:
  - Reads the capital accounts, balances (free amount plus position
    collateral), positions and open orders with the user token.
  - Mints per-account tokens for orders and transactions.
  - Names accounts for their rules (V50K-E-D3-T10-34, V50K-F-D3-34,
    V25K-I-7) so account-type prefixes can match.
  - Takes Vest's own max-drawdown and daily-loss floors. dayStartBalance =
    floor + the type's limit, so the engine's room is never more than
    Vest's.
  - Uses each symbol's taker fee and margin rate from exchangeInfo, and
    hourly volatility over in-session hours only.
  - Places take-profit and stop as position legs, which Vest cancels as an
    OCO pair.
- Daily P&L and fund rows come from /v3/transactions by Vest's 20:00 ET
  day. A books check holds an account, naming the transaction types, when
  the balance and the transactions disagree.
- scripts/check-vest.mjs (in npm run check): Vest's day across DST, account
  names, parsing, volatility gaps, market hours, the session store.

Not yet run against Vest with a live session: the order and exit bodies
come from the app's code, and the transaction classification from its enum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:19:31 -05:00
Brandon LiandClaude Opus 5.5 5ec5abde6e Remove broker session rest
Reverts 062ec07 and 5eb0ea7: every firm's broker session stays logged in
around the clock again, renewing its token on the fixed timer as before.
The tree is back to 6dd2528's. A session_rest row left in settings is
unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:39:53 -05:00
Brandon LiandClaude Opus 5.5 5eb0ea7760 Broker sessions rest while nothing needs them
A firm's client used to stay logged in around the clock, renewing on a fixed
timer overnight and at weekends. Now, once a firm has nothing open, nothing
pending and nothing left to trade before its next session for a random
15–45 minutes, its client disconnects and the dashboard shows it Resting.
It logs back in a random 20–50 minutes before the next session opens, at a
check-in every 3–7 Central daytime hours, or at once when trading starts, a
manual leg, copy trade, manual order or AutoBuyer run needs it. Token
renewals fall at random points (Tradovate 15–45 min before expiry, TopstepX
every 45–150 min). Settings → Trading → Rest idle broker sessions
(session_rest) turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:28:47 -05:00
Brandon LiandClaude Opus 5.5 c0899620d4 Old rules for older accounts, a rules date per account, and a firm cap
When the master changes an account type with "New accounts only", it now
serves the type's old rules as kept rule sets (firm.keptAccountRules), each
for accounts dated inside [openedAt, closedAt). This build applies them:

- getAccountConfig, the one rules lookup trading reads, puts a kept set's
  rule fields over the live type when the account's rules date falls in
  its window. Id, label, prefix, size and the projection fields stay the
  live type's, so every report still names the live type.
- account_assignments gains rules_date and rules_date_manual. A new row is
  dated when the machine first sees the account, on the master's clock
  when this one runs behind and never before the catalog's last change.
  Auto dates come down to the broker's evidence: the end of the first fund
  day in Chicago, or a DXtrade registration instant. Evidence is applied
  before an account's first trade. Rows from before the migration get its
  time; unpinning keeps the row and its date. Backups carry both columns.
- An account on a kept set stays on its type while its prefix and cached
  size still match, so a tie-break flip can't move it off its old rules.
- Kept entries parse through the same normalizer as live types (now
  normalizeAccountType in lib/kept-rules.ts); a malformed one rejects the
  sync like a malformed type. The cached catalog records the parser that
  stored it, and a cache from an older parser is fetched in full rather
  than trusted with ?since= (this also brings in maxConcurrentAccounts).
- Reports add keptRuleIds per firm and live counts per kept set per type.
- The dashboard row and the account page show the rules date next to the
  account name, with an "old rules" chip saying what differs. Clicking
  the date edits it inline (PUT /api/accounts/:id/rules-date), previewing
  which rules the date lands on.

Also: the master's per-firm cap on accounts traded at once
(maxConcurrentAccounts), applied on top of the machine's own max
concurrent when a batch is drawn and in copy trading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:21:55 -05:00
Brandon LiandClaude Opus 5.5 c3b58c8d8b Hold a payout that isn't booked yet, instead of re-earning it
On 2026-10-06 two Topstep Express accounts sat finished, waiting for their
payouts. The hub moved their balances the moment the payouts landed, but
reconcileFunds books a withdrawal only in the next sync. 28 seconds later
the plan read the drop as lost profit, reopened the cycle as stage 1 and
fired a full 3xNQ leg with no stop, on what should have been a drain day.
Both were liquidated against the $0 floor a payout leaves.

- TopstepX: unbookedBalanceChange holds an account while its balance is
  $200+ off its books (today's realized read fresh off the trades, so the
  5 PM roll doesn't trip it); the sync books it as before.
- Every platform: a finished cycle marks its books in account_meta; a later
  drop of $200+ with no fund row booked since holds the account until the
  payout books and moves it to its next stage. Relative to the mark, so
  history missing from before the cache cancels out; a wipe clears it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:13:48 -05:00
Brandon LiandClaude Opus 5.5 0c3a36845a P1.1: Report each account's status to the master's ledger
Every FirmStats now carries statusVersion 1, the catalog firmId and, once
synced, syncedAt (from lastSyncAt), including the no-accounts branch, which
is now built by emptyFirmStats(). Each accounts[] entry adds status, reason,
balance, floor, accountTypeId, firstFundDate, positiveFunds and brokerStatus,
each left out when null or at its default.

accountStatus() in platform-client.ts is pure: a missing balance is unknown
(cash_missing), never dead; with a balance, dead is exactly the old floor
test (legacyDead); then history_pending, locked, live. Only the reporter
uses it. The aggregates, totals and every trading decision keep their own
dead test, unchanged.

The reporter reads history readiness through a new Tradovate
peekHistoryReady(), because historyReady() there can start a hold and move
the balance baseline. DXTrade exposes accountStatus and registrationTime via
brokerAccountMeta(). A throw in the new fields reports the account as
unknown instead of losing the report.

sendReport skips the socket for a firms payload over 900,000 chars (the
master's frame cap is 1,000,000 bytes) and POSTs it, logging once per switch.

scripts/check-account-status.mjs proves the legacy invariant over the full
matrix and the size budget (worst 228 B added per account, 154 B typical
live; 2000 accounts 592,501 B).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:36:55 -05:00
Brandon LiandClaude Opus 5.5 a2cd3a94c9 Legs: cheapest symbol that reaches the target hold, last stands, a reserve that grows with size
- A batch sizes its leg on every mapped candidate, each at the leverage
  the broker states for it, and trades the cheapest (spread, commissions,
  expected swap) that reaches the target hold — else the quickest. The
  dashboard ranks the same way from a half-hourly candidate survey.
- Last stand: when a daily-loss stop-out would leave the account within
  15% of that loss above its max-drawdown floor (the account type's static
  max loss, or the broker's liquidation floor), the leg carries no
  daily-loss stop and is sized against the firm's own breach.
- The daily-loss reserve is 10% of the limit or 0.1% of the position,
  whichever is more, since a stop's slippage grows with the position.
- The account type's max notional is gone: the broker's margin rates cap
  each symbol. planLegSize holds the one sizing rule fireLeg, the pick and
  the dashboard share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:26:12 -05:00
Brandon LiandClaude Opus 5.5 a2d9df5ba4 DXtrade: count closed-trade P&L, week-long volatility, broker leverage caps, swap in the take-profit
- Transfers: a close books its SETTLEMENT and COMMISSION under one
  transaction code, and keying by the code alone let the commission
  overwrite the P&L. Keys now carry the type, so the day-start balance,
  daily-loss room, realized P&L, daily history and books check all see
  closed-trade profit and loss.
- Volatility from a week of hourly closes (was 48 h), still measured once
  per trade date, so one quiet weekend can't triple a hold estimate.
- Each symbol's margin rate from the account's instrument details caps
  the position at available funds / rate (Tradeify: 5:1 crypto, 2:1
  stocks); the account type's max notional stays as an optional ceiling.
- Firms with overnight financing add the swap a winning leg is expected
  to pay (the winners' mean hold, (A²+2AB)/3σ²) to its take-profit, and
  the sizing and hold estimate count it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:11:42 -05:00
Brandon LiandClaude Opus 5.5 d01f03b293 DXtrade sizing: volatility only (12h default hold); fees and every-day only for DXtrade
The symbol map's fixed notional per contract is gone: every DXtrade leg is
sized from the instrument's recent volatility for the firm's target hold
(blank = 12 h), and a candidate without a volatility isn't picked. Percent
commission and every-day trading are ignored on non-DXtrade firms, which keep
measured per-contract fees and the CME calendar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:29:32 -05:00
Brandon LiandClaude Opus 5.5 28f465508d DXtrade platform for Tradeify247: percent fees, hard daily-loss stops, volatility sizing, every-day lanes
- DXtrade client (lib/dxtrade-class.ts): REST session login with lockout-safe
  backoff, 60s sync of accounts/metrics/positions/orders/transfers, history and
  fund transactions from cash transactions, day-start balance at the 22:00 UTC
  reset, CLOSE-by-positionCode exits (native OCO group, else a stop-first
  emulated pair), a cross-account hedge guard, stray-order reaper.
- Instrument selector: the firm's symbol map lists candidate symbols per CME
  root; the cheapest tradable one per unit of recent hourly volatility wins
  (spread + 2x commission [+ financing] / sigma).
- Fees: a firm's commission % of notional prices every fee off the fill; the
  take-profit is solved in closed form so the leg nets its target.
- Hard daily loss limit per account type: every leg carries a stop keeping the
  firm's day inside the limit less a 10% reserve and both fees (rounded in);
  a green day never widens it; no room or a refused bracket flattens.
- Notional sizing where the platform prices positions: target-hold volatility
  sizing or the symbol map's fixed notional, under a per-type notional cap.
- Every-day firms trade weekends/holidays; the scheduler runs lanes (futures,
  one per DXtrade firm) with their own hours, flat gate, mutex and batches.
- The catalog sync skips platforms this build doesn't know instead of treating
  them as Tradovate. One shared fireLeg replaces the duplicated batch/copy code.
- npm run check: fee/stop/sizing/hours/selector checks; scripts/dxtrade-smoke.mjs
  confirms the API against a real login before trading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:04:15 -05:00
Brandon LiandClaude Opus 5.5 57835c01f0 Unknown entries settle only on a sync started after them; filled-after-all and working orders seen
Sixth review round (8e66631):

- An entry of unknown outcome is judged against a good sync that STARTED
  at least 90s after it was declared unknown (lastSyncStartedAt: TopstepX's
  pass start, Tradovate's request time), not one that merely ended then —
  a sequential pass can read the account before a late fill.
- Once the broker shows its fill (a position, or realized P&L moved), the
  entry becomes an ordinary filled one instead of holding the account for
  the rest of the day.
- It never settles while the broker still shows a working order on the
  account: Tradovate now reads working orders from the sync snapshot, and
  its fill-timeout cancel checks Tradovate's answer instead of assuming it.
- A second wipe (after a restart) keeps the pending wipe's pre-wipe fund
  signature instead of overwriting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:38:57 -05:00
Brandon LiandClaude Opus 5.5 088886ed73 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>
2026-09-24 18:45:45 -05:00
Brandon LiandClaude Opus 5.5 77d8c2ecdb 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>
2026-09-24 17:04:09 -05:00
Brandon LiandClaude Opus 5.5 b932452e98 Self-healing broker clients, per-firm scheduler isolation, eager boot
Why the autotrader sometimes broke after a weekend until `pm2 restart`:
`next start` only logs uncaught errors, so nothing ever crashed, and a few
paths could leave a client dead for the life of the process.

Tradovate: a closed socket scheduled exactly one re-login, with no catch —
one failure during weekend maintenance (or a rate-limit penalty returned as
a 200 without a token) and the client never came back. connectAndAuth is now
a single-flight loop that never throws: jittered backoff capped at 5 min,
penalty tickets waited out, captcha demands backed off. A 15s watchdog
abandons silent, never-authorized or stuck-closing sockets and stale syncs;
renewal falls back to a fresh login; orders fail fast (OrderNotPlacedError)
on a dead socket and time out instead of hanging; a bad sync answer no
longer wipes accounts and positions.

TopstepX: hub start/stop/invoke and every REST call are bounded; a wedged
hub is discarded and rebuilt; a stuck sync is superseded after 3 min;
re-login no longer tears down the sync loop; a watchdog re-logs when syncs
go stale; a no-fill timeout cancels the order.

Scheduler: one unready client used to hold every firm forever. It now holds
the tick for a 10-minute grace, then trades the other firms without it.
Per-firm try/catch in batch collection, one batch at a time, accounts marked
in flight, legs un-counted only when the entry never reached the broker, and
the scheduler starts before the first batch fires.

Boot and supervision: instrumentation.ts boots the process at server start
(it used to wait for the first dashboard request); a supervisor rebuilds a
client that has been unhealthy for 20 min (not a TopstepX one holding a
position); broker HTTP calls default to a 60s timeout; GET /api/health shows
each client's liveness and the scheduler's last decision. Shared state the
eager boot would otherwise split across bundles lives on globalThis. Logs no
longer carry access tokens or whole AxiosErrors (which include the encoded
Tradovate password).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:18:33 -05:00
Brandon LiandClaude Opus 5.5 acb8879575 Reporter: name the accounts whose final day completed their cycle
cycleCompletion() says whether today's P&L finished the current cycle
(profit target, consistency and minimum days all met) — projectPerfectPath's
completion probe run without and with today. The reporter counts those as
`completed` (a subset of targetHit) and sends their names, so the master
can keep an account a firm erases after it passes. Checked ahead of the
lockout branch: a firm locking a passed account is no DLL loss. The local
dashboard shows them with a purple Completed pill.

Because the master keeps a completion for good, doubtful reads stay
'none': weekends, a Monday with a weekend-dated row, the first minutes
after the 5 PM roll, and history that hasn't freshly loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:28:41 -05:00
Brandon LiandClaude Fable 5 50a5eb4713 Add TopstepX platform support, drain withdrawal stages, and perfect-path calendar projection
- TopstepXClient (ProjectX Gateway): API-key auth, SignalR user hub,
  Trade/search daily P&L, emulated OCO exits, books-invariant
  withdrawal detection for $0-start accounts
- Platform-aware client pool, credentials (api_key column), and
  credential form; $0-size account types match by prefix alone
- Drain stages: min-day ($5-$15 randomized) MNQ legs for the
  configured days, then wait for the 50% withdrawal; stage pills
  show 'Stage N – Drain'
- Account calendar projects the perfect path: future days book their
  planned targets, simulated 50% withdrawals, payout marker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-12 16:25:52 -05:00