The MCP

Your agent asks the archive. The answer comes back computed.

Connect an agent on your laptop and it can work across the whole archive with nothing to provision and nothing to download. The aggregation happens next to the data, so what reaches your Python is a bar series or a statistic, not a tape to clean first. This page shows the code that takes, measured against the parquet route, including where we lost.

What it saves you

The laptop did not download, clean or deduplicate anything.

Every row below is a real run of the same agent on the same question, once through this MCP and once over the parquet on its own disk. Read it for what the laptop did not have to do: hold the data, write the wrangling code, wait for it. Then read the last columns, because the research rows are where we lost.

What your agent writes One call. The answer comes back computed.
{
  "dataset": "edgex/trades",
  "instrument": "ETHUSDC",
  "start": "2026-09-27T00:00:00Z",
  "end": "2026-09-28T00:00:00Z",
  "sql": "WITH r AS (SELECT ln(close / lag(close) OVER (ORDER BY ts)) AS lr FROM t) SELECT stddev_samp(lr) * sqrt(525600) AS rv FROM r"
}

Returned rv = 0.2661 from 13 shards read server-side. 14,589 tokens, 10 s, nothing downloaded. Transcript

The same number from raw parquet Command 5 of 8, after the files were on disk.
cd /tmp/peerfeed-a21/archive/edgex; P=/tmp/peerfeed-a21/venv/bin/python; $P -c "
import duckdb
print(duckdb.sql(\"select distinct contractName, contractId from 'trades/parquet/2026/09/27/*.parquet' where contractName ilike '%ETH%'\"))
print(duckdb.sql(\"select filename, min(time), max(time), count(*), count(distinct ticketId) from read_parquet('trades/parquet/2026/09/2[678]/*.parquet', filename=true) where contractName='ETHUSD' group by 1 order by 1\").df().to_string())
"

48 lines of shell and Python across 8 commands: the ones before this found the files, read the schema, picked the symbol and checked for duplicate trades. 73,532 tokens, 37 s, with 234.2 MB of parquet already downloaded. Same answer. Transcript

Every question, both routes

QuestionData the parquet route downloaded first Code the agent wroteWall timeTokens Answer
MCPParquetMCPParquet MCPParquetMCPParquet
BTC VWAP on Hyperliquid, the call already known
One call, known
560.6 MB 0 lines
1 call
27 lines
5 calls
10 s32 s 14k39k correctcorrect
BTC notional on Nado, the call already known
One call, known
8.5 MB 0 lines
1 call
37 lines
6 calls
12 s37 s 14k65k correctcorrect
ETH realised vol on edgeX, the call already known
One call, known
234.2 MB 1 line
1 call
48 lines
8 calls
10 s37 s 15k74k correctcorrect
BTC VWAP on Hyperliquid for one day
One number, found
560.6 MB 1 line
4 calls
27 lines
5 calls
17 s32 s 49k39k correctcorrect
BTC traded notional on Nado for one day
One number, found
8.5 MB 5 lines
8 calls
37 lines
6 calls
34 s37 s 193k65k correctcorrect
ETH 1-minute realised vol on edgeX for one day
One number, found
234.2 MB 20 lines
6 calls
48 lines
8 calls
33 s37 s 82k74k correctcorrect
Funding carry across five perp venues
Research
2.0 GB 1 line
13 calls
169 lines
15 calls
53 s134 s 165k297k correctcorrect
Cross-venue lead-lag and price dislocation, BTC, one day
Research
859.7 MB 54 lines
22 calls
113 lines
9 calls
129 s88 s 614k134k partly wrongcorrect
Seven-venue BTC volume share and realised volatility, one week
Research
2.9 GB 78 lines
31 calls
140 lines
14 calls
185 s156 s 963k328k partly wrongcorrect

Code is counted from each agent's own stream: non-blank lines of shell and Python it sent to Bash, plus lines of SQL it sent to the MCP; the other arguments of a call are not counted. Wall time on the parquet side starts with the files already on disk; the download is not in it.

Two servers: one answers, one delivers files.

The first is for questions whose answer is smaller than their data, and is free up to a quota. The second is for when you need every row on your own machine, and is the paid product.

Free up to a quota

market-data-mcp

Answers questions. DuckDB reads the parquet shards next to the archive, computes the aggregate and returns it, so your agent has no rows to hold, store or clean.

Returns
Bars, SQL results over 1-minute bars across venues, funding normalised per venue, metadata, coverage
You receive
Only the answer
Metered by
Rows scanned, per user, per day
Serves today
Trades from the sellable DEX venues, as bars (get_ohlcv) or as free-form SQL over 1-minute bars (run_query, several venues joined in one call), and funding normalised across venues' intervals (get_funding, in the build measured here; live on the public server from its next deploy). Order books and liquidations are next; until they are, they are sold as files.
Paid: the subscription route

PeerFeed delivery MCP

Hands over files. Browse the catalogue, then get_slice returns a presigned download link for one venue, data type and day. This one does put the data on your machine: that is what it is for.

Returns
Catalogue, quality, presigned R2 links valid ~24 h
You receive
The raw parquet, by design
Metered by
GB against your plan
Tools
list_venues describe_venue quality get_slice
Measured 2026-09-29-research-floor

A real research task, run end to end both ways.

The same agent, the same model and the same code sandbox ran each question twice: once through this MCP in the public mode a third party gets, once over the parquet on its own disk. Every turn is counted, the exploration and the mistakes included, and each answer is graded against a reference computed independently. The parquet agent won more of this than we did. Here is all of it, with every transcript.

The parquet side started with the data already on disk. Each of its runs presupposes the files were already downloaded — 8.5 MB to 2.9 GB per task, 4.9 GB for all of them. Obtaining that is our paid product. It is money and time rather than tokens, so it is not in any token count below, and no ratio on this page folds it in.

Three research tasks, each run twice

Research taskThrough the MCPAgent over the parquet
TokensWallAnswerTokensWallAnswer
Funding carry across five perp venues 165,475
6 turns, 13 tool calls, $0.45
53 scorrect 296,714
16 turns, 15 tool calls, $0.57
134 scorrect
Cross-venue lead-lag and price dislocation, BTC, one day 614,489
15 turns, 22 tool calls, $0.80
129 spartly wrong 134,223
10 turns, 9 tool calls, $0.39
88 scorrect
Seven-venue BTC volume share and realised volatility, one week 963,396
16 turns, 31 tool calls, $1.50
185 spartly wrong 328,034
15 turns, 14 tool calls, $0.65
156 scorrect

Tokens are the API's own usage counts, summed over every turn: each turn re-reads its context, so the whole conversation is paid for again at each step (cache reads are billed at a tenth, which the dollar figure reflects). Model claude-opus-5-5 on both sides, same system prompt apart from where the data is, same Bash and Python.

Since the last run, both directions

TaskRouteTokens, previous run Tokens, nowNow / beforeAnswer beforeAnswer now
T1 · Funding carry across five perp venues MCP 126,548 165,475 1.31× correctcorrectanswer unchanged
T1 · Funding carry across five perp venues Parquet 318,603 296,714 0.93× correctcorrectanswer unchanged
T2 · Cross-venue lead-lag and price dislocation, BTC, one day MCP 308,841 614,489 1.99× partly wrongpartly wronganswer unchanged
T2 · Cross-venue lead-lag and price dislocation, BTC, one day Parquet 193,471 134,223 0.69× correctcorrectanswer unchanged
T3 · Seven-venue BTC volume share and realised volatility, one week MCP 707,174 963,396 1.36× partly wrongpartly wronganswer unchanged
T3 · Seven-venue BTC volume share and realised volatility, one week Parquet 337,590 328,034 0.97× correctcorrectanswer unchanged
S1 · BTC VWAP on Hyperliquid for one day MCP 167,019 49,031 0.29× correctcorrectanswer unchanged
S1 · BTC VWAP on Hyperliquid for one day Parquet 53,504 39,076 0.73× correctcorrectanswer unchanged
S2 · BTC traded notional on Nado for one day MCP 279,514 193,395 0.69× correctcorrectanswer unchanged
S2 · BTC traded notional on Nado for one day Parquet 62,072 65,441 1.05× correctcorrectanswer unchanged
S3 · ETH 1-minute realised vol on edgeX for one day MCP 55,384 81,782 1.48× correctcorrectanswer unchanged
S3 · ETH 1-minute realised vol on edgeX for one day Parquet 70,768 73,532 1.04× correctcorrectanswer unchanged
K1 · BTC VWAP on Hyperliquid, the call already known MCP 14,107 14,226 1.01× correctcorrectanswer unchanged
K2 · BTC notional on Nado, the call already known MCP 21,618 14,447 0.67× correctcorrectanswer unchanged
K3 · ETH realised vol on edgeX, the call already known MCP 14,359 14,589 1.02× correctcorrectanswer unchanged

Every task both measurements ran, on both routes. A win and a regression come out of the same loop, so neither can be left off. Read the parquet rows first: its data and harness did not change, so how far they moved is the run-to-run noise of one agent, and a smaller move on the MCP side is not evidence of anything.

Predicted before the run, reported after it

The server's authors registered what this run would show before it ran, so the result could not be read charitably afterwards. Each is reported met or missed.

TaskRegistered before the runOutcomeEvidence
T1T1 stays correct and cheap. met Correct on both routes. 165k tokens through the MCP against 297k over the parquet: still the cheaper route. Against A20's 127k it is 1.31x, inside the 1.5x noise band, so the win holds and did not measurably change.
T3T3 improves, because it is a sum and totals are addable. missed The sum improved exactly as predicted: all seven notionals are now right, because the agent read withheld_by_floor and added the withheld volume back (dYdX $58.6M against $58.5M; A20's agent was 4% short). The grade did not improve: it is still partly wrong, because the floor also biases realised vol, which is not a sum. Pacifica came back 43.8% against 36.4% and dYdX 88.5% against 68.0% on the same definition. The prediction's mechanism worked; the task outcome it predicted did not happen.
T2T2 stays partly wrong, with the best available outcome being an honest refusal. met, for a reason other than the one registered Still partly wrong, and the one cell the floor genuinely blocks (dYdX lead-lag, 34 usable pairs) was correctly declined. But the extremum the prediction was about is not a floor limit: under fill order the floor removes none of the four maxima. What is left wrong (Pacifica's value, ApeX's minute) comes from how the server picks a bar's close among fills sharing a millisecond, which is fixable, not policy.

Where we win: funding carry, and sums

The carry task cost 165k tokens through 4 get_funding calls against 297k for the parquet agent, which had to find five venues' funding feeds, work out each interval and carry a change-only feed forward itself. Both were graded correct. The margin is well outside one run's noise, and the change since the last run is inside it: the win holds.

The floor disclosure did its job on sums. The venue-share agent read withheld_by_floor, added the withheld volume back, and got all seven venues' notional right, where the last run's agent was 4% short on dYdX and Paradex. And finding one number got cheaper: Hyperliquid's VWAP cost 49k tokens through the MCP, down from 167k, because describing one instrument now answers in about 9,000 to 12,000 characters instead of 47,000.

Where we lose: tokens on research

On the lead-lag task the parquet agent spent 134k tokens and the MCP agent 614k; on venue share 328k against 963k, 2.9× the parquet agent. Lead-lag through the MCP cost 2.0× what it did last run, more than the noise, and not because responses grew: the agent made 15 model calls instead of 7, and every call re-reads the whole conversation. The cause is structural: every MCP result lands in the agent's context, while the parquet agent's DuckDB reads gigabytes and prints a summary. When the agent had to find the call itself, the MCP was the cheaper route on none of three single numbers.

Where we lose: answers

The parquet agent got all three research answers right; through the MCP, one of three. Public mode's three-trade floor still costs answers, but not only where we said it did, and in one place not at all.

Volatility is biased by the floor, and nothing says so. On venue share the agent measured 1-minute realised vol over back-to-back minutes, from bars the floor had already thinned. A minute with three or more trades is a busier, more volatile minute, so what survives is biased upward: Pacifica 43.8% against 36.4% on the same definition over every minute, dYdX 88.5% against 68.0%. Removing the floor's minutes from the parquet reproduces both of the agent's figures to the pair. A withheld total repairs a sum; it cannot repair a variance.

Our own extremum case was wrong. Last run we said Pacifica's largest dislocation sat in a 2-trade minute the floor withheld. That is true only if fills sharing a millisecond are ordered by trade id, and Hyperliquid's id is not a sequence. In fill order — the archive's row order, in which one order at 13:31 visibly walks up the book from 85,028 to 85,040 inside one millisecond — the largest dislocation is −11.05 bp at 13:31, a minute the floor keeps. The floor removes none of the four venues' maxima that day. The parquet agent chose fill order on its own and got every one. What the MCP still gets wrong here (Pacifica's value, ApeX's minute by 0.08 bp) comes from which fill the server takes as a bar's close within one millisecond: a bug, handed to the server's authors, not a policy limit.

Where the floor does block an answer, the agent now says so. dYdX traded in 409 minutes that day and the floor withheld 277, leaving 34 usable return pairs. The agent declined to name a leader and showed the withheld minutes. We grade that correctly declined: worth more than a plausible wrong answer.

For anything counted rather than weighed, 4% understates it badly. Measured by the server's authors on the deployed server, dydx/trades over two hours: BTC-USD 47.5% of trades withheld against 5.4% of volume, ETH-USD 21.1% of trades against 9.7% of volume. Thin minutes are numerous and tiny, so notional barely moves while a count-based statistic — trade frequency, average trade size, arrival rates — can be wrong by nearly half. Per instrument, for the tasks above:

InstrumentWindowMinutes withheld Trades withheldVolume withheld
hyperliquid/trades:BTC2026-09-21..28 0.0% 0 (0.0%) 0.00%
edgex/trades:BTCUSDC2026-09-21..28 0.0% 2 (0.0%) 0.00%
apex/trades:BTCUSDT2026-09-21..28 0.0% 0 (0.0%) 0.00%
pacifica/trades:BTC2026-09-21..28 29.7% 5,240 (6.1%) 1.00%
dydx/trades:BTC-USD2026-09-21..28 55.2% 3,262 (17.6%) 3.98%
paradex/trades:BTC-USD-PERP2026-09-21..28 13.7% 274 (1.9%) 3.93%
nado/trades:22026-09-21..28 4.8% 695 (0.3%) 0.13%
hyperliquid/trades:BTC2026-09-27 0.0% 0 (0.0%) 0.00%
edgex/trades:BTCUSDC2026-09-27 0.0% 0 (0.0%) 0.00%
apex/trades:BTCUSDT2026-09-27 0.0% 0 (0.0%) 0.00%
pacifica/trades:BTC2026-09-27 43.2% 1,078 (13.0%) 1.87%
dydx/trades:BTC-USD2026-09-27 67.7% 362 (20.3%) 2.90%
nado/trades:22026-09-27 9.3% 187 (0.8%) 0.47%

Read from withheld_by_floor on the deployed server, public mode, one call per instrument (the responses). Minutes are the share of an instrument's trading minutes; volume is in that instrument's own base unit, so no row is ever added to another.

A bar labelled one minute is often not one minute from the next, and that dwarfs everything on this page. On a thin venue consecutive bars in a returned series are not equally spaced: measured on dYdX BTC-USD over six hours, 6 of 7 consecutive pairs span more than a minute, median gap 16 minutes, largest 106. Computing “1-minute” realised volatility from that treats 16-minute returns as 1-minute returns, and volatility scales with the square root of the interval — roughly 4× on the typical pair. No control of ours causes it: an empty minute makes the same gap in private mode where there is no floor at all, and the floor only makes gaps more frequent. run_query returns a bar_spacing block — median gap, largest gap, and how many gaps exceed a minute — so the interval can be computed instead of assumed. We quote no correction factor, and the reason got stronger rather than weaker. Two measurements of the floor's own effect on volatility disagreed and we published that we could not reconcile them. It has since been reconciled, and the cause was the tree rather than the window: one side had measured on archived shards that carried duplicate rows, and dYdX's were 71.9% duplicated. Re-measured on deduplicated shards, the floor's effect on dYdX is 0.73× — deflation, the opposite sign — while Hyperliquid sits at 1.16× and Pacifica at 1.03×, both unchanged. So the floor does not bias second moments in a consistent direction across venues; it inverts. There is no single factor to quote, which is a better reason not to quote one than “we disagree” was. Compute the interval from bar_spacing and the shortfall from withheld_by_floor instead.

run_query also raises a warning when a query asks for an extreme and the floor removed anything. It fires too often to be read: on 14 of the 17 query results the venue-share agent received, and on a plain daily sum, because any ORDER BY trips it, including the one inside a lag(). We do not publish withheld bars' own high and low: where one bar was withheld, that bar is the trade, and printing its price is the raw row the floor exists to withhold. So for an extreme the honest ceiling is a warning, not a repair — some questions cannot be answered under this floor, and that is a policy we chose rather than a bug we have not fixed.

Where we win: one call you already know

When the agent knows the call, one number costs 14k–15k tokens and about ten seconds, against 39k–74k and 32–37 seconds for the parquet agent, and there is nothing to download. Finding the call through the MCP the first time cost more than working the parquet for every one of the three numbers, because the agent reads the dataset's quality and caveats before it trusts a figure, so the win is for questions your agent asks again, not the first time.

One SQL call against the whole route

One numberOne MCP call, known in advance MCP, agent finds the callAgent over the parquet Parquet it had to hold
TokensWallAnswerTokensWallAnswer TokensWallAnswer
BTC VWAP on Hyperliquid for one day 14,22610 scorrect 49,03117 scorrect 39,07632 scorrect 560.6 MB
BTC traded notional on Nado for one day 14,44712 scorrect 193,39534 scorrect 65,44137 scorrect 8.5 MB
ETH 1-minute realised vol on edgeX for one day 14,58910 scorrect 81,78233 scorrect 73,53237 scorrect 234.2 MB

The first column is a real agent run too: told the exact call, it made it and reported the result. The third is an agent that had to find the files, the columns, the symbol, the epoch and the dedup key before it could compute anything.

T1 · Funding carry across five perp venues

Question, word for word: Funding carry. For the BTC and ETH perpetuals on Hyperliquid, edgeX, Extended, Nado and Pacifica, over 2026-09-21 00:00 to 2026-09-28 00:00 UTC: give the mean funding rate each venue charged longs, annualised as a simple APR in percent, per venue and coin. Then name the single best delta-neutral carry trade (long the perp on one venue, short the same coin on another) and its annualised funding spread. Say how you handled each venue's funding interval.

Reference answer: APR, BTC / ETH: Hyperliquid 11.1 / 12.4, edgeX 6.7 / 10.95 time-weighted (7.1 / 10.95 sampled at its 42 settlements; rate is per 4 h), Extended 7.6 / 11.3, Pacifica 10.0 / 10.95. Nado, on the serving host's copy, to one significant figure: BTC 2, ETH 8 (a 24 h rate carried forward over a change-only feed). Best carry: long BTC on Nado, short BTC on Hyperliquid, about +10% APR. Unchanged from A20: funding is not touched by the trade floor.

Parquet the agent over the parquet had on disk for this: 2.0 GB.

Through the MCP: correct — Four get_funding calls plus checks. Hyperliquid 11.12/12.40, edgeX 6.65/10.95, Extended 7.59/11.26, Pacifica 10.02/10.95 % APR for BTC/ETH, and Nado BTC about 2%, ETH about 8% on the serving host's copy (the agent quoted the Nado range across copies and said why). Best trade long Nado, short Hyperliquid BTC, about +10% APR (it said 9.6%). Stated each interval. 165k tokens against 127k in A20 (1.31x, inside the noise) and 297k for the parquet agent: the funding win holds.
165,475 tokens processed over 6 turns and 13 tool calls (4,082 generated), 53 s, $0.45 at list price. Transcript · raw stream

Agent over the parquet: correct — Time-weighted every rate, carried Nado's change-only feed forward, converted edgeX from 4 h and Nado from 24 h. Same figures as the MCP agent; Nado about 2% BTC on the serving host's copy. 297k tokens against 319k in A20.
296,714 tokens processed over 16 turns and 15 tool calls (12,949 generated), 134 s, $0.57 at list price. Transcript · raw stream

T2 · Cross-venue lead-lag and price dislocation, BTC, one day

Question, word for word: Lead-lag and dislocation. For the BTC perpetual on Hyperliquid, edgeX, ApeX, Pacifica and dYdX on 2026-09-27 (UTC day), using 1-minute closing prices: (1) for each of the other four venues, the cross-correlation of its 1-minute log returns with Hyperliquid's at lags -3..+3 minutes, and which venue leads; (2) for each of the four, the largest absolute 1-minute close dislocation versus Hyperliquid in basis points and the minute it happened. Say how you handled minutes where a venue did not trade.

Reference answer: CORRECTED this run (fill order within a millisecond; see setup.tie_order). Correlation peaks at lag 0 for edgeX (0.96) and ApeX (0.92): no leader at 1 minute. Hyperliquid leads Pacifica (lag +1 about 0.30) and dYdX (lag +1, weaker, on about 197 returns). Largest dislocations: edgeX -5.51 bp 06:27, ApeX -5.68 bp 08:44, Pacifica -11.05 bp 13:31, dYdX +66.36 bp 14:29 (one order sweeping the book inside one millisecond). The floor withholds none of these four minutes.

Parquet the agent over the parquet had on disk for this: 859.7 MB.

Through the MCP: partly wrong — Lead-lag right for edgeX, ApeX and Pacifica, and dYdX's lead correctly declined: the floor withholds 277 of its 409 trading minutes and the agent said 34 pairs cannot decide it, naming the withheld minutes. That is the fourth grade, earned on the one cell the floor genuinely blocks. Dislocations right for edgeX and dYdX (+66.36 bp at 14:29). Pacifica is the right minute with the wrong value (-10.70 against -11.05 bp) and ApeX the wrong minute by 0.08 bp: both come from how the server picks a close among fills sharing a millisecond, not from the floor, which withholds none of the four maxima under fill order. 614k tokens against 309k in A20: dearer by more than the noise, from 15 model calls against 7, each re-reading the conversation (the tool output it received was smaller, about 113k characters against 128k); 4.6x the parquet agent.
614,489 tokens processed over 15 turns and 22 tool calls (10,803 generated), 129 s, $0.80 at list price. Transcript · raw stream

  • lead-lag edgeX: correct
  • lead-lag ApeX: correct
  • lead-lag Pacifica: correct
  • lead-lag dYdX: correctly declined — Said it cannot be determined from 34 usable return pairs, and tabled the 277 dYdX minutes the floor withheld. This is the one T2 cell the floor genuinely blocks.
  • dislocation edgeX: correct
  • dislocation ApeX: partly wrong — -5.60 bp at 07:12 against -5.68 bp at 08:44. Hyperliquid's last millisecond of 08:44 holds 11 fills walking 84,706 to 84,716 in fill order, and the server's close is one of the middle ones. Not the floor.
  • dislocation Pacifica: partly wrong — Right minute (13:31), value -10.70 bp against -11.05 bp: the server's Hyperliquid close for 13:31 is a middle fill of a 29-fill sweep. The agent warned this maximum might sit in a withheld minute; that caution was reasonable but it was not the cause.
  • dislocation dYdX: correct

Agent over the parquet: correct — Deduplicated on trade id and, on its own, kept same-millisecond fills in file order (fill order) rather than id order: edgeX -5.51 bp 06:27, ApeX -5.68 bp 08:44, Pacifica -11.05 bp 13:31, dYdX +66.36 bp 14:29, matching the corrected reference exactly. Hyperliquid leads Pacifica and dYdX (the latter on 197 returns, weaker). 134k tokens against 193k in A20 (0.69x, inside the noise).
134,223 tokens processed over 10 turns and 9 tool calls (8,620 generated), 88 s, $0.39 at list price. Transcript · raw stream

T3 · Seven-venue BTC volume share and realised volatility, one week

Question, word for word: Venue share and realised vol. For BTC perpetuals on Hyperliquid, edgeX, ApeX, Pacifica, dYdX, Paradex and Nado over 2026-09-21 00:00 to 2026-09-28 00:00 UTC: each venue's total traded notional in USD and its share of the seven-venue total; and each venue's realised volatility of 1-minute log returns, annualised. Is realised vol consistent across venues? If not, explain the outliers.

Reference answer: Notional: Hyperliquid 20.87B (73.8%), edgeX 2.99B, ApeX 2.11B, Pacifica 1.38B, Nado about 0.8B (one significant figure, the serving host's copy), dYdX 58.5M, Paradex 10.9M (only 28 h of data in the window). Realised vol about 36-37% on the liquid venues; dYdX and Paradex higher from thin trading. Added this run: on back-to-back minutes only (the MCP agent's definition) the full data gives Pacifica 36.4% and dYdX 68.0%; dropping minutes of fewer than 3 trades gives 43.8% and 88.5% (reference/t3_vol_floor.json).

Parquet the agent over the parquet had on disk for this: 2.9 GB.

Through the MCP: partly wrong — The sum is fixed: every notional and share is right, dYdX $58.6M against $58.5M and Paradex $10.9M, Pacifica 1.38B, Nado about 0.8B on the serving host's copy, because the agent read withheld_by_floor and added the withheld volume back. The vol half is now what the floor costs: Pacifica 43.8% against 36.4% and dYdX 88.5% against 68.0% on the agent's own definition (back-to-back minutes), because a minute with 3 or more trades is a busier, more volatile minute; the parquet reproduces both figures exactly once the floor's minutes are removed. A withheld total cannot repair a second moment, and nothing said so in terms the agent acted on: extremum_warning fired on 14 of the 17 query results it received, because it triggers on any ORDER BY. 963k tokens against 707k in A20 (1.36x, inside the noise); 2.9x the parquet agent.
963,396 tokens processed over 16 turns and 31 tool calls (18,082 generated), 185 s, $1.50 at list price. Transcript · raw stream

  • notional and share, all seven venues: correct — Read withheld_by_floor and added the withheld volume back at the week's average price. A20's agent was 4% short on dYdX and Paradex.
  • realised vol Hyperliquid, edgeX, ApeX: correct
  • realised vol Pacifica, dYdX: partly wrong — 43.8% and 88.5% against 36.4% and 68.0% on the agent's own definition over all minutes. Removing the floor's minutes from the parquet reproduces the agent's figures to the pair (5,217 and 774 pairs). Attributed to thin trading, not to the floor.
  • consistency and outliers: correct — dYdX and Paradex named as the outliers, from thin trading; it also called Pacifica high at 1 minute, which the full data does not show.

Agent over the parquet: correct — Deduplicated across file boundaries, excluded Pacifica settlement prints and Paradex unwind transfers, flagged Paradex's 28 hours. Notional matches the reference, Nado about 0.8B on the serving host's copy; vol with prices carried forward, Pacifica 35.9%, dYdX 39.9%. 328k tokens against 338k in A20.
328,034 tokens processed over 15 turns and 14 tool calls (14,510 generated), 156 s, $0.65 at list price. Transcript · raw stream

S1 · BTC VWAP on Hyperliquid for one day

Question, word for word: What was the volume-weighted average price of the BTC perpetual on Hyperliquid on 2026-09-27 (UTC day)? One number.

Reference answer: 84,600.71 (175,800 trades)

Parquet the agent over the parquet had on disk for this: 560.6 MB.

Through the MCP: correct — 84,600.71 from the same 175,800 trades. 49k tokens against 167k in A20 (0.29x, a real move): describe_dataset for one instrument now answers in about 9,000 to 12,000 characters, against 47,004 last time. Still dearer than the parquet agent's 39k.
49,031 tokens processed over 3 turns and 4 tool calls (1,237 generated), 17 s, $0.17 at list price. Transcript · raw stream

Agent over the parquet: correct — 84,600.71 from 175,800 trades.
39,076 tokens processed over 6 turns and 5 tool calls (2,517 generated), 32 s, $0.14 at list price. Transcript · raw stream

S2 · BTC traded notional on Nado for one day

Question, word for word: What was the total traded notional, in USD, of the BTC perpetual on Nado on 2026-09-27 (UTC day)? One number.

Reference answer: About $50M to one significant figure, on the serving host's copy (product 2; product 1 is spot). Both routes and the reference agree on the exact figure, which is in results and transcripts.

Parquet the agent over the parquet had on disk for this: 8.5 MB.

Through the MCP: correct — About $50M on the serving host's copy, the same figure to the dollar as the reference. It used hourly bars (nothing withheld) and cross-checked the minute bars, whose shortfall it matched to withheld_by_floor exactly: 187 trades, about 3 BTC. 193k tokens against 280k in A20 (0.69x, inside the noise); the parquet agent spent 65k.
193,395 tokens processed over 6 turns and 8 tool calls (2,913 generated), 34 s, $0.39 at list price. Transcript · raw stream

Agent over the parquet: correct — About $50M on the serving host's copy, the same figure to the dollar as the reference.
65,441 tokens processed over 7 turns and 6 tool calls (3,384 generated), 37 s, $0.20 at list price. Transcript · raw stream

S3 · ETH 1-minute realised vol on edgeX for one day

Question, word for word: What was the realised volatility of the ETH perpetual on edgeX on 2026-09-27 (UTC day): the standard deviation of 1-minute log returns of the close, annualised? One number.

Reference answer: 26.6% (0.2661)

Parquet the agent over the parquet had on disk for this: 234.2 MB.

Through the MCP: correct — 26.6% (0.2662). 82k tokens against 55k in A20 (1.48x, just inside the noise) and 74k for the parquet agent.
81,782 tokens processed over 5 turns and 6 tool calls (2,643 generated), 33 s, $0.19 at list price. Transcript · raw stream

Agent over the parquet: correct — 26.6% (0.2662); dropped 14 trades duplicated across files.
73,532 tokens processed over 8 turns and 8 tool calls (3,308 generated), 37 s, $0.19 at list price. Transcript · raw stream

K1 · BTC VWAP on Hyperliquid, the call already known

Question, word for word: Call mcp__peerfeed__get_ohlcv with instrument 'hyperliquid/trades:BTC', start '2026-09-27T00:00:00Z', end '2026-09-28T00:00:00Z', granularity '1d', and report the bar's vwap as the answer. Do nothing else.

Reference answer: 84,600.71

Through the MCP: correct — 84,600.71 in one call.
14,226 tokens processed over 2 turns and 1 tool calls (475 generated), 10 s, $0.03 at list price. Transcript · raw stream

K2 · BTC notional on Nado, the call already known

Question, word for word: Call mcp__peerfeed__get_ohlcv with instrument 'nado/trades:2', start '2026-09-27T00:00:00Z', end '2026-09-28T00:00:00Z', granularity '1d', and report volume x vwap of that bar as the answer. Do nothing else.

Reference answer: About $50M, as s2

Through the MCP: correct — About $50M in one call, on the serving host's copy; see s2.
14,447 tokens processed over 2 turns and 1 tool calls (718 generated), 12 s, $0.03 at list price. Transcript · raw stream

K3 · ETH realised vol on edgeX, the call already known

Question, word for word: Call mcp__peerfeed__run_query with dataset 'edgex/trades', instrument 'ETHUSDC', start '2026-09-27T00:00:00Z', end '2026-09-28T00:00:00Z' and sql "WITH r AS (SELECT ln(close / lag(close) OVER (ORDER BY ts)) AS lr FROM t) SELECT stddev_samp(lr) * sqrt(525600) AS rv FROM r", and report rv as the answer. Do nothing else.

Reference answer: 26.6%

Through the MCP: correct — 0.2661 in one call.
14,589 tokens processed over 2 turns and 1 tool calls (638 generated), 10 s, $0.03 at list price. Transcript · raw stream

Every file: results.json (every count on this page) · verdicts.json (the grades, the reference answers and the setup, the one hand-written input) · reference computations · the server's audit lines for these calls. The harness is scripts/run_research_agents.py; the reference was computed independently from the same parquet, and where the parquet agent disagreed with our first reference, the agent was right and the reference was corrected (same-millisecond fills). The earlier version of this section compared one tool call with pasting the raw rows into the agent's context and reported 78× to 11,728×. No competent agent does that, so we withdrew it; its files stay published.

Fewer wrong answers

What an agent re-deriving from raw rows gets wrong.

None of these raise an error. Each one produced a confident, plausible, wrong number in our own archive before we found it. We do not claim an agent cannot handle them: in our measurement above, a strong agent over the parquet handled every one it met. What the MCP does is apply the fix once, for every caller, instead of each agent rediscovering it.

Epoch units

Timestamps that declare milliseconds and store nanoseconds

Some venue streams write nanosecond epochs into a column typed as milliseconds. Read as declared, 1789516741100071262 lands in the year ~58,700, or the scan aborts on the unit overflow. Cast it by hand and you get plausible dates on some shards and garbage on the next, because the unit is declared per file.

What the MCP does: Each dataset carries its verified time column and its semantics. Where the exchange time is unusable the bars are timed by our observation clock and labelled ts_semantics: observation, so you know which clock you are reading.

Stale events

A ten-month-old price inside today's shard

One venue's trade stream carries events whose exchange time decodes to 2025-11-05 inside a 2026-09-16 shard. Bucket by observation time and that old price lands in a current bar and moves its high or low. Nothing in the rows flags it.

What the MCP does: That dataset is withheld from the MCP until the question is settled upstream. An answer we cannot stand behind is refused rather than served.

Dedup keys

Content keys that merge distinct trades

On one venue, keying trades by (symbol, time, price, size) would have collapsed 36.12% of 29,743,595 rows: distinct trades that share a millisecond, a price and a size. The venue's trade id finds 58 true duplicate groups instead. Worse, a key field that is missing is filled with a constant, not an error, so one mark-price feed keyed on a timestamp it did not carry silently became one record per market per minute.

What the MCP does: The aggregator deduplicates on an identity measured against real rows, and the MCP does it again on the venue's trade id, so a trade held in four shards is counted once. Our first research run found dYdX trades held up to four times in the archive and the server it used counted every copy (2.9× too much notional); counted once it is $58.5M, matching the reference. The archive fix is deployed for new data, but shards written before it still hold the copies, so the MCP's own dedup is what removes them. (In our latest run the agent summed minute bars instead and came back 4% short on dYdX: that was public mode's three-trade floor, not a duplicate, and it is graded as a loss below — see the floor's own entry, because 4% is the small half of what it does.)

File layout

295,735 files for one day of one feed

Partitioning an order-book feed by market turned one day into up to 295,735 files averaging about 10 KiB. An agent that lists a month of it opens millions of objects before it reads a row. New data has been written one file per two-hour shard since 2026-09-15; the history before it has not been rewritten yet.

What the MCP does: Shards are resolved from an index, never by listing directories, and one call opens at most 400 of them.

Funding intervals

An hourly rate averaged with an eight-hour rate

Hyperliquid settles funding every hour and OKX every eight; Extended publishes its rate every minute and Nado every 20 seconds. Average “the funding rate” across them without normalising and you are off by up to 8×. One venue's history before 2026-09-28 is not a rate series at all: a recorder filter compared a field that did not exist, so it kept one row per restart.

What the MCP does: get_funding serves every venue's rate with the interval it is quoted over (Nado 24 h, edgeX 4 h, the rest hourly) and the same rate per hour, per 8 hours and as APR, time-weighted. The evidence for each interval is stated, including which ones are documented rather than measured. It does not make a change-only feed host-independent, and we will not pretend otherwise: the same call gave Nado BTC 1.52% APR on the host that serves this MCP and 1.72% on another of our hosts, because the other held 6% more rows and on a feed that only publishes changes an extra row also changes how long the previous rate stood. covered_fraction read 1.00 in both cases — it counts whether a rate was in force, not whether you saw every change. Read Nado to one significant figure, or take the venue's settled payments (1.58% for this window) where precision matters.

Coverage

A gap that reads as a quiet market

A shard that holds only part of its two-hour window looks exactly like a market that did not trade. Bars built on it understate volume and clip the high and low, and the rows cannot tell you.

What the MCP does: Every answer carries the coverage of the shards behind it and, below a threshold, a PARTIAL DATA warning with the percentage.

Limits, published

Every limit is known up front, so your agent can plan against it.

describe_environment hands these to your agent before its first query. An oversized call is refused before it reads a byte and costs nothing. These are the values in the server's source today; why each exists is in the fourth column and the footnote.

LimitValueWhat it means for your agentWhy it existsSet in
MAX_ROWS_SCANNED 60,000,000 rows Up to 60 million rows read per call, summarised server-side. Past that the call is refused from catalogue counts before a byte is read, so asking too much costs you nothing. One call monopolising a host that also records. config.py
MAX_FILES_SCANNED 400 shards One call can span up to 400 two-hour shards, about 33 days of one dataset, within the row cap. A call that fans out across the whole archive. config.py
MAX_BARS 5,000 bars You get the resolution you asked for or a refusal naming the finest one that fits. Never a quietly coarser series. Silent resampling. config.py
MAX_RESULT_ROWS 5,000 rows Answers, not tables: aggregate in the SQL. A result past the cap arrives flagged as truncated, never silently cut. A query that returns a table instead of an answer. query.py
Per-user row budget rows scanned per user per day Your free quota, metered in rows scanned. The meter is the only thing that changes as you use more. Rebuilding the tape one small call at a time. This is the control that actually matters against bulk copying, and it is the meter for the free quota. audit.py
CALLS_PER_MINUTE 60 per user Room for an agent working quickly. At most 2 queries run at once across all callers, so nobody's loop slows your call. An agent in a tight loop starving the recorders that share the host. audit.py
Audit log every call Your usage is attributable to you, which is what makes a per-user quota possible. Unattributed use. Who asked, which tool, which instrument, what range, rows returned and scanned, duration, error. audit.py
Read-only, single-SELECT SQL one statement, must read from t Any read your SQL can express, over the table t: window functions, CTEs, aggregates. Anything but a read. DuckDB is opened read-only; the statement must start with SELECT or WITH, and write or attach verbs are refused as a second line of defence. query.py

Public mode

  • run_query's table t is 1-minute bars built from every trade, so your SQL starts from clean bars rather than a tape. It is also why a raw row cannot come back: structural, not a SQL parser guessing what is aggregate-only.
  • Bars built from fewer than 3 trades are withheld and counted, because a one-trade bar is that trade. The cost is real: in our measurement this hid Pacifica's largest dislocation, which sat in a 2-trade minute.

The caveat, in the server's own words

Tokens are the identity boundary: the rate limits, the row budget and the audit log are all per user. Without a token every caller is one user, so a single agent can exhaust the budget for everyone and the log cannot say who did what. That is why the server fails closed: if the token file is missing or unreadable, no request authenticates.

Tools

Five tools. Start with describe_environment.

It tells an agent every dataset, the canonical columns, the granularities and every limit above, so it can plan a query instead of discovering the limits by hitting them.

ToolWhat it does
list_instrumentsList instruments this host can serve.
describe_datasetDescribe a dataset, or one instrument in it.
get_ohlcvOHLCV bars aggregated from recorded trades.
run_queryRun read-only SQL over a bounded slice of the data.
get_fundingFunding rates, normalised across venues that quote different intervals.
data_qualityMeasured data-quality findings for the datasets this host indexes.
describe_environmentCapabilities, limits and canonical schema — read this first.
get_activity_barsBars that close on activity rather than on the clock.

Not public yet. The endpoint is published here when access opens. Until then tokens are issued by hand, one per caller, because the limits below only mean something per user.

Footnote: why it answers rather than hands over rows. This is our constraint, not a feature we are selling you. Computing next to the archive is how centralized-exchange data becomes sellable at all: their raw ticks are licensed by the exchange, so we sell only what is derived from them (liquidations, funding, open interest). It is also why the limits above exist: a free tool that invites many cheap calls invites copying the archive a call at a time, and the per-user row budget and the audit log are what bound that. Pagination slows bulk copying down; it does not prevent it. An agent can rebuild a tick series across many calls. The daily row budget and the audit log are the controls that stop that, which is why they are per user and why the free quota is metered in rows scanned. If what you need is the rows, the subscription hands them over for every venue where we may sell them.