Skip to content

feat(orderbook): read how much a market traded in a time window - #181

Merged
MicBun merged 1 commit into
mainfrom
get-market-activity
Oct 14, 2026
Merged

MicBun merged 1 commit into
mainfrom
get-market-activity

Conversation

@MicBun

@MicBun MicBun commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Description

A JavaScript user can now ask how much traded on one order book over a time window. orderbook.getMarketActivity(queryId, fromTs, toTs) calls the node action get_market_activity and returns the filled volume with its direct and mint and burn parts, the unique traders, the fill and share counts, the first and last fill inside the window, and how far back the node's order events reach.

The node action holds the definition of volume, so this SDK reads the same figures as sdk-go, and a change to the definition needs no SDK release. The action and the Go method come from:

What this adds:

  • getMarketActivity checks its arguments before calling. queryId must be a positive integer and both timestamps non-negative integers. A window that ends before it starts throws instead of reading as empty. The method then calls the action and maps its single row. No row means the market does not exist, and the call throws Market not found: <queryId>.
  • A MarketActivity type, exported with the other order book types. The three cents fields stay decimal strings because NUMERIC(78,0) can exceed Number.MAX_SAFE_INTEGER. The client returns the integer columns as strings, and the method maps them to numbers.
  • A Market Activity section in docs/api-reference.md with the field table, an example, and the notes on bridges and coverage, plus a row in the README's quick reference.

The linked issue describes running the Goal's SQL statement as an ad hoc query. It also says that once a node action landed, the method would call it and keep the same signature and return type. The action landed before I started this, so the method calls it from the start.

Where it works

Mainnet has carried get_market_activity since 8 October, and testnet since 13 October.

Related Problem

Part of:

How Has This Been Tested?

The unit tests in src/contracts-api/orderbookAction.test.ts mock the client the way the other order book tests do. They check that:

  • The call goes out once, to get_market_activity, with $query_id, $from_ts, $to_ts and the signer.
  • The row mainnet returned for market 1835 maps to every field, with the integer columns arriving as strings, as the client returns them. Integer columns that arrive as numbers map too.
  • A cents value above Number.MAX_SAFE_INTEGER comes back as its exact string.
  • A row with no fills keeps null fill times and a false coverageComplete.
  • A one-second window goes through, because both ends are inclusive.
  • Zero rows, two rows and a status other than 200 are errors, and an error from the client passes through with its message.
  • Each bad argument is rejected before any call.

I broke the new code in 20 ways, one at a time: each argument check, the window check, the status, missing market and extra row checks, the action name, the parameter order, the signer, and the mapping of each kind of column. Each break failed one or two of the new tests.

tests/integration/marketActivity.test.ts runs when TEST_MAINNET_ENDPOINT and TEST_MAINNET_CHAIN_ID are set. CI sets both for the integration tests, so the suite reads mainnet there, as the market forecast suite does. It finds the newest market with a fill in the last 30 days and checks that volumeCents equals directCents + mintBurnCents, that directFillCount <= fillCount, that the fills fall inside the window, that both ends of the window are inclusive, and that a market that does not exist is an error.

  • Mainnet: all 3 tests passed in about 30 seconds. The suite picked market 1957, with 1000 cents of volume from one mint or burn fill of 10 shares, and complete coverage.
  • Testnet: none of the newest 100 markets had a fill in the last 30 days, so only the missing market test had something to check, and it passed.

I also called the method directly:

  • Mainnet market 1835 over [1788891555, 1791483555]: 1145 cents (545 direct, 600 mint and burn), 2 traders, 6 fills (4 direct), 20 shares, complete coverage. The Go method read the same figures for the same market and window when its pull request was tested.
  • Testnet market 54116, all time: zero volume with coverageComplete false. On 13 October the same action read 18,182,000 cents from 2 fills there. The node has since trimmed those events, and coverageComplete: false marks the zero as truncation rather than inactivity.

pnpm typecheck and pnpm run build are clean, and pnpm test:unit passes 510 tests.

Summary by CodeRabbit

  • New Features
    • Added getMarketActivity to retrieve a market’s trading activity within a time window, including filled volume, trade and share counts, unique traders, and event-time bounds.
    • Activity results indicate whether order-event coverage is complete. Volume is reported in the market’s bridge currency and excludes non-trade events.
  • Documentation
    • Added the market activity method to the API reference and common operations guide.

@MicBun MicBun self-assigned this Oct 14, 2026
@holdex

holdex Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Time Submission Status

Member # Time Running Total Status Last Update
MicBun 4h ✅ Submitted Oct 14, 2026, 11:35 PM

Submit or update total time with:

@holdex pr submit-time 2h

Add time on top of previous submission with:

@holdex pr add-time 1h30m

See available commands to help comply with our Guidelines.

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5befe701-0b6d-4f9a-9cd8-56e496e65c3d
📥 Commits

Reviewing files that changed from the base of the PR and between 841f9e3 and bdcbf81.

📒 Files selected for processing: 7
  • README.md
  • docs/api-reference.md
  • src/contracts-api/orderbookAction.test.ts
  • src/contracts-api/orderbookAction.ts
  • src/internal.ts
  • src/types/orderbook.ts
  • tests/integration/marketActivity.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The SDK adds orderbook.getMarketActivity(queryId, fromTs, toTs) and exports its typed result. The method validates inputs, calls the node action, and maps its response. Unit tests, gated live-network tests, and API documentation cover the method.

Changes: Market activity API

Layer / File(s) Summary
Activity contract and SDK method
src/types/orderbook.ts, src/contracts-api/orderbookAction.ts, src/internal.ts
Adds the MarketActivity result type and getMarketActivity method. The method validates the query ID and time bounds, calls get_market_activity, handles response errors, and maps the result while preserving volume values as strings.
Unit test coverage
src/contracts-api/orderbookAction.test.ts
Tests action parameters, result mapping, exact volume strings, inclusive bounds, invalid inputs, and error cases.
Live checks and API documentation
tests/integration/marketActivity.test.ts, docs/api-reference.md, README.md
Adds gated live-network checks for returned activity and time-window behavior. Documents the method, returned fields, bridge-denominated volume, event coverage, migration requirement, and query cost.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant OrderbookAction
  participant NodeClient
  Caller->>OrderbookAction: getMarketActivity(queryId, fromTs, toTs)
  OrderbookAction->>NodeClient: get_market_activity with query and time bounds
  NodeClient-->>OrderbookAction: activity result rows
  OrderbookAction-->>Caller: parsed MarketActivity
Loading
Merge Risk: ⚪ Minimal · up to bdcbf

No actionable issue was established in the new market-activity API. It is ready for normal merge checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Linked Issues check Warning Issue #180 requires the SDK to execute its specified read-only SQL through selectQuery, with the SQL kept verbatim in one shared module and without relying on a node action. This PR implements `getM… Implement getMarketActivity with issue #180's specified SQL through selectQuery. Keep that statement verbatim in one shared module. Update the related unit tests and documentation to cover the required ad hoc-query and sequential-scan b…
Passed Passed checks: 4 passed
Check name Status Explanation
Out of Scope Changes check Passed The changed files implement the getMarketActivity API from issue #180. The method, exported types, mapping tests, gated integration tests, API documentation, and README entry support that feature. T…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 5 files. (2 skipped: 2 …
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding orderbook market-activity reads for a specified time window.
Full details: Linked Issues check

Explanation

Issue #180 requires the SDK to execute its specified read-only SQL through selectQuery, with the SQL kept verbatim in one shared module and without relying on a node action. This PR implements getMarketActivity by calling get_market_activity through kwilClient.call, as stated in the PR description and shown by the changed OrderbookAction implementation. The PR satisfies the requested API, typed mapping, validation, tests, and documentation areas, but it does not satisfy the required query implementation.

Resolution

Implement getMarketActivity with issue #180's specified SQL through selectQuery. Keep that statement verbatim in one shared module. Update the related unit tests and documentation to cover the required ad hoc-query and sequential-scan behavior.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MicBun MicBun changed the title feat: read a market's volume and traders over a time window feat(orderbook): read how much a market traded in a time window Oct 14, 2026
@MicBun

MicBun commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

@holdex pr submit-time 4h

@MicBun
MicBun merged commit 200d3a1 into main Oct 14, 2026
7 checks passed
@MicBun
MicBun deleted the get-market-activity branch October 14, 2026 23:35
MicBun added a commit to trufnetwork/sdk-py that referenced this pull request Oct 15, 2026
## Description

A Python user can now ask how much traded on one order book over a time
window. `client.get_market_activity(query_id, from_ts, to_ts)` returns a
`MarketActivity` dict with the filled volume and its direct and mint and
burn parts, the unique traders, the fill and share counts, the first and
last fill inside the window, and how far back the node's order events
reach.

The call goes through sdk-go's `OrderBook.GetMarketActivity`, which
reads the node action `get_market_activity`. The node action holds the
definition of volume, so Python reads the same figures as Go and
JavaScript, and a change to the definition needs no SDK release. The
action and the other two SDKs' methods come from:

- trufnetwork/node#1444
- trufnetwork/sdk-go#206
- trufnetwork/sdk-js#181

What this adds:

- `go.mod` pins sdk-go v0.7.8, the release that carries
`GetMarketActivity`. Only the sdk-go lines change, and kwil-db stays
where it was.
- `GetMarketActivity` in `bindings/bindings.go` calls sdk-go and returns
one JSON object whose keys are the action's column names. The three
cents fields stay strings because `NUMERIC(78,0)` exceeds what a JSON
number holds exactly, and the fill times are `null` when nothing traded.
sdk-go checks the arguments and reports a missing market, and the
binding passes both errors on, so Python raises `RuntimeError` with
sdk-go's message in it.
- `TNClient.get_market_activity` and the `MarketActivity` TypedDict in
`client.py`, next to `get_full_market_depth`.
- A Market Activity section in `docs/api-reference.md` with the key
table, an example, and the notes on bridges and coverage.

The linked issue describes ad hoc SQL running in sdk-go. sdk-go calls
the node action instead, as its pull request above explains. For Python
nothing changes, since the binding, the dict and its keys are the ones
the issue specifies.

The issue also asks for two notes in the docs, both written for the ad
hoc SQL. Neither applies to the action, so I left both out, as the Go
and JavaScript docs do:

- The note "Requires a node that allows ad hoc queries (not Kwil private
mode)". A node in private mode refuses ad hoc SQL but still serves
action calls that carry the caller's signature. In private mode the
client adds that signature on its own, with the key every `TNClient` is
created with.
- The note on the sequential scan. The node now indexes order events on
`(query_id, block_timestamp)`, the fix the issue points to.

### Where it works

Mainnet has carried `get_market_activity` since 8 October, and testnet
since 13 October.

## Related Problem

- resolves: #139

Part of:

- trufnetwork/node#1429

## How Has This Been Tested?

- `tests/test_market_activity.py` monkeypatches the binding, as
`tests/test_full_market_depth.py` does, so it needs no node. It checks
that the three arguments reach the binding in order, that the dict
decodes to every field, that cents above 2**63 stay exact strings, that
`null` fill times and a false coverage survive, that an error from the
binding propagates, and that an empty answer raises instead of reading
as no activity.
- `bindings/market_activity_test.go` checks the JSON the binding builds:
the action's column names, cents above `math.MaxInt64` as strings, and
`null` fill times with block 0 and false coverage when nothing traded.
- The existence checks in `tests/test_order_book.py` include the new
method and type.

I broke the change in 15 ways, one at a time: 6 in the Python wrapper
(argument order, the market id, an empty answer read as no activity, a
swallowed error, cents turned into numbers, fill times defaulted to 0),
8 in the JSON the binding builds (a key name, cents as a JSON number,
each fill time, the coverage fields, a count read from the wrong field),
and the window arguments swapped inside the binding. A test failed every
time. Only a live run can catch the last one, and the live suite did.

`tests/test_market_activity_live.py` runs when `TN_LIVE_NODE_URL` is
set, like the other live suites, so CI skips it. It finds the newest
market with a fill in the last 30 days and checks that `volume_cents`
equals `direct_cents + mint_burn_cents`, that `direct_fill_count <=
fill_count`, that the fills fall inside the window, that both ends of
the window are inclusive, and that a market that does not exist raises.

- Mainnet: all 3 tests passed in 35 seconds, through Python, the
binding, sdk-go and the node. The suite picked market 1957, with 1000
cents of volume from one mint or burn fill of 10 shares, and complete
coverage. The JavaScript suite picked the same market and read the same
figures.
- Testnet: none of the newest 100 markets had a fill in the last 30
days, so the two checks that need fills skipped, and the missing market
test passed.

Called directly, mainnet market 1835 over [1788891555, 1791483555]
returns 1145 cents (545 direct, 600 mint and burn), 2 traders, 6 fills
(4 direct) and 20 shares with complete coverage, the figures the Go and
JavaScript methods read. An inverted window and `query_id` 0 raise
`RuntimeError` with sdk-go's messages.

`make gopy_build`, `go build`, `go vet` and `gofmt` are clean. The 157
tests in the 11 test files that need no node pass. pyright reports the
same 2 errors on `client.py` as on main, and none new. The tests that
need a Docker node run in CI.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added market activity reporting for an inclusive time range, including
trading volume, trader and fill counts, traded shares, and first and
last fill times.
* Results include per-bridge volume details and coverage information to
help interpret activity, including when no trades are reported. Missing
markets and invalid time ranges return errors.
* **Documentation**
* Added API reference details and an example for retrieving market
activity, including volume units and guidance for interpreting coverage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

Problem: getMarketActivity in sdk-js

1 participant