A Vest firm's credential is the wallet of the login it trades. It no longer
has to be typed or clicked in: when the extension relays a Vest tab's
session and a Vest firm here has no credential, that wallet is saved as the
credential (lib/vest-wallet.ts), the client starts, and the backup is
pushed.
- A saved wallet is never replaced. When the tab is signed in to another
login, the firm now says which one and how to switch: sign in as the
saved wallet, or remove the credential.
- With two logins relayed at once nothing binds itself; the one-click form
decides.
- Extension 0.17.1 re-posts the session every minute even when it hasn't
changed. The dashboard holds it in memory only, so a restart there was
unauthenticated until the tab's next renewal (up to ~15 min); now it is
back within a minute, and a firm created later binds within a minute too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
A firm resting across the trade-date roll kept reporting its last snapshot,
so yesterday's realized P&L and DLL lockouts counted as accounts already
traded today (Nik's Topstep read 5/5 traded at 5:31 PM from 3:29 PM data).
Its report is now the unsynced one until it logs back in; a rest within one
trade date reports as before.
/api/health also counts a firm logging back in after a rest as nothing
wrong, within its 15-minute grace, rather than reading not ok for the
seconds before its first sync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Every machine sized a like account in a like state to the same position as
every other person's, so two people's positions could match to the unit.
Each person now draws a size factor in [0.85, 1.15] per trade date, seeded
by the master's uuid for them; all of one person's accounts share it.
The factor moves the hold a DXtrade leg is sized for (hold / factor², since
size goes as 1/√hold), so the instrument pick, the dashboard's next leg and
the Firms page's reach test all see the hold the size was made for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PM2's cron for autofirmer-update dies inside a live daemon (six Windows
machines in October), and nothing on the machine noticed. The app now
watches scripts/.last-check and, past 10 minutes, runs
update-check.mjs --heal: a checker PM2 still lists is restarted (which
re-registers its cron), one it lost is registered again, and one whose
errored entry still holds a live pid is left alone, since PM2 would kill
that pid. The heal scrubs PM2's per-app fields from its environment first.
The master's new 'update-now' command runs --heal --now, skipping a
failed update's retry wait. A failed pull, npm install or build records
one line of reason (or the chown to run when a file isn't ours), which
rides on .last-check to the master. A package-lock.json rewritten by npm
is put back before the pull. The fetch timeout drops under the cron's
minute. scripts/.no-auto-update opts a checkout out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
`next build` imports lib/db.ts from several workers at once. On a fresh
machine they all saw an empty instruments table and raced to insert the
same symbols, failing the build with "UNIQUE constraint failed:
instruments.symbol". The emptiness check and the inserts now share one
IMMEDIATE transaction, and the insert is OR IGNORE as a backstop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sync reads the balance first and each account's positions and trades
after it, so a fill landing in between shows in the trades but not yet in
the balance, and for that one pass the books are off by the whole fill.
reconcileFunds booked that as a fund event, and the next pass booked the
reversal. Same-day rows merge, so the two netted to the commission drift
of the day and left a tiny row:
- 2026-10-05, Express 43788281 and 23275136: +3,001.16 "deposit" (balance
-2.16 against the -3,001.16 stop-out already in the trades), then
-3,002.16, merged to -1.00.
- 2026-10-08, combine 99745612: +2,474.01, then -2,492.01, left -18.00.
- 2026-09-24, five 150K combines on another instance: +3,049.45, then
-3,059.45, left -10.00 each.
A negative row is a withdrawal. It started a new cycle, dropped its day
out of the cycle (the -3,003.32 on 10-05) and counted toward the stage:
both Express accounts traded drain days from 10-06 on a $1 "payout".
- fundReconcileStep (trading-logic): an unexplained balance is booked
only when a check at least 30s after the first sighting still sees it,
to within $5. A transient is gone by the next pass and books nothing.
- A transient that outlasts that, and so books, is taken back whole once
its reversal nets the day under FUND_EVENT_MIN. The day's first booking
saves what it replaced (fund_day_before), and dropFundTransaction
deletes the row and puts last_fund_date and ended_cycle_start back in
one transaction. A row reconcileFunds didn't book (an import) merges as
before.
- funds_reconciled is set only once a check settles, so a first-check
lump that waits on a second pass is still booked as the first check.
Existing tiny rows are left as they are; cleaning them up is a separate,
one-time step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TopstepX's gateway reports a fill's exchange and NFA fees only (NQ $1.39 a
side, GC $1.66, CL $1.51, MNQ $0.36). Topstep's own commission, $0.50 a side
on a full-size contract and $0.25 on a micro, comes off the balance with no
trade row carrying it, so our P&L ran $1.00 per full-size round turn above
Topstep's. On 2026-10-18 the combines sat $6 to $8 under their books and an
XFA $27.50 under, and one combine had its $18 booked as a "withdrawal".
Topstep's published round turns (NQ $3.78, MNQ $1.22, GC $4.32, CL $4.02)
are the reported fees plus this commission.
- lib/topstep-fees.ts: the per-product commission table, and TopstepX
contract ids mapped to roots (ENQ, GCE and CLE as seen on real fills,
overridden by contracts the client resolves itself; an unknown product
pays the full-size rate and is logged once).
- Daily P&L, today's realized, and so the measured entry fee a take-profit
is placed from, all net the commission. The 90-day refetch rewrites the
cached days.
- npm run check: the table against the micro flags, the id mapping, and
the published round turns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-10-08 a Tradeify min day ($150) stopped out at −$306 on its 2×
stop of $300. The dashboard then read "stop $912": the next-stop label is
recomputed from the remaining target, which the loss grew to $456, for a
leg that never fires — the day is booked and the make-back rides tomorrow,
stopless.
- obfuscationDayBooked() holds the scheduler's booked-day check; the state
route asks it too and shows no next stop, last stand or hold on a
booked day.
- The account page's "Daily Loss Limit" showed minDayPnL. It now shows the
type's real daily loss limit, with Min Day P&L on its own row; the
objective that passed on total profit ≥ minDayPnL is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
4217.6 − 87 × 0.1 is 4208.900000000001 in floating point. TopstepX refused
that take-profit ("Invalid price. Price is outside allowed range") on two
GC shorts on 2026-10-02, and with no stop on the leg nothing else was
working: both rode far past their target with no exit. ES and NQ never hit
it because 0.25 is exact in binary; GC and CL ticks aren't.
- onTick puts the measured-fee take-profit and every stop on the grid —
the take-profit away from the entry, the stop toward it — so an averaged
multi-contract fill between ticks is handled the same way.
- A leg's lone take-profit (no stop, or the fallback after a refused
bracket) that the broker refuses now flattens the position and benches
the account for the day, like a refused bracket. An unknown outcome still
places nothing more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a firm site, the extension draws a small corner chip with this
machine's person's authenticator code for that site, a countdown, and a
Copy button. It can be minimised to a "2FA" pill for the tab's session.
- /api/autobuyer/otp?host= relays to the master's
/api/otp?person=<instance_id>&host=, which matches the host to a firm's
Site link and the person's assigned codes. No master link, a master
that can't be reached, or no match all come back as no codes. An answer
without scope: 'person' is refused: an older master ignores ?person=
and would return every stored code.
- The chip lives in a shadow root, counts down on its own, refetches
when a code expires, and stays hidden for a minute after the runner
last located something, so automated clicks never land on it. Copy
uses execCommand inside the click, since the async Clipboard API can
stall on a permission prompt, and leaves focus in the site's code field.
- Apex (*.apextraderfunding.com) joins host_permissions, which also
brings it into capture like the other firm sites. Version 0.16.0.
- CatalogFirm mirrors the master's new optional siteUrl.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A firm can cap how many contracts of an instrument its accounts may hold
(the catalog's instrumentLimits, set on the master's Firms page). A leg
trades one contract per $1,000, capped by the account type's max contracts
and that limit. The Auto draw sizes each instrument's hold with the capped
count, so a limit that slows a leg past 12 hours takes it out of the draw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Tradovate position opened before this build (so its entry wasn't recorded)
was named from the contract cache, whose entries expire after 30 minutes:
Majd's CL longs showed as #3273057. The resolver now also keeps a lasting
contract id → root index, which the mix reads after its recorded entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They traded the micro of whatever the firm listed: MNQ with NQ, but MGC
for a firm listing only GC and CL. Now every futures firm's tiny legs go on
MNQ, the one micro the bot has always drained on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The master knew how many accounts were in a trade, not in what. Each firm's
report now carries its open positions by instrument (accounts long and
short, contracts held) and today's entries the same way.
A position's contract is named by its instrument: DXtrade's carries the
symbol; a futures one is looked up among the contracts this machine has
entered (recorded at the fill and kept), then the resolver's cache and
TopstepX's own. Entries are counted as they fill, per trade date, in a
setting, so a restart doesn't lose the day.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The master saw a scheduler's last run time and nothing of what it did. Each
tick now records, per lane, its outcome, a line on what happened (what
fired, or the spread, contract or hold cap that stopped it) and why the
lane's accounts sat it out, counted by reason with a few names each. It
rides the scheduler status as lastTicks.
Each report also carries the auto-updater's state from the files it keeps
and from git: its last check, a failed update waiting to retry, how far the
checkout is behind origin, and tracked files edited by hand, which stop the
fast-forward pull.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tradovate moves each account's cash record to the new trade date on its own
schedule, an idle one perhaps not until it trades, and the bot holds any
account still on the old day until 5:45. Opening at 5:05 fired the ready
accounts alone (TopstepX, the early rollers) while the rest, released at
5:45, waited an hour or more for them to go flat. A 5:45 open puts every
account in the first batch and skips the open's widest spreads. The day's
0–180 minute delay still comes on top: first entries land 5:45–8:45 PM.
Every-day firms keep their own 5:05 PM end of the roll guard; the equity
hours open is unchanged. The draw's session progress now starts at 5:45.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The day's delay was 1–20 minutes. It is now 0–180, so the evening open
lands anywhere from 5:05 to 8:05 PM CT. The day's roll is saved as a
setting: with a three-hour window, a restart during the wait (an update, a
crash) would otherwise roll again and could start the session early.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A first funds check can only explain an account's balance with the trades
the history window returns. P&L older than that is booked as a deposit,
which the consistency math leaves out: an Express account whose only
earlier trade was 64 days back read $5,071 short of its 40% rule instead
of $1,542, and aimed tonight's leg at its best day. Tradovate is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One quote feed per machine: a TopstepX client's market hub, else a
Tradovate client's market-data socket, subscribed to every listed root's
front month and the drain micro. A supervisor keeps it on a working client,
dropping one that fails or goes quiet while the market trades.
The Auto draw weighs each instrument by its live spread (the typical tick
when no fresh quote is at hand). No futures entry pays more than the
master's max entry spread ($15 until set) to cross the book: the draw
leaves such an instrument out, an explicit symbol sits the tick out, and
fireLeg and the extra-day trade check it last before sending. The Settings
page and the master's stats show the live spreads and the feed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fireLeg clamped the order to the instrument's max and min size, but the
size the pick ranked by (and the Firms page shows) didn't: an instrument
whose largest order falls short of the ideal notional ranked on a hold it
can't reach. planLegSize now holds the notional to the margin cap and the
broker's largest order, then its smallest, so the ranking, the stats and
the trade agree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The firm rolls its day at 22:00 UTC; our trade date at 5 PM CT. In summer
they coincide, in winter the firm's rolls an hour earlier. New entries are
already kept out of that hour, but a take-profit or stop that fills in it
was booked to the day before. firmDayOf labels the firm's day the way
currentTradeDateCT labels ours, and the sync's transfer window now starts
at the firm day behind today's trade date.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It checked the account, then awaited the instrument draw and the contract
lookup, and only then locked the lane. A batch that fired in that gap left
it trading on numbers from before: a second leg, or on DXtrade the other
side of the position the batch had just opened. Now it locks first, as the
batch and copy trade do, and checks the lease still holds before firing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rule is: when hitting the day's loss limit would leave the account 15%
of that loss or less above its max-drawdown floor, ride without a
daily-loss stop. It had compared against the stop-out after the 10%
reserve, which made it stricter — $3,294.70 to the floor with $3,000 of
daily room (9.8% left) wasn't a last stand, and now is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lib/instrument-specs.ts (pure, tested): the CME specs — point value,
tick size, one-tick spread, Yahoo ticker — replacing POINT_VALUES; the
draw's maths; the 12-hour hold cap; the micro for drain and extra-day
legs; reading a firm's list (the master's, or the old rules against an
older master: this machine's enabled set less bans, the symbol map).
- lib/instruments.ts: the one module the trading paths ask. Measures every
future's daily move from Yahoo's hourly candles once per trade date, and
shows entry cost (a tick of spread plus Tradovate's fee parameters or
TopstepX's last measured fill) without ever choosing by it.
- Futures Auto draws one root per batch from the batch firm's list: movement
per dollar of spread early in the session with a 10% floor each, sliding
to the fastest movers by the last entry; nothing expected to hold 12h or
more; micros only if nothing else is listed. Every draw logs its odds.
- The batch is chosen before its instrument, and a group that can't trade
anything yields to the next. Copy trade matches the held contract against
the listed roots and won't copy an instrument it can't identify. Manual
legs, manual orders and drain/extra-day legs follow the lists.
- DXtrade firms rank their own list whatever root was drawn.
- Capabilities, the dashboard's trade bar and contract resolves follow the
lists; the trade bar and scheduler default to Auto. The master link
reconnects when master_dashboard_url changes, and the catalog drops
?since after a switch so two masters' versions can't collide.
- Reports carry each futures firm's instrumentStats for the master's Firms
page; GET /api/instruments shows them (?simulate=N runs the draw, no
orders); the Settings page shows the lists read-only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lib/leg-estimate: an account's next leg sized on every surveyed
candidate (planLegSize, ranked by rankForTarget) — notional, its
multiple of the account and the broker's maximum, expected hold, cost
and win chance. The dashboard's hold estimate and the reporter share it.
- The reporter sends it per DXtrade firm as symbolStats, for one live
account from the largest prefix group with a leg left to trade.
- The symbol map's per-symbol commission is gone: the firm's applies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- 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>
Fires one leg on one account through the scheduler's own fire path (sizing,
take-profit, daily-loss stop, exit bracket), overriding only the one-trade-
per-day rule for unsplit accounts. Flat, history, daily-loss room, trading
hours, a flat lane and the lane mutex still apply. The daily-loss check is
factored out so both paths share it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each instrument's volatility is measured at the first good sync (or pick) of
a trade date and held for the day, so a day's sizing and hold estimate stay
put. The dashboard shows each DXtrade account's next-leg hold ("hold ~Nh")
with its symbol, notional, volatility and win odds. Accounts the firm has
closed (TERMINATED, EXPIRED, NO_TRADING) read as dead instead of inactive,
and skip the books check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tradeify247's API user may not read DXtrade market data (code 34), which
skipped every batch at the instrument pick. Quotes and hourly candles then
come from Hyperliquid's public API (BTC, ETH on the main dex; XYZ100, SP500
and tokenized stocks on trade[XYZ]) — for the pick and sizing only; orders,
fills and exit prices stay DXtrade's.
Closing STOP/LIMIT orders close the whole position by positionCode and must
not carry a quantity (code 33) — the exit bracket was refused, and the
daily-loss guard flattened both accounts. A close's fill price also falls
back to its executions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- 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>
The master clears a completed cycle once the payout it was waiting for has
left the account, and the balance going down is only visible from here.
Omitted rather than zeroed while an account's fund history hasn't loaded:
undercounting leaves an entry standing, a wrong zero would clear one early.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seventh review round (57835c0):
- An entry of unknown outcome is judged — filled after all, or never
filled — only on a good sync that started after its settle window and
with no order still working. A partial pass (position read failed or
predated the fill, trades read after it) can no longer release it as
"filled and flat" into a second entry.
- Tradovate counts every order not Filled/Canceled/Rejected/Expired/
Completed as working (PendingCancel, Unknown included).
- TopstepX: an account whose order ended with its outcome unknown gets its
open orders checked (Order/searchOpen) on each sync until none show, and
counts as holding a working order until then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Fifth review round (1eb0736):
- An entry now has three outcomes. Filled (confirmed): one-and-done is done
for the day. Never placed: leg taken back, free to retry. Unknown (an ack
or fill that never came back): the account — any mode — is held until a
good sync at least 90s later shows it flat with realized P&L unchanged,
then the leg is taken back and it may retry. Stop-after-all counts it in
play meanwhile. This closes both the day-long bench of 52ed9c7 and the
stacked re-entries of 1eb0736.
- Tradovate cancels an acked order whose fill didn't come within 30s (as
TopstepX already does), so a retry can't sit beside a still-working one.
- A wipe keeps the rebuild's guards: wipeAccountData leaves the Cash History
baseline, roll base, wipe hold, pre-wipe fund signature and the
resolver's inferred size in account_meta; the routes forget history
before wiping (so the stored rows are still there to read), and a wipe
hold restored after a restart keeps its pre-wipe yardstick.
- Wipe routes take the set of typed accounts before wiping, so an account
the resolver un-assigns mid-rebuild still counts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fourth review round (52ed9c7):
- The one-and-done guard counts only entries the broker confirmed filled,
so an entry that timed out or was rejected unfilled no longer benches the
account for the day.
- TopstepX: an account is promoted only by a sync that actually checked its
funds; one with no account type stays out (not traded) without costing a
sync per pass, and once typed waits for that check. Wipe routes don't
count untyped accounts as failures.
- Tradovate: an unchecked balance-hold release needs Cash History refused
for a full hour straight (a 4xx other than 401/403/408/429, or an answer
without data); any other outcome resets it, so one stray reply during a
5xx outage can't release a payout.
- A wipe keeps the pre-wipe baseline and fund rows as its yardstick: the
rebuild must explain today's balance against them, so a payout the
rebuilt report hasn't booked yet stays held.
- The roll base of an unrolled record is kept per trade date in
account_meta, so a restart later the same trade date counts from the same
point instead of from a value that already includes the day's fills.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third review round (088886e):
- Tradovate: a record still on the ended trade date waits only inside
17:00-17:45 CT (every account rolled 17:17-17:21 on 2026-09-24); nothing
trades on it there, so no pre-roll fill can be lost or double-counted by
the broker's roll. Past the window an unrolled record (an idle account)
counts from its value when first read, so nothing waits indefinitely —
across restarts, stale bases or accounts the broker rolls only on a fill.
- One-and-done accounts also stop once this process fired a leg today.
- A balance hold is released unchecked only when Cash History is refused
outright (4xx / errorText, e.g. a passed account's 404s); a 5xx, timeout
or queue outage keeps the payout held until it recovers.
- A Cash History is judged against the baseline as it stood when requested,
so a fold observed while it was in flight is not taken for a payout.
- TopstepX: a cache fallback for an account not loaded before waits for a
funds check; accounts with no account type (not ours) aren't left
pending; a fetch awaits a sync already running and syncs again if its
accounts still wait, so wipe routes report the real outcome.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Follow-up to 77d8c2e from its adversarial review:
- Stop-after-all no longer shuts down before an open while histories are
still loading (a regression in 77d8c2e): a pending account counts as in
play everywhere but the weekday 2:55-5:00 PM CT stretch after the close.
- Tradovate balance holds lift only when a Cash History books a fund
transaction the account didn't have when the hold began; one that never
does is released after 3 clean reports over 15 minutes, with a warning.
The baseline comes from the balance when the report was requested, so a
payout landing mid-report is held for the next one. Held accounts
refetch every 5 minutes, not every sync.
- The Cash History baseline is kept in account_meta, so a restart onto a
failing report server still catches a payout from the DB cache.
- A wipe holds the account until a Cash History rebuilds its fund
transactions (the Fills report and the cache can't).
- A report still queued after its polls, or answered without data, is a
failure, not an empty history.
- TopstepX: a newly fetched account (new, wiped, failed) waits for a sync
that reconciles its funds, and the fetch starts that sync right away.
- Accounts a fetch is already working through aren't fetched again by the
targeted pass; the TopstepX REST fill search spans ±15 minutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tradovate rolls each account's cash record to the new trade date on its own
schedule (18 minutes after the 17:00 CT open on 2026-09-24, or not until the
account trades), and until then the latest record still carries the ended
day's realizedPnL. Plans sized legs off it, one-and-done accounts read as
already traded, the reporter scored every account partial/loss, and an entry
across the record roll took a whole day's P&L as its "commission", parking
the take-profit out of reach.
- Cash records keep their trade date; realized P&L from an earlier trade
date reads as 0 (judged when read, so a record stored before the roll
counts as ended after it). Archived or older records never overwrite a
newer one, whichever order the roll pair arrives in.
- The entry-commission delta falls back to $2.50/contract when it exceeds
any real fee ($10/contract).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
Only the drain cycle's first day draws from the $4-$15 grid. Under a
consistency rule, later days follow the standard consistency math and
the day that can close the cycle aims at the midpoint of the amounts
that close it, so accounts drained together finish together whatever
their first draw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
PM2 runs update-check.mjs as a cron *restart*, and PM2 7 stops a process
that is still running at the next minute boundary. Any update that needed
npm install or next build (more than ~55s) was killed half-way: the lock
was left behind, HEAD had already moved at the pull so the next run saw
nothing to do, and next build had already wiped .next under the live
server — a box that broke after a weekend push and stayed broken.
- The cron run is now a quick CHECK: fetch, decide, and hand the work to a
detached worker (--apply) that PM2's next tick can't reach.
- "Up to date" is judged against scripts/.built-commit (what .next was built
from), not HEAD, so a pull that was never built finishes on the next check.
First run on a machine adopts a complete build that is newer than HEAD's
last move and rebuilds anything else — that rescues boxes the old updater
already stranded.
- The previous build is set aside as .next-prev and restored if the build
fails, so a broken push never leaves a box without a build; failures retry
after 15 min, then hourly (scripts/.update-failed).
- The lock holds a pid and is atomic; build dirs are deleted without ever
following the links in .next/node_modules into the real node_modules.
- Every instance in scripts/instances.json is restarted, and python/pip
children stay hidden on Windows now that the worker has no console.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`node -` puts '-' in argv[1], so the registry snippets in add-instance.sh
and remove-instance.sh were one slot off: they wrote the registry to a file
named "-" in the working directory, with the instances.json path as the
instance name, a null port and the port as the db. scripts/instances.json
was never created, so the updater and start-all only ever knew instance 1 —
after an update, instance 2 and up kept running old code against the new
build until someone ran `pm2 restart all`.
Devices that already ran add-instance.sh need their scripts/instances.json
written by hand (and the stray "-" file deleted).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a fund transaction starts a new cycle (the days revert to 0), the
cycle it ended is checked against the completion rule for a week after and
named if it stood complete — its final day may never have been reported.
Only for a cycle this instance saw begin: the clients now note where the
ended cycle started as they move last_fund_date forward. Completions carry
the cycle they belong to, and each firm reports its whole account list so
the master can tell erased accounts and spot new funded ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
lib/db.ts switched to WAL at module load before setting busy_timeout.
next build imports it from several workers while the live server holds
the same file, and a journal-mode change needs the file to itself — so
the pragma failed instantly with SQLITE_BUSY and took the whole build
down ("Failed to collect page data"), blocking updates on any box whose
database was still in rollback mode. The timeout is now set first and
the WAL switch is wrapped: a held file keeps its current mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fleet tooling is npm-only (setup-linux.sh installs with npm,
update-check.mjs triggers installs off package-lock.json), so the
committed yarn.lock did nothing for any VPS — but yarn regenerating it
locally dirtied working trees and blocked git pull on machines where
yarn had ever been run. Now gitignored: yarn dev still works wherever
someone wants it, with a local lockfile git never sees.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matches the master's tokenless-push change: pushBackup no longer waits
for master_backup_token — every instance with a uuid backs up on its
own, and the 5-minute tick force-pushes so a poisoned master copy
self-heals. The token now only gates pulling a backup back (restore),
so setup-linux.sh asks for it only when restoring and the Settings
help text says so.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four features:
- Backup: lib/backup-sync.ts pushes the full settings table and firm
credentials (the deliberate exception to "credentials never leave the
machine") to the master's bearer-gated /api/instances/backup — every
5 minutes when the content hash changed, within seconds of a settings
or credential edit, and on the new backup-now command. Enabled by the
master_backup_token setting.
- Restore: a dead VPS's replacement pulls the backup and adopts its
uuid/name, so roster, payouts and card links carry on. Pre-boot via
seed-settings.js --restore-from (setup-linux.sh prompts for it) or
the Settings page's restore picker (lib/backup-restore.ts). Stop the
old machine first; the master 409s a live-name rename.
- Proxy: the proxy_url setting routes BROKER traffic only — TopstepX
REST + SignalR hub, Tradovate REST + websocket, Yahoo quotes — via
lib/outbound-proxy.ts (axios agents, undici dispatcher, ws agent).
The master link, reporter, catalog and backup push stay direct, so a
dead proxy cannot take down the control channel. Restart to apply.
- Multi-instance: AUTOFIRMER_DB moves the SQLite file so one checkout
runs N instances; scripts/instances.json (per-device, gitignored)
drives start-all/update-check, and add-instance.sh / remove-instance.sh
manage the extra PM2 processes. Absent registry = single-instance
behavior, unchanged. Instance 1 keeps port 3000 for the clicker and
extension.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A drain stage zeroed consistency, so the payout was planned (and the
account stopped trading) as soon as the min days were booked — even when
the cycle's biggest day still exceeded the firm's ratio (Topstep holds
its 40% through the drain). Carry stage.consistency into the effective
config and keep booking drain days until the cycle total covers
maxDay / consistency.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a popup button that scans the Topstep dashboard's GraphQL API
(crystal.topstep.com) for passed evaluations that can still be
activated into an Express Funded account.
An eval is activatable when its status is "Passed" and its subscription
id is not shared by ANY funded account it ever spawned — regardless of
that funded account's current status, since activation is one-way. The
result and its progress are mirrored to chrome.storage so the popup
renders them independent of the message port (avoids the "message port
closed" error on this ~20s scan). Activatable accounts link to their
Topstep detail page.
Adds https://*.topstep.com/* host permission; bumps to 0.15.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the single hardcoded signed-out check with a guards list on each
firm: any guard can trip mid-run via a URL substring (checked on every
step) or a selector probe (checked when a step fails). Each guard defines
recovery steps or a human-wait fallback, a per-run trigger cap, and a
resume policy — 'continue' retries the tripped step, 'restart' rewinds
the run to step 1.
Runner bumped to 0.20.0; the server still ships the legacy
signedOut/authSteps shape for stale runners, and a new runner synthesizes
a guard from a legacy payload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both the MLL floor and the books-invariant fund detector assumed a $0
starting balance (correct for Express, wrong for funded/eval sizes). A
150K account (accountSize 150000) built its balance curve from $0, so
the floor came out near -$4,500 and a $145k balance never crossed it —
the account never marked dead — while reconcileFunds saw the whole
$150k as an unexplained deposit. Seed both with accountSize so the floor
is an absolute balance (150K/$4,500 drawdown → 145,500, locked at the
funded size once the EOD high climbs a drawdown above it) and the books
invariant nets out the starting balance. Skip reconcile until the
account type is known, since the start balance is unknowable before then.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
projectPerfectPath worked on the raw daily P&L, so for a combining firm a
holiday-session trade (e.g. the Labor Day session) counted as its own
trading day — an account one real day short of its min days looked
complete and anchored its payout to the last traded day. The projection
now folds sessions through combineCmeSessions like the live path, and
projected days skip dates that clear into the next full day.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A completed cycle's payout now lands on its last traded day (requestable
after that session's close) instead of whatever day the projection ran —
a Friday finish viewed over the weekend stayed pinned to Sunday. Also
stop inserting the platform's stale weekend realizedPnL as a Sat/Sun
P&L row, which fabricated an extra qualifying day and made accounts one
day short look already complete.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The MLL floor no longer recomputes while a position is open or while
the balance disagrees with the books beyond fee noise — the last
settled floor stands, so balance ≤ floor can only come true when a
booked fill crosses a settled floor (Topstep's own liquidation fill
included). The floor also survives canTrade flipping off, so a blown
account stays ☠ instead of demoting to inactive.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
23 deterministic values per (account, date) instead of $5/$10/$15; the
qualifying floor moves to the grid minimum so every draw counts toward
the cycle. The per-stage min-day field no longer drives the amount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TopstepX never reports the Maximum Loss Limit, but it's deterministic:
floor = min(0, eodBalanceHigh − trailingDrawdown), locking at the $0
starting balance. The EOD curve rebuilds from daily P&L + fund events,
with off-book fee drift spread across days proportionally to reported
fees (validated against a platform-reported MLL to ~$0.10). Stored in
autoLiqThresholds — negative, or +$0.01 once locked — so dead detection,
dashboards, and the ruin model's startThreshold all use the real room.
Guards move from '> 0' to '!== 0'; 0 still means no floor. canTrade=false
accounts are skipped. daily_pnl gains a fees column for the weighting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@microsoft/signalr does an unconditional require('ws')/require('eventsource')
when constructing a connection; bundled (turbopack) builds throw 'dynamic
usage of require is not supported', which the login chain mislabelled as a
login failure and retried every 60s. Externalize the package and never let
hub setup fail the login — REST polling covers everything the hub does.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
setup-linux.sh accepted being run as root and simply set SUDO="". That quietly
broke everything user-scoped: PM2 keeps its process list in $HOME/.pm2 and the
login entry goes in $HOME/.config/autostart, so under sudo both landed in /root.
The install reported success, then `pm2 list` as the real user showed nothing -
it was talking to a different daemon - and the autostart entry sat in a home
directory that never logs in.
Both scripts now refuse, and name the account to use when SUDO_USER says how
they were invoked. setup-linux.sh still uses sudo for apt, which is the only
part that needs it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A payoutable account with no withdrawal stages (e.g. MFFU same-equity)
now withdraws its share and re-targets the same equity each cycle instead
of stopping after the first payout. Non-payoutable evals still stop at
one completion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When target and min trading days are already satisfied, the payout fires
on the current trade date instead of being pushed to the next session —
so an account that has already hit its target with its days in shows the
payout as requestable now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An eval that completes its target passes to the next account rather than
paying out — the projected completion marker reads 'Passed ✓' unless the
type is marked payoutable on the master.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The version is the checkout's HEAD commit (short hash + commit time),
read once at startup — updates arrive as `git pull`, so there is no
release number to bump. It rides on the socket hello, socket reports,
and the HTTP report fallback; the field is optional in the protocol so
older masters ignore it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- projectPerfectPath books each cycle's payout ON its final trading day
and anchors ruin probabilities to the platform's real liquidation floor
(Tradovate auto-liq) when reported, else the configured trailing
drawdown with the maxLoss/$2,000 fallback
- Account calendar shows the cumulative reach % on every projected day,
caps the projection at two payouts ahead, and renders potential days
as dashed outlines instead of shaded cells
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
projectPerfectPath prices every projected withdrawal as
min(withdrawalPct × equity profit, cap) × profit split, and tracks the
cumulative probability of reaching it under the fair-walk ruin model —
room/(room + dayTarget) per day (the per-leg product telescopes), trailing
threshold locked at breakeven after the first withdrawal, no obfuscation
branches. The reporter attaches the first 6 payouts per account so the
master's payout calendar renders real amounts and probabilities instead of
one static expectedPayout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
Stop-enabled accounts show an amber "stop $X" beside the day target:
the day-1 net floor (live against realized P&L) or 2x a min-day's
target, mirroring exactly what the trade path will place — and nothing
when the next trade rides stopless.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A stop-enabled account whose day booked red via its own stop shows an
amber "Personal DLL" badge on the dashboard (the firm's lockout keeps
its red "Hit DLL") and reports as personalDll in the day outcomes
instead of loss, per account type and per firm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When an account type opts in, legs are placed as an OCO pair via
order/placeoco - the usual limit plus a stop: the first cycle day's stop
keeps the day's net floor at -$1,000, min-day trades stop at 2x their
target, and the day after any red day rides stopless to make it back. A
stop-out books the day: eligibility refuses the account once realized
P&L falls short of the legs fired (or is simply negative on an opted-in
account, which survives restarts). If placeoco is rejected the leg falls
back to the classic lone limit with a loud log rather than leave a
position without an exit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Big days (> the $1,700 split threshold) must carve at least the account
type's minLegs from the catalog — pickLegAmount reserves the minimum
piece for each leg still owed, forcing a carve even once the residual
dips below the threshold, clamped to what the pieces allow. Days at or
below the threshold still trade as one leg. The smallest split piece
drops from $800 to $500. An older master without the field means no
minimum.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sessions roll at 5 PM CT, so after tonight's open a day booked on
tomorrow's trade date is already in the day count — lastDay is what lets
the master tell those accounts apart from ones that have not traded the
current session yet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
payoutStates: one entry per live account (negative included, dead not)
with its current-cycle progress — qualifying days, full equity profit,
max qualifying day, P&L sum, and the cycle's end-of-day high-water mark —
plus the withdrawal count and the cycle's effective target/consistency/
min-days from resolveEffectiveConfig. client.dailyPnL is already the
active-cycle slice, so a withdrawal resets the reported path on its own.
Feeds the master's payout calendar; an older master ignores the field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The payout calendar projects accounts still on the winning path, so the
daysHist now skips accounts below their starting balance or flipped to
liquidate-only, alongside the dead ones the report already drops. They
still count in the slice totals — only the calendar stops seeing them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds daysHist ({daysTraded: accountCount}) to AccountTypeStats, computed
from the same combined-day view the trade path uses and covering every
live account including ones mid-trade. Feeds the master's payout
calendar; an older master simply ignores the field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>