feat: UTA snapshot system + equity curve + broker reliability fixes#86
Merged
luokerenx4 merged 9 commits intomasterfrom Mar 24, 2026
Merged
feat: UTA snapshot system + equity curve + broker reliability fixes#86luokerenx4 merged 9 commits intomasterfrom
luokerenx4 merged 9 commits intomasterfrom
Conversation
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
getAccount()now recomputenetLiquidationfrom fresh position markPrice data instead of relying on cached broker/exchange totals.__snapshot__,__heartbeat__) no longer accidentally routed to AI engine.Test plan
pnpm buildpasses (backend + UI)pnpm test— 285+ trading tests, 35 snapshot tests, all passingnpx tsc --noEmit— zero type errorsdata/trading/{id}/snapshots/🤖 Generated with Claude Code