The state API was returning client.daysTraded (any non-zero day) as the
displayed daysTraded. Firms count only days that hit minDayPnL toward
the min trading day requirement.
When cfg.min_day_pnl > 0, filter dailyPnL by that threshold and use the
filtered count. Otherwise fall back to client.daysTraded.
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>
State API now returns effectiveProfitTarget = max(stage profit target,
maxDay/consistency). When a big day forces the consistency rule, this
reflects the actual amount needed to complete the stage — not just the
base profit target.
Main dashboard and account detail page now display this instead of the
raw cfg.profitTarget.
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>
- Display "Auto" instead of "Random" for symbol/action selectors
- Last withdrawal stage always shows "+" suffix (even with one stage)
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>