@qtsurfer/api-client 0.23.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,46 @@ export const ResponseErrorSchema = {
15
15
  type: "string",
16
16
  example: "Invalid request",
17
17
  },
18
+ reason: {
19
+ description: `A stable token naming the cause, for a client to branch on without reading \`message\`.
20
+ Present only on an error that has one, and each operation that can return one lists
21
+ the values it uses. A client treats a value it does not know like an absent \`reason\`.
22
+ `,
23
+ type: "string",
24
+ example: "OUTSIDE_WINDOW",
25
+ },
26
+ next: {
27
+ $ref: "#/components/schemas/ErrorNextCall",
28
+ },
29
+ },
30
+ } as const;
31
+
32
+ export const ErrorNextCallSchema = {
33
+ description: `The call that gets past an error, ready to send: a client that wants the same thing by
34
+ another route makes this request. Present only on an error that has one.
35
+ `,
36
+ type: "object",
37
+ required: ["method", "path"],
38
+ properties: {
39
+ method: {
40
+ description: "HTTP method of the call.",
41
+ type: "string",
42
+ enum: ["GET", "POST", "PUT", "DELETE"],
43
+ example: "POST",
44
+ },
45
+ path: {
46
+ description:
47
+ "Path of the call, relative to the server URL (the one that already ends in `/v1`).",
48
+ type: "string",
49
+ example: "/datasets/imports",
50
+ },
51
+ body: {
52
+ description: `JSON body of the call, when it has one. Whatever depends on the caller and cannot be
53
+ known here (such as the \`name\` of a dataset) is left for the client to add.
54
+ `,
55
+ type: "object",
56
+ additionalProperties: true,
57
+ },
18
58
  },
19
59
  } as const;
20
60
 
@@ -39,6 +79,20 @@ export const InstrumentSchema = {
39
79
  example: "BTC/USDT",
40
80
  } as const;
41
81
 
82
+ export const InstrumentCatalogueResponseSchema = {
83
+ description: `An exchange's instrument catalogue: centralized-exchange instruments or decentralized-exchange
84
+ pools. Read \`meta.venueType\` to distinguish \`cex\` from \`dex\`, including when \`data\` is empty.
85
+ `,
86
+ oneOf: [
87
+ {
88
+ $ref: "#/components/schemas/InstrumentListResponse",
89
+ },
90
+ {
91
+ $ref: "#/components/schemas/PoolListResponse",
92
+ },
93
+ ],
94
+ } as const;
95
+
42
96
  export const InstrumentListResponseSchema = {
43
97
  description: "HAL-style response envelope for the instruments listing",
44
98
  type: "object",
@@ -63,7 +117,7 @@ export const InstrumentListResponseSchema = {
63
117
  export const InstrumentListMetaSchema = {
64
118
  description: "Metadata describing the instruments listing",
65
119
  type: "object",
66
- required: ["updatedAt", "exchange", "segment"],
120
+ required: ["updatedAt", "exchange", "segment", "venueType"],
67
121
  properties: {
68
122
  updatedAt: {
69
123
  type: "string",
@@ -82,6 +136,13 @@ export const InstrumentListMetaSchema = {
82
136
  description: "The market segment served in `data`",
83
137
  example: "spot",
84
138
  },
139
+ venueType: {
140
+ type: "string",
141
+ enum: ["cex"],
142
+ description:
143
+ "The venue type of the exchange, as `GET /exchanges` lists it. Always `cex` here; a listing of pools says `dex`.",
144
+ example: "cex",
145
+ },
85
146
  },
86
147
  } as const;
87
148
 
@@ -119,6 +180,231 @@ export const InstrumentLinksSchema = {
119
180
  },
120
181
  } as const;
121
182
 
183
+ export const PoolListResponseSchema = {
184
+ description:
185
+ "HAL-style response envelope for the instruments listing of a decentralized exchange, whose instruments are pools",
186
+ type: "object",
187
+ required: ["data", "meta", "_links"],
188
+ properties: {
189
+ data: {
190
+ type: "array",
191
+ description:
192
+ "The pools worth running, the most traded in the last 24 hours first",
193
+ items: {
194
+ $ref: "#/components/schemas/PoolDetail",
195
+ },
196
+ },
197
+ meta: {
198
+ $ref: "#/components/schemas/PoolListMeta",
199
+ },
200
+ _links: {
201
+ $ref: "#/components/schemas/PoolLinks",
202
+ },
203
+ },
204
+ example: {
205
+ data: [
206
+ {
207
+ id: "0x52e65b17fb6e5ba00ed806f37afcd2daa50271ca",
208
+ symbol: "WETH/USDG",
209
+ base: "WETH",
210
+ quote: "USDG",
211
+ baseAddress: "0x0bd7d308f8e1639fab988df18a8011f41eacad73",
212
+ quoteAddress: "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
213
+ feePercent: 0.01,
214
+ lastPrice: 2411.285967,
215
+ volume24h: 274033077.4,
216
+ tvlUsd: 19857902.02,
217
+ volume24hUsd: 273876056.45,
218
+ createdAt: "2026-07-02T20:51:08Z",
219
+ },
220
+ ],
221
+ meta: {
222
+ updatedAt: "2026-10-14T22:32:34Z",
223
+ exchange: "robinhood",
224
+ segment: "uniswap-v3",
225
+ venueType: "dex",
226
+ minTvlUsd: 10000,
227
+ limit: 50,
228
+ },
229
+ _links: {
230
+ self: {
231
+ href: "/v1/exchange/robinhood/uniswap-v3/instruments",
232
+ },
233
+ "uniswap-v3": {
234
+ href: "/v1/exchange/robinhood/uniswap-v3/instruments",
235
+ },
236
+ },
237
+ },
238
+ } as const;
239
+
240
+ export const PoolListMetaSchema = {
241
+ description:
242
+ "Metadata describing the pools listing of a decentralized exchange",
243
+ type: "object",
244
+ required: [
245
+ "updatedAt",
246
+ "exchange",
247
+ "segment",
248
+ "venueType",
249
+ "minTvlUsd",
250
+ "limit",
251
+ ],
252
+ properties: {
253
+ updatedAt: {
254
+ type: "string",
255
+ format: "date-time",
256
+ description:
257
+ "When this listing was last refreshed. It is refreshed every hour, and the `lastPrice` of each pool is as of this time.",
258
+ example: "2026-10-14T22:32:34Z",
259
+ },
260
+ exchange: {
261
+ type: "string",
262
+ description: "The exchange the pools belong to",
263
+ example: "robinhood",
264
+ },
265
+ segment: {
266
+ type: "string",
267
+ description:
268
+ "The protocol served in `data`, one of the exchange's `segments`",
269
+ example: "uniswap-v3",
270
+ },
271
+ venueType: {
272
+ type: "string",
273
+ enum: ["dex"],
274
+ description:
275
+ "The venue type of the exchange, as `GET /exchanges` lists it. Always `dex` here; a listing of instruments of a centralized exchange says `cex`.",
276
+ example: "dex",
277
+ },
278
+ minTvlUsd: {
279
+ type: "number",
280
+ description:
281
+ "A pool with less liquidity than this (in US dollars) is not listed",
282
+ example: 10000,
283
+ },
284
+ limit: {
285
+ type: "integer",
286
+ description: "The most pools the listing has",
287
+ example: 50,
288
+ },
289
+ },
290
+ } as const;
291
+
292
+ export const PoolLinksSchema = {
293
+ description:
294
+ "HAL `_links` — protocol discovery for the pools listing of a decentralized exchange",
295
+ type: "object",
296
+ required: ["self"],
297
+ properties: {
298
+ self: {
299
+ allOf: [
300
+ {
301
+ $ref: "#/components/schemas/HalLink",
302
+ },
303
+ ],
304
+ description: "Link to this listing",
305
+ },
306
+ },
307
+ additionalProperties: {
308
+ allOf: [
309
+ {
310
+ $ref: "#/components/schemas/HalLink",
311
+ },
312
+ ],
313
+ description:
314
+ "Link to the pools listing of one of the exchange's protocols, keyed by its name. Present when that listing exists.",
315
+ },
316
+ } as const;
317
+
318
+ export const PoolDetailSchema = {
319
+ description: `A pool of a decentralized exchange that a live run can read. The listing is a place to start, not the
320
+ whole market: a live run can read any pool of the protocol, listed or not.
321
+ `,
322
+ type: "object",
323
+ required: [
324
+ "id",
325
+ "symbol",
326
+ "base",
327
+ "quote",
328
+ "baseAddress",
329
+ "quoteAddress",
330
+ "feePercent",
331
+ "tvlUsd",
332
+ "volume24hUsd",
333
+ ],
334
+ properties: {
335
+ id: {
336
+ type: "string",
337
+ pattern: "^0x[0-9a-f]{40}$",
338
+ description:
339
+ "The pool's address, in lower case. It is the value to put in `instruments` of a live run's source.",
340
+ example: "0x52e65b17fb6e5ba00ed806f37afcd2daa50271ca",
341
+ },
342
+ symbol: {
343
+ type: "string",
344
+ description:
345
+ "The pair written `BASE/QUOTE`, the way the signals of a run on this pool name it (`instrument`)",
346
+ example: "WETH/USDG",
347
+ },
348
+ base: {
349
+ type: "string",
350
+ description: "Symbol of the base token",
351
+ example: "WETH",
352
+ },
353
+ quote: {
354
+ type: "string",
355
+ description: "Symbol of the quote token",
356
+ example: "USDG",
357
+ },
358
+ baseAddress: {
359
+ type: "string",
360
+ description: "Address of the base token, in lower case",
361
+ example: "0x0bd7d308f8e1639fab988df18a8011f41eacad73",
362
+ },
363
+ quoteAddress: {
364
+ type: "string",
365
+ description: "Address of the quote token, in lower case",
366
+ example: "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
367
+ },
368
+ feePercent: {
369
+ type: "number",
370
+ description:
371
+ "The pool's fee tier, in percent (`0.05` is a 0.05 % fee). A protocol can have several pools of the same pair, one per fee tier.",
372
+ example: 0.01,
373
+ },
374
+ lastPrice: {
375
+ type: "number",
376
+ format: "double",
377
+ description:
378
+ "The price of one base token in the quote token, as the signals of a run on this pool name the pair. It is as of `meta.updatedAt`, so up to one hour old. Absent when there is no price for the pool.",
379
+ example: 2411.285967,
380
+ },
381
+ volume24h: {
382
+ type: "number",
383
+ format: "double",
384
+ description:
385
+ "What traded in the pool over the last 24 hours, in the quote token, the same meaning as `volume24h` of an instrument of a centralized exchange. Absent when the quote token has no dollar price to convert from.",
386
+ example: 274033077.4,
387
+ },
388
+ tvlUsd: {
389
+ type: "number",
390
+ description: "The liquidity held by the pool, in US dollars",
391
+ example: 19857902.02,
392
+ },
393
+ volume24hUsd: {
394
+ type: "number",
395
+ description:
396
+ "What traded in the pool over the last 24 hours, in US dollars",
397
+ example: 273876056.45,
398
+ },
399
+ createdAt: {
400
+ type: "string",
401
+ format: "date-time",
402
+ description: "When the pool was created. Absent when it is not known",
403
+ example: "2026-07-02T20:51:08Z",
404
+ },
405
+ },
406
+ } as const;
407
+
122
408
  export const StrategyLinksSchema = {
123
409
  description: `HAL \`_links\` for a strategy — present on a full \`StrategyState\` body (\`GET
124
410
  /strategy/{strategyId}\`, and \`POST /strategy/{strategyId}/validate\`'s already-validated
@@ -173,6 +459,7 @@ one doesn't.
173
459
  required: [
174
460
  "userId",
175
461
  "tier",
462
+ "scopes",
176
463
  "maxExecute",
177
464
  "maxRangeDays",
178
465
  "maxSweepCartesian",
@@ -193,6 +480,9 @@ one doesn't.
193
480
  description: "Your current subscription tier.",
194
481
  example: "free",
195
482
  },
483
+ scopes: {
484
+ $ref: "#/components/schemas/KeyScopes",
485
+ },
196
486
  maxExecute: {
197
487
  type: "integer",
198
488
  description: `Maximum number of strategy executions (and sweeps) you can have running at the same time
@@ -368,13 +658,19 @@ export const InstrumentDetailSchema = {
368
658
  description:
369
659
  "Exchange instrument with per-data-type coverage and market info",
370
660
  type: "object",
371
- required: ["id", "base", "quote"],
661
+ required: ["id", "symbol", "base", "quote"],
372
662
  properties: {
373
663
  id: {
374
664
  type: "string",
375
665
  description: "Instrument identifier (e.g. currency pair)",
376
666
  example: "BTC/USDT",
377
667
  },
668
+ symbol: {
669
+ type: "string",
670
+ description:
671
+ "The instrument's name, the same as `id`. A pool of a decentralized exchange has the same field (`PoolDetail`), so both listings are read alike.",
672
+ example: "BTC/USDT",
673
+ },
378
674
  base: {
379
675
  type: "string",
380
676
  description: "Base currency",
@@ -385,19 +681,26 @@ export const InstrumentDetailSchema = {
385
681
  description: "Quote currency",
386
682
  example: "USDT",
387
683
  },
684
+ settle: {
685
+ type: "string",
686
+ description: "Settlement currency. Present on a futures instrument only.",
687
+ example: "USDT",
688
+ },
388
689
  coverage: {
389
690
  $ref: "#/components/schemas/InstrumentCoverage",
390
691
  },
391
692
  lastPrice: {
392
693
  type: "number",
393
694
  format: "double",
394
- description: "Last traded price",
695
+ description:
696
+ "Last traded price. Absent for an instrument with no recent price.",
395
697
  example: 84250.5,
396
698
  },
397
699
  volume24h: {
398
700
  type: "number",
399
701
  format: "double",
400
- description: "Trading volume in the last 24 hours (in quote currency)",
702
+ description:
703
+ "Trading volume in the last 24 hours (in quote currency). Absent for an instrument with no recent volume.",
401
704
  example: 1234567.89,
402
705
  },
403
706
  },
@@ -456,7 +759,7 @@ export const CoverageWindowSchema = {
456
759
  export const ExchangeSchema = {
457
760
  description: "Exchange service provider",
458
761
  type: "object",
459
- required: ["id", "name"],
762
+ required: ["id", "name", "venueType", "segments", "liveSegments"],
460
763
  properties: {
461
764
  id: {
462
765
  description: "Unique identifier for the exchange",
@@ -473,6 +776,34 @@ export const ExchangeSchema = {
473
776
  type: "string",
474
777
  example: "Binance cryptocurrency exchange",
475
778
  },
779
+ venueType: {
780
+ description:
781
+ "Venue category, written as the `venueType` of a live run's source: `cex` is a centralized exchange and `dex` a decentralized one. A `dex` exchange has no market data to prepare or backtest, so its id is not valid in the routes that take an `exchangeId`, except the two instruments routes, which list its pools (the instrument of a run on it is a pool's address).",
782
+ type: "string",
783
+ enum: ["cex", "dex"],
784
+ example: "cex",
785
+ },
786
+ segments: {
787
+ description:
788
+ "The segments the exchange offers, written as in the path of `GET /exchange/{exchangeId}/{segment}/instruments` and as the `segment` of a live run's source. For a `cex` exchange, its market segments (`spot`, `futures`: the ones its instrument catalogues are served under); for a `dex` exchange, the protocols whose pools a run can read.",
789
+ type: "array",
790
+ minItems: 1,
791
+ uniqueItems: true,
792
+ items: {
793
+ type: "string",
794
+ },
795
+ example: ["spot", "futures"],
796
+ },
797
+ liveSegments: {
798
+ description:
799
+ "The segments among `segments` for which live data is published, so the ones a live run can read: a live run on any other segment of the exchange is refused with `400`. It can be empty (an exchange whose live data is not published yet).",
800
+ type: "array",
801
+ uniqueItems: true,
802
+ items: {
803
+ type: "string",
804
+ },
805
+ example: ["spot"],
806
+ },
476
807
  },
477
808
  } as const;
478
809
 
@@ -553,11 +884,10 @@ what omitting it means, depends on the source:
553
884
  * Managed exchange, \`ticker\` or \`funding\` — one of \`1s\`, \`5s\`, \`1m\`, \`3m\`, \`5m\`, \`15m\`,
554
885
  \`30m\`, \`1h\`, \`2h\`, \`4h\`, \`8h\`, \`12h\`, \`1d\`, \`1w\`, \`1q\`; any other label returns \`400\`.
555
886
  Omitted = \`1s\`, the publisher's native cadence.
556
- * Managed exchange, \`kline\` — one of \`1s\`, \`1m\`, \`5m\`, \`15m\`, \`30m\`, \`1h\`, \`4h\`, \`1d\`;
557
- any other label, including \`5s\`, \`3m\` and \`8h\`, returns \`400\` and names the accepted
558
- ones. Omitted = \`1s\`. This is the width of the bars a run reads: it is chosen here,
559
- once, and the same strategy can be run at several cadences by preparing the range at
560
- each.
887
+ * Managed exchange, \`kline\` — the same labels as above, \`1s\` to \`1q\`; a label that is
888
+ not a whole number of seconds returns \`400\`. Omitted = \`1s\`. This is the width of the
889
+ bars a run reads: it is chosen here, once, and the same strategy can be run at
890
+ several cadences by preparing the range at each.
561
891
  * Dataset (\`exchangeId: user\`) — omitted = the dataset version's own discovered
562
892
  \`cadence\` (see \`DatasetVersion.cadence\`), served as-is. Any cadence equal to or
563
893
  coarser than it and an exact multiple of it is accepted, including ones outside the
@@ -2214,9 +2544,11 @@ dataset covers.
2214
2544
  type: {
2215
2545
  type: "string",
2216
2546
  enum: ["ticker", "klines"],
2217
- description: `\`ticker\` for an upload or a \`dex\` import with no \`cadence\` requested (native per-trade
2218
- data). \`klines\` for a \`dex\` import that requested a candle \`cadence\` — pre-aggregated
2219
- bars rather than raw ticks. Purely informational; both shapes are read the same way.
2547
+ description: `\`ticker\` for an upload, a \`dex\` import with no \`cadence\` requested (native per-trade
2548
+ data) or a \`cex\` import with \`dataType: ticker\`. \`klines\` for a \`dex\` import that
2549
+ requested a candle \`cadence\` — pre-aggregated bars rather than raw ticks — or a \`cex\`
2550
+ import with \`dataType: klines\`. Purely informational; both shapes are read the same
2551
+ way.
2220
2552
  `,
2221
2553
  example: "ticker",
2222
2554
  },
@@ -2490,11 +2822,12 @@ not declared by the caller.
2490
2822
  cadence: {
2491
2823
  type: "string",
2492
2824
  description: `The cadence discovered from the data's own timestamps. Either a fixed grid — \`1s\`,
2493
- \`5s\`, \`15s\`, \`1m\`, \`5m\`, \`15m\`, \`30m\`, \`1h\`, \`4h\`, \`1d\` — when at least half the
2494
- intervals between consecutive rows fall on that step (small clock jitter tolerated), or
2495
- \`rt\`: native data at the rate it was captured, each row at its own timestamp with no
2496
- fixed step — per-trade on-chain swaps, block-spaced or sub-second ticks, irregular
2497
- intervals. An \`rt\` dataset can be resampled to any fixed cadence at prepare time.
2825
+ \`5s\`, \`15s\`, \`1m\`, \`3m\`, \`5m\`, \`15m\`, \`30m\`, \`1h\`, \`2h\`, \`4h\`, \`8h\`, \`12h\`, \`1d\`,
2826
+ \`1w\` — when at least half the intervals between consecutive rows fall on that step
2827
+ (small clock jitter tolerated), or \`rt\`: native data at the rate it was captured, each
2828
+ row at its own timestamp with no fixed step — per-trade on-chain swaps, block-spaced
2829
+ or sub-second ticks, irregular intervals. An \`rt\` dataset can be resampled to any
2830
+ fixed cadence at prepare time.
2498
2831
  `,
2499
2832
  example: "1s",
2500
2833
  },
@@ -2593,7 +2926,8 @@ fact you poll — not tied to how recently the failure happened.
2593
2926
  export const DatasetImportRequestSchema = {
2594
2927
  type: "object",
2595
2928
  description: `\`POST /datasets/imports\`'s request body. A common block plus one type-specific block,
2596
- selected by \`type\` — \`dex\` is the only value today.
2929
+ selected by \`type\`: \`dex\` (block \`dex\`) or \`cex\` (block \`cex\`), the two \`venueType\`s of
2930
+ \`GET /exchanges\`.
2597
2931
  `,
2598
2932
  required: ["name", "instrument", "from", "to", "type"],
2599
2933
  properties: {
@@ -2622,25 +2956,61 @@ request wider than that ceiling is \`400\`, regardless of source type.
2622
2956
  },
2623
2957
  cadence: {
2624
2958
  type: "string",
2625
- enum: ["1s", "1m", "5m"],
2626
- description: `Optional. Omitted/blank keeps native per-trade event cadence — each swap at its own
2627
- timestamp, so the resulting version's \`cadence\` is \`rt\` unless the swaps happen to sit
2628
- on a fixed grid (see \`DatasetVersion.cadence\`). Set to \`1s\`, \`1m\` or
2629
- \`5m\` instead to get pre-aggregated candles at that width rather than raw trades (the
2630
- resulting dataset's \`type\` becomes \`klines\`); any other value is \`400\`. Not every
2631
- network supports every cadence — an unsupported combination fails asynchronously, not
2632
- at request time (see \`DatasetImportState.error\`).
2959
+ enum: [
2960
+ "1s",
2961
+ "5s",
2962
+ "1m",
2963
+ "3m",
2964
+ "5m",
2965
+ "15m",
2966
+ "30m",
2967
+ "1h",
2968
+ "2h",
2969
+ "4h",
2970
+ "8h",
2971
+ "12h",
2972
+ "1d",
2973
+ "1w",
2974
+ "1q",
2975
+ ],
2976
+ description: `Optional; what it means, and what it accepts, depends on \`type\`. Any other value is
2977
+ \`400\`.
2978
+
2979
+ * \`dex\` — omitted/blank keeps native per-trade event cadence: each swap at its own
2980
+ timestamp, so the resulting version's \`cadence\` is \`rt\` unless the swaps happen to
2981
+ sit on a fixed grid (see \`DatasetVersion.cadence\`). Set to \`1s\`, \`1m\` or \`5m\`
2982
+ instead to get pre-aggregated candles at that width rather than raw trades (the
2983
+ resulting dataset's \`type\` becomes \`klines\`). Not every network supports every
2984
+ cadence — an unsupported combination fails asynchronously, not at request time (see
2985
+ \`DatasetImportState.error\`).
2986
+ * \`cex\` — one of \`1s\`, \`5s\`, \`1m\`, \`3m\`, \`5m\`, \`15m\`, \`30m\`, \`1h\`, \`2h\`, \`4h\`, \`8h\`,
2987
+ \`12h\`, \`1d\`, \`1w\`, \`1q\`, whether \`dataType\` is \`ticker\` or \`klines\`. Omitted = the
2988
+ finest cadence every part of the range holds.
2989
+
2990
+ For \`cex\` the bars are built from the finest data every part of the range holds. A
2991
+ \`cadence\` finer than that, or not an exact multiple of it, fails the import
2992
+ asynchronously (\`failed\`; the \`error\` names the finest cadence the range holds) rather
2993
+ than returning a dataset with a different cadence from the one asked for. So a range
2994
+ whose history changes cadence part-way (from \`1m\` to \`1s\`, say) comes out entirely at
2995
+ the coarser one when \`cadence\` is omitted: the dataset has no gap that is only a
2996
+ change of cadence.
2633
2997
  `,
2634
2998
  },
2635
2999
  type: {
2636
3000
  type: "string",
2637
- enum: ["dex"],
2638
- description: "The source to fetch from. `dex` is the only value today.",
2639
- example: "dex",
3001
+ enum: ["dex", "cex"],
3002
+ description: `The kind of venue to import from, the same two values as the \`venueType\` of
3003
+ \`GET /exchanges\`: \`dex\` — a pool/pair's own on-chain market (block \`dex\`) — or \`cex\` —
3004
+ a centralized exchange's market history (block \`cex\`).
3005
+ `,
3006
+ example: "cex",
2640
3007
  },
2641
3008
  dex: {
2642
3009
  $ref: "#/components/schemas/DatasetImportDexRequest",
2643
3010
  },
3011
+ cex: {
3012
+ $ref: "#/components/schemas/DatasetImportCexRequest",
3013
+ },
2644
3014
  },
2645
3015
  } as const;
2646
3016
 
@@ -2700,6 +3070,45 @@ import rather than silently fetching from the wrong pool. Ignored if the top-lev
2700
3070
  },
2701
3071
  } as const;
2702
3072
 
3073
+ export const DatasetImportCexRequestSchema = {
3074
+ type: "object",
3075
+ description: `The \`cex\` source's own fields — required when \`type\` is \`cex\`. They name a market the way
3076
+ \`GET /exchanges\` and the instruments routes do: \`exchange\` is the \`id\` of a centralized
3077
+ exchange, \`segment\` one of its \`segments\`.
3078
+ `,
3079
+ required: ["exchange", "segment", "dataType"],
3080
+ properties: {
3081
+ exchange: {
3082
+ type: "string",
3083
+ description: `The id of a centralized exchange (\`venueType\` \`cex\`) of \`GET /exchanges\`, lower-case.
3084
+ A decentralized exchange's id is \`400\`: import its pools with \`type: dex\`.
3085
+ `,
3086
+ example: "binance",
3087
+ },
3088
+ segment: {
3089
+ type: "string",
3090
+ enum: ["spot"],
3091
+ description: `The market segment, one of the exchange's \`segments\`. \`spot\` is the only segment a
3092
+ \`cex\` import takes today, which matches \`instrument\` being a plain spot pair.
3093
+ `,
3094
+ example: "spot",
3095
+ },
3096
+ dataType: {
3097
+ type: "string",
3098
+ enum: ["ticker", "klines"],
3099
+ description: `What to import, and the resulting dataset's \`type\`: \`ticker\` — the exchange's ticker
3100
+ series — or \`klines\` — its aggregated bars (open, high, low, close and volume).
3101
+ `,
3102
+ example: "klines",
3103
+ },
3104
+ },
3105
+ example: {
3106
+ exchange: "binance",
3107
+ segment: "spot",
3108
+ dataType: "klines",
3109
+ },
3110
+ } as const;
3111
+
2703
3112
  export const DatasetImportCreatedSchema = {
2704
3113
  type: "object",
2705
3114
  description:
@@ -2767,7 +3176,8 @@ import's doesn't, until this source finishes fetching it.
2767
3176
  type: "string",
2768
3177
  description: `A human-readable reason, present when \`status\` is \`failed\` — an unresolvable
2769
3178
  pool/pair, no data in the requested range, a range older than the configured source
2770
- retains, the fetch exceeding your tier's time ceiling, or any of
3179
+ retains, a \`cex\` \`cadence\` finer than the range holds (the message names the finest
3180
+ one), the fetch exceeding your tier's time ceiling, or any of
2771
3181
  \`DatasetUploadState.error\`'s own ingest-side reasons once fetching hands off to it.
2772
3182
  Durably recorded, same as on the upload path.
2773
3183
  `,
@@ -2784,6 +3194,29 @@ Durably recorded, same as on the upload path.
2784
3194
  },
2785
3195
  } as const;
2786
3196
 
3197
+ export const KeyScopesSchema = {
3198
+ type: "object",
3199
+ description: `What your API key may do, as a map from a resource to the actions allowed on it. Names are
3200
+ lower case: \`{ "backtests": ["read", "write", "execute"], "exchanges": ["read"] }\`. A scope
3201
+ is a resource and an action: a resource is listed only with at least one action, and a key
3202
+ with no scope is \`{}\`. \`GET /account\` and \`POST /auth/token\` return the same map for the
3203
+ same key. The API reports the scopes of a key; it does not yet refuse an operation because
3204
+ a scope is missing.
3205
+ `,
3206
+ additionalProperties: {
3207
+ type: "array",
3208
+ description:
3209
+ "The actions allowed on this resource (for example `read`, `write`, `execute`).",
3210
+ items: {
3211
+ type: "string",
3212
+ },
3213
+ },
3214
+ example: {
3215
+ backtests: ["read", "write", "execute"],
3216
+ exchanges: ["read"],
3217
+ },
3218
+ } as const;
3219
+
2787
3220
  export const AuthTokenResponseSchema = {
2788
3221
  type: "object",
2789
3222
  required: ["access_token", "token_type", "expires_in", "tier"],
@@ -2804,13 +3237,7 @@ export const AuthTokenResponseSchema = {
2804
3237
  example: 3600,
2805
3238
  },
2806
3239
  scopes: {
2807
- type: "array",
2808
- description:
2809
- "Scopes granted to this token. Reserved for future use; currently always empty.",
2810
- items: {
2811
- type: "string",
2812
- },
2813
- example: [],
3240
+ $ref: "#/components/schemas/KeyScopes",
2814
3241
  },
2815
3242
  tier: {
2816
3243
  type: "string",
@@ -2826,11 +3253,16 @@ export const AuthTokenErrorSchema = {
2826
3253
  type: "object",
2827
3254
  required: ["code", "message"],
2828
3255
  description:
2829
- "Error envelope returned by `POST /auth/token` when the API key is rejected.",
3256
+ "Error envelope returned by `POST /auth/token` when the API key is rejected (`401`) or carries no scopes where scopes are required (`403`).",
2830
3257
  properties: {
2831
3258
  code: {
2832
3259
  type: "string",
2833
- enum: ["invalid_apikey", "apikey_revoked", "apikey_expired"],
3260
+ enum: [
3261
+ "invalid_apikey",
3262
+ "apikey_revoked",
3263
+ "apikey_expired",
3264
+ "apikey_without_scopes",
3265
+ ],
2834
3266
  description: "Machine-readable error reason.",
2835
3267
  },
2836
3268
  message: {
@@ -2848,23 +3280,28 @@ export const LiveSourceSchema = {
2848
3280
  properties: {
2849
3281
  venueType: {
2850
3282
  type: "string",
2851
- example: "cx",
3283
+ enum: ["cex", "dex"],
3284
+ example: "cex",
2852
3285
  description:
2853
- "Venue category. `cx` (centralized exchange) is the only one live runs support today.",
3286
+ 'Venue category. `cex` is a centralized exchange and `dex` is one pool on a decentralized exchange, which takes its own `exchange`, `segment`, `type` and `instruments` (see "Sources" in the Live execution guide). `cx` and `dx`, the spellings used before, are still read as `cex` and `dex`; the run always reports the new ones.',
2854
3287
  },
2855
3288
  exchange: {
2856
3289
  type: "string",
2857
3290
  example: "binance",
3291
+ description:
3292
+ "The exchange id, as `GET /exchanges` lists it. For a `dex` source, the chain the pool is on; `robinhood` is the only one today.",
2858
3293
  },
2859
3294
  segment: {
2860
3295
  type: "string",
2861
3296
  example: "spot",
3297
+ description:
3298
+ "The segment, as `GET /exchanges` lists it for that exchange, among its `liveSegments` (a segment with no live data is refused with `400`). For a `cex` source, the market segment (`spot` today); for a `dex` source, the protocol of the pool, `uniswap-v3` the only one today.",
2862
3299
  },
2863
3300
  type: {
2864
3301
  type: "string",
2865
3302
  enum: ["ticker", "kline"],
2866
3303
  description:
2867
- "Both `ticker` and `kline` connect to the lightest (fastest) cadence available for the exchange — today, 1 tick/second on every supported exchange. Choosing a specific cadence is not offered yet.",
3304
+ "Both `ticker` and `kline` connect to the lightest (fastest) cadence available for the exchange — today, 1 tick/second on every supported exchange. Choosing a specific cadence is not offered yet. A `dex` source supports only `kline`: one candle per second of the pool's trades.",
2868
3305
  },
2869
3306
  instruments: {
2870
3307
  type: "array",
@@ -2873,7 +3310,7 @@ export const LiveSourceSchema = {
2873
3310
  },
2874
3311
  example: ["BTC/USDT"],
2875
3312
  description:
2876
- 'The instruments the run reads. For a run started with a list, that list as sent; for one started without `instruments`, or with `["*"]`, what the start resolved: the list of pairs the compilation of the strategy records as the instruments it accepts, when it records one, or `["*"]` for every instrument the exchange/segment offers (tier-gated). An entry is written `BASE/QUOTE` and either side can be `*` to mean any, such as any base paired with USDT. Symbols are not case-sensitive and spaces around them are ignored, so `btc/usdt` and `BTC/USDT` are the same instrument.',
3313
+ 'The instruments the run reads. For a run started with a list, that list as sent; for one started without `instruments`, or with `["*"]`, what the start resolved: the list of pairs the compilation of the strategy records as the instruments it accepts, when it records one, or `["*"]` for every instrument the exchange/segment offers (tier-gated). An entry is written `BASE/QUOTE` and either side can be `*` to mean any, such as any base paired with USDT. Symbols are not case-sensitive and spaces around them are ignored, so `btc/usdt` and `BTC/USDT` are the same instrument. A `dex` source holds exactly one entry, the address of its pool.',
2877
3314
  },
2878
3315
  },
2879
3316
  } as const;
@@ -2886,23 +3323,28 @@ export const LiveSourceRequestSchema = {
2886
3323
  properties: {
2887
3324
  venueType: {
2888
3325
  type: "string",
2889
- example: "cx",
3326
+ enum: ["cex", "dex"],
3327
+ example: "cex",
2890
3328
  description:
2891
- "Venue category. `cx` (centralized exchange) is the only one live runs support today.",
3329
+ 'Venue category. `cex` is a centralized exchange and `dex` is one pool on a decentralized exchange, which takes its own `exchange`, `segment`, `type` and `instruments` (see "Sources" in the Live execution guide). `cx` and `dx`, the spellings used before, are still read as `cex` and `dex`; the run always reports the new ones.',
2892
3330
  },
2893
3331
  exchange: {
2894
3332
  type: "string",
2895
3333
  example: "binance",
3334
+ description:
3335
+ "The exchange id, as `GET /exchanges` lists it. For a `dex` source, the chain the pool is on; `robinhood` is the only one today.",
2896
3336
  },
2897
3337
  segment: {
2898
3338
  type: "string",
2899
3339
  example: "spot",
3340
+ description:
3341
+ "The segment, as `GET /exchanges` lists it for that exchange, among its `liveSegments` (a segment with no live data is refused with `400`). For a `cex` source, the market segment (`spot` today); for a `dex` source, the protocol of the pool, `uniswap-v3` the only one today.",
2900
3342
  },
2901
3343
  type: {
2902
3344
  type: "string",
2903
3345
  enum: ["ticker", "kline"],
2904
3346
  description:
2905
- "Both `ticker` and `kline` connect to the lightest (fastest) cadence available for the exchange — today, 1 tick/second on every supported exchange. Choosing a specific cadence is not offered yet.",
3347
+ "Both `ticker` and `kline` connect to the lightest (fastest) cadence available for the exchange — today, 1 tick/second on every supported exchange. Choosing a specific cadence is not offered yet. A `dex` source supports only `kline`: one candle per second of the pool's trades.",
2906
3348
  },
2907
3349
  instruments: {
2908
3350
  type: "array",
@@ -2910,7 +3352,7 @@ export const LiveSourceRequestSchema = {
2910
3352
  type: "string",
2911
3353
  },
2912
3354
  example: ["BTC/USDT"],
2913
- description: `Which instruments the run reads. A QTScript strategy can declare which instruments it accepts with an \`instruments\` line in its source. When the compilation of the strategy records that selection as a list of pairs, leaving \`instruments\` out, or sending \`["*"]\`, takes that list; otherwise the run reads every instrument the exchange/segment offers. A list of instruments is taken as sent, and \`[]\` and \`null\` are refused with \`400\`. An entry is written \`BASE/QUOTE\`; either side can be \`*\` to mean any: any base can be paired with USDT, and BTC can be paired with any quote. Entries are not case-sensitive and spaces around them are ignored, so \`btc/usdt\` and \`BTC/USDT\` are the same instrument. An entry that is not of that form (no \`/\`, an empty side, a \`.\` or a space inside a side, or a \`*\` mixed with other characters of a side) makes the run fail when it starts. Plan limits: a list with an entry that has a \`*\` on a side counts like \`["*"]\` and only plans that include the wildcard accept it; the other entries are counted one by one. The instruments the run actually reads come back in the run's \`sources\`.`,
3355
+ description: `Which instruments the run reads. A QTScript strategy can declare which instruments it accepts with an \`instruments\` line in its source. When the compilation of the strategy records that selection as a list of pairs, leaving \`instruments\` out, or sending \`["*"]\`, takes that list; otherwise the run reads every instrument the exchange/segment offers. A list of instruments is taken as sent, and \`[]\` and \`null\` are refused with \`400\`. An entry is written \`BASE/QUOTE\`; either side can be \`*\` to mean any: any base can be paired with USDT, and BTC can be paired with any quote. Entries are not case-sensitive and spaces around them are ignored, so \`btc/usdt\` and \`BTC/USDT\` are the same instrument. An entry that is not of that form (no \`/\`, an empty side, a \`.\` or a space inside a side, or a \`*\` mixed with other characters of a side) makes the run fail when it starts. Plan limits: a list with an entry that has a \`*\` on a side counts like \`["*"]\` and only plans that include the wildcard accept it; the other entries are counted one by one. The instruments the run actually reads come back in the run's \`sources\`. A \`dex\` source needs exactly one entry, the address of its pool (\`0x\` and 40 hexadecimal digits, not case-sensitive): it cannot be left out and \`*\` is not accepted.`,
2914
3356
  },
2915
3357
  },
2916
3358
  } as const;
@@ -2938,13 +3380,23 @@ export const StartLiveRequestSchema = {
2938
3380
  enum: ["private", "public"],
2939
3381
  default: "private",
2940
3382
  description:
2941
- "A `public` run appears in `GET /live/public` and its signal channel accepts subscriptions from anyone, not only you — from the moment it is promoted to `live`. While it is a `sandbox` trial, `public` is only what you asked for, and only you can read it.",
3383
+ "A `public` run appears in `GET /live/public` and its signal channel accepts subscriptions from anyone, not only you — from the moment it is in the `live` stage, which is when it is promoted or, for a run that starts there, at once. While it is a `sandbox` trial, `public` is only what you asked for, and only you can read it.",
2942
3384
  },
2943
3385
  relay: {
2944
3386
  type: "boolean",
2945
3387
  default: false,
2946
3388
  description: `Request that this run's signals be relayed over its WebSocket channel, from its first signal — in the \`sandbox\` stage too, where only you can subscribe to it. See the "Live execution" guide.`,
2947
3389
  },
3390
+ sandbox: {
3391
+ type: "boolean",
3392
+ default: false,
3393
+ description: `Start in the \`sandbox\` stage even though an earlier run of yours already took this compiled strategy
3394
+ through it. Without it, such a strategy starts in \`LIVE\` at once; with \`true\` it starts in \`SANDBOX\` like
3395
+ a first run, which is how to read its signals back over the connection (\`history\`, kept for the
3396
+ \`sandbox\` stage only) while you debug it. For a strategy that has not been through the sandbox it
3397
+ changes nothing: it always starts there. Anything other than \`true\` or \`false\` is \`400\`.
3398
+ `,
3399
+ },
2948
3400
  stream: {
2949
3401
  type: "boolean",
2950
3402
  default: false,
@@ -3135,7 +3587,7 @@ export const LiveRunSchema = {
3135
3587
  type: "string",
3136
3588
  enum: ["SANDBOX", "LIVE"],
3137
3589
  description:
3138
- "A new run always starts `SANDBOX`, a 24-hour trial in which it is compared against a second execution and checked for resource use and stability. A run that passes moves to `LIVE` automatically when the 24 hours are up.",
3590
+ "A new run starts `SANDBOX`, a 24-hour trial in which it is compared against a second execution and checked for resource use and stability. A run that passes moves to `LIVE` automatically when the 24 hours are up. A run of a strategy that an earlier run of yours already took through the trial starts `LIVE`, unless you started it with `sandbox: true`.",
3139
3591
  },
3140
3592
  state: {
3141
3593
  type: "string",
@@ -3154,12 +3606,15 @@ export const LiveRunSchema = {
3154
3606
  stack trace or an internal message. Either \`resource: ...\` (the platform stopped the run for
3155
3607
  exceeding its resource allowance; the text says which limit) or one of a fixed set of sentences for
3156
3608
  a \`FAILED\` run: the strategy cannot consume the source type the run was started with, the run's
3157
- definition was refused, the run could not start after several attempts, the strategy failed while
3158
- processing data, the run lost its data feed, or the generic \`The run failed.\`. The set may grow:
3609
+ definition was refused, the pool address of a \`dex\` run is not a pool of its protocol on its chain,
3610
+ the platform has no room to follow the pool of a \`dex\` run, the run could not start after several
3611
+ attempts, the strategy failed while processing data, the run lost its data feed, or the generic
3612
+ \`The run failed.\`. The set may grow:
3159
3613
  read an unrecognised sentence as a failure and do not parse it. A \`FAILED\` run usually stays
3160
3614
  \`desired: RUNNING\` until you stop it, and counts as active (\`409\` on a new start, and toward your
3161
- live-run limit) until then; one that can never run because its strategy cannot consume its source
3162
- type is stopped by the platform itself (\`desired: STOPPED\`), so it holds no place.
3615
+ live-run limit) until then; one that cannot run, and will not by waiting, because its strategy cannot
3616
+ consume its source type or because the pool of a \`dex\` run is not a pool or has no room to be followed,
3617
+ is stopped by the platform itself (\`desired: STOPPED\`), so it holds no place.
3163
3618
  `,
3164
3619
  },
3165
3620
  sources: {
@@ -3190,7 +3645,7 @@ type is stopped by the platform itself (\`desired: STOPPED\`), so it holds no pl
3190
3645
  type: "object",
3191
3646
  additionalProperties: true,
3192
3647
  description:
3193
- "The sandbox trial's promotion verdict. Absent for the whole 24-hour trial and present once it ends, so an absent `gate` means the trial has not finished. `passed` is the verdict; the rest is diagnostic detail whose shape is not yet stabilized as public API: treat it as opaque.",
3648
+ "The sandbox trial's promotion verdict. Absent for the whole 24-hour trial and present once it ends, so an absent `gate` means the trial has not finished. A run that started in `LIVE` without a trial has it from the start: the verdict of the earlier run it relies on, with `inheritedFrom` naming that run. `passed` is the verdict; the rest is diagnostic detail whose shape is not yet stabilized as public API: treat it as opaque.",
3194
3649
  },
3195
3650
  paper: {
3196
3651
  allOf: [
@@ -3429,7 +3884,7 @@ export const LiveRunSummarySchema = {
3429
3884
  type: "string",
3430
3885
  enum: ["SANDBOX", "LIVE"],
3431
3886
  description:
3432
- "A new run always starts `SANDBOX`, a 24-hour trial in which it is compared against a second execution and checked for resource use and stability. A run that passes moves to `LIVE` automatically when the 24 hours are up.",
3887
+ "A new run starts `SANDBOX`, a 24-hour trial in which it is compared against a second execution and checked for resource use and stability. A run that passes moves to `LIVE` automatically when the 24 hours are up. A run of a strategy that an earlier run of yours already took through the trial starts `LIVE`, unless you started it with `sandbox: true`.",
3433
3888
  },
3434
3889
  state: {
3435
3890
  type: "string",
@@ -3448,12 +3903,15 @@ export const LiveRunSummarySchema = {
3448
3903
  stack trace or an internal message. Either \`resource: ...\` (the platform stopped the run for
3449
3904
  exceeding its resource allowance; the text says which limit) or one of a fixed set of sentences for
3450
3905
  a \`FAILED\` run: the strategy cannot consume the source type the run was started with, the run's
3451
- definition was refused, the run could not start after several attempts, the strategy failed while
3452
- processing data, the run lost its data feed, or the generic \`The run failed.\`. The set may grow:
3906
+ definition was refused, the pool address of a \`dex\` run is not a pool of its protocol on its chain,
3907
+ the platform has no room to follow the pool of a \`dex\` run, the run could not start after several
3908
+ attempts, the strategy failed while processing data, the run lost its data feed, or the generic
3909
+ \`The run failed.\`. The set may grow:
3453
3910
  read an unrecognised sentence as a failure and do not parse it. A \`FAILED\` run usually stays
3454
3911
  \`desired: RUNNING\` until you stop it, and counts as active (\`409\` on a new start, and toward your
3455
- live-run limit) until then; one that can never run because its strategy cannot consume its source
3456
- type is stopped by the platform itself (\`desired: STOPPED\`), so it holds no place.
3912
+ live-run limit) until then; one that cannot run, and will not by waiting, because its strategy cannot
3913
+ consume its source type or because the pool of a \`dex\` run is not a pool or has no room to be followed,
3914
+ is stopped by the platform itself (\`desired: STOPPED\`), so it holds no place.
3457
3915
  `,
3458
3916
  },
3459
3917
  sources: {