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>
A firm on a platform the build didn't know was skipped, yet the cache was
stamped current, so after updating to a build that knows the platform the
sync sent ?since= and the master answered "unchanged": the firm (Vest, on
machines that synced before 96fdd9c) stayed missing until an unrelated
catalog edit. The parser version goes to 3, so those caches fetch in full
once, and a payload now names the platforms it skipped (skippedPlatforms):
a cache whose skipped platform this build knows is no longer current. Kept
rule ids still report from such a cache. scripts/check-catalog.mjs syncs
against a stand-in master on a local port.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TopstepX and Tradovate report fills, so a trade is built from them, flat
to flat (lib/fill-trades.ts), off the window each sync already reads:
TopstepX's two days of Trade/search, Tradovate's sync snapshot. Neither
says who placed an order, so every order the bot places is noted in
own_orders, and a trade is the bot's when its opening fill's order is
one; its stop or take-profit closing it reads as such. A contract is
written only when the position before its window comes out the same
two passes running, so a fill landing between the position and history
reads can't build the wrong trades.
DXtrade reports positions: its record groups the last two days' filled
orders by position code, with each close's SETTLEMENT and COMMISSION.
The bot's own orders carry af- codes, so nothing is kept locally. Only
accounts with something going on are read, at most every 5 minutes.
The Trades button shows on every firm with a login, and the account
page lists any account's trades.
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'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>
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>
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>
- 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>
- 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>
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>
- 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>
playwright was declared but referenced nowhere in the source — no import, no
require, no script. Its postinstall downloads several hundred MB of browsers
on every install, which setup-windows.bat had been working around with
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD. Removing the dependency removes the need for
the workaround, so that goes too.
Nothing else depends on it; the remaining lockfile mentions are Next declaring
@playwright/test as an optional peer, which installs nothing.
Instances pick this up through the normal update path: package-lock.json
changed, so update-check runs npm install and playwright disappears from
node_modules. Any browsers already downloaded on a machine live outside
node_modules and are not removed by this — see README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Replace fill/ldeps and fill/list approaches with Tradovate reports API
- Add bearer auth to getreport polling (root cause of prior 404s)
- Use endDate = tomorrow to ensure current-session fills are included
- Count all traded days when minDayPnL is 0, otherwise count days >= minDayPnL
- Add PATCH /api/debug endpoint for proxying raw Tradovate API calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SQLite DB (better-sqlite3) with firms, account_configs, firm_fees, instruments tables
- REST API routes: firms CRUD, account configs CRUD, state, accounts, instruments
- Live Tradovate WebSocket client: login, sync, positions, auto-liq thresholds
- Dashboard (app/page.tsx): per-firm account list with balance, day P&L, days traded,
target progress, and Dead/Inactive/Flat status based on Tradovate auto-liq floors
- Account detail page: objectives progress, daily P&L chart, consistency tracking
- Per-firm settings page: account configs and instrument fee management
- Dead detection uses trailingMaxDrawdownLimit - trailingMaxDrawdown from
userAccountAutoLiqs; filters Tradovate sentinel value (999999999 = no limit)
- FIFO P&L engine with commission accounting for daily P&L history
- Removed manual maxLoss fallback in favour of live Tradovate auto-liq data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>