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>
An account Tradovate flips to liquidate-only at the daily loss limit is
done for the day, but the reporter only counted a loss when the booked
realizedPnL was already negative — so a locked-out account whose P&L
hadn't caught up (or that had no matching config) showed as still to
trade. Count inactive accounts as traded at a loss.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Like accounts in a batch previously carved legs with independent
Math.random, so near-identical day targets could split very differently.
Leg fractions are now drawn from groupLegFraction(prefix, tradeDate,
legIndex) — FNV-1a seeded like dailyBump — so accounts of the same
account type on the same leg of the same day split identically when
their remaining targets match, and proportionally close otherwise.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The per-account wipe works one account at a time; when every account is
carrying stale cached rows (the pre-roll dates again), that's a lot of
clicking. POST /api/accounts/wipe-all drops the cached rows for every
account on every firm and rebuilds them all from fresh reports — firms
in parallel, accounts within a firm at fetchDaysTraded's own pacing.
The toolbar button confirms inline with the same 28-day-window warning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The daily target sometimes inflates to base + consistency×todayPnL (e.g.
1200 + 0.4×1218.48 = 1687.39), which happens when a session's cached
daily-P&L row is dated something other than the current tradeDate: it
then lands in BOTH the prior-day list and realizedPnL, so day-start
equity reads too low while the prior-day sum stays high.
Detect it with a book-balance invariant that is coincidence-proof (it
does not care whether prior and today P&L happen to be equal): the
reconstructed day-start equity (equityProfit − realizedPnL) must equal
priorProfit + sum(prior days). The state route now logs the full account
state whenever those disagree by more than $50, and GET /api/debug
reports the same per-account breakdown on demand (?account=<substring>)
so the exact inputs can be captured the moment a number looks wrong.
Read-only; no behavior change. Remove once the trigger is fixed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cached daily P&L rows outlive what the Cash History report still
produces (stale pre-roll dates being the recent example), and nothing
deletes them any more. POST /api/accounts/[id]/wipe drops the account's
cached P&L, fund transactions and meta, clears the in-memory view, and
rebuilds from a fresh report — fetchDaysTraded now takes an optional
account filter so one account doesn't cost a report round-trip on every
other. The button confirms inline first: rows older than the report's
28-day window cannot be refetched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cash History rows can carry a weekend calendar date (Sunday-evening
activity attributed pre-roll), and those slipped through the holiday
combining as their own trading day — accounts traded only across the
Labor Day Sun/Mon/Tue stretch read 2/5 instead of 1/5. Weekends never
clear at CME any more than holidays do, so treat Saturday and Sunday as
unsettled dates and roll them forward with the rest of the stretch.
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>
Cash History rows are stamped with the Central calendar date, but trades
in the evening session (>= 5:00 PM CT) belong to the next trade date -
matching currentTradeDateCT and when Tradovate resets realizedPnL. The
mismatch made computeDayPlan count the same session twice (as a phantom
prior day and as today's realized PnL), inflating the day target into a
moving one: 40% consistency accounts showed dayTarget = base + 0.4 x
today's PnL instead of the anchored day-start target.
Roll those rows forward at ingestion, and make a successful Cash History
report authoritative for its 28-day window so rows cached under the old
calendar date are dropped rather than double-counted. An empty report
never deletes anything (it can't distinguish "no trades" from "no
data"), and a report still pending after polling now throws instead of
masquerading as an empty one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
runTrade now draws each batch from a single account-type prefix group
(largest group first) instead of slicing across prefixes; a group's
remainder fires alone on a later tick. copyTrade fills open slots with
accounts matching the positioned accounts' prefix before mismatched ones.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reporter already computed targetHit per account to count it as
traded; now it also classifies each account that finished the day flat
and sends targetHit/partial/loss counts per firm and account type. A
realized loss reports as loss even on a grind day where any nonzero PnL
counts as done. Fields are additive, so an older master ignores them.
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>
.validate-required is on every required checkout field, so the old selector's
first match was usually a text input, and on the terms row the click landed in
the empty half of a full-width <p> beside the box. Target the checkbox input in
a required row and gate on :not(:checked) so the two clicks tick box one then
box two instead of toggling the same box twice; the second is skipIfNotFound in
case a checkout has only one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The no-change path is a single `git fetch`, so polling master every minute
costs nothing and lines up with the VPS's own 60s deploy timer instead of
lagging it by up to five.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A frame-targeted step resolved the element inside the checkout iframe and
reported that iframe's URL as the result `url`. The signed-out guard keys off
`url` to decide whether the tab is on the login page, and Lucid's checkout
iframe sits on the apex host (lucidtrading.com) that its signedOutPattern
matches — so a signed-in checkout looked signed out, ran the login step, and
failed with no login button present.
Report the TOP tab's URL as `url` (from pageFrameOffset), keeping the child
frame's URL as `frameUrl`. Extension 0.14.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The frame-aware step vocabulary only the 0.19.0 runner understands, so the
server must require it too. Missed in the previous commit, which left the
dashboard flagging the restarted 0.19.0 runner as out of date.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selectors were only resolved against the top document, so steps targeting
the Lucid WooCommerce checkout (rendered in a lucidtrading.com iframe under
the dash.lucidtrading.com app) never matched — e.g. input#coupon_code_field.
Add an optional `frame` on click/type/waitFor naming the <iframe> element.
The extension measures the iframe in the top frame (pageFrameOffset), finds
the element across child frames (pageLocate `relative` mode), and composes
the two into desktop coordinates; the Python side is unchanged since the
result keeps the same shape. Without `frame`, every path behaves as before.
Point the Lucid checkout steps at the frame and harden the coupon selectors
(the exact field id is still unconfirmed against the live iframe).
Extension 0.13.0, runner 0.19.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>