Commit Graph
240 Commits
Author SHA1 Message Date
Brandon LiandClaude Opus 5.5 d6bb5a7221 Show the person's 2FA code on the firm site the extension is on
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>
2026-09-29 17:19:32 -05:00
Brandon LiandClaude Opus 5.5 58e7414849 Futures legs respect a firm's per-instrument contract limit
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>
2026-09-28 13:45:44 -05:00
Brandon LiandClaude Opus 5.5 b125257d4e Trade mix: name positions by a contract index that never expires
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>
2026-09-27 20:04:42 -05:00
Brandon LiandClaude Opus 5.5 ed62788480 Drain and extra-day legs always trade MNQ
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>
2026-09-27 18:54:41 -05:00
Brandon LiandClaude Opus 5.5 5696f2b036 Report each firm's trading mix: open positions and today's entries by instrument
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>
2026-09-27 18:50:56 -05:00
Brandon LiandClaude Opus 5.5 ab3132182a Report each tick's outcome and the updater's state to the master
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>
2026-09-27 18:31:21 -05:00
Brandon LiandClaude Opus 5.5 db0ad99a76 CME evening session: first entry at 5:45 PM CT, not 5:05
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>
2026-09-27 17:45:08 -05:00
Brandon LiandClaude Opus 5.5 ff97a59b49 Session open: a random 0–180 minute delay, kept across restarts
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>
2026-09-27 17:30:01 -05:00
Brandon LiandClaude Opus 5.5 7866d67676 TopstepX: look back 90 days for trade history, not 27
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>
2026-09-27 16:56:18 -05:00
Brandon LiandClaude Opus 5.5 db1d12327a Live futures spreads off one client, and the master's max entry spread
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>
2026-09-27 15:06:44 -05:00
Brandon LiandClaude Opus 5.5 1782c16145 Sizing: the broker's largest and smallest order bind the leg's size
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>
2026-09-27 13:59:27 -05:00
Brandon LiandClaude Opus 5.5 7f55e49755 DXtrade: date transactions by the firm's day, not the CT trade date
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>
2026-09-27 13:59:25 -05:00
Brandon LiandClaude Opus 5.5 934620e8e9 Manual leg: take the lane's lock before reading the account
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>
2026-09-27 13:59:23 -05:00
Brandon LiandClaude Opus 5.5 5a4e85cc18 Last stand: measured against the full daily loss, not the stop after its reserve
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>
2026-09-27 13:43:07 -05:00
Brandon LiandClaude Opus 5.5 ff9b107452 Instruments: no fee lookup — commissions barely differ between instruments
Entry cost is one tick of spread; fees can be added later if they matter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 19:51:56 -05:00
Brandon LiandClaude Opus 5.5 02544fbdf5 Instruments: each firm's list from the master, and a session-aware futures draw
- 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>
2026-09-26 19:47:32 -05:00
Brandon LiandClaude Opus 5.5 302e9b4911 Symbol stats: the win chance with no commission, spread or swap beside the real one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:47:50 -05:00
Brandon LiandClaude Opus 5.5 957b9a50fe Sizing: no financing floor when a trade costs nothing (zero commission and spread)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:46:04 -05:00
Brandon LiandClaude Opus 5.5 169fe19fad Report each symbol's next-leg stats to the master; drop per-symbol commission
- 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>
2026-09-26 18:41:01 -05:00
Brandon LiandClaude Opus 5.5 a2cd3a94c9 Legs: cheapest symbol that reaches the target hold, last stands, a reserve that grows with size
- A batch sizes its leg on every mapped candidate, each at the leverage
  the broker states for it, and trades the cheapest (spread, commissions,
  expected swap) that reaches the target hold — else the quickest. The
  dashboard ranks the same way from a half-hourly candidate survey.
- Last stand: when a daily-loss stop-out would leave the account within
  15% of that loss above its max-drawdown floor (the account type's static
  max loss, or the broker's liquidation floor), the leg carries no
  daily-loss stop and is sized against the firm's own breach.
- The daily-loss reserve is 10% of the limit or 0.1% of the position,
  whichever is more, since a stop's slippage grows with the position.
- The account type's max notional is gone: the broker's margin rates cap
  each symbol. planLegSize holds the one sizing rule fireLeg, the pick and
  the dashboard share.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:11:42 -05:00
Brandon LiandClaude Opus 5.5 675b4c89f8 Manual leg: may join a lane on the same instrument and side, never the other
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:46:16 -05:00
Brandon LiandClaude Opus 5.5 b5ed423145 Manual one-leg trigger: POST /api/accounts/[id]/fire-leg
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>
2026-09-26 17:43:51 -05:00
Brandon LiandClaude Opus 5.5 6876223fa4 DXtrade: volatility measured once a day, next-leg hold on the dashboard, closed accounts dead
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>
2026-09-26 17:40:42 -05:00
Brandon LiandClaude Opus 5.5 9fc1ed6908 DXtrade: Hyperliquid prices when /marketdata is denied; closing stop/limit without quantity
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>
2026-09-26 17:35:23 -05:00
Brandon LiandClaude Opus 5.5 d01f03b293 DXtrade sizing: volatility only (12h default hold); fees and every-day only for DXtrade
The symbol map's fixed notional per contract is gone: every DXtrade leg is
sized from the instrument's recent volatility for the firm's target hold
(blank = 12 h), and a candidate without a volatility isn't picked. Percent
commission and every-day trading are ignored on non-DXtrade firms, which keep
measured per-contract fees and the CME calendar.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:04:15 -05:00
Brandon LiandClaude Opus 5 49c4725e0c Reporter: name each account's withdrawal count
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>
2026-09-25 00:17:08 -05:00
Brandon LiandClaude Opus 5.5 809f337055 Unknown entries: judge only on a fresh good sync; all live orders count as working
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>
2026-09-24 22:13:47 -05:00
Brandon LiandClaude Opus 5.5 57835c01f0 Unknown entries settle only on a sync started after them; filled-after-all and working orders seen
Sixth review round (8e66631):

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:38:57 -05:00
Brandon LiandClaude Opus 5.5 8e66631b0e Entries of unknown outcome hold the account until settled; wipes keep their guards
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>
2026-09-24 21:12:51 -05:00
Brandon LiandClaude Opus 5.5 1eb0736fbe One-and-done counts confirmed fills; holds need a persistent refusal; wipes keep their yardstick
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>
2026-09-24 20:46:35 -05:00
Brandon LiandClaude Opus 5.5 52ed9c75e4 Roll window instead of a roll-time base; transient outages keep payout holds
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>
2026-09-24 19:37:14 -05:00
Brandon LiandClaude Opus 5.5 088886ed73 Pre-roll fills stay visible; holds survive restarts and judge fund amounts
Second review round (e496404, 9b2d677):

- Tradovate: past the 5 PM roll an old-dated cash record counts only what
  it gained since the roll (its realizedPnL as last seen while current is
  kept as the roll base), so tonight's fills booked onto it before
  Tradovate rolls the account stay visible — no re-trading a one-and-done
  account. A record this process never saw before the roll leaves the
  account waiting for the broker's roll (ROLL_PENDING, counted in play for
  stop-after-all). The 60s snapshot carries the roll base and can't undo a
  newer record the socket delivered.
- A wipe's hold is kept in account_meta, so a restart onto a failing Cash
  History still holds the account.
- A Cash History whose pre-request balance the last vouched-for baseline
  can't explain, and which booked nothing, holds the account instead of
  rebasing onto an unbooked payout.
- Holds compare fund transactions by date and amount (a second payout on a
  date that already has one), and an in-flight hold is judged against the
  rows from before its report.
- The saved baseline tracks the day's realized P&L and folds, so a restart
  judges against current figures; held refetches back off to an hour, and
  a balance hold no Cash History can check is released after an hour.
- TopstepX: a fetched account is promoted only by a sync whose
  reconcileFunds actually checked the books (not one skipped for an open
  position), and a direct fetch awaits that sync, so wipe routes report
  the real outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:45:45 -05:00
Brandon LiandClaude Opus 5.5 9b2d677662 History holds: only a booked fund transaction lifts them; pace, persist, and cover wipes
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>
2026-09-24 17:55:32 -05:00
Brandon LiandClaude Opus 5.5 e49640498a Tradovate: yesterday's realized P&L no longer reads as today's after the roll
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>
2026-09-24 17:22:43 -05:00
Brandon LiandClaude Opus 5.5 77d8c2ecdb Trade only on loaded history; catch early TopstepX fills; fix SI/M2K point values
- Both clients track which accounts this process has loaded history for
  (historyReady). An account restarted, rebuilt, wiped or newly listed is
  ineligible until its fetch lands, instead of trading as a fresh stage-1
  first day; a failed fetch with no cache no longer reads as empty history.
  Waiting accounts get a targeted fetch on the next sync.
- Tradovate holds an account whose start-of-day balance moves without a fill
  (a payout between hourly Cash History runs) until a Cash History books it,
  so a payout-pending account isn't re-traded on its ended cycle.
- Wipe routes forget history through the client and count accounts whose
  history didn't come back as failures.
- TopstepX sendOrder picks up a fill that arrived before the place response,
  and its REST backstop uses the documented Trade/search and Order/search.
- POINT_VALUES: SI 5000 (was 50), M2K 5 (was 10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:04:09 -05:00
Brandon LiandClaude Opus 5.5 61384c9fa6 Drain: random first day, then size the rest by consistency
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>
2026-09-24 16:34:43 -05:00
Brandon LiandClaude Opus 5.5 b932452e98 Self-healing broker clients, per-firm scheduler isolation, eager boot
Why the autotrader sometimes broke after a weekend until `pm2 restart`:
`next start` only logs uncaught errors, so nothing ever crashed, and a few
paths could leave a client dead for the life of the process.

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

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

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:28:41 -05:00
Brandon LiandClaude Opus 5.5 a47a86a59c Reporter: per-day target-hit counts for each account type
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 10:11:01 -05:00
Brandon LiandClaude Opus 5.5 1d4d45bd79 Fix build SQLITE_BUSY: busy_timeout before a best-effort WAL switch
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>
2026-09-22 22:07:38 -05:00
Brandon LiandClaude Fable 5 ba8bd50da0 Backups push automatically; the token is only for restore
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>
2026-09-22 14:08:03 -05:00
Brandon LiandClaude Fable 5 8154f25bb9 Backups to master, replacement-VPS restore, broker proxy, multi-instance
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>
2026-09-22 13:55:35 -05:00
Brandon LiandClaude Fable 5 97947bffde Drain stages keep the firm's consistency rule
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>
2026-09-16 05:43:55 -05:00
Brandon LiandClaude Fable 5 b1dccd7dec Revamp login guards into general per-firm page guards
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>
2026-09-14 12:50:14 -05:00
Brandon Li 82b98b9d2d Agree to terms if it didnt work lucid 2026-09-13 16:31:04 -05:00
Brandon Li 5632d347c3 Wait for success modal to close 2026-09-13 15:27:05 -05:00