Turns the autobuyer from a page scraper into something that acts. A dashboard
button queues a run; a desktop process executes it against the real browser.
lib/automations.ts — automations are declarative step lists nested inside the
firm whose site they drive. Steps are click / type / wait / navigate, and they
inherit the firm's tab pattern and URL, so one firm's automation can't act on
another's tab. Adding a button means adding an entry here; the page renders
buttons from the API and the runner receives steps from the server, so neither
needs editing. Runs key on firm:automation — every firm will plausibly have its
own "buy-accounts", and a bare id would resolve to the wrong one.
clicker/runner.py — the daemon behind the buttons. Claims a queued run, works
through the steps, reports each one back for the page's live log. Only one run
executes at a time: two processes driving one physical mouse would interleave
clicks. Heartbeats on its own thread, because a step can block for tens of
seconds and folding the beat into the main loop would show the runner as offline
in the middle of the run it was executing.
clicker/actions.py — one implementation of the safety checks, shared by the CLI
and the runner. Refuses to act when the element is covered by an overlay, when
coordinates fall off-screen, when the browser can't be confirmed frontmost, or
(for type) when the target isn't an editable field.
Typing: uneven human cadence, and the field is read back afterwards and compared
against what was typed — a field that never took focus fails silently and looks
identical to success otherwise. Non-ASCII is rejected because pyautogui skips
those characters without complaint, and newlines because Enter may submit the
form. Typos are deliberately not simulated: a mistyped digit in a trading form
is a real loss, and the correction is the part that can go wrong.
Extension: opens the firm's page when no tab matches, navigates to a specific
page for a navigate step (skipped when already there, so page state survives),
and retries the locate while a freshly loaded React app mounts — `complete` only
means the document loaded.
Staleness reporting, after it cost three debugging rounds: Chrome doesn't reload
an unpacked extension and Python doesn't reload a running process, so both now
report their version. A stale runner gets a red banner naming both versions and
the automation buttons are disabled, rather than failing mid-run on a step type
it predates.
Scale detection is now conservative: a raw OS/browser width ratio is only
trusted when it lands on a real scaling factor. On this multi-monitor desktop
the previous logic would have silently halved every coordinate.
Verified end to end against the live browser: navigate, locate, and a real
click (run #12, all three steps). API round-trips, claim-once semantics, run
cancellation, the heartbeat online/offline lifecycle, motion geometry and
timing, focus activation, and typing verification all pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Builds the pipeline the autobuyer needs: see the page, find an element,
click it.
extension/ — MV3 Chromium extension. Polls /api/autobuyer/status and,
while on, scrapes the target tab's HTML and posts it back. Also serves
locate requests: focuses the window, scrolls the element into view, and
reports its position. host_permissions is scoped to tradeify plus
localhost so it cannot read other sites — an empty target pattern would
otherwise capture whatever tab happened to be active, including banking
or mail.
app/api/autobuyer/ — status toggle, capture store, and the locate request
queue. CORS is open because the extension's origin changes every time an
unpacked extension is reloaded.
app/autobuyer/page.tsx — ON switch, source view (default) and a rendered
view. The render uses sandbox="allow-scripts" without allow-same-origin:
the page's own JS is needed because sites ship content at opacity:0 and
fade it in, but the frame must not reach the dashboard's same-origin API
routes, which serve firm credentials.
clicker/ — Python CLI. Asks the extension where a selector is, adds the
element rect to the window's screen position and the browser chrome
height to get desktop coordinates, then clicks with a human motion model
(curved path, eased velocity, occasional overshoot, dwell before press).
Raises the browser application first, since macOS consumes a click on an
unfocused window rather than delivering it.
Refuses to click when the element is covered by an overlay, when the
coordinates fall off-screen, or when the browser cannot be confirmed
frontmost.
Verified: API round-trips, capture pruning, locate claim-once semantics,
motion geometry and timing, and focus activation — the last two against
stubs, since pyautogui and pyobjc are not installed here. NOT verified
end to end: Chrome is still running a stale build of the extension, so a
locate request has never completed against a real page and no real click
has been sent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
State API now returns stage = 1 + number of withdrawals. Both the main
dashboard and the account detail page show a small Stage N pill next to
the profit target value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
- 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>
Skip POINT_VALUES validation for 'Random' — the symbol is resolved
to a real instrument inside runTrade before any point value lookup.
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>
- POST /api/instruments/contracts now accepts optional { symbols[] } body
to resolve a subset rather than all enabled instruments
- Settings toggle() fires a targeted resolve when enabling a symbol,
merging the result into contracts state without a full page refresh
- Active Contract cell is hidden (null) when the instrument is disabled
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>
- 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>
- 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>