One line per account on every sync, which on a multi-firm instance buries
anything worth reading in `pm2 logs autofirmer`. The threshold is still
recorded on the client and still drives the dead-account checks; it just is
not narrated any more.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`npm run build` failed on a clean checkout, so nothing on master could be
built for production. `npm run dev` does not hard-fail on type errors, which
is why it went unnoticed.
- state route returned client.perContractFees, which has never existed on
TradovateClient on any branch; nothing consumed it
- mapFirmConfig omitted bannedSymbols. Type gap only: the trade path calls
isSymbolBanned() against the DB directly, so bans were always enforced
- initClient's sync callback was sync where the constructor wants
() => Promise<void>
- accessInfo and ws are assigned during async connect/auth, never in the
constructor, so they take definite-assignment assertions
- the socket payload's inline entityType union had drifted five members
behind the indirect-callback union above it, making the 'position' and
'cashBalance' branches unreachable to the compiler. Both now share a
TradovateEntityType alias. Type-only: those handlers ran fine at runtime
Behaviour is unchanged throughout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Some firms record payouts as "Challenge Payout" in the Cash History
rather than "Fund Transaction" or "Manual Adjustment". Without this the
payout was summed into that day's P&L (a -2,000 payout turned a +1,416
day into -584) and never reset the cycle, so daysTraded kept counting
and the stage never advanced.
Verified against a captured report: the payout is now recorded as a
fund transaction, excluded from daily P&L, and because its timestamp
precedes the day's first trade the day's trades land in the new cycle.
Co-Authored-By: Claude <noreply@anthropic.com>
Tradovate's report server occasionally returns 502, causing accounts
like PAAPEX5776400000019 to end up with empty data when both Cash
History and Fills fail. Now retries up to 3 times with exponential
backoff (500ms, 1s, 2s) on transient errors before falling through to
the existing 5-min outer retry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of the 9 AM CT heuristic, compare the withdrawal timestamp
against the day's earliest trade timestamp. If trades happened AFTER
the withdrawal, those trades count toward the new cycle.
Falls back to the 9 AM heuristic when first-trade timestamp is
unavailable (e.g., cache-only fallback path).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some firms record withdrawals as "Manual Adjustment" instead of
"Fund Transaction" in the Cash History. Include both types so the
withdrawal-aware cycle filtering still works.
Co-Authored-By: Claude Opus 4.6 (1M context) <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>
- Persist fund transactions to SQLite (fund_transactions table) so they
survive beyond Tradovate's 28-day report window
- Calendar: highlight W/D dates in amber with the amount shown below the day
- Equity curve: reduce running equity at withdrawal dates and show a vertical
dashed amber line labelled W/D
- New Cash History table below calendar listing all trades and W/D events
sorted newest-first
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tracks Fund Transaction entries in the Cash History report to find the
most recent account funding/reset date. Only trading days on or after
that date count toward daysTraded and the daily target calculation.
The last fund date is persisted in SQLite so it survives beyond the
28-day Tradovate report window.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a daily_pnl SQLite table that accumulates trade history indefinitely.
On each hourly fetch, fresh API data is upserted (not replaced) so entries
older than Tradovate's 28-day limit are preserved. Cache is loaded at startup
and used as fallback when both report requests fail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract connection logic into connectAndAuth() so it can be called
on both initial connect and reconnect
- Add ws.onclose handler: if not an intentional disconnect, clear stale
intervals and retry connectAndAuth() after 5 seconds
- Track sync, heartbeat, and tokenRenewal interval handles so they are
cleared and recreated cleanly on each reconnect
- Reset syncComplete = false on reconnect so the scheduler waits for
a fresh sync before trading
- disconnect() sets intentionalDisconnect = true and clears all intervals
to prevent reconnect loops when a client is removed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace the reactive throttle with a proactive setInterval in requestSync
that fires fetchDaysTraded exactly once per hour per client
- Remove post-fill fetchDaysTraded calls from auto-trade.ts — no longer
needed and were causing bursts of report API requests on simultaneous fills
- Guard against duplicate intervals if requestSync fires more than once
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multiple simultaneous fills were triggering concurrent report API calls
to Tradovate for every account in the firm, causing rate limiting.
Added a 1-hour cooldown — the first call always runs (lastDaysFetch=0),
subsequent calls within the same hour are no-ops.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New lib/contract-resolver.ts: picks the best contract month for each
symbol by comparing Yahoo Finance volume between the front month
(Tradovate suggest API) and the roll target (rollcontract API)
- lib/clients.ts: auto-resolves all enabled instruments 15s after startup
and again daily at midnight via a setInterval check
- lib/tradovate-class.ts: findFrontMonthContract checks resolver cache
first before falling back to the suggest API
- app/api/instruments/contracts/route.ts: GET returns cached contracts,
POST triggers a fresh resolve
- app/settings/page.tsx: shows active contract + rolled badge per symbol;
auto-resolves on load if cache is empty; removed manual Resolve button
- app/api/debug/route.ts: include entity data in recentEntityEvents
- CLAUDE.md: instructs Claude to always work on main
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Primary: Cash History report sums non-Fund-Transaction Deltas per day,
capturing broker platform fees not present in the Fills report
- Fallback: Fills + FIFO used when Cash History 404s (passed/completed accounts)
- Extracts requestReport() as shared helper to reduce duplication
- debug PATCH endpoint now accepts optional `name` param to test any report
Co-Authored-By: Claude Sonnet 4.6 <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>
trading-logic: when baseAmount=0 (consistency=0%), target cappedByFuture
directly instead of collapsing to minDayPnL. For a $4000 target with $150
min-day and 5 days, day 1 now correctly targets $3400 ($4000 - 4×$150)
then $150 for each remaining mandatory day.
tradovate-class: make fetchDaysTraded() public so auto-trade can call it
immediately after a trade exits. Fix daysTraded to count only positive-P&L
days from the FIFO results, consistent with computeDailyTarget's
positiveDays.length — previously counted all raw fill dates.
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>