Repository navigation
feat(orderbook): read how much a market traded in a time window - #181
Conversation
Time Submission Status
Submit or update total time with: Add time on top of previous submission with: See available commands to help comply with our Guidelines. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
|
@holdex pr submit-time 4h |
## 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 -->
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 actionget_market_activityand 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:
getMarketActivitychecks its arguments before calling.queryIdmust 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 throwsMarket not found: <queryId>.MarketActivitytype, exported with the other order book types. The three cents fields stay decimal strings becauseNUMERIC(78,0)can exceedNumber.MAX_SAFE_INTEGER. The client returns the integer columns as strings, and the method maps them to numbers.docs/api-reference.mdwith 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_activitysince 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.tsmock the client the way the other order book tests do. They check that:get_market_activity, with$query_id,$from_ts,$to_tsand the signer.Number.MAX_SAFE_INTEGERcomes back as its exact string.nullfill times and a falsecoverageComplete.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.tsruns whenTEST_MAINNET_ENDPOINTandTEST_MAINNET_CHAIN_IDare 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 thatvolumeCentsequalsdirectCents + mintBurnCents, thatdirectFillCount <= 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.I also called the method directly:
coverageCompletefalse. On 13 October the same action read 18,182,000 cents from 2 fills there. The node has since trimmed those events, andcoverageComplete: falsemarks the zero as truncation rather than inactivity.pnpm typecheckandpnpm run buildare clean, andpnpm test:unitpasses 510 tests.Summary by CodeRabbit
getMarketActivityto retrieve a market’s trading activity within a time window, including filled volume, trade and share counts, unique traders, and event-time bounds.