Skip to content

feat: UTA snapshot system + equity curve + broker reliability fixes#86

Merged
luokerenx4 merged 9 commits intomasterfrom
dev
Mar 24, 2026
Merged

feat: UTA snapshot system + equity curve + broker reliability fixes#86
luokerenx4 merged 9 commits intomasterfrom
dev

Conversation

@luokerenx4
Copy link
Copy Markdown
Contributor

Summary

  • UTA Snapshot System — periodic (configurable interval) + event-driven (post-push/reject) account state capture. Chunked JSONL storage with index, write serialization, retry on failure. Never stores fake zero-value data.
  • Equity Curve UI — Recharts AreaChart in PortfolioPage with time range filters (1H–30D), per-account switcher, click-to-inspect snapshot detail panel showing positions and orders at that point in time.
  • Broker netLiq fix — CCXT and IBKR getAccount() now recompute netLiquidation from fresh position markPrice data instead of relying on cached broker/exchange totals.
  • IBKR socket crash fix — Socket errors (ECONNRESET) no longer crash the process. Adapted to match Python's graceful disconnect pattern.
  • Cron listener fix — Internal jobs (__snapshot__, __heartbeat__) no longer accidentally routed to AI engine.

Test plan

  • pnpm build passes (backend + UI)
  • pnpm test — 285+ trading tests, 35 snapshot tests, all passing
  • npx tsc --noEmit — zero type errors
  • Manual: snapshot data accumulates in data/trading/{id}/snapshots/
  • Manual: equity curve renders per-account, no false ramp-up
  • Manual: TWS disconnect → process survives, UTA degrades + auto-recovers
  • Manual: snapshot interval configurable from frontend

🤖 Generated with Claude Code

Ame and others added 9 commits March 24, 2026 09:13
… capture

Snapshots capture full account state (equity, positions, orders, health)
independently of trading operations. Triggered by cron schedule (every 15m),
post-push/post-reject hooks, or manual call. Stored as chunked JSONL (50 per
chunk) with an index file for efficient time-range queries.

Includes write serialization to prevent concurrent append corruption, store
caching per account, and 35 test cases covering builder, store, service,
scheduler, and UTA hook integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: two new endpoints — per-account snapshots and aggregated equity
curve (minute-aligned, cross-account summation). SnapshotService exposed
via EngineContext.

Frontend: Recharts AreaChart with time range filters (1H–30D), per-account
tooltip breakdown, and click-to-inspect snapshot detail panel showing
positions and open orders at that point in time. Integrated into PortfolioPage
between hero metrics and account strip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Snapshot scheduler reads enabled/every from config/snapshot.json instead
of hardcoded '15m'. Frontend PortfolioPage shows inline toggle + interval
input with auto-save. Config auto-seeds on first boot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Internal cron jobs (prefixed with __) like __snapshot__ and __heartbeat__
have dedicated handlers. The cron listener was only excluding __heartbeat__,
causing __snapshot__ fires to be routed to agentCenter.askWithSession().

Fixed by checking the __name__ convention instead of hardcoding job names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ward in equity curve

Builder: returns null instead of fabricating zero-value snapshots when
broker queries fail. Offline/disabled accounts also return null.

Service: skips storage when builder returns null (logs snapshot.skipped),
retries failed accounts once after 3s delay in takeAllSnapshots.

Equity curve: carry-forward fill for missing account values at any time
point, preventing dips when one account's snapshot is absent.

Store: baseDir now configurable (tests use tmpdir instead of data/).
Cleaned 7 historical zero-value snapshots from Alpaca and Bybit chunks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default view now shows the selected account's own snapshot data instead
of merging all accounts. Account switcher buttons let users toggle between
individual UTAs and an "All" combined view. Single-account users won't
see the switcher. Eliminates the false ramp-up artifact from accounts
appearing at different times.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hed broker totals

CCXT: balance.total.USDT is a cached wallet snapshot that doesn't update
between funding/settlement cycles. Now computed as free + sum of position
market values using real-time markPrice from fetchPositions().

IBKR: values.NetLiquidation from TWS is cached server-side and may not
refresh between market sessions. Now computed as totalCashValue + sum of
position marketValue from updatePortfolio() callbacks. Falls back to TWS
value for cash-only accounts (no positions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
buildSnapshot() returns UTASnapshot | null — tests for successful
snapshots now assert non-null before accessing properties.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onnect

Socket errors (ECONNRESET, etc.) were emitted as EventEmitter 'error'
events with no listener, causing Node to crash the process. This was
a Python→Node adaptation bug: Python's recvMsg() catches socket.error
inline and calls self.disconnect(), never propagating the error.

Now socket errors call this.disconnect() directly, triggering
wrapper.connectionClosed() which feeds into UTA health degradation
and auto-recovery. Also fixed disconnect() to prevent double-calling
connectionClosed() (once from disconnect, once from the close event).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@luokerenx4 luokerenx4 merged commit af39134 into master Mar 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant