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>
- 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>
- 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>
- 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>
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>
Account types with the catalog's new combineCmeHolidays flag are read
through a combined-day view: holiday-session P&L entries fold into the
next full trading day (computed from the federal-holiday rules, so any
year works), the day count matches how the firm records it (Apex: Sun/
Mon/Tue around a Monday holiday is ONE day), and on the later sessions
of the stretch the earlier sessions' booked P&L carries into realized —
so day targets, already-traded gates, target-hit status and the panel's
day P&L all treat the stretch as a single day. Off by default; older
masters that don't send the flag behave exactly as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the master marks an account type splitEnabled=false, big days trade
as one unsplit leg (the legacy one-and-done behavior); min-day bumps are
unaffected. Missing field — older master or pre-toggle cached catalog —
defaults to enabled.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This box no longer decides what firms exist. It caches the master's catalog
and stores one thing of its own: the Tradovate login for each firm. Creating
firms locally is gone — app/add, POST /api/firms, the account-config routes
and the banned-symbol route with it.
The cache is the point, not an optimisation. The scheduler needs every
account's target and size on each 60s tick, so fetching them per tick would
stop trading with positions open the first time the master rebooted. It is
one JSON blob in settings, replaced only by a complete valid response, and a
response with zero firms is refused outright: a misconfigured FIRMS_DB serves
{"firms":[]} as a healthy 200, and mirroring that would idle the whole fleet
at once.
Accounts bind to an account type by prefix AND size, the size taken from the
opening funding transaction. That does not arrive with the first sync, so an
account sits Unassigned and the resolver tries again next sync rather than
guessing. A type picked by hand is never overwritten by an auto pass. This
replaces the longest-prefix helper that had been copy-pasted into five files,
which could not tell two types with the same prefix apart at all.
firm_credentials and account_assignments deliberately have no foreign key to
the cached catalog. foreign_keys is ON, so a cascade would erase the Tradovate
passwords the moment a firm was removed on the master — routine, not
exceptional. They survive it, and are re-keyed by name if the master's database
is ever rebuilt with fresh ids.
The legacy firms/account_configs/firm_banned_symbols tables are left on disk,
unreferenced. firms is the only table that ever held plaintext credentials and
dropping it would make a rollback unrecoverable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Add withdrawal stage system: each stage defines profit target, consistency,
and min trading days for post-withdrawal challenge cycles
- Target Same Equity mode accounts for withdrawn amounts when computing
effective profit target (profitTarget - remainingProfit)
- Store fund transaction timestamps for time-aware cycle filtering
(withdrawals before 9 AM CT include that day in new cycle)
- Expose full P&L history (fullDailyPnL) for calendar/equity curve display
across all cycles, with DB fallback for pre-restart data
- Show stage number (#1, #2, etc.) on calendar cells
- Hide consistency reference line when consistency is 0% or 100%
- Settings UI: "After First W/D" column with same-equity checkbox,
expandable stage sub-rows with profit/consistency/days inputs
- Default target_same_equity to 1 for new and existing account configs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- lib/db.ts: new firm_banned_symbols table with getBannedSymbols,
isSymbolBanned, and setBannedSymbol helpers
- app/api/firms/[id]/banned-symbols/route.ts: GET lists banned symbols,
PATCH toggles a ban for a given symbol
- app/api/firms/route.ts: include bannedSymbols[] in firm list response
- app/firms/[id]/settings/page.tsx: Instruments section shows all
globally-enabled symbols with a red toggle to ban/unban; banned
symbols display a "SYMBOL BANNED" pill next to their name
- app/page.tsx: FirmRows shows "ES BANNED" (or current symbol) pill next
to the firm name when the selected trade symbol is banned for that firm
- lib/auto-trade.ts: skip firms entirely when the trade symbol is banned
- types.ts: add bannedSymbols field to FirmConfig
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB migration: ALTER TABLE account_configs ADD COLUMN max_position_size INTEGER NOT NULL DEFAULT 0
- Added max_position_size to AccountConfigRow, createAccountConfig, updateAccountConfig in lib/db.ts
- Added maxPositionSize to AccountConfig type in types.ts (0 = no limit)
- GET /api/firms/[id] now returns maxPositionSize per account
- POST /api/firms/[id]/accounts and PUT /api/account-configs/[id] accept maxPositionSize
- Firm settings page: new Max Contracts column (blank = no limit)
- auto-trade: contracts capped at maxPositionSize when > 0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-trade scheduler fires every 60s; uses Promise.allSettled batch so no new trades fire while any position from the current batch is open
- Commission gross-up: read entryCommission from cash.realizedPnL after fill (fallback 2.5×contracts), grossTarget = target + 2×entryCommission
- Sync gate: TradovateClient.syncComplete flag; scheduler skips tick until every client finishes initial position/balance sync
- Contracts formula changed to Math.ceil so $1500 target = 2 contracts
- Removed all fee caching (perContractFees, recentFills, fillFee handler) from tradovate-class.ts
- Removed firm_fees table, getFirmFees, upsertFirmFee from db.ts
- Deleted instrument-configs API routes; removed Fees UI from firm settings page
- /api/instruments returns full {symbol, enabled}[] objects; dashboard filters to enabled-only for trade selector
- Added auto-trade, debug, orders, settings, and trade API routes
- Instrument selector on dashboard now driven by enabled instruments from DB
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>