Stage 1 = bg-slate-100 (matches Flat status pill), each subsequent stage
gets a deeper blue. Shared helper in lib/stage-colors.ts used by both
the main dashboard and the account detail page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Caps consistencyCap at the largest day the consistency rule would have
allowed (profitTarget × consistency). Prevents previous overshoots or
losses from expanding future targets beyond what consistency permits.
Day 1: min(remaining × consistency, profitTarget × consistency)
Day 2+: min(maxDay, profitTarget × consistency)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a big day has been made, total trading profit must be >= maxDay/consistency
for the consistency ratio to be satisfied. Previously the function only
checked equity vs profitTarget and would coast on min-day even when
consistency was still violated.
Now the remaining = max(equityShortfall, consistencyShortfall).
Per-stage: dailyPnL is already filtered to post-withdrawal entries by
filterActivePnL, so tradingProfit, qualifyingDays, and maxDay naturally
scope to the current stage.
Example PAAPEX5466170000038 (consistency 50%, maxDay $3307.60, total $3306.05):
- equityShortfall = 0 (equity way above $6600 target due to $150k deposit)
- consistencyShortfall = $6615.20 - $3306.05 = $3309.15
- Target now aims for $3309.15 spread across remaining days, not $350 min-day
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
computeDailyTarget now requires equityProfit (amount - accountSize) and
returns null when it's undefined/null/NaN. 0 is still a valid value.
- Removed totalProfit parameter (was only used as fallback)
- Callers handle null by skipping the account (eligibility) or throwing
(execution paths)
- State API sets dailyTarget to null when no valid balance, avoids
incorrect targetHit computation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the tangled first_day/consistency/min-day branches with a
cleaner flow:
1. If profit target met, coast on min-day (or nothing)
2. Compute cappedByFuture (reserve future min-days)
3. Compute consistencyCap:
- Day 1 of cycle: remaining × consistency
- Day 2+: current maxDay
- 0/100% consistency: no cap
4. Combine and floor at minDayPnL when mandatory days remain
Fixes a bug where Stage 2+ Day 1 used the full profitTarget × consistency
instead of remaining × consistency, allowing day 1 to exceed 50% of
cycle-local profit.
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>
When consistency is between 0 and 1 (exclusive) and profitTarget not met,
the daily target should cap at maxDay (can't exceed the current max without
breaking the consistency ratio). The previous 'needed' calc could return
less than maxDay, stalling progress toward profitTarget.
Also restored Math.min(baseAmount, cappedByFuture) in the min-day
reservation so consistency is enforced when both mandates are active.
Traces:
- 0 days, $11111 target, 50%: first_day $5555.50, cappedByFuture $10511
-> consistencyCap = min($5555.50, $10511) = $5555.50 ✓
- 3 days, $5946.44 equity, maxDay $2415.20, 50%: baseAmount $2415.20,
cappedByFuture $5014.56 -> min = $2415.20 ✓
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously Mode A computed effective target = profitTarget - (priorProfit
+ totalWithdrawals), which produced inflated targets after withdrawals.
Since computeDailyTarget now uses equityProfit (amount - accountSize)
directly, Mode A just needs to return the base profitTarget unchanged.
The equity-based comparison naturally handles 'get back to same target
above accountSize' semantics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a withdrawal stage has minTradingDays unset (0 or missing), use
the account's base minTradingDays instead of overriding to 0. This
was causing min-day reservation to skip and return consistency-based
targets instead of the min-day floor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of relying on dailyPnL sum (which can miss reports or be out
of sync with account balance), use (amount - accountSize) as the actual
profit when comparing against profitTarget. Consistency calc still uses
dailyPnL totalProfit for realTarget.
Also unconditionally floor the min-day reservation at minDayPnL — if
equity + (days × minDay) >= target, we coast on min-day.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The min-day block was capping the target at baseAmount (consistency-derived)
via Math.min(baseAmount, cappedByFuture), which could produce a target too
low to hit profitTarget with min-day reservations for remaining days.
Now uses cappedByFuture directly so the target always stays on track to
hit profitTarget when there are remaining mandatory days. Consistency can
still be enforced via the firm's consistency setting (1.0 = no cap).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When totalProfit >= profitTarget, set baseAmount = 0 so the min-day
reservation block takes over. This returns effectiveMinDay ($150) for
remaining mandatory days instead of climbing toward maxDay/consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
computeDailyTarget was only solving for consistency (realTarget =
maxDay / consistency). When realTarget < profitTarget, the function
would stop trading before hitting the actual profit goal.
Now uses Math.max(realTarget, profitTarget) - totalProfit as the
remaining needed, capped at maxDay for the daily target.
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>
The POST handler calls runTrade() directly, bypassing the scheduler
tick's position gate. If you stop and restart with open positions,
other accounts could get new trades while existing ones haven't closed.
Now runTrade itself checks for open positions before proceeding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Math.max(baseAmount, effectiveMinDay) ensures the floor is always
enforced when minDayPnL > 0, not just inside the min-day reservation block.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of generating candidates from Tradovate and matching by price,
now parses Yahoo's continuous contract shortName (e.g. "Gold Jun 26")
to determine the exact month/year, constructs the Tradovate name
(e.g. "GCM6"), and looks it up directly. Falls back to Tradovate
suggest if Yahoo is unavailable.
This eliminates all price comparison, volume comparison, and the
rollcontract API calls entirely — Yahoo already knows the active month.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adjacent futures months (e.g. 6EK vs 6EM) have nearly identical prices,
so the first-match-within-tolerance approach picked the front month
(6EK/May) instead of the actual active contract (6EM/June). Now picks
the candidate with the closest price to the continuous contract instead
of breaking on the first match within 0.1% tolerance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace volume-based contract selection with Yahoo Finance continuous
contract price matching ({PRODUCT}=F). The old approach compared volumes
across front/roll candidates, which failed when serial months had
deceptive volume (6EJ26 > 6EM26) or Yahoo was rate-limited (all 0s).
Now fetches the continuous contract price and matches it against
candidates within 0.1% tolerance. Falls back to roll1 if Yahoo fails.
Also adds User-Agent header to avoid 429 rate limiting.
Verified: GC=F price matches GCM26, 6E=F price matches 6EM26.
Also fixes stale 'Random' comment in auto-trade.ts and cleans up
frontVolume/rolledVolume references from settings page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of relying on the scheduler state (which may be 'Auto'), look up
each enabled symbol's front-month contract and match by contractId to
determine which symbol the positioned accounts are actually trading.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bug fixes:
- Fix computeDailyTarget when consistency is 0% or 100%: treat as no constraint,
letting min-day reservation or full remaining profit drive the target
- Rename 'Random' to 'Auto' across entire codebase (types, API, UI, scheduler)
Features:
- Add "Stop after all eligible" checkbox: auto-stops scheduler when all
configured accounts are dead, inactive, already traded, or challenge complete
- Show position direction in status pill: "Long" (green) / "Short" (red)
instead of generic "In Trade" (blue)
- Add "Copy to Max" button: copies current trade direction to remaining
eligible accounts up to max_concurrent_accounts limit
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>
Dropdown on settings page with two options:
- Full CME (5:00 PM – 3:00 PM CT) with 5 min buffer
- Equity Hours (8:30 AM – 3:00 PM CT) with 5 min buffer
Setting is read live each tick, no restart needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gives a full hour buffer before the CME daily halt at 4 PM.
No-trade window is now 3:00 PM – 5:00 PM Central (Mon–Thu),
and after 3:00 PM on Fridays.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes > to >= so when both contracts have 0 volume (e.g. off-hours),
the further-out standard month wins. Fixes SI resolving to SIJ (April,
non-standard) instead of SIK (May, standard delivery month).
Also fixes SI/SIL prefix collision by requiring month+digit after symbol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New reporter module that pushes firm stats (total accounts, accounts
traded, in trade) to a configurable master dashboard every 30 seconds
- Add Instance Name and Dashboard URL fields to the settings page
- Register master_dashboard_url and instance_name in settings API
- Seed default (empty) values for new settings in db
Co-Authored-By: Claude Opus 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>
- Seed tick_interval_seconds setting (default 60s)
- Expose tick_interval_seconds via GET/PATCH /api/settings
- startScheduler reads the setting at start time; enforces 5s minimum
- getSchedulerStatus returns intervalSeconds for the UI
- Main page: "Every [__] s" input in idle bar — saves on blur, persists across
restarts; running state displays "every Ns" next to symbol/action
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Symbol dropdown now includes a "Random" option alongside enabled instruments
- runTrade resolves 'Random' to a random enabled instrument once per batch,
so all accounts in the same tick trade the same symbol
- Import getInstruments in auto-trade.ts to support the resolution
Co-Authored-By: Claude Sonnet 4.6 <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>
- 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>
All accounts in the same batch trade the same resolved direction.
Each new batch (after positions are flat) picks a fresh random direction.
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>
isInNoTradeWindow() checks the current hour in America/Chicago (handles
CST/CDT automatically) and returns true from 15:00–16:59. runTrade()
returns early if the window is active, blocking all entries regardless of
whether the scheduler is running or a manual trade is triggered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plaintext usernames and passwords for Alpha and TakeProfitTrader have
been removed from the startup seed block. Firms must now be added
manually through the UI or API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Privacy button: masks account names beyond the first 5 chars with bullets;
eye/eye-off icon toggles the mode in the header toolbar
- computeDailyTarget: accepts minDayPnL + minTradingDays params; when a
positive min floor is set and mandatory days remain, reserves future-day
profit so each day hits the floor (cap = remaining - futureReserve, floor
= minDayPnL); returns effectiveMinDay directly once profit target is met
but days are not yet satisfied
- auto-trade: passes minDayPnL/minTradingDays to computeDailyTarget; for
zero-floor accounts that have met the profit target but still owe trading
days, trades 1 MNQ in-and-out at market (extra-day mode) and bypasses the
normal target=0 skip gate via isMnqExtraDay flag
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>
- lib/trading-logic.ts: computeDailyTarget() computes the next trading
day's profit target via two paths:
• No positive days yet → profitTarget × consistency (first day)
• Positive days exist → maxDay / consistency gives the total profit
needed to satisfy the consistency rule; target maxDay when far away,
or the exact remaining amount when close
- Account detail page: display "Next Trading Day Amount" in Objectives card
- Equity curve: add indigo dashed reference line for the consistency target
(maxDay / consistency), labelled top-left to avoid overlapping the amber
profit-target line (top-right)
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>