feat(ts-sdk): account value on the PnL history - #311
Open
majesticwizardcat wants to merge 5 commits into
Open
majesticwizardcat wants to merge 5 commits into
majesticwizardcat wants to merge 5 commits into
Conversation
poszu
added this pull request to stack #312
October 2, 2026 06:16
majesticwizardcat
force-pushed
the
giannis/account-value
branch
from
October 2, 2026 08:58
b370d70 to
fd666c7
Compare
Every point of `pnlHistory` and `pnlHistoryStream` carries the absolute `accountValue` when the node serves the activity feed. Account value is PnL plus every movement that is not PnL, so the backward walk keeps one more running sum over money events: - native deposits, transfers in and out, and backstop cash sweeps from `/clob/activity` (types backstop, bridge_transfer, transfer), fetched per window next to the fills and cached with them; - native withdrawals from the bridge feed already read; - spot token movements as size events: a withdrawal leaves at the average cost, a deposit arrives marked at the tick's clearing price, and the value that moved is what the event is priced at. Refused rows are skipped. On a node without the feed the points omit `accountValue` and a warning says so; PnL is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A lookup for the newest solutions row at or before t that comes back empty proves the market has no rows at any earlier time. The cache now keeps that bound per market and skips every older point for it, and the range path skips a tick before every known market's first row. Before this, a window reaching back past a market's birth asked the node for the same empty rows on every run: a yearly window on a canary account issued about 4,800 lookups cold and 4,300 again warm. It is now about 600 cold and none warm, 6.7 s to 3.4 s cold and 5.7 s to 1.6 s warm. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Chunks now start at 10 points and grow 20, 40, 50, so the newest points draw after one small window instead of fifty points' worth. Backstop and withdrawal reads ride with the snapshot round, and the first windows are fetched while the indexer watermark is checked; if the indexer was behind the engine tick the newest window is forgotten and fetched again. Staging market maker, 30 minutes: first chunk 1.7 s to 1.0 s, warm run 0.36 s to 0.31 s. Canary account, one year: first chunk 2.3 s to 2.0 s, warm 1.6 s to 1.3 s, the rest being round trips to Singapore. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The node now serves `/clob/fills/{account}` and `/clob/solutions` with the
same JSON as the `ob_*` reads, so the walk uses them: one request per fills
page, per grid tick, and per fill-batch lookup, at most 24 in flight. The
JSON-RPC batch helper goes; `pod_getSolverState` stays the one JSON-RPC
read. Nodes that predate the routes answer 404 and get the `ob_*` twins
behind the same calls, to be removed once every network has the routes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
majesticwizardcat
force-pushed
the
giannis/account-value
branch
from
October 5, 2026 12:09
c5e0ca2 to
9ba1abd
Compare
majesticwizardcat
marked this pull request as ready for review
October 5, 2026 12:09
0zzy-o
reviewed
Oct 5, 2026
0zzy-o
left a comment
Contributor
There was a problem hiding this comment.
Looks good, just a couple of things raised by the agent that seems worth checking on.
…the run Two review findings. Tick lookups started alongside the watermark check, so on a lagging indexer the reference tick was answered with the newest indexed row and cached under the current tick; every point but the last was then off by that tick's move, and the lag retry only refetched fills. Now every solutions lookup awaits the watermark check, while fills still start at once and are refetched on lag as before. drainFlows swallowed every error, so a 503 or a timeout on the activity route cached the range without its flows and later account values were wrong without a warning. Only a 404 now means the node has no feed; any other error fails the run and nothing is cached. A cache that was filled from a node without the feed is marked, and the next run that finds the feed drops it and fetches everything with flows. Co-Authored-By: Claude Fable 5.1 <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.
What
Every point of
pnlHistoryandpnlHistoryStreamnow carries the absoluteaccountValue, next topnl, when the node serves the activity feed (ADR 0057 on the node). Builds on the merged activity resource (#310) and PnL history.How
Account value is PnL plus every movement that is not PnL:
The backward walk keeps one more running sum over money events, fetched per window next to the fills and cached with them:
bridge_transferwith a positive amount, native tokencashbridge_transferwith a positive amount, spot tokendeposit, a size eventtransfer, either signcashor a size event by tokenbackstopwithoutorderbook_idcashof−cashcasherrorNegative
bridge_transferrows and backstop legs are skipped because withdrawals and sweep legs already come from the existing feeds. Within a tick, events are undone as withdrawals, cash, fills, deposits, then sweeps.On a node without
/clob/activitythe points omitaccountValueand a warning says so; PnL is unchanged.Also on this branch: two loading fixes
tthat comes back empty proves the market has no rows beforet, so the cache keeps that bound per market and skips every older point. A yearly window on a canary account went from about 4,800 lookups cold and 4,300 again warm to about 600 cold and none warm: 6.7 s to 3.4 s cold, 5.7 s to 1.6 s warm.The frontend's chart branch calls the one-shot
pnlHistoryand waits for every point; it needspnlHistoryStreamto see the chunks arrive.Also on this branch: fills and solutions over REST
The node's
/clob/fills/{account}and/clob/solutionsroutes (backend #129, same JSON as theob_*reads, microsecond_usparams) replaceob_getFillsandob_getSolutionsin the walk.pod_getSolverStateis the one JSON-RPC read left. Nodes that predate the routes answer 404 and get theob_*twins behind the same calls; that fallback goes once every network has the routes.What REST changes in cost: JSON-RPC let the walk batch 200 tick lookups into one request, REST has no batch, so each lookup is its own request, at most 24 in flight. Quiet accounts hardly notice; a yearly window is a few hundred requests. For a market maker, where the funding index of every fill batch is a lookup, the request count is in the thousands per hour of history. Two things on the node would remove that: a
times=oruntil_us=list on/clob/solutionsto answer several lookups in one response, andCache-Control: immutableon past windows instead of today'sno-store, which would let the browser cache do what the SDK cache does across reloads.No deployed network has the routes yet, so the REST path is exercised by the unit tests and the fallback by the live checks until one does.
Verification
Unit tests only so far. The forward engine simulation tracks cash and account value through a cash deposit, a token deposit in the same tick as a fill, and a cash withdrawal, and the fold matches it at every tick; a stub node pushes a deposit, an outgoing transfer, a refused deposit and a cash sweep through the stream. No network serves the activity feed yet, so there is no live comparison against the positions endpoint's account value.
🤖 Generated with Claude Code