@qtsurfer/api-client 0.7.0 → 0.8.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.
@@ -4,14 +4,14 @@
4
4
  * General response error
5
5
  */
6
6
  export type ResponseError = {
7
- /**
8
- * Status code
9
- */
10
- code: number;
11
- /**
12
- * Error description
13
- */
14
- message: string;
7
+ /**
8
+ * Status code
9
+ */
10
+ code: number;
11
+ /**
12
+ * Error description
13
+ */
14
+ message: string;
15
15
  };
16
16
 
17
17
  /**
@@ -23,211 +23,211 @@ export type Instrument = string;
23
23
  * HAL-style response envelope for the instruments listing
24
24
  */
25
25
  export type InstrumentListResponse = {
26
- /**
27
- * The list of instruments for the segment
28
- */
29
- data: Array<InstrumentDetail>;
30
- meta: InstrumentListMeta;
31
- _links: InstrumentLinks;
26
+ /**
27
+ * The list of instruments for the segment
28
+ */
29
+ data: Array<InstrumentDetail>;
30
+ meta: InstrumentListMeta;
31
+ _links: InstrumentLinks;
32
32
  };
33
33
 
34
34
  /**
35
35
  * Metadata describing the instruments listing
36
36
  */
37
37
  export type InstrumentListMeta = {
38
- /**
39
- * When this listing was last refreshed
40
- */
41
- updatedAt: string;
42
- /**
43
- * The exchange the instruments belong to
44
- */
45
- exchange: string;
46
- /**
47
- * The market segment served in `data`
48
- */
49
- segment: 'spot' | 'futures';
38
+ /**
39
+ * When this listing was last refreshed
40
+ */
41
+ updatedAt: string;
42
+ /**
43
+ * The exchange the instruments belong to
44
+ */
45
+ exchange: string;
46
+ /**
47
+ * The market segment served in `data`
48
+ */
49
+ segment: "spot" | "futures";
50
50
  };
51
51
 
52
52
  /**
53
53
  * HAL `_links` — segment discovery for the instruments listing
54
54
  */
55
55
  export type InstrumentLinks = {
56
- /**
57
- * Link to this listing
58
- */
59
- self: HalLink;
60
- /**
61
- * Link to the spot instruments listing. Present when the exchange has a spot segment.
62
- */
63
- spot?: HalLink;
64
- /**
65
- * Link to the futures instruments listing. Present only when the exchange has a futures segment.
66
- */
67
- futures?: HalLink;
56
+ /**
57
+ * Link to this listing
58
+ */
59
+ self: HalLink;
60
+ /**
61
+ * Link to the spot instruments listing. Present when the exchange has a spot segment.
62
+ */
63
+ spot?: HalLink;
64
+ /**
65
+ * Link to the futures instruments listing. Present only when the exchange has a futures segment.
66
+ */
67
+ futures?: HalLink;
68
68
  };
69
69
 
70
70
  /**
71
71
  * A HAL link object (Hypertext Application Language)
72
72
  */
73
73
  export type HalLink = {
74
- /**
75
- * The link target as an absolute-path URI reference (resolve against the API base). A URI Template (RFC 6570) when `templated` is true.
76
- */
77
- href: string;
78
- /**
79
- * True when `href` is an RFC 6570 URI Template.
80
- */
81
- templated?: boolean;
74
+ /**
75
+ * The link target as an absolute-path URI reference (resolve against the API base). A URI Template (RFC 6570) when `templated` is true.
76
+ */
77
+ href: string;
78
+ /**
79
+ * True when `href` is an RFC 6570 URI Template.
80
+ */
81
+ templated?: boolean;
82
82
  };
83
83
 
84
84
  /**
85
85
  * Exchange instrument with per-data-type coverage and market info
86
86
  */
87
87
  export type InstrumentDetail = {
88
- /**
89
- * Instrument identifier (e.g. currency pair)
90
- */
91
- id: string;
92
- /**
93
- * Base currency
94
- */
95
- base: string;
96
- /**
97
- * Quote currency
98
- */
99
- quote: string;
100
- coverage?: InstrumentCoverage;
101
- /**
102
- * Last traded price
103
- */
104
- lastPrice?: number;
105
- /**
106
- * Trading volume in the last 24 hours (in quote currency)
107
- */
108
- volume24h?: number;
88
+ /**
89
+ * Instrument identifier (e.g. currency pair)
90
+ */
91
+ id: string;
92
+ /**
93
+ * Base currency
94
+ */
95
+ base: string;
96
+ /**
97
+ * Quote currency
98
+ */
99
+ quote: string;
100
+ coverage?: InstrumentCoverage;
101
+ /**
102
+ * Last traded price
103
+ */
104
+ lastPrice?: number;
105
+ /**
106
+ * Trading volume in the last 24 hours (in quote currency)
107
+ */
108
+ volume24h?: number;
109
109
  };
110
110
 
111
111
  /**
112
112
  * Time coverage of available data for this instrument, per data type
113
113
  */
114
114
  export type InstrumentCoverage = {
115
- /**
116
- * Coverage of ticker data
117
- */
118
- tickers?: CoverageWindow;
119
- /**
120
- * Coverage of kline (candlestick) data
121
- */
122
- klines?: CoverageWindow;
115
+ /**
116
+ * Coverage of ticker data
117
+ */
118
+ tickers?: CoverageWindow;
119
+ /**
120
+ * Coverage of kline (candlestick) data
121
+ */
122
+ klines?: CoverageWindow;
123
123
  };
124
124
 
125
125
  /**
126
126
  * The time range of available data for a single data type
127
127
  */
128
128
  export type CoverageWindow = {
129
- /**
130
- * Earliest timestamp with data available
131
- */
132
- from?: string;
133
- /**
134
- * Latest timestamp with data available
135
- */
136
- to?: string;
137
- /**
138
- * If the instrument stopped producing this data type (delisted/inactive), the timestamp it went inactive. Optional — omitted while the instrument is active.
139
- */
140
- inactiveSince?: string;
129
+ /**
130
+ * Earliest timestamp with data available
131
+ */
132
+ from?: string;
133
+ /**
134
+ * Latest timestamp with data available
135
+ */
136
+ to?: string;
137
+ /**
138
+ * If the instrument stopped producing this data type (delisted/inactive), the timestamp it went inactive. Optional — omitted while the instrument is active.
139
+ */
140
+ inactiveSince?: string;
141
141
  };
142
142
 
143
143
  /**
144
144
  * Exchange service provider
145
145
  */
146
146
  export type Exchange = {
147
- /**
148
- * Unique identifier for the exchange
149
- */
150
- id: string;
151
- /**
152
- * Name of the exchange
153
- */
154
- name: string;
155
- /**
156
- * Description of the exchange
157
- */
158
- description?: string;
147
+ /**
148
+ * Unique identifier for the exchange
149
+ */
150
+ id: string;
151
+ /**
152
+ * Name of the exchange
153
+ */
154
+ name: string;
155
+ /**
156
+ * Description of the exchange
157
+ */
158
+ description?: string;
159
159
  };
160
160
 
161
161
  /**
162
162
  * Managed exchange data sources available for backtesting.
163
163
  */
164
- export type DataSourceType = 'ticker';
164
+ export type DataSourceType = "ticker";
165
165
 
166
166
  export type PrepareRequest = {
167
- instrument: Instrument;
168
- /**
169
- * Start date for the preparation process. Supports the following formats:
170
- * - ISO-8601 (e.g. 2024-12-14T23:59:59Z)
171
- * - ISO DATE (e.g. 2024-12-14)
172
- * - BASIC ISO DATE (e.g., 20241214)
173
- *
174
- */
175
- from: string;
176
- /**
177
- * End date for the preparation process. Supports the following formats:
178
- * - ISO-8601 (e.g. 2024-12-14T23:59:59Z)
179
- * - ISO DATE (e.g. 2024-12-14)
180
- * - BASIC ISO DATE (e.g., 20241214)
181
- *
182
- */
183
- to: string;
184
- /**
185
- * Output bar cadence for the prepared range. Defaults to the publisher's
186
- * native cadence (`1s`); coarser cadences are produced on demand via
187
- * resampling and stored alongside the native blob in cache. Coarser-than-
188
- * source values must be exact multiples of the source cadence — invalid
189
- * labels return `400`.
190
- *
191
- */
192
- cadence?: '1s' | '5s' | '1m' | '5m' | '15m' | '1h' | '4h' | '1d';
167
+ instrument: Instrument;
168
+ /**
169
+ * Start date for the preparation process. Supports the following formats:
170
+ * - ISO-8601 (e.g. 2024-12-14T23:59:59Z)
171
+ * - ISO DATE (e.g. 2024-12-14)
172
+ * - BASIC ISO DATE (e.g., 20241214)
173
+ *
174
+ */
175
+ from: string;
176
+ /**
177
+ * End date for the preparation process. Supports the following formats:
178
+ * - ISO-8601 (e.g. 2024-12-14T23:59:59Z)
179
+ * - ISO DATE (e.g. 2024-12-14)
180
+ * - BASIC ISO DATE (e.g., 20241214)
181
+ *
182
+ */
183
+ to: string;
184
+ /**
185
+ * Output bar cadence for the prepared range. Defaults to the publisher's
186
+ * native cadence (`1s`); coarser cadences are produced on demand via
187
+ * resampling and stored alongside the native blob in cache. Coarser-than-
188
+ * source values must be exact multiples of the source cadence — invalid
189
+ * labels return `400`.
190
+ *
191
+ */
192
+ cadence?: "1s" | "5s" | "1m" | "5m" | "15m" | "1h" | "4h" | "1d";
193
193
  };
194
194
 
195
195
  /**
196
196
  * Information about a single job
197
197
  */
198
198
  export type JobState = {
199
- /**
200
- * Opaque context identifier for the job
201
- */
202
- contextId: string;
203
- /**
204
- * Current status of the job. Treat `Completed | Aborted | Failed` as
205
- * terminal; `New | Started` mean keep polling. A single-instrument prepare
206
- * is always terminal (`Completed`) — decide from
207
- * `PrepareJobState.coverageRatio`, not by polling.
208
- *
209
- */
210
- status: 'New' | 'Started' | 'Completed' | 'Aborted' | 'Failed';
211
- /**
212
- * Detailed status information, if available
213
- */
214
- statusDetail?: string | null;
215
- /**
216
- * Total size of the data being prepared
217
- */
218
- size: number;
219
- /**
220
- * The amount of data processed so far
221
- */
222
- completed: number;
223
- /**
224
- * Timestamp for when the preparation started
225
- */
226
- startTime?: string | null;
227
- /**
228
- * Timestamp for when the preparation finished
229
- */
230
- endTime?: string | null;
199
+ /**
200
+ * Opaque context identifier for the job
201
+ */
202
+ contextId: string;
203
+ /**
204
+ * Current status of the job. Treat `Completed | Aborted | Failed` as
205
+ * terminal; `New | Started` mean keep polling. A single-instrument prepare
206
+ * is always terminal (`Completed`) — decide from
207
+ * `PrepareJobState.coverageRatio`, not by polling.
208
+ *
209
+ */
210
+ status: "New" | "Started" | "Completed" | "Aborted" | "Failed";
211
+ /**
212
+ * Detailed status information, if available
213
+ */
214
+ statusDetail?: string | null;
215
+ /**
216
+ * Total size of the data being prepared
217
+ */
218
+ size: number;
219
+ /**
220
+ * The amount of data processed so far
221
+ */
222
+ completed: number;
223
+ /**
224
+ * Timestamp for when the preparation started
225
+ */
226
+ startTime?: string | null;
227
+ /**
228
+ * Timestamp for when the preparation finished
229
+ */
230
+ endTime?: string | null;
231
231
  };
232
232
 
233
233
  /**
@@ -240,175 +240,381 @@ export type JobState = {
240
240
  *
241
241
  */
242
242
  export type PrepareJobState = JobState & {
243
- /**
244
- * Start of the available data range for the prepared instrument.
245
- */
246
- dataFrom?: string | null;
247
- /**
248
- * End of the available data range for the prepared instrument.
249
- */
250
- dataTo?: string | null;
251
- /**
252
- * `hoursWithData / totalHours` in `[0,1]` (`1.0` when `totalHours` is 0) — the
253
- * fraction of hours in the requested range that have served data.
243
+ /**
244
+ * Start of the available data range for the prepared instrument.
245
+ */
246
+ dataFrom?: string | null;
247
+ /**
248
+ * End of the available data range for the prepared instrument.
249
+ */
250
+ dataTo?: string | null;
251
+ /**
252
+ * `hoursWithData / totalHours` in `[0,1]` (`1.0` when `totalHours` is 0) — the
253
+ * fraction of hours in the requested range that have served data.
254
+ *
255
+ */
256
+ coverageRatio?: number;
257
+ /**
258
+ * Number of whole hours in the requested prepare range.
259
+ */
260
+ totalHours?: number;
261
+ /**
262
+ * Number of hours in the range that have data.
263
+ */
264
+ hoursWithData?: number;
265
+ /**
266
+ * One entry per hour in the range that has no data, with a rationale.
267
+ */
268
+ hoursWithoutData?: Array<{
269
+ /**
270
+ * The hour (UTC, hour-aligned) that has no data.
271
+ */
272
+ hour?: string;
273
+ /**
274
+ * Expected row count for the hour (currently always 0; reserved for
275
+ * future use). The rationale never depends on it.
254
276
  *
255
277
  */
256
- coverageRatio?: number;
278
+ expected?: number;
257
279
  /**
258
- * Number of whole hours in the requested prepare range.
259
- */
260
- totalHours?: number;
261
- /**
262
- * Number of hours in the range that have data.
263
- */
264
- hoursWithData?: number;
265
- /**
266
- * One entry per hour in the range that has no data, with a rationale.
280
+ * Why the hour has no data. `pending_conversion`: data for this hour is
281
+ * still being produced — a re-poll may fill it. `low_activity`: the
282
+ * instrument did not trade that hour. `unknown`: no data to classify by.
283
+ *
267
284
  */
268
- hoursWithoutData?: Array<{
269
- /**
270
- * The hour (UTC, hour-aligned) that has no data.
271
- */
272
- hour?: string;
273
- /**
274
- * Expected row count for the hour (currently always 0; reserved for
275
- * future use). The rationale never depends on it.
276
- *
277
- */
278
- expected?: number;
279
- /**
280
- * Why the hour has no data. `pending_conversion`: data for this hour is
281
- * still being produced — a re-poll may fill it. `low_activity`: the
282
- * instrument did not trade that hour. `unknown`: no data to classify by.
283
- *
284
- */
285
- rationale?: 'pending_conversion' | 'low_activity' | 'unknown';
286
- }>;
285
+ rationale?: "pending_conversion" | "low_activity" | "unknown";
286
+ }>;
287
287
  };
288
288
 
289
289
  /**
290
290
  * A numeric range or an explicit list of values for one strategy property.
291
291
  */
292
- export type SweepAxis = {
293
- from: number;
294
- to: number;
295
- step: number;
296
- } | {
297
- values: Array<number | boolean>;
298
- };
292
+ export type SweepAxis =
293
+ | {
294
+ from: number;
295
+ to: number;
296
+ step: number;
297
+ }
298
+ | {
299
+ values: Array<number | boolean>;
300
+ };
299
301
 
300
302
  export type SweepSpecRequest = {
301
- sampler?: 'grid' | 'random' | 'lhs';
302
- /**
303
- * Reproducibility seed. If omitted, the server generates one with Java's
304
- * `L64X128MixRandom` generator and returns the effective value. The range
305
- * is limited to JavaScript-safe integers so generated clients can replay it exactly.
306
- *
307
- */
308
- seed?: number;
309
- /**
310
- * Number of samples for `random` and `lhs`; ignored by `grid`.
311
- */
312
- samples?: number;
313
- objective?: 'sharpe' | 'sortino' | 'pnl' | 'maxdd';
314
- params: {
315
- [key: string]: SweepAxis;
316
- };
303
+ sampler?: "grid" | "random" | "lhs";
304
+ /**
305
+ * Reproducibility seed. If omitted, the server generates one with Java's
306
+ * `L64X128MixRandom` generator and returns the effective value. The range
307
+ * is limited to JavaScript-safe integers so generated clients can replay it exactly.
308
+ *
309
+ */
310
+ seed?: number;
311
+ /**
312
+ * Number of samples for `random` and `lhs`; ignored by `grid`.
313
+ */
314
+ samples?: number;
315
+ objective?: "sharpe" | "sortino" | "pnl" | "maxdd";
316
+ params: {
317
+ [key: string]: SweepAxis;
318
+ };
317
319
  };
318
320
 
319
321
  export type SweepBaseConfig = {
320
- initialFunding?: number;
321
- feeRate?: number;
322
- buyFeeRate?: number;
323
- sellFeeRate?: number;
324
- feeLeg?: 'RECEIVED' | 'QUOTE' | 'BASE';
325
- percentAmountToLock?: number;
322
+ initialFunding?: number;
323
+ feeRate?: number;
324
+ buyFeeRate?: number;
325
+ sellFeeRate?: number;
326
+ feeLeg?: "RECEIVED" | "QUOTE" | "BASE";
327
+ percentAmountToLock?: number;
326
328
  };
327
329
 
328
330
  export type ExecuteSweepRequest = {
329
- strategyId: StrategyId;
330
- sweep: SweepSpecRequest;
331
- baseConfig?: SweepBaseConfig;
332
- /**
333
- * Store signals for every trial. Keep false for normal sweeps.
334
- */
335
- storeSignals?: boolean;
336
- /**
337
- * Requested horizontal shard count; 0 or omitted selects automatically.
338
- */
339
- shards?: number;
340
- /**
341
- * Trials below this trade count are flagged but remain in the results.
342
- */
343
- minTradeFloor?: number;
331
+ strategyId: StrategyId;
332
+ sweep: SweepSpecRequest;
333
+ baseConfig?: SweepBaseConfig;
334
+ /**
335
+ * Store signals for every trial. Keep false for normal sweeps.
336
+ */
337
+ storeSignals?: boolean;
338
+ /**
339
+ * Requested horizontal shard count; 0 or omitted selects automatically.
340
+ */
341
+ shards?: number;
342
+ /**
343
+ * Trials below this trade count are flagged but remain in the results.
344
+ */
345
+ minTradeFloor?: number;
346
+ walkForward?: WalkForwardRequest;
347
+ };
348
+
349
+ /**
350
+ * Opt in to walk-forward validation. Present, the sweep runs as F sequential folds and the result gains a `walkForward` section; absent, nothing about the sweep changes. Two requests that differ only in this block are two different sweeps and do not deduplicate against each other.
351
+ */
352
+ export type WalkForwardRequest = {
353
+ /**
354
+ * How many sequential optimize-then-score windows to run. Two is the minimum for a reason, and it is structural rather than a tuning choice: parameter drift is measured between consecutive fold winners, and a single fold — one train/test split with no sequence — has no consecutive pair to compare, so it would report the strongest possible stability having measured nothing.
355
+ * The upper bound is a server setting (12 by default) and is deliberately not pinned here, since a spec that hardcodes a tunable limit lies the day it is raised. Exceeding it, or exceeding the sweep budget once multiplied by the grid size, is a 400.
356
+ */
357
+ folds: number;
358
+ /**
359
+ * Share of the session each fold spends optimizing; the remainder is where its winner is scored. Lower values leave more data to be scored on and, on short sessions, are also what lets the requested fold count tile the data at all.
360
+ */
361
+ inSamplePct?: number;
344
362
  };
345
363
 
346
364
  export type ExecuteSweepAccepted = {
347
- sweepId: string;
348
- requestId: string;
349
- totalRuns: number;
350
- shards: number;
351
- /**
352
- * Effective seed used to expand the sweep.
353
- */
354
- seed: number;
355
- /**
356
- * False when an identical sweep already exists and was not enqueued again.
357
- */
358
- queued: boolean;
365
+ sweepId: string;
366
+ requestId: string;
367
+ totalRuns: number;
368
+ shards: number;
369
+ /**
370
+ * Effective seed used to expand the sweep.
371
+ */
372
+ seed: number;
373
+ /**
374
+ * False when an identical sweep already exists and was not enqueued again.
375
+ */
376
+ queued: boolean;
377
+ walkForward?: WalkForwardAccepted;
378
+ };
379
+
380
+ /**
381
+ * Echo of the accepted walk-forward configuration, present only when the submit carried one. `inSamplePct` is the resolved value, so a request that omitted it can see what it got.
382
+ */
383
+ export type WalkForwardAccepted = {
384
+ folds: number;
385
+ inSamplePct: number;
386
+ /**
387
+ * What this sweep actually costs, `folds × (grid size + 1)` — the in-sample runs for every fold plus each fold's one out-of-sample run. Deliberately distinct from the top-level `totalRuns`, which stays the size of the grid that was submitted.
388
+ */
389
+ totalRuns: number;
359
390
  };
360
391
 
392
+ /**
393
+ * How far along a sweep is, and — when the sweep is still running — enough to tell a healthy one from a stuck one. The counts partition the shards (or, for a walk-forward sweep, the folds): every unit is either finished, failed, waiting to be retried, or not yet started.
394
+ */
361
395
  export type SweepProgress = {
362
- done: number;
363
- total: number;
364
- aborted: number;
365
- shardCount: number;
366
- pendingShards: number;
396
+ done: number;
397
+ total: number;
398
+ /**
399
+ * Individual runs that executed and aborted. A row-level count: a shard that fails before producing any rows leaves this at 0, which is why `failedShards` exists alongside it.
400
+ */
401
+ aborted: number;
402
+ shardCount: number;
403
+ pendingShards: number;
404
+ /**
405
+ * Shards (or folds) that failed and will not be retried. Distinct from `aborted`: this counts whole units that never reported, not runs that ran badly.
406
+ */
407
+ failedShards: number;
408
+ /**
409
+ * Units whose last attempt failed on something transient — an I/O error, a worker that died mid-read — and which are queued to be attempted again. Not counted as failures, because they have not failed yet; a sweep with a non-zero value here is still expected to complete.
410
+ */
411
+ retrying: number;
412
+ /**
413
+ * Units that have not reported anything yet. Covers both work still queued behind other work and work claimed by a worker that stopped before it began, which is why a sweep with a persistent value here and a rising `stalledSeconds` is worth looking at.
414
+ */
415
+ notStarted: number;
416
+ /**
417
+ * Seconds since anything last advanced. Omitted on a finished sweep, where it would only measure how long ago it finished, and on sweeps submitted before this field existed.
418
+ */
419
+ stalledSeconds?: number;
420
+ /**
421
+ * Rough seconds remaining, extrapolated from the rate observed so far and assuming nothing else competes for workers. Runs conservative in practice — it has measured 2–5× long when a sweep spent part of its life waiting to be retried, since that wait dilutes the observed rate. **Omitted, never zero, when it cannot be computed**: a sweep with nothing finished yet has no rate to extrapolate from, and a zero would read as "about to finish". Excludes queue wait entirely; `retrying` and `stalledSeconds` are where that shows up.
422
+ */
423
+ etaSeconds?: number;
367
424
  };
368
425
 
369
426
  export type SweepRunRow = {
370
- /**
371
- * Deterministic zero-based expansion index, stable across shards and ranking.
372
- */
373
- runIx: number;
374
- /**
375
- * Present only in the `ranked` view.
376
- */
377
- rank?: number;
378
- params: {
379
- [key: string]: unknown;
380
- };
381
- sharpe: number;
382
- sortino: number;
383
- /**
384
- * Absolute net PnL in the output currency.
385
- */
386
- pnl: number;
387
- pnlPct: number;
388
- cagr: number;
389
- maxDdPct: number;
390
- trades: number;
391
- winRate: number;
392
- belowTradeFloor: boolean;
393
- aborted: boolean;
394
- runtimeMs: number;
427
+ /**
428
+ * Deterministic zero-based expansion index, stable across shards and ranking.
429
+ */
430
+ runIx: number;
431
+ /**
432
+ * Present only in the `ranked` view.
433
+ */
434
+ rank?: number;
435
+ /**
436
+ * The objective of the worst run in this point's immediate neighbourhood — how well the region around it holds up, not how well it scored itself. Present only in the `ranked` view when plateau ranking applied. Always read together with `neighbourCount`.
437
+ */
438
+ plateauScore?: number;
439
+ /**
440
+ * How many neighbouring parameter points backed the `plateauScore`. Zero means the point had no neighbours in the grid, so its score is unevidenced rather than confirmed — the value alone cannot be distinguished from a genuinely robust one.
441
+ */
442
+ neighbourCount?: number;
443
+ /**
444
+ * Probability that this run's Sharpe reflects real edge rather than the best draw from however many parameter vectors were tried. Above ~0.95 the result survives the multiple-testing correction; near 0.5 or below it is indistinguishable from the best of a pile of coin flips. Absent on aborted runs, and on sweeps with too few trials to establish any dispersion to deflate against.
445
+ */
446
+ deflatedSharpe?: number;
447
+ params: {
448
+ [key: string]: unknown;
449
+ };
450
+ sharpe: number;
451
+ sortino: number;
452
+ /**
453
+ * Absolute net PnL in the output currency.
454
+ */
455
+ pnl: number;
456
+ pnlPct: number;
457
+ cagr: number;
458
+ maxDdPct: number;
459
+ trades: number;
460
+ winRate: number;
461
+ belowTradeFloor: boolean;
462
+ aborted: boolean;
463
+ runtimeMs: number;
464
+ };
465
+
466
+ /**
467
+ * Sensitivity aggregates over a sweep's stored rows. Marginals are always complete; heatmaps may be capped, in which case `heatmapsTruncated` is true.
468
+ */
469
+ export type SweepSensitivity = {
470
+ sweepId?: string;
471
+ status?: "RUNNING" | "COMPLETED" | "PARTIAL" | "CANCELLED";
472
+ objective?: "sharpe" | "sortino" | "pnl" | "maxdd";
473
+ /**
474
+ * Rows available when this was computed. Grows while a sweep is still running.
475
+ */
476
+ rowsAnalysed?: number;
477
+ marginals?: Array<SweepMarginal>;
478
+ heatmaps?: Array<SweepHeatmap>;
479
+ /**
480
+ * True when at least one two-parameter surface was left out to stay inside the response budget. Told explicitly because a silently short list would read as "these are all the interactions", which is the wrong thing to conclude from a sensitivity view.
481
+ */
482
+ heatmapsTruncated?: boolean;
483
+ };
484
+
485
+ /**
486
+ * One axis, with every other axis collapsed away.
487
+ */
488
+ export type SweepMarginal = {
489
+ param?: string;
490
+ points?: Array<SweepMarginalPoint>;
491
+ };
492
+
493
+ /**
494
+ * How the objective behaved at one value of one axis. `best` and `mean` disagreeing is informative rather than noise: a high `best` with a poor `mean` marks a value that only works alongside particular settings of the other axes.
495
+ */
496
+ export type SweepMarginalPoint = {
497
+ /**
498
+ * The axis value, as it appears in a run's parameters.
499
+ */
500
+ value?: unknown;
501
+ /**
502
+ * Non-aborted runs that used this value.
503
+ */
504
+ count?: number;
505
+ best?: number;
506
+ mean?: number;
507
+ worst?: number;
508
+ };
509
+
510
+ /**
511
+ * The surface for one pair of axes, with all others collapsed away.
512
+ */
513
+ export type SweepHeatmap = {
514
+ paramA?: string;
515
+ paramB?: string;
516
+ cells?: Array<SweepHeatmapCell>;
517
+ };
518
+
519
+ export type SweepHeatmapCell = {
520
+ valueA?: unknown;
521
+ valueB?: unknown;
522
+ count?: number;
523
+ best?: number;
524
+ mean?: number;
395
525
  };
396
526
 
397
527
  export type ExecuteSweepResult = {
398
- sweepId: string;
399
- status: 'RUNNING' | 'COMPLETED' | 'PARTIAL' | 'CANCELLED';
400
- objective: 'sharpe' | 'sortino' | 'pnl' | 'maxdd';
401
- order: 'ranked' | 'natural';
402
- progress: SweepProgress;
403
- /**
404
- * Total result rows currently available.
405
- */
406
- leaderboardSize: number;
407
- /**
408
- * True only when the ranked view exceeds its display limit.
409
- */
410
- truncated: boolean;
411
- leaderboard: Array<SweepRunRow>;
528
+ sweepId: string;
529
+ status: "RUNNING" | "COMPLETED" | "PARTIAL" | "CANCELLED";
530
+ objective: "sharpe" | "sortino" | "pnl" | "maxdd";
531
+ order: "ranked" | "natural";
532
+ /**
533
+ * Which ordering was actually applied, which is not always the one requested: a sweep with no stored parameter grid cannot be plateau-ranked and falls back to `raw`. Always `raw` when `order=natural`.
534
+ */
535
+ ranking?: "plateau" | "raw";
536
+ /**
537
+ * Probability of backtest overfitting for the sweep as a whole, by combinatorially symmetric cross-validation: how often the configuration that won in-sample lands below median out-of-sample. Above ~0.5 the sweep is selecting noise, whatever its top row says. Computed once when the last shard finishes, so it is absent while the sweep is still running and on sweeps too small for the statistic to mean anything.
538
+ */
539
+ pbo?: number;
540
+ /**
541
+ * How many train/test splits the `pbo` figure was averaged over.
542
+ */
543
+ pboSplits?: number;
544
+ progress: SweepProgress;
545
+ /**
546
+ * Total result rows currently available.
547
+ */
548
+ leaderboardSize: number;
549
+ /**
550
+ * True only when the ranked view exceeds its display limit.
551
+ */
552
+ truncated: boolean;
553
+ leaderboard: Array<SweepRunRow>;
554
+ walkForward?: WalkForwardResult;
555
+ };
556
+
557
+ /**
558
+ * Present only on a sweep submitted with `walkForward`, and present from acceptance onward — its presence, not its contents, is what identifies a walk-forward sweep. `completedFolds` is 0 while the first fold is still running.
559
+ */
560
+ export type WalkForwardResult = {
561
+ /**
562
+ * Folds requested at submit.
563
+ */
564
+ folds: number;
565
+ /**
566
+ * Resolved in-sample share each fold optimized on.
567
+ */
568
+ inSamplePct?: number;
569
+ /**
570
+ * Folds that have finished and reported a winner.
571
+ */
572
+ completedFolds: number;
573
+ /**
574
+ * Mean normalized lattice distance between consecutive fold winners. Low is good: winners that stay in a tight band fold after fold are evidence the parameter means something, while winners that jump across the grid every time are the sweep re-fitting noise, and that backtest will not survive contact with live data. **Absent is not zero** — the field is omitted whenever the figure could not be computed (fewer than two folds finished, no stored grid to place winners on), because zero is itself a meaningful reading here and a placeholder would be indistinguishable from perfect stability.
575
+ */
576
+ paramDrift?: number;
577
+ /**
578
+ * One entry per completed fold, oldest first.
579
+ */
580
+ results: Array<WalkForwardFold>;
581
+ };
582
+
583
+ /**
584
+ * What one fold concluded. The out-of-sample row is the answer; the in-sample figure is only there to be compared against it, since any grid produces a flattering in-sample winner — that is what optimizing does. The gap between them is the whole reading.
585
+ */
586
+ export type WalkForwardFold = {
587
+ /**
588
+ * Position in the walk-forward sequence, oldest first.
589
+ */
590
+ foldIx: number;
591
+ /**
592
+ * First index of the optimization window, into the prepared session.
593
+ */
594
+ inSampleFrom: number;
595
+ /**
596
+ * End of the optimization window, exclusive — and where scoring begins.
597
+ */
598
+ inSampleTo: number;
599
+ /**
600
+ * End of the scoring window, exclusive.
601
+ */
602
+ outOfSampleTo: number;
603
+ /**
604
+ * The parameter vector that won this fold's optimization window.
605
+ */
606
+ params: {
607
+ [key: string]: unknown;
608
+ };
609
+ /**
610
+ * How that winner scored on the window it was chosen on.
611
+ */
612
+ inSampleSharpe: number;
613
+ outOfSample: SweepRunRow;
614
+ /**
615
+ * Vectors this fold evaluated in-sample before picking its winner.
616
+ */
617
+ vectorsRun: number;
412
618
  };
413
619
 
414
620
  /**
@@ -417,141 +623,141 @@ export type ExecuteSweepResult = {
417
623
  *
418
624
  */
419
625
  export type AcceptedJob = {
420
- /**
421
- * Unique job identifier; use this to poll for completion.
422
- */
423
- jobId: string;
626
+ /**
627
+ * Unique job identifier; use this to poll for completion.
628
+ */
629
+ jobId: string;
424
630
  };
425
631
 
426
632
  /**
427
633
  * Backtest job result.
428
634
  */
429
635
  export type BacktestJobResult = {
430
- results: ResultMap;
431
- state: JobState;
636
+ results: ResultMap;
637
+ state: JobState;
432
638
  };
433
639
 
434
640
  /**
435
641
  * Execution result map. Always includes core fields (hostName, iops, strategyId, instrument). Yield metrics (pnlTotal, pnlTotalPercent, totalTrades, winRate, equityCurve, etc.) are present when the strategy emitted at least one trade. When signal storage is enabled, includes signal fields described below. `notices` carries what the run had to say about itself, and is absent when it had nothing.
436
642
  */
437
643
  export type ResultMap = {
438
- /**
439
- * Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.
440
- */
441
- hostName?: string;
442
- /**
443
- * Instrument operations per second throughput during execution
444
- */
445
- iops?: number;
446
- /**
447
- * **Not the `strategyId` you compiled with** — this is the execution context id,
448
- * `strategy:<user>:<strategyId>`. The compiled strategy's id is the last `:`-separated
449
- * segment; that, not this whole string, is what `GET /strategy/{strategyId}` takes.
450
- *
451
- * Take the segment after the last `:` rather than counting from the front: the shape has
452
- * changed once already and callers that indexed a fixed position broke on it.
453
- *
454
- */
455
- strategyId: string;
456
- /**
457
- * The instrument (currency pair) that was backtested
458
- */
459
- instrument: string;
460
- /**
461
- * Diagnostics the engine raised over this run, each with `provenance: execute`.
462
- *
463
- * **Absent means nothing was raised.** This is the one surface where silence is a real
464
- * answer: the run happened, over your data, start to finish, and the engine found nothing
465
- * worth saying. That is not true of the compile path, where an empty list only means a
466
- * short synthetic series reached nothing — see `GET /strategy/{strategyId}`.
467
- *
468
- * Notices are raised on failed and aborted runs too, and those are the ones most worth
469
- * reading: a run that produced no trades often did so for a reason stated here.
470
- *
471
- */
472
- notices?: Array<Notice>;
473
- /**
474
- * How many notices were dropped past the cap of 50. Absent when none were. A large value usually means one fault repeating per instrument or per parameter vector rather than 50 distinct problems.
475
- */
476
- noticesTruncated?: number;
477
- /**
478
- * Total profit and loss in the output currency
479
- */
480
- pnlTotal?: number;
481
- /**
482
- * Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.
483
- */
484
- pnlTotalPercent?: number;
485
- /**
486
- * Total number of trades executed by the strategy
487
- */
488
- totalTrades?: number;
489
- /**
490
- * Percentage of profitable trades (0-100)
491
- */
492
- winRate?: number;
493
- /**
494
- * Risk-adjusted return ratio (mean return / standard deviation of returns)
495
- */
496
- sharpeRatio?: number;
497
- /**
498
- * Downside risk-adjusted return ratio (mean return / downside deviation)
499
- */
500
- sortinoRatio?: number;
501
- /**
502
- * Compound Annual Growth Rate
503
- */
504
- cagr?: number;
505
- /**
506
- * Maximum absolute drawdown in the output currency
507
- */
508
- maxDrawdown?: number;
509
- /**
510
- * Maximum percentage drawdown from peak equity
511
- */
512
- maxDrawdownPercent?: number;
513
- /**
514
- * Equity curve over the backtest. Element 0 is an anchor at the backtest `from` with `initialCapital`; the remaining points are one sample per emitted yield, in order. Use it to plot the strategy's running equity without re-deriving it from the yield history.
515
- */
516
- equityCurve?: Array<EquityPoint>;
517
- /**
518
- * Number of signals emitted during strategy execution
519
- */
520
- signalCount?: number;
521
- /**
522
- * Storage key for the signals file. Treat as opaque; use signalsUrl to download.
523
- */
524
- signalsId?: string;
525
- /**
526
- * HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.
527
- */
528
- signalsUrl?: string;
529
- /**
530
- * Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.
531
- */
532
- signalsUpload?: 'Done' | 'Failed' | 'Skipped';
533
- /**
534
- * ISO 8601 timestamp of when the upload completed. Only present when signalsUpload is Done.
535
- */
536
- signalsUploadedAt?: string;
537
- /**
538
- * Human-readable reason when signalsUpload is Failed or Skipped.
539
- */
540
- signalsUploadReason?: string;
644
+ /**
645
+ * Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.
646
+ */
647
+ hostName?: string;
648
+ /**
649
+ * Instrument operations per second throughput during execution
650
+ */
651
+ iops?: number;
652
+ /**
653
+ * **Not the `strategyId` you compiled with** — this is the execution context id,
654
+ * `strategy:<user>:<strategyId>`. The compiled strategy's id is the last `:`-separated
655
+ * segment; that, not this whole string, is what `GET /strategy/{strategyId}` takes.
656
+ *
657
+ * Take the segment after the last `:` rather than counting from the front: the shape has
658
+ * changed once already and callers that indexed a fixed position broke on it.
659
+ *
660
+ */
661
+ strategyId: string;
662
+ /**
663
+ * The instrument (currency pair) that was backtested
664
+ */
665
+ instrument: string;
666
+ /**
667
+ * Diagnostics the engine raised over this run, each with `provenance: execute`.
668
+ *
669
+ * **Absent means nothing was raised.** This is the one surface where silence is a real
670
+ * answer: the run happened, over your data, start to finish, and the engine found nothing
671
+ * worth saying. That is not true of the compile path, where an empty list only means a
672
+ * short synthetic series reached nothing — see `GET /strategy/{strategyId}`.
673
+ *
674
+ * Notices are raised on failed and aborted runs too, and those are the ones most worth
675
+ * reading: a run that produced no trades often did so for a reason stated here.
676
+ *
677
+ */
678
+ notices?: Array<Notice>;
679
+ /**
680
+ * How many notices were dropped past the cap of 50. Absent when none were. A large value usually means one fault repeating per instrument or per parameter vector rather than 50 distinct problems.
681
+ */
682
+ noticesTruncated?: number;
683
+ /**
684
+ * Total profit and loss in the output currency
685
+ */
686
+ pnlTotal?: number;
687
+ /**
688
+ * Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.
689
+ */
690
+ pnlTotalPercent?: number;
691
+ /**
692
+ * Total number of trades executed by the strategy
693
+ */
694
+ totalTrades?: number;
695
+ /**
696
+ * Percentage of profitable trades (0-100)
697
+ */
698
+ winRate?: number;
699
+ /**
700
+ * Risk-adjusted return ratio (mean return / standard deviation of returns)
701
+ */
702
+ sharpeRatio?: number;
703
+ /**
704
+ * Downside risk-adjusted return ratio (mean return / downside deviation)
705
+ */
706
+ sortinoRatio?: number;
707
+ /**
708
+ * Compound Annual Growth Rate
709
+ */
710
+ cagr?: number;
711
+ /**
712
+ * Maximum absolute drawdown in the output currency
713
+ */
714
+ maxDrawdown?: number;
715
+ /**
716
+ * Maximum percentage drawdown from peak equity
717
+ */
718
+ maxDrawdownPercent?: number;
719
+ /**
720
+ * Equity curve over the backtest. Element 0 is an anchor at the backtest `from` with `initialCapital`; the remaining points are one sample per emitted yield, in order. Use it to plot the strategy's running equity without re-deriving it from the yield history.
721
+ */
722
+ equityCurve?: Array<EquityPoint>;
723
+ /**
724
+ * Number of signals emitted during strategy execution
725
+ */
726
+ signalCount?: number;
727
+ /**
728
+ * Storage key for the signals file. Treat as opaque; use signalsUrl to download.
729
+ */
730
+ signalsId?: string;
731
+ /**
732
+ * HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.
733
+ */
734
+ signalsUrl?: string;
735
+ /**
736
+ * Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.
737
+ */
738
+ signalsUpload?: "Done" | "Failed" | "Skipped";
739
+ /**
740
+ * ISO 8601 timestamp of when the upload completed. Only present when signalsUpload is Done.
741
+ */
742
+ signalsUploadedAt?: string;
743
+ /**
744
+ * Human-readable reason when signalsUpload is Failed or Skipped.
745
+ */
746
+ signalsUploadReason?: string;
541
747
  };
542
748
 
543
749
  /**
544
750
  * Single sample of the running equity at a yield event.
545
751
  */
546
752
  export type EquityPoint = {
547
- /**
548
- * Epoch milliseconds. The first point in an equity curve is anchored at the backtest `from`; subsequent points carry the timestamp of each emitted yield.
549
- */
550
- timestamp: number;
551
- /**
552
- * Running equity at this point (`initialCapital + cumulativePnl`).
553
- */
554
- equity: number;
753
+ /**
754
+ * Epoch milliseconds. The first point in an equity curve is anchored at the backtest `from`; subsequent points carry the timestamp of each emitted yield.
755
+ */
756
+ timestamp: number;
757
+ /**
758
+ * Running equity at this point (`initialCapital + cumulativePnl`).
759
+ */
760
+ equity: number;
555
761
  };
556
762
 
557
763
  /**
@@ -568,25 +774,25 @@ export type StrategyId = string;
568
774
  *
569
775
  */
570
776
  export type Notice = {
571
- /**
572
- * Severity as the engine classified it.
573
- */
574
- level: string;
575
- /**
576
- * Stable identifier for the kind of finding; safe to match on.
577
- */
578
- code: string;
579
- /**
580
- * Human-readable explanation.
581
- */
582
- message: string;
583
- /**
584
- * Where it came from, which matters because the two silences differ: an empty list from a
585
- * real run (`execute`) is a clean bill of health, while an empty list from
586
- * `compile-dry-run` is only a lower bound over a bounded synthetic series.
587
- *
588
- */
589
- provenance?: 'execute' | 'compile-dry-run';
777
+ /**
778
+ * Severity as the engine classified it.
779
+ */
780
+ level: string;
781
+ /**
782
+ * Stable identifier for the kind of finding; safe to match on.
783
+ */
784
+ code: string;
785
+ /**
786
+ * Human-readable explanation.
787
+ */
788
+ message: string;
789
+ /**
790
+ * Where it came from, which matters because the two silences differ: an empty list from a
791
+ * real run (`execute`) is a clean bill of health, while an empty list from
792
+ * `compile-dry-run` is only a lower bound over a bounded synthetic series.
793
+ *
794
+ */
795
+ provenance?: "execute" | "compile-dry-run";
590
796
  };
591
797
 
592
798
  /**
@@ -598,796 +804,869 @@ export type Notice = {
598
804
  *
599
805
  */
600
806
  export type StrategyState = {
601
- strategyId: StrategyId;
602
- /**
603
- * * `not_validated` — registered, never checked. `POST /strategy/{strategyId}/validate`
604
- * checks it.
605
- * * `pending` — a check was asked for and has not answered yet.
606
- * * `passed` — the class loaded and survived its first event.
607
- * * `failed` — it did not; `detail` says how.
608
- *
609
- */
610
- validation: 'not_validated' | 'pending' | 'passed' | 'failed';
611
- /**
612
- * When the live compilation was produced.
613
- */
614
- compiledAt?: string;
615
- /**
616
- * The market data a strategy needs, read off the compiled class rather than off anything
617
- * you sent — `TickerStrategy`, `KlineStrategy` and `FundingRateStrategy` each declare one,
618
- * and a `MultiSourceStrategy` declares a set.
619
- *
620
- * **Absent is not "needs nothing".** A strategy always needs market data, so an absent
621
- * field never means an empty requirement — it means the platform could not establish the
622
- * answer without constructing your strategy, which it will not do to fill in a field.
623
- * That happens for a `MultiSourceStrategy`, for a class that overrides
624
- * `getMarketDataSource()`, and for anything registered before this field existed;
625
- * re-registering the source fills it in.
626
- *
627
- */
628
- requiredSources?: Array<'Ticker' | 'KLine' | 'FundingRate'>;
629
- /**
630
- * When the verdict was recorded. Absent until there is one.
631
- */
632
- validatedAt?: string;
633
- /**
634
- * Why validation failed, or why a queued check has not reported. Present on `failed`, and
635
- * alongside `validationStalled`.
636
- *
637
- */
638
- detail?: string;
639
- /**
640
- * What the run surfaced. An empty or absent list is not a clean bill of health when
641
- * `dryRunIncomplete` is true — see that field.
642
- *
643
- */
644
- notices?: Array<Notice>;
645
- /**
646
- * How many notices were dropped past the cap. Absent when none were.
647
- */
648
- noticesTruncated?: number;
649
- /**
650
- * The check did not finish its budget — it ran out of time, was refused because the
651
- * platform was already holding too many unfinishable runs, or hit a failure attributable to
652
- * the synthetic instrument rather than to your strategy. The verdict stands as far as it
653
- * went; it simply reached less than a full run would.
654
- *
655
- */
656
- dryRunIncomplete?: boolean;
657
- /**
658
- * A queued check has not reported for far longer than one takes. Nothing is disproved about
659
- * the strategy — the check has not run. Stop waiting and re-request it later.
660
- *
661
- */
662
- validationStalled?: boolean;
807
+ strategyId: StrategyId;
808
+ /**
809
+ * * `not_validated` — registered, never checked. `POST /strategy/{strategyId}/validate`
810
+ * checks it.
811
+ * * `pending` — a check was asked for and has not answered yet.
812
+ * * `passed` — the class loaded and survived its first event.
813
+ * * `failed` — it did not; `detail` says how.
814
+ *
815
+ */
816
+ validation: "not_validated" | "pending" | "passed" | "failed";
817
+ /**
818
+ * When the live compilation was produced.
819
+ */
820
+ compiledAt?: string;
821
+ /**
822
+ * The market data a strategy needs, read off the compiled class rather than off anything
823
+ * you sent — `TickerStrategy`, `KlineStrategy` and `FundingRateStrategy` each declare one,
824
+ * and a `MultiSourceStrategy` declares a set.
825
+ *
826
+ * **Absent is not "needs nothing".** A strategy always needs market data, so an absent
827
+ * field never means an empty requirement — it means the platform could not establish the
828
+ * answer without constructing your strategy, which it will not do to fill in a field.
829
+ * That happens for a `MultiSourceStrategy`, for a class that overrides
830
+ * `getMarketDataSource()`, and for anything registered before this field existed;
831
+ * re-registering the source fills it in.
832
+ *
833
+ */
834
+ requiredSources?: Array<"Ticker" | "KLine" | "FundingRate">;
835
+ /**
836
+ * When the verdict was recorded. Absent until there is one.
837
+ */
838
+ validatedAt?: string;
839
+ /**
840
+ * Why validation failed, or why a queued check has not reported. Present on `failed`, and
841
+ * alongside `validationStalled`.
842
+ *
843
+ */
844
+ detail?: string;
845
+ /**
846
+ * What the run surfaced. An empty or absent list is not a clean bill of health when
847
+ * `dryRunIncomplete` is true — see that field.
848
+ *
849
+ */
850
+ notices?: Array<Notice>;
851
+ /**
852
+ * How many notices were dropped past the cap. Absent when none were.
853
+ */
854
+ noticesTruncated?: number;
855
+ /**
856
+ * The check did not finish its budget — it ran out of time, was refused because the
857
+ * platform was already holding too many unfinishable runs, or hit a failure attributable to
858
+ * the synthetic instrument rather than to your strategy. The verdict stands as far as it
859
+ * went; it simply reached less than a full run would.
860
+ *
861
+ */
862
+ dryRunIncomplete?: boolean;
863
+ /**
864
+ * A queued check has not reported for far longer than one takes. Nothing is disproved about
865
+ * the strategy — the check has not run. Stop waiting and re-request it later.
866
+ *
867
+ */
868
+ validationStalled?: boolean;
663
869
  };
664
870
 
665
871
  export type AuthTokenResponse = {
666
- /**
667
- * Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.
668
- */
669
- access_token: string;
670
- /**
671
- * Always `Bearer`.
672
- */
673
- token_type: 'Bearer';
674
- /**
675
- * Seconds until the JWT expires (typically 3600).
676
- */
677
- expires_in: number;
678
- /**
679
- * Scopes granted to this token. Reserved for future use; currently always empty.
680
- */
681
- scopes?: Array<string>;
682
- /**
683
- * Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.
684
- */
685
- tier: 'free' | 'basic' | 'pro' | 'elite';
872
+ /**
873
+ * Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.
874
+ */
875
+ access_token: string;
876
+ /**
877
+ * Always `Bearer`.
878
+ */
879
+ token_type: "Bearer";
880
+ /**
881
+ * Seconds until the JWT expires (typically 3600).
882
+ */
883
+ expires_in: number;
884
+ /**
885
+ * Scopes granted to this token. Reserved for future use; currently always empty.
886
+ */
887
+ scopes?: Array<string>;
888
+ /**
889
+ * Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.
890
+ */
891
+ tier: "free" | "basic" | "pro" | "elite";
686
892
  };
687
893
 
688
894
  /**
689
895
  * Error envelope returned by `POST /auth/token` when the API key is rejected.
690
896
  */
691
897
  export type AuthTokenError = {
692
- /**
693
- * Machine-readable error reason.
694
- */
695
- code: 'invalid_apikey' | 'apikey_revoked' | 'apikey_expired';
696
- /**
697
- * Human-readable description of the failure.
698
- */
699
- message: string;
898
+ /**
899
+ * Machine-readable error reason.
900
+ */
901
+ code: "invalid_apikey" | "apikey_revoked" | "apikey_expired";
902
+ /**
903
+ * Human-readable description of the failure.
904
+ */
905
+ message: string;
700
906
  };
701
907
 
702
908
  export type AuthenticateData = {
703
- body?: never;
704
- path?: never;
705
- query?: never;
706
- url: '/auth/token';
909
+ body?: never;
910
+ path?: never;
911
+ query?: never;
912
+ url: "/auth/token";
707
913
  };
708
914
 
709
915
  export type AuthenticateErrors = {
710
- /**
711
- * API key is invalid, revoked, or expired.
712
- */
713
- 401: AuthTokenError;
714
- /**
715
- * Rate limit exceeded for this API key.
716
- */
717
- 429: unknown;
916
+ /**
917
+ * API key is invalid, revoked, or expired.
918
+ */
919
+ 401: AuthTokenError;
920
+ /**
921
+ * Rate limit exceeded for this API key.
922
+ */
923
+ 429: unknown;
718
924
  };
719
925
 
720
926
  export type AuthenticateError = AuthenticateErrors[keyof AuthenticateErrors];
721
927
 
722
928
  export type AuthenticateResponses = {
723
- /**
724
- * API key accepted; JWT returned.
725
- */
726
- 200: AuthTokenResponse;
929
+ /**
930
+ * API key accepted; JWT returned.
931
+ */
932
+ 200: AuthTokenResponse;
727
933
  };
728
934
 
729
- export type AuthenticateResponse = AuthenticateResponses[keyof AuthenticateResponses];
935
+ export type AuthenticateResponse =
936
+ AuthenticateResponses[keyof AuthenticateResponses];
730
937
 
731
938
  export type ListExchangesData = {
732
- body?: never;
733
- path?: never;
734
- query?: never;
735
- url: '/exchanges';
939
+ body?: never;
940
+ path?: never;
941
+ query?: never;
942
+ url: "/exchanges";
736
943
  };
737
944
 
738
945
  export type ListExchangesResponses = {
739
- /**
740
- * A JSON array of Exchanges
741
- */
742
- 200: Array<Exchange>;
946
+ /**
947
+ * A JSON array of Exchanges
948
+ */
949
+ 200: Array<Exchange>;
743
950
  };
744
951
 
745
- export type ListExchangesResponse = ListExchangesResponses[keyof ListExchangesResponses];
952
+ export type ListExchangesResponse =
953
+ ListExchangesResponses[keyof ListExchangesResponses];
746
954
 
747
955
  export type ListInstrumentsData = {
748
- body?: never;
749
- path: {
750
- /**
751
- * ID of the exchange to retrieve instruments for
752
- */
753
- exchangeId: string;
754
- };
755
- query?: never;
756
- url: '/exchange/{exchangeId}/instruments';
956
+ body?: never;
957
+ path: {
958
+ /**
959
+ * ID of the exchange to retrieve instruments for
960
+ */
961
+ exchangeId: string;
962
+ };
963
+ query?: never;
964
+ url: "/exchange/{exchangeId}/instruments";
757
965
  };
758
966
 
759
967
  export type ListInstrumentsErrors = {
760
- /**
761
- * Exchange not found or instrument catalog not available
762
- */
763
- 404: ResponseError;
968
+ /**
969
+ * Exchange not found or instrument catalog not available
970
+ */
971
+ 404: ResponseError;
764
972
  };
765
973
 
766
- export type ListInstrumentsError = ListInstrumentsErrors[keyof ListInstrumentsErrors];
974
+ export type ListInstrumentsError =
975
+ ListInstrumentsErrors[keyof ListInstrumentsErrors];
767
976
 
768
977
  export type ListInstrumentsResponses = {
769
- /**
770
- * The default (spot) segment's instruments in `data`, `meta`, and HAL `_links` (self + spot/futures segment discovery)
771
- */
772
- 200: InstrumentListResponse;
978
+ /**
979
+ * The default (spot) segment's instruments in `data`, `meta`, and HAL `_links` (self + spot/futures segment discovery)
980
+ */
981
+ 200: InstrumentListResponse;
773
982
  };
774
983
 
775
- export type ListInstrumentsResponse = ListInstrumentsResponses[keyof ListInstrumentsResponses];
984
+ export type ListInstrumentsResponse =
985
+ ListInstrumentsResponses[keyof ListInstrumentsResponses];
776
986
 
777
987
  export type ListSegmentInstrumentsData = {
778
- body?: never;
779
- path: {
780
- /**
781
- * ID of the exchange to retrieve instruments for
782
- */
783
- exchangeId: string;
784
- /**
785
- * Market segment to list instruments for
786
- */
787
- segment: 'spot' | 'futures';
788
- };
789
- query?: never;
790
- url: '/exchange/{exchangeId}/{segment}/instruments';
988
+ body?: never;
989
+ path: {
990
+ /**
991
+ * ID of the exchange to retrieve instruments for
992
+ */
993
+ exchangeId: string;
994
+ /**
995
+ * Market segment to list instruments for
996
+ */
997
+ segment: "spot" | "futures";
998
+ };
999
+ query?: never;
1000
+ url: "/exchange/{exchangeId}/{segment}/instruments";
791
1001
  };
792
1002
 
793
1003
  export type ListSegmentInstrumentsErrors = {
794
- /**
795
- * Exchange, segment, or instrument catalog not found
796
- */
797
- 404: ResponseError;
1004
+ /**
1005
+ * Exchange, segment, or instrument catalog not found
1006
+ */
1007
+ 404: ResponseError;
798
1008
  };
799
1009
 
800
- export type ListSegmentInstrumentsError = ListSegmentInstrumentsErrors[keyof ListSegmentInstrumentsErrors];
1010
+ export type ListSegmentInstrumentsError =
1011
+ ListSegmentInstrumentsErrors[keyof ListSegmentInstrumentsErrors];
801
1012
 
802
1013
  export type ListSegmentInstrumentsResponses = {
803
- /**
804
- * An object with a `data` array of instrument details (each with per-data-type coverage) and a `meta` block
805
- */
806
- 200: InstrumentListResponse;
1014
+ /**
1015
+ * An object with a `data` array of instrument details (each with per-data-type coverage) and a `meta` block
1016
+ */
1017
+ 200: InstrumentListResponse;
807
1018
  };
808
1019
 
809
- export type ListSegmentInstrumentsResponse = ListSegmentInstrumentsResponses[keyof ListSegmentInstrumentsResponses];
1020
+ export type ListSegmentInstrumentsResponse =
1021
+ ListSegmentInstrumentsResponses[keyof ListSegmentInstrumentsResponses];
810
1022
 
811
1023
  export type DownloadTickersData = {
812
- body?: never;
813
- path: {
814
- /**
815
- * ID of the exchange (e.g. `binance`).
816
- */
817
- exchangeId: string;
818
- /**
819
- * Base asset symbol (first leg of the pair).
820
- */
821
- base: string;
822
- /**
823
- * Quote asset symbol (second leg of the pair).
824
- */
825
- quote: string;
826
- };
827
- query: {
828
- /**
829
- * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
830
- * `[HH:00:00Z, HH+1:00:00Z)`.
831
- *
832
- */
833
- hour: string;
834
- /**
835
- * Response wire format. `lastra` (default) returns raw Lastra bytes.
836
- * `parquet` returns Parquet via on-the-fly conversion using
837
- * [lastra-convert](https://github.com/QTSurfer/lastra-convert).
838
- *
839
- */
840
- format?: 'lastra' | 'parquet';
841
- };
842
- url: '/exchange/{exchangeId}/tickers/{base}/{quote}';
843
- };
844
-
845
- export type DownloadTickersErrors = {
1024
+ body?: never;
1025
+ path: {
846
1026
  /**
847
- * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
1027
+ * ID of the exchange (e.g. `binance`).
848
1028
  */
849
- 400: ResponseError;
1029
+ exchangeId: string;
850
1030
  /**
851
- * No Lastra segment exists for the requested instrument/hour.
1031
+ * Base asset symbol (first leg of the pair).
852
1032
  */
853
- 404: ResponseError;
1033
+ base: string;
854
1034
  /**
855
- * Unexpected I/O error serving the file.
1035
+ * Quote asset symbol (second leg of the pair).
856
1036
  */
857
- 500: ResponseError;
858
- };
859
-
860
- export type DownloadTickersError = DownloadTickersErrors[keyof DownloadTickersErrors];
861
-
862
- export type DownloadTickersResponses = {
1037
+ quote: string;
1038
+ };
1039
+ query: {
1040
+ /**
1041
+ * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
1042
+ * `[HH:00:00Z, HH+1:00:00Z)`.
1043
+ *
1044
+ */
1045
+ hour: string;
863
1046
  /**
864
- * One hour of tickers for the instrument. `Content-Type` is
865
- * `application/vnd.lastra` by default or
866
- * `application/vnd.apache.parquet` when `format=parquet` was
867
- * requested.
1047
+ * Response wire format. `lastra` (default) returns raw Lastra bytes.
1048
+ * `parquet` returns Parquet via on-the-fly conversion using
1049
+ * [lastra-convert](https://github.com/QTSurfer/lastra-convert).
868
1050
  *
869
1051
  */
870
- 200: Blob | File;
1052
+ format?: "lastra" | "parquet";
1053
+ };
1054
+ url: "/exchange/{exchangeId}/tickers/{base}/{quote}";
871
1055
  };
872
1056
 
873
- export type DownloadTickersResponse = DownloadTickersResponses[keyof DownloadTickersResponses];
1057
+ export type DownloadTickersErrors = {
1058
+ /**
1059
+ * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
1060
+ */
1061
+ 400: ResponseError;
1062
+ /**
1063
+ * No Lastra segment exists for the requested instrument/hour.
1064
+ */
1065
+ 404: ResponseError;
1066
+ /**
1067
+ * Unexpected I/O error serving the file.
1068
+ */
1069
+ 500: ResponseError;
1070
+ };
1071
+
1072
+ export type DownloadTickersError =
1073
+ DownloadTickersErrors[keyof DownloadTickersErrors];
874
1074
 
875
- export type DownloadKlinesData = {
876
- body?: never;
877
- path: {
878
- /**
879
- * ID of the exchange (e.g. `binance`).
880
- */
881
- exchangeId: string;
882
- /**
883
- * Base asset symbol.
884
- */
885
- base: string;
886
- /**
887
- * Quote asset symbol.
888
- */
889
- quote: string;
890
- };
891
- query: {
892
- /**
893
- * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
894
- * `[HH:00:00Z, HH+1:00:00Z)`.
895
- *
896
- */
897
- hour: string;
898
- /**
899
- * Response wire format. `lastra` (default) returns raw Lastra bytes.
900
- * `parquet` returns Parquet via on-the-fly conversion.
901
- *
902
- */
903
- format?: 'lastra' | 'parquet';
904
- };
905
- url: '/exchange/{exchangeId}/klines/{base}/{quote}';
1075
+ export type DownloadTickersResponses = {
1076
+ /**
1077
+ * One hour of tickers for the instrument. `Content-Type` is
1078
+ * `application/vnd.lastra` by default or
1079
+ * `application/vnd.apache.parquet` when `format=parquet` was
1080
+ * requested.
1081
+ *
1082
+ */
1083
+ 200: Blob | File;
906
1084
  };
907
1085
 
908
- export type DownloadKlinesErrors = {
1086
+ export type DownloadTickersResponse =
1087
+ DownloadTickersResponses[keyof DownloadTickersResponses];
1088
+
1089
+ export type DownloadKlinesData = {
1090
+ body?: never;
1091
+ path: {
1092
+ /**
1093
+ * ID of the exchange (e.g. `binance`).
1094
+ */
1095
+ exchangeId: string;
909
1096
  /**
910
- * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
1097
+ * Base asset symbol.
911
1098
  */
912
- 400: ResponseError;
1099
+ base: string;
913
1100
  /**
914
- * No Lastra segment exists for the requested instrument/hour.
1101
+ * Quote asset symbol.
915
1102
  */
916
- 404: ResponseError;
1103
+ quote: string;
1104
+ };
1105
+ query: {
917
1106
  /**
918
- * Unexpected I/O error serving the file.
1107
+ * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
1108
+ * `[HH:00:00Z, HH+1:00:00Z)`.
1109
+ *
919
1110
  */
920
- 500: ResponseError;
1111
+ hour: string;
1112
+ /**
1113
+ * Response wire format. `lastra` (default) returns raw Lastra bytes.
1114
+ * `parquet` returns Parquet via on-the-fly conversion.
1115
+ *
1116
+ */
1117
+ format?: "lastra" | "parquet";
1118
+ };
1119
+ url: "/exchange/{exchangeId}/klines/{base}/{quote}";
921
1120
  };
922
1121
 
923
- export type DownloadKlinesError = DownloadKlinesErrors[keyof DownloadKlinesErrors];
1122
+ export type DownloadKlinesErrors = {
1123
+ /**
1124
+ * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
1125
+ */
1126
+ 400: ResponseError;
1127
+ /**
1128
+ * No Lastra segment exists for the requested instrument/hour.
1129
+ */
1130
+ 404: ResponseError;
1131
+ /**
1132
+ * Unexpected I/O error serving the file.
1133
+ */
1134
+ 500: ResponseError;
1135
+ };
1136
+
1137
+ export type DownloadKlinesError =
1138
+ DownloadKlinesErrors[keyof DownloadKlinesErrors];
924
1139
 
925
1140
  export type DownloadKlinesResponses = {
926
- /**
927
- * One hour of klines for the instrument. `Content-Type` is
928
- * `application/vnd.lastra` by default or
929
- * `application/vnd.apache.parquet` when `format=parquet`.
930
- *
931
- */
932
- 200: Blob | File;
1141
+ /**
1142
+ * One hour of klines for the instrument. `Content-Type` is
1143
+ * `application/vnd.lastra` by default or
1144
+ * `application/vnd.apache.parquet` when `format=parquet`.
1145
+ *
1146
+ */
1147
+ 200: Blob | File;
933
1148
  };
934
1149
 
935
- export type DownloadKlinesResponse = DownloadKlinesResponses[keyof DownloadKlinesResponses];
1150
+ export type DownloadKlinesResponse =
1151
+ DownloadKlinesResponses[keyof DownloadKlinesResponses];
936
1152
 
937
1153
  export type CompileStrategyData = {
938
- /**
939
- * Raw strategy Java source code
940
- */
941
- body: string;
942
- path?: never;
943
- query?: never;
944
- url: '/strategy';
1154
+ /**
1155
+ * Raw strategy Java source code
1156
+ */
1157
+ body: string;
1158
+ path?: never;
1159
+ query?: never;
1160
+ url: "/strategy";
945
1161
  };
946
1162
 
947
1163
  export type CompileStrategyErrors = {
948
- /**
949
- * The source is not valid Java; the message carries the compiler diagnostics. Nothing is
950
- * registered, so there is no id to look up afterwards.
951
- *
952
- */
953
- 400: ResponseError;
954
- /**
955
- * Too many compilations in flight. Retry later.
956
- */
957
- 429: ResponseError;
1164
+ /**
1165
+ * The source is not valid Java; the message carries the compiler diagnostics. Nothing is
1166
+ * registered, so there is no id to look up afterwards.
1167
+ *
1168
+ */
1169
+ 400: ResponseError;
1170
+ /**
1171
+ * Too many compilations in flight. Retry later.
1172
+ */
1173
+ 429: ResponseError;
958
1174
  };
959
1175
 
960
- export type CompileStrategyError = CompileStrategyErrors[keyof CompileStrategyErrors];
1176
+ export type CompileStrategyError =
1177
+ CompileStrategyErrors[keyof CompileStrategyErrors];
961
1178
 
962
1179
  export type CompileStrategyResponses = {
963
- /**
964
- * Compiled and registered
965
- */
966
- 200: {
967
- strategyId: StrategyId;
968
- };
1180
+ /**
1181
+ * Compiled and registered
1182
+ */
1183
+ 200: {
1184
+ strategyId: StrategyId;
1185
+ };
969
1186
  };
970
1187
 
971
- export type CompileStrategyResponse = CompileStrategyResponses[keyof CompileStrategyResponses];
1188
+ export type CompileStrategyResponse =
1189
+ CompileStrategyResponses[keyof CompileStrategyResponses];
972
1190
 
973
1191
  export type ValidateStrategyData = {
974
- body?: never;
975
- path: {
976
- /**
977
- * The id returned by `POST /strategy`
978
- */
979
- strategyId: StrategyId;
980
- };
981
- query?: never;
982
- url: '/strategy/{strategyId}/validate';
1192
+ body?: never;
1193
+ path: {
1194
+ /**
1195
+ * The id returned by `POST /strategy`
1196
+ */
1197
+ strategyId: StrategyId;
1198
+ };
1199
+ query?: never;
1200
+ url: "/strategy/{strategyId}/validate";
983
1201
  };
984
1202
 
985
1203
  export type ValidateStrategyErrors = {
986
- /**
987
- * No such registered strategy for this user
988
- */
989
- 404: ResponseError;
1204
+ /**
1205
+ * No such registered strategy for this user
1206
+ */
1207
+ 404: ResponseError;
990
1208
  };
991
1209
 
992
- export type ValidateStrategyError = ValidateStrategyErrors[keyof ValidateStrategyErrors];
1210
+ export type ValidateStrategyError =
1211
+ ValidateStrategyErrors[keyof ValidateStrategyErrors];
993
1212
 
994
1213
  export type ValidateStrategyResponses = {
995
- /**
996
- * Already validated; the recorded verdict, unchanged
997
- */
998
- 200: StrategyState;
999
- /**
1000
- * Validation queued. Not a terminal outcome — poll `GET /strategy/{strategyId}` until
1001
- * `validation` leaves `pending`.
1002
- *
1003
- */
1004
- 202: {
1005
- strategyId: StrategyId;
1006
- validation: 'pending';
1007
- };
1214
+ /**
1215
+ * Already validated; the recorded verdict, unchanged
1216
+ */
1217
+ 200: StrategyState;
1218
+ /**
1219
+ * Validation queued. Not a terminal outcome — poll `GET /strategy/{strategyId}` until
1220
+ * `validation` leaves `pending`.
1221
+ *
1222
+ */
1223
+ 202: {
1224
+ strategyId: StrategyId;
1225
+ validation: "pending";
1226
+ };
1008
1227
  };
1009
1228
 
1010
- export type ValidateStrategyResponse = ValidateStrategyResponses[keyof ValidateStrategyResponses];
1229
+ export type ValidateStrategyResponse =
1230
+ ValidateStrategyResponses[keyof ValidateStrategyResponses];
1011
1231
 
1012
1232
  export type GetStrategyData = {
1013
- body?: never;
1014
- path: {
1015
- /**
1016
- * The id returned by `POST /strategy`
1017
- */
1018
- strategyId: StrategyId;
1019
- };
1020
- query?: never;
1021
- url: '/strategy/{strategyId}';
1233
+ body?: never;
1234
+ path: {
1235
+ /**
1236
+ * The id returned by `POST /strategy`
1237
+ */
1238
+ strategyId: StrategyId;
1239
+ };
1240
+ query?: never;
1241
+ url: "/strategy/{strategyId}";
1022
1242
  };
1023
1243
 
1024
1244
  export type GetStrategyErrors = {
1025
- /**
1026
- * No such registered strategy for this user
1027
- */
1028
- 404: ResponseError;
1245
+ /**
1246
+ * No such registered strategy for this user
1247
+ */
1248
+ 404: ResponseError;
1029
1249
  };
1030
1250
 
1031
1251
  export type GetStrategyError = GetStrategyErrors[keyof GetStrategyErrors];
1032
1252
 
1033
1253
  export type GetStrategyResponses = {
1034
- /**
1035
- * Strategy state
1036
- */
1037
- 200: StrategyState;
1254
+ /**
1255
+ * Strategy state
1256
+ */
1257
+ 200: StrategyState;
1038
1258
  };
1039
1259
 
1040
- export type GetStrategyResponse = GetStrategyResponses[keyof GetStrategyResponses];
1260
+ export type GetStrategyResponse =
1261
+ GetStrategyResponses[keyof GetStrategyResponses];
1041
1262
 
1042
1263
  export type PrepareBacktestData = {
1264
+ /**
1265
+ * The required data to prepare a backtesting
1266
+ */
1267
+ body: PrepareRequest;
1268
+ path: {
1043
1269
  /**
1044
- * The required data to prepare a backtesting
1270
+ * ID of the exchange to prepare the backtesting for
1045
1271
  */
1046
- body: PrepareRequest;
1047
- path: {
1048
- /**
1049
- * ID of the exchange to prepare the backtesting for
1050
- */
1051
- exchangeId: string;
1052
- /**
1053
- * The type of data source to prepare from
1054
- */
1055
- type: DataSourceType;
1056
- };
1057
- query?: never;
1058
- url: '/backtest/{exchangeId}/{type}/prepare';
1059
- };
1060
-
1061
- export type PrepareBacktestErrors = {
1272
+ exchangeId: string;
1062
1273
  /**
1063
- * Invalid request or parameters. Also returned when `from` is older than the configured
1064
- * lookback window or `to` is in the future.
1065
- *
1274
+ * The type of data source to prepare from
1066
1275
  */
1067
- 400: ResponseError;
1068
- /**
1069
- * Exchange or data source type not found
1070
- */
1071
- 404: ResponseError;
1072
- /**
1073
- * Rate limited. Returned when the global queue exceeds capacity or the user has too many
1074
- * active backtests.
1075
- *
1076
- */
1077
- 429: ResponseError;
1276
+ type: DataSourceType;
1277
+ };
1278
+ query?: never;
1279
+ url: "/backtest/{exchangeId}/{type}/prepare";
1078
1280
  };
1079
1281
 
1080
- export type PrepareBacktestError = PrepareBacktestErrors[keyof PrepareBacktestErrors];
1282
+ export type PrepareBacktestErrors = {
1283
+ /**
1284
+ * Invalid request or parameters. Also returned when `from` is older than the configured
1285
+ * lookback window or `to` is in the future.
1286
+ *
1287
+ */
1288
+ 400: ResponseError;
1289
+ /**
1290
+ * Exchange or data source type not found
1291
+ */
1292
+ 404: ResponseError;
1293
+ /**
1294
+ * Rate limited. Returned when the global queue exceeds capacity or the user has too many
1295
+ * active backtests.
1296
+ *
1297
+ */
1298
+ 429: ResponseError;
1299
+ };
1300
+
1301
+ export type PrepareBacktestError =
1302
+ PrepareBacktestErrors[keyof PrepareBacktestErrors];
1081
1303
 
1082
1304
  export type PrepareBacktestResponses = {
1083
- /**
1084
- * Prepare task accepted (queued for processing)
1085
- */
1086
- 202: AcceptedJob;
1305
+ /**
1306
+ * Prepare task accepted (queued for processing)
1307
+ */
1308
+ 202: AcceptedJob;
1087
1309
  };
1088
1310
 
1089
- export type PrepareBacktestResponse = PrepareBacktestResponses[keyof PrepareBacktestResponses];
1311
+ export type PrepareBacktestResponse =
1312
+ PrepareBacktestResponses[keyof PrepareBacktestResponses];
1090
1313
 
1091
1314
  export type GetPrepareStatusData = {
1092
- body?: never;
1093
- path: {
1094
- /**
1095
- * ID of the exchange for the backtesting process
1096
- */
1097
- exchangeId: string;
1098
- /**
1099
- * The type of data source to prepare from
1100
- */
1101
- type: DataSourceType;
1102
- /**
1103
- * Job ID returned by `POST /prepare`
1104
- */
1105
- jobId: string;
1106
- };
1107
- query?: never;
1108
- url: '/backtest/{exchangeId}/{type}/prepare/{jobId}';
1109
- };
1110
-
1111
- export type GetPrepareStatusErrors = {
1315
+ body?: never;
1316
+ path: {
1317
+ /**
1318
+ * ID of the exchange for the backtesting process
1319
+ */
1320
+ exchangeId: string;
1112
1321
  /**
1113
- * Invalid request or parameters
1322
+ * The type of data source to prepare from
1114
1323
  */
1115
- 400: ResponseError;
1324
+ type: DataSourceType;
1116
1325
  /**
1117
- * Prepare job not found or expired
1326
+ * Job ID returned by `POST /prepare`
1118
1327
  */
1119
- 404: ResponseError;
1328
+ jobId: string;
1329
+ };
1330
+ query?: never;
1331
+ url: "/backtest/{exchangeId}/{type}/prepare/{jobId}";
1120
1332
  };
1121
1333
 
1122
- export type GetPrepareStatusError = GetPrepareStatusErrors[keyof GetPrepareStatusErrors];
1334
+ export type GetPrepareStatusErrors = {
1335
+ /**
1336
+ * Invalid request or parameters
1337
+ */
1338
+ 400: ResponseError;
1339
+ /**
1340
+ * Prepare job not found or expired
1341
+ */
1342
+ 404: ResponseError;
1343
+ };
1344
+
1345
+ export type GetPrepareStatusError =
1346
+ GetPrepareStatusErrors[keyof GetPrepareStatusErrors];
1123
1347
 
1124
1348
  export type GetPrepareStatusResponses = {
1125
- /**
1126
- * Current prepare job state
1127
- */
1128
- 200: PrepareJobState;
1349
+ /**
1350
+ * Current prepare job state
1351
+ */
1352
+ 200: PrepareJobState;
1129
1353
  };
1130
1354
 
1131
- export type GetPrepareStatusResponse = GetPrepareStatusResponses[keyof GetPrepareStatusResponses];
1355
+ export type GetPrepareStatusResponse =
1356
+ GetPrepareStatusResponses[keyof GetPrepareStatusResponses];
1132
1357
 
1133
1358
  export type ExecuteSweepData = {
1134
- body: ExecuteSweepRequest;
1135
- path: {
1136
- exchangeId: string;
1137
- type: DataSourceType;
1138
- /**
1139
- * Job ID returned by `POST /backtest/{exchangeId}/{type}/prepare`.
1140
- */
1141
- requestId: string;
1142
- };
1143
- query?: never;
1144
- url: '/backtest/{exchangeId}/{type}/executeSweep/{requestId}';
1359
+ body: ExecuteSweepRequest;
1360
+ path: {
1361
+ exchangeId: string;
1362
+ type: DataSourceType;
1363
+ /**
1364
+ * Job ID returned by `POST /backtest/{exchangeId}/{type}/prepare`.
1365
+ */
1366
+ requestId: string;
1367
+ };
1368
+ query?: never;
1369
+ url: "/backtest/{exchangeId}/{type}/executeSweep/{requestId}";
1145
1370
  };
1146
1371
 
1147
1372
  export type ExecuteSweepErrors = {
1148
- /**
1149
- * Invalid sweep specification or the expanded grid exceeds the server limit.
1150
- */
1151
- 400: ResponseError;
1152
- /**
1153
- * Prepared request not found or expired.
1154
- */
1155
- 404: ResponseError;
1156
- /**
1157
- * Sweep queue or user concurrency limit reached.
1158
- */
1159
- 429: ResponseError;
1373
+ /**
1374
+ * Invalid sweep specification or the expanded grid exceeds the server limit.
1375
+ */
1376
+ 400: ResponseError;
1377
+ /**
1378
+ * Prepared request not found or expired.
1379
+ */
1380
+ 404: ResponseError;
1381
+ /**
1382
+ * Sweep queue or user concurrency limit reached.
1383
+ */
1384
+ 429: ResponseError;
1160
1385
  };
1161
1386
 
1162
1387
  export type ExecuteSweepError = ExecuteSweepErrors[keyof ExecuteSweepErrors];
1163
1388
 
1164
1389
  export type ExecuteSweepResponses = {
1165
- /**
1166
- * Sweep accepted. The effective seed is returned for reproducibility.
1167
- */
1168
- 202: ExecuteSweepAccepted;
1390
+ /**
1391
+ * Sweep accepted. The effective seed is returned for reproducibility.
1392
+ */
1393
+ 202: ExecuteSweepAccepted;
1169
1394
  };
1170
1395
 
1171
- export type ExecuteSweepResponse = ExecuteSweepResponses[keyof ExecuteSweepResponses];
1396
+ export type ExecuteSweepResponse =
1397
+ ExecuteSweepResponses[keyof ExecuteSweepResponses];
1172
1398
 
1173
1399
  export type CancelSweepData = {
1174
- body?: never;
1175
- path: {
1176
- exchangeId: string;
1177
- type: DataSourceType;
1178
- requestId: string;
1179
- sweepId: string;
1180
- };
1181
- query?: never;
1182
- url: '/backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}';
1400
+ body?: never;
1401
+ path: {
1402
+ exchangeId: string;
1403
+ type: DataSourceType;
1404
+ requestId: string;
1405
+ sweepId: string;
1406
+ };
1407
+ query?: never;
1408
+ url: "/backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}";
1183
1409
  };
1184
1410
 
1185
1411
  export type CancelSweepErrors = {
1186
- /**
1187
- * Sweep not found.
1188
- */
1189
- 404: ResponseError;
1412
+ /**
1413
+ * Sweep not found.
1414
+ */
1415
+ 404: ResponseError;
1190
1416
  };
1191
1417
 
1192
1418
  export type CancelSweepError = CancelSweepErrors[keyof CancelSweepErrors];
1193
1419
 
1194
1420
  export type CancelSweepResponses = {
1195
- /**
1196
- * Cancellation requested.
1197
- */
1198
- 200: {
1199
- status: 'cancelling';
1200
- sweepId: string;
1201
- };
1421
+ /**
1422
+ * Cancellation requested.
1423
+ */
1424
+ 200: {
1425
+ status: "cancelling";
1426
+ sweepId: string;
1427
+ };
1202
1428
  };
1203
1429
 
1204
- export type CancelSweepResponse = CancelSweepResponses[keyof CancelSweepResponses];
1430
+ export type CancelSweepResponse =
1431
+ CancelSweepResponses[keyof CancelSweepResponses];
1205
1432
 
1206
1433
  export type GetSweepResultData = {
1207
- body?: never;
1208
- path: {
1209
- exchangeId: string;
1210
- type: DataSourceType;
1211
- requestId: string;
1212
- sweepId: string;
1213
- };
1214
- query?: {
1215
- objective?: 'sharpe' | 'sortino' | 'pnl' | 'maxdd';
1216
- /**
1217
- * `natural` is stable materialisation order; `ranked` is the display view.
1218
- */
1219
- order?: 'ranked' | 'natural';
1220
- };
1221
- url: '/backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}';
1434
+ body?: never;
1435
+ path: {
1436
+ exchangeId: string;
1437
+ type: DataSourceType;
1438
+ requestId: string;
1439
+ sweepId: string;
1440
+ };
1441
+ query?: {
1442
+ objective?: "sharpe" | "sortino" | "pnl" | "maxdd";
1443
+ /**
1444
+ * `natural` is stable materialisation order; `ranked` is the display view.
1445
+ */
1446
+ order?: "ranked" | "natural";
1447
+ /**
1448
+ * How the `ranked` view is ordered. `plateau` prefers points whose neighbourhood also scores well; `raw` uses the objective alone. Ignored when `order=natural`, which is always ordered by `runIx`.
1449
+ */
1450
+ ranking?: "plateau" | "raw";
1451
+ };
1452
+ url: "/backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}";
1222
1453
  };
1223
1454
 
1224
1455
  export type GetSweepResultErrors = {
1225
- /**
1226
- * Sweep not found or expired.
1227
- */
1228
- 404: ResponseError;
1456
+ /**
1457
+ * Sweep not found or expired.
1458
+ */
1459
+ 404: ResponseError;
1229
1460
  };
1230
1461
 
1231
- export type GetSweepResultError = GetSweepResultErrors[keyof GetSweepResultErrors];
1462
+ export type GetSweepResultError =
1463
+ GetSweepResultErrors[keyof GetSweepResultErrors];
1232
1464
 
1233
1465
  export type GetSweepResultResponses = {
1466
+ /**
1467
+ * Current sweep snapshot and all currently available result rows for the selected view.
1468
+ */
1469
+ 200: ExecuteSweepResult;
1470
+ };
1471
+
1472
+ export type GetSweepResultResponse =
1473
+ GetSweepResultResponses[keyof GetSweepResultResponses];
1474
+
1475
+ export type GetSweepSensitivityData = {
1476
+ body?: never;
1477
+ path: {
1478
+ exchangeId: string;
1479
+ type: DataSourceType;
1480
+ requestId: string;
1481
+ sweepId: string;
1482
+ };
1483
+ query?: {
1234
1484
  /**
1235
- * Current sweep snapshot and all currently available result rows for the selected view.
1485
+ * Which metric to aggregate. Defaults to the objective the sweep was submitted with.
1236
1486
  */
1237
- 200: ExecuteSweepResult;
1487
+ objective?: "sharpe" | "sortino" | "pnl" | "maxdd";
1488
+ };
1489
+ url: "/backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}/sensitivity";
1238
1490
  };
1239
1491
 
1240
- export type GetSweepResultResponse = GetSweepResultResponses[keyof GetSweepResultResponses];
1492
+ export type GetSweepSensitivityErrors = {
1493
+ /**
1494
+ * Sweep not found or expired.
1495
+ */
1496
+ 404: ResponseError;
1497
+ };
1498
+
1499
+ export type GetSweepSensitivityError =
1500
+ GetSweepSensitivityErrors[keyof GetSweepSensitivityErrors];
1501
+
1502
+ export type GetSweepSensitivityResponses = {
1503
+ /**
1504
+ * Sensitivity aggregates over the rows available so far.
1505
+ */
1506
+ 200: SweepSensitivity;
1507
+ };
1508
+
1509
+ export type GetSweepSensitivityResponse =
1510
+ GetSweepSensitivityResponses[keyof GetSweepSensitivityResponses];
1241
1511
 
1242
1512
  export type ExecuteBacktestData = {
1513
+ /**
1514
+ * Execute task parameters
1515
+ */
1516
+ body: {
1243
1517
  /**
1244
- * Execute task parameters
1518
+ * Job ID returned by `POST /prepare` (must be in `Completed` state)
1245
1519
  */
1246
- body: {
1247
- /**
1248
- * Job ID returned by `POST /prepare` (must be in `Completed` state)
1249
- */
1250
- prepareJobId: string;
1251
- strategyId: StrategyId;
1252
- /**
1253
- * When true, the worker uploads emitted signals to object storage and the
1254
- * response includes `signalsUrl` / `signalsId` fields. Defaults to false.
1255
- *
1256
- */
1257
- storeSignals?: boolean;
1258
- };
1259
- path: {
1260
- /**
1261
- * ID of the exchange for the backtesting process
1262
- */
1263
- exchangeId: string;
1264
- /**
1265
- * The type of data source to execute from
1266
- */
1267
- type: DataSourceType;
1268
- };
1269
- query?: never;
1270
- url: '/backtest/{exchangeId}/{type}/execute';
1271
- };
1272
-
1273
- export type ExecuteBacktestErrors = {
1520
+ prepareJobId: string;
1521
+ strategyId: StrategyId;
1274
1522
  /**
1275
- * Invalid request or parameters
1523
+ * When true, the worker uploads emitted signals to object storage and the
1524
+ * response includes `signalsUrl` / `signalsId` fields. Defaults to false.
1525
+ *
1276
1526
  */
1277
- 400: ResponseError;
1527
+ storeSignals?: boolean;
1528
+ };
1529
+ path: {
1278
1530
  /**
1279
- * Prepare job not found or expired
1531
+ * ID of the exchange for the backtesting process
1280
1532
  */
1281
- 404: ResponseError;
1533
+ exchangeId: string;
1282
1534
  /**
1283
- * Rate limited (global queue at capacity or per-user limit reached)
1535
+ * The type of data source to execute from
1284
1536
  */
1285
- 429: ResponseError;
1537
+ type: DataSourceType;
1538
+ };
1539
+ query?: never;
1540
+ url: "/backtest/{exchangeId}/{type}/execute";
1286
1541
  };
1287
1542
 
1288
- export type ExecuteBacktestError = ExecuteBacktestErrors[keyof ExecuteBacktestErrors];
1543
+ export type ExecuteBacktestErrors = {
1544
+ /**
1545
+ * Invalid request or parameters
1546
+ */
1547
+ 400: ResponseError;
1548
+ /**
1549
+ * Prepare job not found or expired
1550
+ */
1551
+ 404: ResponseError;
1552
+ /**
1553
+ * Rate limited (global queue at capacity or per-user limit reached)
1554
+ */
1555
+ 429: ResponseError;
1556
+ };
1557
+
1558
+ export type ExecuteBacktestError =
1559
+ ExecuteBacktestErrors[keyof ExecuteBacktestErrors];
1289
1560
 
1290
1561
  export type ExecuteBacktestResponses = {
1291
- /**
1292
- * Execute task accepted (queued for processing)
1293
- */
1294
- 202: AcceptedJob;
1562
+ /**
1563
+ * Execute task accepted (queued for processing)
1564
+ */
1565
+ 202: AcceptedJob;
1295
1566
  };
1296
1567
 
1297
- export type ExecuteBacktestResponse = ExecuteBacktestResponses[keyof ExecuteBacktestResponses];
1568
+ export type ExecuteBacktestResponse =
1569
+ ExecuteBacktestResponses[keyof ExecuteBacktestResponses];
1298
1570
 
1299
1571
  export type CancelBacktestData = {
1300
- body?: never;
1301
- path: {
1302
- exchangeId: string;
1303
- type: DataSourceType;
1304
- /**
1305
- * Job ID returned by `POST /execute`
1306
- */
1307
- jobId: string;
1308
- };
1309
- query?: never;
1310
- url: '/backtest/{exchangeId}/{type}/execute/{jobId}';
1572
+ body?: never;
1573
+ path: {
1574
+ exchangeId: string;
1575
+ type: DataSourceType;
1576
+ /**
1577
+ * Job ID returned by `POST /execute`
1578
+ */
1579
+ jobId: string;
1580
+ };
1581
+ query?: never;
1582
+ url: "/backtest/{exchangeId}/{type}/execute/{jobId}";
1311
1583
  };
1312
1584
 
1313
1585
  export type CancelBacktestErrors = {
1314
- /**
1315
- * Execution not found
1316
- */
1317
- 404: ResponseError;
1586
+ /**
1587
+ * Execution not found
1588
+ */
1589
+ 404: ResponseError;
1318
1590
  };
1319
1591
 
1320
- export type CancelBacktestError = CancelBacktestErrors[keyof CancelBacktestErrors];
1592
+ export type CancelBacktestError =
1593
+ CancelBacktestErrors[keyof CancelBacktestErrors];
1321
1594
 
1322
1595
  export type CancelBacktestResponses = {
1323
- /**
1324
- * Cancellation request accepted
1325
- */
1326
- 200: {
1327
- status?: 'cancelling';
1328
- jobId?: string;
1329
- };
1596
+ /**
1597
+ * Cancellation request accepted
1598
+ */
1599
+ 200: {
1600
+ status?: "cancelling";
1601
+ jobId?: string;
1602
+ };
1330
1603
  };
1331
1604
 
1332
- export type CancelBacktestResponse = CancelBacktestResponses[keyof CancelBacktestResponses];
1605
+ export type CancelBacktestResponse =
1606
+ CancelBacktestResponses[keyof CancelBacktestResponses];
1333
1607
 
1334
1608
  export type GetBacktestResultData = {
1335
- body?: never;
1336
- path: {
1337
- /**
1338
- * ID of the exchange for the backtesting process
1339
- */
1340
- exchangeId: string;
1341
- /**
1342
- * The type of data source to execute from
1343
- */
1344
- type: DataSourceType;
1345
- /**
1346
- * Job ID returned by `POST /execute`
1347
- */
1348
- jobId: string;
1349
- };
1350
- query?: never;
1351
- url: '/backtest/{exchangeId}/{type}/execute/{jobId}';
1352
- };
1353
-
1354
- export type GetBacktestResultErrors = {
1609
+ body?: never;
1610
+ path: {
1355
1611
  /**
1356
- * Invalid request or parameters
1612
+ * ID of the exchange for the backtesting process
1357
1613
  */
1358
- 400: ResponseError;
1359
- /**
1360
- * Execution job not found
1361
- */
1362
- 404: ResponseError;
1363
- };
1364
-
1365
- export type GetBacktestResultError = GetBacktestResultErrors[keyof GetBacktestResultErrors];
1366
-
1367
- export type GetBacktestResultResponses = {
1614
+ exchangeId: string;
1368
1615
  /**
1369
- * Backtesting execution result
1616
+ * The type of data source to execute from
1370
1617
  */
1371
- 200: BacktestJobResult;
1618
+ type: DataSourceType;
1372
1619
  /**
1373
- * The job is known but its result is not readable yet — keep polling.
1374
- *
1375
- * Returned in two situations, both of which mean "ask again", never "you are done":
1376
- * the job has not produced its result yet, or the job reached a terminal status while
1377
- * its stored result could not be read back. The response body is an empty object: it
1378
- * deliberately carries no `state`, so a client cannot mistake it for a finished result.
1379
- *
1380
- * Treat any `202` as a signal to continue the poll loop under your existing timeout.
1381
- * Never treat it as a terminal outcome.
1382
- *
1620
+ * Job ID returned by `POST /execute`
1383
1621
  */
1384
- 202: {
1385
- [key: string]: never;
1386
- };
1622
+ jobId: string;
1623
+ };
1624
+ query?: never;
1625
+ url: "/backtest/{exchangeId}/{type}/execute/{jobId}";
1626
+ };
1627
+
1628
+ export type GetBacktestResultErrors = {
1629
+ /**
1630
+ * Invalid request or parameters
1631
+ */
1632
+ 400: ResponseError;
1633
+ /**
1634
+ * Execution job not found
1635
+ */
1636
+ 404: ResponseError;
1387
1637
  };
1388
1638
 
1389
- export type GetBacktestResultResponse = GetBacktestResultResponses[keyof GetBacktestResultResponses];
1639
+ export type GetBacktestResultError =
1640
+ GetBacktestResultErrors[keyof GetBacktestResultErrors];
1641
+
1642
+ export type GetBacktestResultResponses = {
1643
+ /**
1644
+ * Backtesting execution result
1645
+ */
1646
+ 200: BacktestJobResult;
1647
+ /**
1648
+ * The job is known but its result is not readable yet — keep polling.
1649
+ *
1650
+ * Returned in two situations, both of which mean "ask again", never "you are done":
1651
+ * the job has not produced its result yet, or the job reached a terminal status while
1652
+ * its stored result could not be read back. The response body is an empty object: it
1653
+ * deliberately carries no `state`, so a client cannot mistake it for a finished result.
1654
+ *
1655
+ * Treat any `202` as a signal to continue the poll loop under your existing timeout.
1656
+ * Never treat it as a terminal outcome.
1657
+ *
1658
+ */
1659
+ 202: {
1660
+ [key: string]: never;
1661
+ };
1662
+ };
1663
+
1664
+ export type GetBacktestResultResponse =
1665
+ GetBacktestResultResponses[keyof GetBacktestResultResponses];
1390
1666
 
1391
1667
  export type ClientOptions = {
1392
- baseUrl: 'https://api.staging.qtsurfer.com/v1' | 'https://api.qtsurfer.com/v1' | (string & {});
1393
- };
1668
+ baseUrl:
1669
+ | "https://api.staging.qtsurfer.com/v1"
1670
+ | "https://api.qtsurfer.com/v1"
1671
+ | (string & {});
1672
+ };