@qtsurfer/api-client 0.6.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.
@@ -1,997 +1,1574 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
2
 
3
3
  export const ResponseErrorSchema = {
4
- description: 'General response error',
5
- type: 'object',
6
- required: ['code', 'message'],
7
- properties: {
8
- code: {
9
- description: 'Status code',
10
- type: 'integer',
11
- example: 400
12
- },
13
- message: {
14
- description: 'Error description',
15
- type: 'string',
16
- example: 'Invalid request'
17
- }
18
- }
4
+ description: "General response error",
5
+ type: "object",
6
+ required: ["code", "message"],
7
+ properties: {
8
+ code: {
9
+ description: "Status code",
10
+ type: "integer",
11
+ example: 400,
12
+ },
13
+ message: {
14
+ description: "Error description",
15
+ type: "string",
16
+ example: "Invalid request",
17
+ },
18
+ },
19
19
  } as const;
20
20
 
21
21
  export const InstrumentSchema = {
22
- description: 'Exchange instrument identifier (e.g. a currency pair)',
23
- type: 'string',
24
- example: 'BTC/USDT'
22
+ description: "Exchange instrument identifier (e.g. a currency pair)",
23
+ type: "string",
24
+ example: "BTC/USDT",
25
25
  } as const;
26
26
 
27
27
  export const InstrumentListResponseSchema = {
28
- description: 'HAL-style response envelope for the instruments listing',
29
- type: 'object',
30
- required: ['data', 'meta', '_links'],
31
- properties: {
32
- data: {
33
- type: 'array',
34
- description: 'The list of instruments for the segment',
35
- items: {
36
- '$ref': '#/components/schemas/InstrumentDetail'
37
- }
38
- },
39
- meta: {
40
- '$ref': '#/components/schemas/InstrumentListMeta'
41
- },
42
- _links: {
43
- '$ref': '#/components/schemas/InstrumentLinks'
44
- }
45
- }
28
+ description: "HAL-style response envelope for the instruments listing",
29
+ type: "object",
30
+ required: ["data", "meta", "_links"],
31
+ properties: {
32
+ data: {
33
+ type: "array",
34
+ description: "The list of instruments for the segment",
35
+ items: {
36
+ $ref: "#/components/schemas/InstrumentDetail",
37
+ },
38
+ },
39
+ meta: {
40
+ $ref: "#/components/schemas/InstrumentListMeta",
41
+ },
42
+ _links: {
43
+ $ref: "#/components/schemas/InstrumentLinks",
44
+ },
45
+ },
46
46
  } as const;
47
47
 
48
48
  export const InstrumentListMetaSchema = {
49
- description: 'Metadata describing the instruments listing',
50
- type: 'object',
51
- required: ['updatedAt', 'exchange', 'segment'],
52
- properties: {
53
- updatedAt: {
54
- type: 'string',
55
- format: 'date-time',
56
- description: 'When this listing was last refreshed',
57
- example: '2026-07-09T19:09:07Z'
58
- },
59
- exchange: {
60
- type: 'string',
61
- description: 'The exchange the instruments belong to',
62
- example: 'binance'
63
- },
64
- segment: {
65
- type: 'string',
66
- enum: ['spot', 'futures'],
67
- description: 'The market segment served in `data`',
68
- example: 'spot'
69
- }
70
- }
49
+ description: "Metadata describing the instruments listing",
50
+ type: "object",
51
+ required: ["updatedAt", "exchange", "segment"],
52
+ properties: {
53
+ updatedAt: {
54
+ type: "string",
55
+ format: "date-time",
56
+ description: "When this listing was last refreshed",
57
+ example: "2026-07-09T19:09:07Z",
58
+ },
59
+ exchange: {
60
+ type: "string",
61
+ description: "The exchange the instruments belong to",
62
+ example: "binance",
63
+ },
64
+ segment: {
65
+ type: "string",
66
+ enum: ["spot", "futures"],
67
+ description: "The market segment served in `data`",
68
+ example: "spot",
69
+ },
70
+ },
71
71
  } as const;
72
72
 
73
73
  export const InstrumentLinksSchema = {
74
- description: 'HAL `_links` — segment discovery for the instruments listing',
75
- type: 'object',
76
- required: ['self'],
77
- properties: {
78
- self: {
79
- allOf: [
80
- {
81
- '$ref': '#/components/schemas/HalLink'
82
- }
83
- ],
84
- description: 'Link to this listing'
74
+ description: "HAL `_links` — segment discovery for the instruments listing",
75
+ type: "object",
76
+ required: ["self"],
77
+ properties: {
78
+ self: {
79
+ allOf: [
80
+ {
81
+ $ref: "#/components/schemas/HalLink",
85
82
  },
86
- spot: {
87
- allOf: [
88
- {
89
- '$ref': '#/components/schemas/HalLink'
90
- }
91
- ],
92
- description: 'Link to the spot instruments listing. Present when the exchange has a spot segment.'
83
+ ],
84
+ description: "Link to this listing",
85
+ },
86
+ spot: {
87
+ allOf: [
88
+ {
89
+ $ref: "#/components/schemas/HalLink",
93
90
  },
94
- futures: {
95
- allOf: [
96
- {
97
- '$ref': '#/components/schemas/HalLink'
98
- }
99
- ],
100
- description: 'Link to the futures instruments listing. Present only when the exchange has a futures segment.'
101
- }
102
- }
91
+ ],
92
+ description:
93
+ "Link to the spot instruments listing. Present when the exchange has a spot segment.",
94
+ },
95
+ futures: {
96
+ allOf: [
97
+ {
98
+ $ref: "#/components/schemas/HalLink",
99
+ },
100
+ ],
101
+ description:
102
+ "Link to the futures instruments listing. Present only when the exchange has a futures segment.",
103
+ },
104
+ },
103
105
  } as const;
104
106
 
105
107
  export const HalLinkSchema = {
106
- description: 'A HAL link object (Hypertext Application Language)',
107
- type: 'object',
108
- required: ['href'],
109
- properties: {
110
- href: {
111
- type: 'string',
112
- format: 'uri-reference',
113
- description: 'The link target as an absolute-path URI reference (resolve against the API base). A URI Template (RFC 6570) when `templated` is true.',
114
- example: '/v1/exchange/binance/spot/instruments'
115
- },
116
- templated: {
117
- type: 'boolean',
118
- description: 'True when `href` is an RFC 6570 URI Template.',
119
- example: false
120
- }
121
- }
108
+ description: "A HAL link object (Hypertext Application Language)",
109
+ type: "object",
110
+ required: ["href"],
111
+ properties: {
112
+ href: {
113
+ type: "string",
114
+ format: "uri-reference",
115
+ description:
116
+ "The link target as an absolute-path URI reference (resolve against the API base). A URI Template (RFC 6570) when `templated` is true.",
117
+ example: "/v1/exchange/binance/spot/instruments",
118
+ },
119
+ templated: {
120
+ type: "boolean",
121
+ description: "True when `href` is an RFC 6570 URI Template.",
122
+ example: false,
123
+ },
124
+ },
122
125
  } as const;
123
126
 
124
127
  export const InstrumentDetailSchema = {
125
- description: 'Exchange instrument with per-data-type coverage and market info',
126
- type: 'object',
127
- required: ['id', 'base', 'quote'],
128
- properties: {
129
- id: {
130
- type: 'string',
131
- description: 'Instrument identifier (e.g. currency pair)',
132
- example: 'BTC/USDT'
133
- },
134
- base: {
135
- type: 'string',
136
- description: 'Base currency',
137
- example: 'BTC'
138
- },
139
- quote: {
140
- type: 'string',
141
- description: 'Quote currency',
142
- example: 'USDT'
143
- },
144
- coverage: {
145
- '$ref': '#/components/schemas/InstrumentCoverage'
146
- },
147
- lastPrice: {
148
- type: 'number',
149
- format: 'double',
150
- description: 'Last traded price',
151
- example: 84250.5
152
- },
153
- volume24h: {
154
- type: 'number',
155
- format: 'double',
156
- description: 'Trading volume in the last 24 hours (in quote currency)',
157
- example: 1234567.89
158
- }
159
- }
128
+ description:
129
+ "Exchange instrument with per-data-type coverage and market info",
130
+ type: "object",
131
+ required: ["id", "base", "quote"],
132
+ properties: {
133
+ id: {
134
+ type: "string",
135
+ description: "Instrument identifier (e.g. currency pair)",
136
+ example: "BTC/USDT",
137
+ },
138
+ base: {
139
+ type: "string",
140
+ description: "Base currency",
141
+ example: "BTC",
142
+ },
143
+ quote: {
144
+ type: "string",
145
+ description: "Quote currency",
146
+ example: "USDT",
147
+ },
148
+ coverage: {
149
+ $ref: "#/components/schemas/InstrumentCoverage",
150
+ },
151
+ lastPrice: {
152
+ type: "number",
153
+ format: "double",
154
+ description: "Last traded price",
155
+ example: 84250.5,
156
+ },
157
+ volume24h: {
158
+ type: "number",
159
+ format: "double",
160
+ description: "Trading volume in the last 24 hours (in quote currency)",
161
+ example: 1234567.89,
162
+ },
163
+ },
160
164
  } as const;
161
165
 
162
166
  export const InstrumentCoverageSchema = {
163
- description: 'Time coverage of available data for this instrument, per data type',
164
- type: 'object',
165
- properties: {
166
- tickers: {
167
- allOf: [
168
- {
169
- '$ref': '#/components/schemas/CoverageWindow'
170
- }
171
- ],
172
- description: 'Coverage of ticker data'
167
+ description:
168
+ "Time coverage of available data for this instrument, per data type",
169
+ type: "object",
170
+ properties: {
171
+ tickers: {
172
+ allOf: [
173
+ {
174
+ $ref: "#/components/schemas/CoverageWindow",
173
175
  },
174
- klines: {
175
- allOf: [
176
- {
177
- '$ref': '#/components/schemas/CoverageWindow'
178
- }
179
- ],
180
- description: 'Coverage of kline (candlestick) data'
181
- }
182
- }
176
+ ],
177
+ description: "Coverage of ticker data",
178
+ },
179
+ klines: {
180
+ allOf: [
181
+ {
182
+ $ref: "#/components/schemas/CoverageWindow",
183
+ },
184
+ ],
185
+ description: "Coverage of kline (candlestick) data",
186
+ },
187
+ },
183
188
  } as const;
184
189
 
185
190
  export const CoverageWindowSchema = {
186
- description: 'The time range of available data for a single data type',
187
- type: 'object',
188
- properties: {
189
- from: {
190
- type: 'string',
191
- format: 'date-time',
192
- description: 'Earliest timestamp with data available',
193
- example: '2026-04-10T21:00:00Z'
194
- },
195
- to: {
196
- type: 'string',
197
- format: 'date-time',
198
- description: 'Latest timestamp with data available',
199
- example: '2026-07-09T20:31:08Z'
200
- },
201
- inactiveSince: {
202
- type: 'string',
203
- format: 'date-time',
204
- description: 'If the instrument stopped producing this data type (delisted/inactive), the timestamp it went inactive. Optional — omitted while the instrument is active.',
205
- example: '2026-06-30T12:00:00Z'
206
- }
207
- }
191
+ description: "The time range of available data for a single data type",
192
+ type: "object",
193
+ properties: {
194
+ from: {
195
+ type: "string",
196
+ format: "date-time",
197
+ description: "Earliest timestamp with data available",
198
+ example: "2026-04-10T21:00:00Z",
199
+ },
200
+ to: {
201
+ type: "string",
202
+ format: "date-time",
203
+ description: "Latest timestamp with data available",
204
+ example: "2026-07-09T20:31:08Z",
205
+ },
206
+ inactiveSince: {
207
+ type: "string",
208
+ format: "date-time",
209
+ description:
210
+ "If the instrument stopped producing this data type (delisted/inactive), the timestamp it went inactive. Optional — omitted while the instrument is active.",
211
+ example: "2026-06-30T12:00:00Z",
212
+ },
213
+ },
208
214
  } as const;
209
215
 
210
216
  export const ExchangeSchema = {
211
- description: 'Exchange service provider',
212
- type: 'object',
213
- required: ['id', 'name'],
214
- properties: {
215
- id: {
216
- description: 'Unique identifier for the exchange',
217
- type: 'string',
218
- example: 'binance'
219
- },
220
- name: {
221
- description: 'Name of the exchange',
222
- type: 'string',
223
- example: 'Binance'
224
- },
225
- description: {
226
- description: 'Description of the exchange',
227
- type: 'string',
228
- example: 'Binance cryptocurrency exchange'
229
- }
230
- }
217
+ description: "Exchange service provider",
218
+ type: "object",
219
+ required: ["id", "name"],
220
+ properties: {
221
+ id: {
222
+ description: "Unique identifier for the exchange",
223
+ type: "string",
224
+ example: "binance",
225
+ },
226
+ name: {
227
+ description: "Name of the exchange",
228
+ type: "string",
229
+ example: "Binance",
230
+ },
231
+ description: {
232
+ description: "Description of the exchange",
233
+ type: "string",
234
+ example: "Binance cryptocurrency exchange",
235
+ },
236
+ },
231
237
  } as const;
232
238
 
233
239
  export const DataSourceTypeSchema = {
234
- type: 'string',
235
- description: 'Managed exchange data sources available for backtesting.',
236
- enum: ['ticker'],
237
- example: 'ticker'
240
+ type: "string",
241
+ description: "Managed exchange data sources available for backtesting.",
242
+ enum: ["ticker"],
243
+ example: "ticker",
238
244
  } as const;
239
245
 
240
246
  export const PrepareRequestSchema = {
241
- type: 'object',
242
- required: ['instrument', 'from', 'to'],
243
- properties: {
244
- instrument: {
245
- '$ref': '#/components/schemas/Instrument'
246
- },
247
- from: {
248
- type: 'string',
249
- description: `Start date for the preparation process. Supports the following formats:
247
+ type: "object",
248
+ required: ["instrument", "from", "to"],
249
+ properties: {
250
+ instrument: {
251
+ $ref: "#/components/schemas/Instrument",
252
+ },
253
+ from: {
254
+ type: "string",
255
+ description: `Start date for the preparation process. Supports the following formats:
250
256
  - ISO-8601 (e.g. 2024-12-14T23:59:59Z)
251
257
  - ISO DATE (e.g. 2024-12-14)
252
258
  - BASIC ISO DATE (e.g., 20241214)
253
259
  `,
254
- example: '2024-12-13T00:00:00Z'
255
- },
256
- to: {
257
- type: 'string',
258
- description: `End date for the preparation process. Supports the following formats:
260
+ example: "2024-12-13T00:00:00Z",
261
+ },
262
+ to: {
263
+ type: "string",
264
+ description: `End date for the preparation process. Supports the following formats:
259
265
  - ISO-8601 (e.g. 2024-12-14T23:59:59Z)
260
266
  - ISO DATE (e.g. 2024-12-14)
261
267
  - BASIC ISO DATE (e.g., 20241214)
262
268
  `,
263
- example: '2024-12-14'
264
- },
265
- cadence: {
266
- type: 'string',
267
- description: `Output bar cadence for the prepared range. Defaults to the publisher's
269
+ example: "2024-12-14",
270
+ },
271
+ cadence: {
272
+ type: "string",
273
+ description: `Output bar cadence for the prepared range. Defaults to the publisher's
268
274
  native cadence (\`1s\`); coarser cadences are produced on demand via
269
275
  resampling and stored alongside the native blob in cache. Coarser-than-
270
276
  source values must be exact multiples of the source cadence — invalid
271
277
  labels return \`400\`.
272
278
  `,
273
- enum: ['1s', '5s', '1m', '5m', '15m', '1h', '4h', '1d'],
274
- default: '1s'
275
- }
279
+ enum: ["1s", "5s", "1m", "5m", "15m", "1h", "4h", "1d"],
280
+ default: "1s",
276
281
  },
277
- example: {
278
- instrument: 'BTC/USDT',
279
- from: '2024-12-13T00:00:00Z',
280
- to: '2024-12-14T00:00:00Z',
281
- cadence: '1m'
282
- }
282
+ },
283
+ example: {
284
+ instrument: "BTC/USDT",
285
+ from: "2024-12-13T00:00:00Z",
286
+ to: "2024-12-14T00:00:00Z",
287
+ cadence: "1m",
288
+ },
283
289
  } as const;
284
290
 
285
291
  export const JobStateSchema = {
286
- type: 'object',
287
- description: 'Information about a single job',
288
- required: ['contextId', 'status', 'size', 'completed'],
289
- properties: {
290
- contextId: {
291
- type: 'string',
292
- description: 'Opaque context identifier for the job',
293
- example: 'ctx_2o8heaioicr0edvx5ybcap'
294
- },
295
- status: {
296
- type: 'string',
297
- description: `Current status of the job. Treat \`Completed | Aborted | Failed\` as
292
+ type: "object",
293
+ description: "Information about a single job",
294
+ required: ["contextId", "status", "size", "completed"],
295
+ properties: {
296
+ contextId: {
297
+ type: "string",
298
+ description: "Opaque context identifier for the job",
299
+ example: "ctx_2o8heaioicr0edvx5ybcap",
300
+ },
301
+ status: {
302
+ type: "string",
303
+ description: `Current status of the job. Treat \`Completed | Aborted | Failed\` as
298
304
  terminal; \`New | Started\` mean keep polling. A single-instrument prepare
299
305
  is always terminal (\`Completed\`) — decide from
300
306
  \`PrepareJobState.coverageRatio\`, not by polling.
301
307
  `,
302
- enum: ['New', 'Started', 'Completed', 'Aborted', 'Failed'],
303
- example: 'Completed'
304
- },
305
- statusDetail: {
306
- type: ['string', 'null'],
307
- description: 'Detailed status information, if available',
308
- example: 'Job completed with error code 5001'
309
- },
310
- size: {
311
- type: 'integer',
312
- description: 'Total size of the data being prepared',
313
- example: 100
314
- },
315
- completed: {
316
- type: 'integer',
317
- description: 'The amount of data processed so far',
318
- example: 50
319
- },
320
- startTime: {
321
- type: ['string', 'null'],
322
- format: 'date-time',
323
- description: 'Timestamp for when the preparation started',
324
- example: '2025-01-04T14:00:00Z'
325
- },
326
- endTime: {
327
- type: ['string', 'null'],
328
- format: 'date-time',
329
- description: 'Timestamp for when the preparation finished',
330
- example: '2025-01-04T14:00:20Z'
331
- }
332
- }
308
+ enum: ["New", "Started", "Completed", "Aborted", "Failed"],
309
+ example: "Completed",
310
+ },
311
+ statusDetail: {
312
+ type: ["string", "null"],
313
+ description: "Detailed status information, if available",
314
+ example: "Job completed with error code 5001",
315
+ },
316
+ size: {
317
+ type: "integer",
318
+ description: "Total size of the data being prepared",
319
+ example: 100,
320
+ },
321
+ completed: {
322
+ type: "integer",
323
+ description: "The amount of data processed so far",
324
+ example: 50,
325
+ },
326
+ startTime: {
327
+ type: ["string", "null"],
328
+ format: "date-time",
329
+ description: "Timestamp for when the preparation started",
330
+ example: "2025-01-04T14:00:00Z",
331
+ },
332
+ endTime: {
333
+ type: ["string", "null"],
334
+ format: "date-time",
335
+ description: "Timestamp for when the preparation finished",
336
+ example: "2025-01-04T14:00:20Z",
337
+ },
338
+ },
333
339
  } as const;
334
340
 
335
341
  export const PrepareJobStateSchema = {
336
- description: `State of a single-instrument prepare job — the \`JobState\` shape plus a per-hour
342
+ description: `State of a single-instrument prepare job — the \`JobState\` shape plus a per-hour
337
343
  data-coverage summary. A single-instrument prepare is always terminal
338
344
  (\`status: Completed\`): the client decides what to do from \`coverageRatio\` (e.g.
339
345
  execute if it is at or above a chosen threshold) rather than polling for missing
340
346
  hours that may never arrive — a missing hour for one instrument usually means low
341
347
  activity, not missing data.
342
348
  `,
343
- allOf: [
344
- {
345
- '$ref': '#/components/schemas/JobState'
346
- },
347
- {
348
- type: 'object',
349
- properties: {
350
- dataFrom: {
351
- type: ['string', 'null'],
352
- format: 'date-time',
353
- description: 'Start of the available data range for the prepared instrument.',
354
- example: '2026-04-14T13:00:00Z'
355
- },
356
- dataTo: {
357
- type: ['string', 'null'],
358
- format: 'date-time',
359
- description: 'End of the available data range for the prepared instrument.',
360
- example: '2026-04-14T15:30:05Z'
361
- },
362
- coverageRatio: {
363
- type: 'number',
364
- format: 'double',
365
- minimum: 0,
366
- maximum: 1,
367
- description: `\`hoursWithData / totalHours\` in \`[0,1]\` (\`1.0\` when \`totalHours\` is 0) — the
349
+ allOf: [
350
+ {
351
+ $ref: "#/components/schemas/JobState",
352
+ },
353
+ {
354
+ type: "object",
355
+ properties: {
356
+ dataFrom: {
357
+ type: ["string", "null"],
358
+ format: "date-time",
359
+ description:
360
+ "Start of the available data range for the prepared instrument.",
361
+ example: "2026-04-14T13:00:00Z",
362
+ },
363
+ dataTo: {
364
+ type: ["string", "null"],
365
+ format: "date-time",
366
+ description:
367
+ "End of the available data range for the prepared instrument.",
368
+ example: "2026-04-14T15:30:05Z",
369
+ },
370
+ coverageRatio: {
371
+ type: "number",
372
+ format: "double",
373
+ minimum: 0,
374
+ maximum: 1,
375
+ description: `\`hoursWithData / totalHours\` in \`[0,1]\` (\`1.0\` when \`totalHours\` is 0) — the
368
376
  fraction of hours in the requested range that have served data.
369
377
  `,
370
- example: 0.994
371
- },
372
- totalHours: {
373
- type: 'integer',
374
- description: 'Number of whole hours in the requested prepare range.',
375
- example: 168
376
- },
377
- hoursWithData: {
378
- type: 'integer',
379
- description: 'Number of hours in the range that have data.',
380
- example: 167
381
- },
382
- hoursWithoutData: {
383
- type: 'array',
384
- description: 'One entry per hour in the range that has no data, with a rationale.',
385
- items: {
386
- type: 'object',
387
- properties: {
388
- hour: {
389
- type: 'string',
390
- format: 'date-time',
391
- description: 'The hour (UTC, hour-aligned) that has no data.',
392
- example: '2026-04-14T02:00:00Z'
393
- },
394
- expected: {
395
- type: 'integer',
396
- description: `Expected row count for the hour (currently always 0; reserved for
378
+ example: 0.994,
379
+ },
380
+ totalHours: {
381
+ type: "integer",
382
+ description: "Number of whole hours in the requested prepare range.",
383
+ example: 168,
384
+ },
385
+ hoursWithData: {
386
+ type: "integer",
387
+ description: "Number of hours in the range that have data.",
388
+ example: 167,
389
+ },
390
+ hoursWithoutData: {
391
+ type: "array",
392
+ description:
393
+ "One entry per hour in the range that has no data, with a rationale.",
394
+ items: {
395
+ type: "object",
396
+ properties: {
397
+ hour: {
398
+ type: "string",
399
+ format: "date-time",
400
+ description: "The hour (UTC, hour-aligned) that has no data.",
401
+ example: "2026-04-14T02:00:00Z",
402
+ },
403
+ expected: {
404
+ type: "integer",
405
+ description: `Expected row count for the hour (currently always 0; reserved for
397
406
  future use). The rationale never depends on it.
398
407
  `,
399
- example: 0
400
- },
401
- rationale: {
402
- type: 'string',
403
- description: `Why the hour has no data. \`pending_conversion\`: data for this hour is
408
+ example: 0,
409
+ },
410
+ rationale: {
411
+ type: "string",
412
+ description: `Why the hour has no data. \`pending_conversion\`: data for this hour is
404
413
  still being produced — a re-poll may fill it. \`low_activity\`: the
405
414
  instrument did not trade that hour. \`unknown\`: no data to classify by.
406
415
  `,
407
- enum: ['pending_conversion', 'low_activity', 'unknown'],
408
- example: 'low_activity'
409
- }
410
- }
411
- }
412
- }
413
- }
414
- }
415
- ]
416
+ enum: ["pending_conversion", "low_activity", "unknown"],
417
+ example: "low_activity",
418
+ },
419
+ },
420
+ },
421
+ },
422
+ },
423
+ },
424
+ ],
416
425
  } as const;
417
426
 
418
427
  export const SweepAxisSchema = {
419
- description: 'A numeric range or an explicit list of values for one strategy property.',
420
- oneOf: [
421
- {
422
- type: 'object',
423
- required: ['from', 'to', 'step'],
424
- additionalProperties: false,
425
- properties: {
426
- from: {
427
- type: 'number',
428
- format: 'double'
429
- },
430
- to: {
431
- type: 'number',
432
- format: 'double'
433
- },
434
- step: {
435
- type: 'number',
436
- format: 'double',
437
- exclusiveMinimum: 0
438
- }
439
- }
428
+ description:
429
+ "A numeric range or an explicit list of values for one strategy property.",
430
+ oneOf: [
431
+ {
432
+ type: "object",
433
+ required: ["from", "to", "step"],
434
+ additionalProperties: false,
435
+ properties: {
436
+ from: {
437
+ type: "number",
438
+ format: "double",
440
439
  },
441
- {
442
- type: 'object',
443
- required: ['values'],
444
- additionalProperties: false,
445
- properties: {
446
- values: {
447
- type: 'array',
448
- minItems: 1,
449
- items: {
450
- oneOf: [
451
- {
452
- type: 'number'
453
- },
454
- {
455
- type: 'boolean'
456
- }
457
- ]
458
- }
459
- }
460
- }
461
- }
462
- ]
440
+ to: {
441
+ type: "number",
442
+ format: "double",
443
+ },
444
+ step: {
445
+ type: "number",
446
+ format: "double",
447
+ exclusiveMinimum: 0,
448
+ },
449
+ },
450
+ },
451
+ {
452
+ type: "object",
453
+ required: ["values"],
454
+ additionalProperties: false,
455
+ properties: {
456
+ values: {
457
+ type: "array",
458
+ minItems: 1,
459
+ items: {
460
+ oneOf: [
461
+ {
462
+ type: "number",
463
+ },
464
+ {
465
+ type: "boolean",
466
+ },
467
+ ],
468
+ },
469
+ },
470
+ },
471
+ },
472
+ ],
463
473
  } as const;
464
474
 
465
475
  export const SweepSpecRequestSchema = {
466
- type: 'object',
467
- required: ['params'],
468
- properties: {
469
- sampler: {
470
- type: 'string',
471
- enum: ['grid', 'random', 'lhs'],
472
- default: 'grid'
473
- },
474
- seed: {
475
- type: 'integer',
476
- format: 'int64',
477
- minimum: -9007199254740991,
478
- maximum: 9007199254740991,
479
- description: `Reproducibility seed. If omitted, the server generates one with Java's
476
+ type: "object",
477
+ required: ["params"],
478
+ properties: {
479
+ sampler: {
480
+ type: "string",
481
+ enum: ["grid", "random", "lhs"],
482
+ default: "grid",
483
+ },
484
+ seed: {
485
+ type: "integer",
486
+ format: "int64",
487
+ minimum: -9007199254740991,
488
+ maximum: 9007199254740991,
489
+ description: `Reproducibility seed. If omitted, the server generates one with Java's
480
490
  \`L64X128MixRandom\` generator and returns the effective value. The range
481
491
  is limited to JavaScript-safe integers so generated clients can replay it exactly.
482
- `
483
- },
484
- samples: {
485
- type: 'integer',
486
- minimum: 1,
487
- description: 'Number of samples for `random` and `lhs`; ignored by `grid`.'
488
- },
489
- objective: {
490
- type: 'string',
491
- enum: ['sharpe', 'sortino', 'pnl', 'maxdd'],
492
- default: 'sharpe'
493
- },
494
- params: {
495
- type: 'object',
496
- minProperties: 1,
497
- additionalProperties: {
498
- '$ref': '#/components/schemas/SweepAxis'
499
- }
500
- }
501
- },
502
- example: {
503
- sampler: 'lhs',
504
- seed: 487221,
505
- samples: 100,
506
- objective: 'sharpe',
507
- params: {
508
- rsiPeriod: {
509
- from: 7,
510
- to: 28,
511
- step: 1
512
- },
513
- useTrendFilter: {
514
- values: [true, false]
515
- }
516
- }
517
- }
492
+ `,
493
+ },
494
+ samples: {
495
+ type: "integer",
496
+ minimum: 1,
497
+ description:
498
+ "Number of samples for `random` and `lhs`; ignored by `grid`.",
499
+ },
500
+ objective: {
501
+ type: "string",
502
+ enum: ["sharpe", "sortino", "pnl", "maxdd"],
503
+ default: "sharpe",
504
+ },
505
+ params: {
506
+ type: "object",
507
+ minProperties: 1,
508
+ additionalProperties: {
509
+ $ref: "#/components/schemas/SweepAxis",
510
+ },
511
+ },
512
+ },
513
+ example: {
514
+ sampler: "lhs",
515
+ seed: 487221,
516
+ samples: 100,
517
+ objective: "sharpe",
518
+ params: {
519
+ rsiPeriod: {
520
+ from: 7,
521
+ to: 28,
522
+ step: 1,
523
+ },
524
+ useTrendFilter: {
525
+ values: [true, false],
526
+ },
527
+ },
528
+ },
518
529
  } as const;
519
530
 
520
531
  export const SweepBaseConfigSchema = {
521
- type: 'object',
522
- properties: {
523
- initialFunding: {
524
- type: 'number',
525
- format: 'double',
526
- exclusiveMinimum: 0,
527
- default: 10000
528
- },
529
- feeRate: {
530
- type: 'number',
531
- format: 'double',
532
- minimum: 0,
533
- default: 0.001
534
- },
535
- buyFeeRate: {
536
- type: 'number',
537
- format: 'double',
538
- minimum: 0
539
- },
540
- sellFeeRate: {
541
- type: 'number',
542
- format: 'double',
543
- minimum: 0
544
- },
545
- feeLeg: {
546
- type: 'string',
547
- enum: ['RECEIVED', 'QUOTE', 'BASE'],
548
- default: 'RECEIVED'
549
- },
550
- percentAmountToLock: {
551
- type: 'number',
552
- format: 'double',
553
- exclusiveMinimum: 0,
554
- maximum: 100
555
- }
556
- }
532
+ type: "object",
533
+ properties: {
534
+ initialFunding: {
535
+ type: "number",
536
+ format: "double",
537
+ exclusiveMinimum: 0,
538
+ default: 10000,
539
+ },
540
+ feeRate: {
541
+ type: "number",
542
+ format: "double",
543
+ minimum: 0,
544
+ default: 0.001,
545
+ },
546
+ buyFeeRate: {
547
+ type: "number",
548
+ format: "double",
549
+ minimum: 0,
550
+ },
551
+ sellFeeRate: {
552
+ type: "number",
553
+ format: "double",
554
+ minimum: 0,
555
+ },
556
+ feeLeg: {
557
+ type: "string",
558
+ enum: ["RECEIVED", "QUOTE", "BASE"],
559
+ default: "RECEIVED",
560
+ },
561
+ percentAmountToLock: {
562
+ type: "number",
563
+ format: "double",
564
+ exclusiveMinimum: 0,
565
+ maximum: 100,
566
+ },
567
+ },
557
568
  } as const;
558
569
 
559
570
  export const ExecuteSweepRequestSchema = {
560
- type: 'object',
561
- required: ['strategyId', 'sweep'],
562
- properties: {
563
- strategyId: {
564
- '$ref': '#/components/schemas/strategyId'
565
- },
566
- sweep: {
567
- '$ref': '#/components/schemas/SweepSpecRequest'
568
- },
569
- baseConfig: {
570
- '$ref': '#/components/schemas/SweepBaseConfig'
571
- },
572
- storeSignals: {
573
- type: 'boolean',
574
- default: false,
575
- description: 'Store signals for every trial. Keep false for normal sweeps.'
576
- },
577
- shards: {
578
- type: 'integer',
579
- minimum: 0,
580
- description: 'Requested horizontal shard count; 0 or omitted selects automatically.',
581
- default: 0
582
- },
583
- minTradeFloor: {
584
- type: 'integer',
585
- minimum: 0,
586
- default: 30,
587
- description: 'Trials below this trade count are flagged but remain in the results.'
588
- }
589
- }
571
+ type: "object",
572
+ required: ["strategyId", "sweep"],
573
+ properties: {
574
+ strategyId: {
575
+ $ref: "#/components/schemas/strategyId",
576
+ },
577
+ sweep: {
578
+ $ref: "#/components/schemas/SweepSpecRequest",
579
+ },
580
+ baseConfig: {
581
+ $ref: "#/components/schemas/SweepBaseConfig",
582
+ },
583
+ storeSignals: {
584
+ type: "boolean",
585
+ default: false,
586
+ description:
587
+ "Store signals for every trial. Keep false for normal sweeps.",
588
+ },
589
+ shards: {
590
+ type: "integer",
591
+ minimum: 0,
592
+ description:
593
+ "Requested horizontal shard count; 0 or omitted selects automatically.",
594
+ default: 0,
595
+ },
596
+ minTradeFloor: {
597
+ type: "integer",
598
+ minimum: 0,
599
+ default: 30,
600
+ description:
601
+ "Trials below this trade count are flagged but remain in the results.",
602
+ },
603
+ walkForward: {
604
+ $ref: "#/components/schemas/WalkForwardRequest",
605
+ },
606
+ },
607
+ } as const;
608
+
609
+ export const WalkForwardRequestSchema = {
610
+ type: "object",
611
+ description:
612
+ "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.",
613
+ required: ["folds"],
614
+ properties: {
615
+ folds: {
616
+ type: "integer",
617
+ minimum: 2,
618
+ description: `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.
619
+ 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.`,
620
+ },
621
+ inSamplePct: {
622
+ type: "integer",
623
+ minimum: 10,
624
+ maximum: 90,
625
+ default: 66,
626
+ description:
627
+ "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.",
628
+ },
629
+ },
590
630
  } as const;
591
631
 
592
632
  export const ExecuteSweepAcceptedSchema = {
593
- type: 'object',
594
- required: ['sweepId', 'requestId', 'totalRuns', 'shards', 'seed', 'queued'],
595
- properties: {
596
- sweepId: {
597
- type: 'string',
598
- example: 'swp_95e47a7f0966ce11'
599
- },
600
- requestId: {
601
- type: 'string'
602
- },
603
- totalRuns: {
604
- type: 'integer',
605
- minimum: 1
606
- },
607
- shards: {
608
- type: 'integer',
609
- minimum: 1
610
- },
611
- seed: {
612
- type: 'integer',
613
- format: 'int64',
614
- minimum: -9007199254740991,
615
- maximum: 9007199254740991,
616
- description: 'Effective seed used to expand the sweep.'
617
- },
618
- queued: {
619
- type: 'boolean',
620
- description: 'False when an identical sweep already exists and was not enqueued again.'
621
- }
622
- }
633
+ type: "object",
634
+ required: ["sweepId", "requestId", "totalRuns", "shards", "seed", "queued"],
635
+ properties: {
636
+ sweepId: {
637
+ type: "string",
638
+ example: "swp_95e47a7f0966ce11",
639
+ },
640
+ requestId: {
641
+ type: "string",
642
+ },
643
+ totalRuns: {
644
+ type: "integer",
645
+ minimum: 1,
646
+ },
647
+ shards: {
648
+ type: "integer",
649
+ minimum: 1,
650
+ },
651
+ seed: {
652
+ type: "integer",
653
+ format: "int64",
654
+ minimum: -9007199254740991,
655
+ maximum: 9007199254740991,
656
+ description: "Effective seed used to expand the sweep.",
657
+ },
658
+ queued: {
659
+ type: "boolean",
660
+ description:
661
+ "False when an identical sweep already exists and was not enqueued again.",
662
+ },
663
+ walkForward: {
664
+ $ref: "#/components/schemas/WalkForwardAccepted",
665
+ },
666
+ },
667
+ } as const;
668
+
669
+ export const WalkForwardAcceptedSchema = {
670
+ type: "object",
671
+ description:
672
+ "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.",
673
+ required: ["folds", "inSamplePct", "totalRuns"],
674
+ properties: {
675
+ folds: {
676
+ type: "integer",
677
+ },
678
+ inSamplePct: {
679
+ type: "integer",
680
+ },
681
+ totalRuns: {
682
+ type: "integer",
683
+ description:
684
+ "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.",
685
+ },
686
+ },
623
687
  } as const;
624
688
 
625
689
  export const SweepProgressSchema = {
626
- type: 'object',
627
- required: ['done', 'total', 'aborted', 'shardCount', 'pendingShards'],
628
- properties: {
629
- done: {
630
- type: 'integer',
631
- format: 'int64'
632
- },
633
- total: {
634
- type: 'integer'
635
- },
636
- aborted: {
637
- type: 'integer',
638
- format: 'int64'
639
- },
640
- shardCount: {
641
- type: 'integer'
642
- },
643
- pendingShards: {
644
- type: 'integer'
645
- }
646
- }
690
+ type: "object",
691
+ description:
692
+ "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.",
693
+ required: [
694
+ "done",
695
+ "total",
696
+ "aborted",
697
+ "shardCount",
698
+ "pendingShards",
699
+ "failedShards",
700
+ "retrying",
701
+ "notStarted",
702
+ ],
703
+ properties: {
704
+ done: {
705
+ type: "integer",
706
+ format: "int64",
707
+ },
708
+ total: {
709
+ type: "integer",
710
+ },
711
+ aborted: {
712
+ type: "integer",
713
+ format: "int64",
714
+ description:
715
+ "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.",
716
+ },
717
+ shardCount: {
718
+ type: "integer",
719
+ },
720
+ pendingShards: {
721
+ type: "integer",
722
+ },
723
+ failedShards: {
724
+ type: "integer",
725
+ format: "int64",
726
+ description:
727
+ "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.",
728
+ },
729
+ retrying: {
730
+ type: "integer",
731
+ description:
732
+ "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.",
733
+ },
734
+ notStarted: {
735
+ type: "integer",
736
+ description:
737
+ "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.",
738
+ },
739
+ stalledSeconds: {
740
+ type: "integer",
741
+ format: "int64",
742
+ description:
743
+ "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.",
744
+ },
745
+ etaSeconds: {
746
+ type: "integer",
747
+ format: "int64",
748
+ description:
749
+ '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.',
750
+ },
751
+ },
647
752
  } as const;
648
753
 
649
754
  export const SweepRunRowSchema = {
650
- type: 'object',
651
- required: ['runIx', 'params', 'sharpe', 'sortino', 'pnl', 'pnlPct', 'cagr', 'maxDdPct', 'trades', 'winRate', 'belowTradeFloor', 'aborted', 'runtimeMs'],
652
- properties: {
653
- runIx: {
654
- type: 'integer',
655
- minimum: 0,
656
- description: 'Deterministic zero-based expansion index, stable across shards and ranking.'
657
- },
658
- rank: {
659
- type: 'integer',
660
- minimum: 1,
661
- description: 'Present only in the `ranked` view.'
662
- },
663
- params: {
664
- type: 'object',
665
- additionalProperties: true
666
- },
667
- sharpe: {
668
- type: 'number',
669
- format: 'double'
670
- },
671
- sortino: {
672
- type: 'number',
673
- format: 'double'
674
- },
675
- pnl: {
676
- type: 'number',
677
- format: 'double',
678
- description: 'Absolute net PnL in the output currency.'
679
- },
680
- pnlPct: {
681
- type: 'number',
682
- format: 'double'
683
- },
684
- cagr: {
685
- type: 'number',
686
- format: 'double'
687
- },
688
- maxDdPct: {
689
- type: 'number',
690
- format: 'double'
691
- },
692
- trades: {
693
- type: 'integer',
694
- format: 'int64'
695
- },
696
- winRate: {
697
- type: 'number',
698
- format: 'double'
699
- },
700
- belowTradeFloor: {
701
- type: 'boolean'
702
- },
703
- aborted: {
704
- type: 'boolean'
705
- },
706
- runtimeMs: {
707
- type: 'integer',
708
- format: 'int64'
709
- }
710
- }
755
+ type: "object",
756
+ required: [
757
+ "runIx",
758
+ "params",
759
+ "sharpe",
760
+ "sortino",
761
+ "pnl",
762
+ "pnlPct",
763
+ "cagr",
764
+ "maxDdPct",
765
+ "trades",
766
+ "winRate",
767
+ "belowTradeFloor",
768
+ "aborted",
769
+ "runtimeMs",
770
+ ],
771
+ properties: {
772
+ runIx: {
773
+ type: "integer",
774
+ minimum: 0,
775
+ description:
776
+ "Deterministic zero-based expansion index, stable across shards and ranking.",
777
+ },
778
+ rank: {
779
+ type: "integer",
780
+ minimum: 1,
781
+ description: "Present only in the `ranked` view.",
782
+ },
783
+ plateauScore: {
784
+ type: "number",
785
+ format: "double",
786
+ description:
787
+ "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`.",
788
+ },
789
+ neighbourCount: {
790
+ type: "integer",
791
+ minimum: 0,
792
+ description:
793
+ "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.",
794
+ },
795
+ deflatedSharpe: {
796
+ type: "number",
797
+ format: "double",
798
+ minimum: 0,
799
+ maximum: 1,
800
+ description:
801
+ "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.",
802
+ },
803
+ params: {
804
+ type: "object",
805
+ additionalProperties: true,
806
+ },
807
+ sharpe: {
808
+ type: "number",
809
+ format: "double",
810
+ },
811
+ sortino: {
812
+ type: "number",
813
+ format: "double",
814
+ },
815
+ pnl: {
816
+ type: "number",
817
+ format: "double",
818
+ description: "Absolute net PnL in the output currency.",
819
+ },
820
+ pnlPct: {
821
+ type: "number",
822
+ format: "double",
823
+ },
824
+ cagr: {
825
+ type: "number",
826
+ format: "double",
827
+ },
828
+ maxDdPct: {
829
+ type: "number",
830
+ format: "double",
831
+ },
832
+ trades: {
833
+ type: "integer",
834
+ format: "int64",
835
+ },
836
+ winRate: {
837
+ type: "number",
838
+ format: "double",
839
+ },
840
+ belowTradeFloor: {
841
+ type: "boolean",
842
+ },
843
+ aborted: {
844
+ type: "boolean",
845
+ },
846
+ runtimeMs: {
847
+ type: "integer",
848
+ format: "int64",
849
+ },
850
+ },
851
+ } as const;
852
+
853
+ export const SweepSensitivitySchema = {
854
+ type: "object",
855
+ description:
856
+ "Sensitivity aggregates over a sweep's stored rows. Marginals are always complete; heatmaps may be capped, in which case `heatmapsTruncated` is true.",
857
+ properties: {
858
+ sweepId: {
859
+ type: "string",
860
+ },
861
+ status: {
862
+ type: "string",
863
+ enum: ["RUNNING", "COMPLETED", "PARTIAL", "CANCELLED"],
864
+ },
865
+ objective: {
866
+ type: "string",
867
+ enum: ["sharpe", "sortino", "pnl", "maxdd"],
868
+ },
869
+ rowsAnalysed: {
870
+ type: "integer",
871
+ description:
872
+ "Rows available when this was computed. Grows while a sweep is still running.",
873
+ },
874
+ marginals: {
875
+ type: "array",
876
+ items: {
877
+ $ref: "#/components/schemas/SweepMarginal",
878
+ },
879
+ },
880
+ heatmaps: {
881
+ type: "array",
882
+ items: {
883
+ $ref: "#/components/schemas/SweepHeatmap",
884
+ },
885
+ },
886
+ heatmapsTruncated: {
887
+ type: "boolean",
888
+ description:
889
+ '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.',
890
+ },
891
+ },
892
+ } as const;
893
+
894
+ export const SweepMarginalSchema = {
895
+ type: "object",
896
+ description: "One axis, with every other axis collapsed away.",
897
+ properties: {
898
+ param: {
899
+ type: "string",
900
+ },
901
+ points: {
902
+ type: "array",
903
+ items: {
904
+ $ref: "#/components/schemas/SweepMarginalPoint",
905
+ },
906
+ },
907
+ },
908
+ } as const;
909
+
910
+ export const SweepMarginalPointSchema = {
911
+ type: "object",
912
+ description:
913
+ "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.",
914
+ properties: {
915
+ value: {
916
+ description: "The axis value, as it appears in a run's parameters.",
917
+ },
918
+ count: {
919
+ type: "integer",
920
+ description: "Non-aborted runs that used this value.",
921
+ },
922
+ best: {
923
+ type: "number",
924
+ format: "double",
925
+ },
926
+ mean: {
927
+ type: "number",
928
+ format: "double",
929
+ },
930
+ worst: {
931
+ type: "number",
932
+ format: "double",
933
+ },
934
+ },
935
+ } as const;
936
+
937
+ export const SweepHeatmapSchema = {
938
+ type: "object",
939
+ description:
940
+ "The surface for one pair of axes, with all others collapsed away.",
941
+ properties: {
942
+ paramA: {
943
+ type: "string",
944
+ },
945
+ paramB: {
946
+ type: "string",
947
+ },
948
+ cells: {
949
+ type: "array",
950
+ items: {
951
+ $ref: "#/components/schemas/SweepHeatmapCell",
952
+ },
953
+ },
954
+ },
955
+ } as const;
956
+
957
+ export const SweepHeatmapCellSchema = {
958
+ type: "object",
959
+ properties: {
960
+ valueA: {},
961
+ valueB: {},
962
+ count: {
963
+ type: "integer",
964
+ },
965
+ best: {
966
+ type: "number",
967
+ format: "double",
968
+ },
969
+ mean: {
970
+ type: "number",
971
+ format: "double",
972
+ },
973
+ },
711
974
  } as const;
712
975
 
713
976
  export const ExecuteSweepResultSchema = {
714
- type: 'object',
715
- required: ['sweepId', 'status', 'objective', 'order', 'progress', 'leaderboardSize', 'truncated', 'leaderboard'],
716
- properties: {
717
- sweepId: {
718
- type: 'string'
719
- },
720
- status: {
721
- type: 'string',
722
- enum: ['RUNNING', 'COMPLETED', 'PARTIAL', 'CANCELLED']
723
- },
724
- objective: {
725
- type: 'string',
726
- enum: ['sharpe', 'sortino', 'pnl', 'maxdd']
727
- },
728
- order: {
729
- type: 'string',
730
- enum: ['ranked', 'natural']
731
- },
732
- progress: {
733
- '$ref': '#/components/schemas/SweepProgress'
734
- },
735
- leaderboardSize: {
736
- type: 'integer',
737
- description: 'Total result rows currently available.'
738
- },
739
- truncated: {
740
- type: 'boolean',
741
- description: 'True only when the ranked view exceeds its display limit.'
742
- },
743
- leaderboard: {
744
- type: 'array',
745
- items: {
746
- '$ref': '#/components/schemas/SweepRunRow'
747
- }
748
- }
749
- }
977
+ type: "object",
978
+ required: [
979
+ "sweepId",
980
+ "status",
981
+ "objective",
982
+ "order",
983
+ "progress",
984
+ "leaderboardSize",
985
+ "truncated",
986
+ "leaderboard",
987
+ ],
988
+ properties: {
989
+ sweepId: {
990
+ type: "string",
991
+ },
992
+ status: {
993
+ type: "string",
994
+ enum: ["RUNNING", "COMPLETED", "PARTIAL", "CANCELLED"],
995
+ },
996
+ objective: {
997
+ type: "string",
998
+ enum: ["sharpe", "sortino", "pnl", "maxdd"],
999
+ },
1000
+ order: {
1001
+ type: "string",
1002
+ enum: ["ranked", "natural"],
1003
+ },
1004
+ ranking: {
1005
+ type: "string",
1006
+ enum: ["plateau", "raw"],
1007
+ description:
1008
+ "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`.",
1009
+ },
1010
+ pbo: {
1011
+ type: "number",
1012
+ format: "double",
1013
+ minimum: 0,
1014
+ maximum: 1,
1015
+ description:
1016
+ "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.",
1017
+ },
1018
+ pboSplits: {
1019
+ type: "integer",
1020
+ description:
1021
+ "How many train/test splits the `pbo` figure was averaged over.",
1022
+ },
1023
+ progress: {
1024
+ $ref: "#/components/schemas/SweepProgress",
1025
+ },
1026
+ leaderboardSize: {
1027
+ type: "integer",
1028
+ description: "Total result rows currently available.",
1029
+ },
1030
+ truncated: {
1031
+ type: "boolean",
1032
+ description: "True only when the ranked view exceeds its display limit.",
1033
+ },
1034
+ leaderboard: {
1035
+ type: "array",
1036
+ items: {
1037
+ $ref: "#/components/schemas/SweepRunRow",
1038
+ },
1039
+ },
1040
+ walkForward: {
1041
+ $ref: "#/components/schemas/WalkForwardResult",
1042
+ },
1043
+ },
1044
+ } as const;
1045
+
1046
+ export const WalkForwardResultSchema = {
1047
+ type: "object",
1048
+ description:
1049
+ "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.",
1050
+ required: ["folds", "completedFolds", "results"],
1051
+ properties: {
1052
+ folds: {
1053
+ type: "integer",
1054
+ description: "Folds requested at submit.",
1055
+ },
1056
+ inSamplePct: {
1057
+ type: "integer",
1058
+ description: "Resolved in-sample share each fold optimized on.",
1059
+ },
1060
+ completedFolds: {
1061
+ type: "integer",
1062
+ description: "Folds that have finished and reported a winner.",
1063
+ },
1064
+ paramDrift: {
1065
+ type: "number",
1066
+ format: "double",
1067
+ minimum: 0,
1068
+ description:
1069
+ "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.",
1070
+ },
1071
+ results: {
1072
+ type: "array",
1073
+ description: "One entry per completed fold, oldest first.",
1074
+ items: {
1075
+ $ref: "#/components/schemas/WalkForwardFold",
1076
+ },
1077
+ },
1078
+ },
1079
+ } as const;
1080
+
1081
+ export const WalkForwardFoldSchema = {
1082
+ type: "object",
1083
+ description:
1084
+ "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.",
1085
+ required: [
1086
+ "foldIx",
1087
+ "inSampleFrom",
1088
+ "inSampleTo",
1089
+ "outOfSampleTo",
1090
+ "params",
1091
+ "inSampleSharpe",
1092
+ "outOfSample",
1093
+ "vectorsRun",
1094
+ ],
1095
+ properties: {
1096
+ foldIx: {
1097
+ type: "integer",
1098
+ description: "Position in the walk-forward sequence, oldest first.",
1099
+ },
1100
+ inSampleFrom: {
1101
+ type: "integer",
1102
+ description:
1103
+ "First index of the optimization window, into the prepared session.",
1104
+ },
1105
+ inSampleTo: {
1106
+ type: "integer",
1107
+ description:
1108
+ "End of the optimization window, exclusive — and where scoring begins.",
1109
+ },
1110
+ outOfSampleTo: {
1111
+ type: "integer",
1112
+ description: "End of the scoring window, exclusive.",
1113
+ },
1114
+ params: {
1115
+ type: "object",
1116
+ additionalProperties: true,
1117
+ description:
1118
+ "The parameter vector that won this fold's optimization window.",
1119
+ },
1120
+ inSampleSharpe: {
1121
+ type: "number",
1122
+ format: "double",
1123
+ description: "How that winner scored on the window it was chosen on.",
1124
+ },
1125
+ outOfSample: {
1126
+ $ref: "#/components/schemas/SweepRunRow",
1127
+ },
1128
+ vectorsRun: {
1129
+ type: "integer",
1130
+ description:
1131
+ "Vectors this fold evaluated in-sample before picking its winner.",
1132
+ },
1133
+ },
750
1134
  } as const;
751
1135
 
752
1136
  export const AcceptedJobSchema = {
753
- type: 'object',
754
- description: `Response returned by async endpoints (\`202 Accepted\`). The \`jobId\` is deterministic for the
1137
+ type: "object",
1138
+ description: `Response returned by async endpoints (\`202 Accepted\`). The \`jobId\` is deterministic for the
755
1139
  same input parameters — repeated calls with identical params return the same id.
756
1140
  `,
757
- required: ['jobId'],
758
- properties: {
759
- jobId: {
760
- type: 'string',
761
- description: 'Unique job identifier; use this to poll for completion.',
762
- example: '13RBLGQlPnfDjO6wyKSX8i'
763
- }
1141
+ required: ["jobId"],
1142
+ properties: {
1143
+ jobId: {
1144
+ type: "string",
1145
+ description: "Unique job identifier; use this to poll for completion.",
1146
+ example: "13RBLGQlPnfDjO6wyKSX8i",
764
1147
  },
765
- example: {
766
- jobId: '13RBLGQlPnfDjO6wyKSX8i'
767
- }
1148
+ },
1149
+ example: {
1150
+ jobId: "13RBLGQlPnfDjO6wyKSX8i",
1151
+ },
768
1152
  } as const;
769
1153
 
770
1154
  export const BacktestJobResultSchema = {
771
- type: 'object',
772
- description: 'Backtest job result.',
773
- required: ['results', 'state'],
774
- properties: {
775
- results: {
776
- '$ref': '#/components/schemas/ResultMap'
777
- },
778
- state: {
779
- '$ref': '#/components/schemas/JobState'
780
- }
781
- }
1155
+ type: "object",
1156
+ description: "Backtest job result.",
1157
+ required: ["results", "state"],
1158
+ properties: {
1159
+ results: {
1160
+ $ref: "#/components/schemas/ResultMap",
1161
+ },
1162
+ state: {
1163
+ $ref: "#/components/schemas/JobState",
1164
+ },
1165
+ },
782
1166
  } as const;
783
1167
 
784
1168
  export const ResultMapSchema = {
785
- type: 'object',
786
- description: '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.',
787
- required: ['strategyId', 'instrument'],
788
- properties: {
789
- hostName: {
790
- type: 'string',
791
- description: 'Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.',
792
- example: 'executor10'
793
- },
794
- iops: {
795
- type: 'number',
796
- format: 'double',
797
- description: 'Instrument operations per second throughput during execution',
798
- example: 123956.53
799
- },
800
- strategyId: {
801
- type: 'string',
802
- description: 'Identifier of the compiled strategy that produced this result',
803
- example: 'strategy:00000000-0000-0000-0000-000000000000:ticker:2iyvtenlzh9dabqtxn7nbv'
804
- },
805
- instrument: {
806
- type: 'string',
807
- description: 'The instrument (currency pair) that was backtested',
808
- example: 'BTC/USDT'
809
- },
810
- pnlTotal: {
811
- type: 'number',
812
- format: 'double',
813
- description: 'Total profit and loss in the output currency',
814
- example: 42.75
815
- },
816
- pnlTotalPercent: {
817
- type: 'number',
818
- format: 'double',
819
- description: 'Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.',
820
- example: 42.75
821
- },
822
- totalTrades: {
823
- type: 'integer',
824
- format: 'int64',
825
- description: 'Total number of trades executed by the strategy',
826
- example: 156
827
- },
828
- winRate: {
829
- type: 'number',
830
- format: 'double',
831
- description: 'Percentage of profitable trades (0-100)',
832
- example: 58.33
833
- },
834
- sharpeRatio: {
835
- type: 'number',
836
- format: 'double',
837
- description: 'Risk-adjusted return ratio (mean return / standard deviation of returns)',
838
- example: 1.245
839
- },
840
- sortinoRatio: {
841
- type: 'number',
842
- format: 'double',
843
- description: 'Downside risk-adjusted return ratio (mean return / downside deviation)',
844
- example: 1.872
845
- },
846
- cagr: {
847
- type: 'number',
848
- format: 'double',
849
- description: 'Compound Annual Growth Rate',
850
- example: 0.1534
851
- },
852
- maxDrawdown: {
853
- type: 'number',
854
- format: 'double',
855
- description: 'Maximum absolute drawdown in the output currency',
856
- example: 12.5
857
- },
858
- maxDrawdownPercent: {
859
- type: 'number',
860
- format: 'double',
861
- description: 'Maximum percentage drawdown from peak equity',
862
- example: 8.75
863
- },
864
- equityCurve: {
865
- type: 'array',
866
- description: "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.",
867
- items: {
868
- '$ref': '#/components/schemas/EquityPoint'
869
- },
870
- example: [
871
- {
872
- timestamp: 1700000000000,
873
- equity: 100
874
- },
875
- {
876
- timestamp: 1700000060000,
877
- equity: 110.5
878
- },
879
- {
880
- timestamp: 1700000120000,
881
- equity: 90.25
882
- }
883
- ]
884
- },
885
- signalCount: {
886
- type: 'integer',
887
- description: 'Number of signals emitted during strategy execution',
888
- example: 100000
889
- },
890
- signalsId: {
891
- type: 'string',
892
- description: 'Storage key for the signals file. Treat as opaque; use signalsUrl to download.',
893
- example: '00000000-0000-0000-0000-000000000000/exec/binance/3vsndwikcuaatjmb83fjtl'
894
- },
895
- signalsUrl: {
896
- type: 'string',
897
- format: 'uri',
898
- description: "HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.",
899
- example: 'https://storage.qtsurfer.com/00000000-0000-0000-0000-000000000000/exec/binance/3vsndwikcuaatjmb83fjtl.parquet'
1169
+ type: "object",
1170
+ description:
1171
+ "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.",
1172
+ required: ["strategyId", "instrument"],
1173
+ properties: {
1174
+ hostName: {
1175
+ type: "string",
1176
+ description:
1177
+ "Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.",
1178
+ example: "executor10",
1179
+ },
1180
+ iops: {
1181
+ type: "number",
1182
+ format: "double",
1183
+ description:
1184
+ "Instrument operations per second throughput during execution",
1185
+ example: 123956.53,
1186
+ },
1187
+ strategyId: {
1188
+ type: "string",
1189
+ description: `**Not the \`strategyId\` you compiled with** — this is the execution context id,
1190
+ \`strategy:<user>:<strategyId>\`. The compiled strategy's id is the last \`:\`-separated
1191
+ segment; that, not this whole string, is what \`GET /strategy/{strategyId}\` takes.
1192
+
1193
+ Take the segment after the last \`:\` rather than counting from the front: the shape has
1194
+ changed once already and callers that indexed a fixed position broke on it.
1195
+ `,
1196
+ example:
1197
+ "strategy:00000000-0000-0000-0000-000000000000:2iyvtenlzh9dabqtxn7nbv",
1198
+ },
1199
+ instrument: {
1200
+ type: "string",
1201
+ description: "The instrument (currency pair) that was backtested",
1202
+ example: "BTC/USDT",
1203
+ },
1204
+ notices: {
1205
+ type: "array",
1206
+ description: `Diagnostics the engine raised over this run, each with \`provenance: execute\`.
1207
+
1208
+ **Absent means nothing was raised.** This is the one surface where silence is a real
1209
+ answer: the run happened, over your data, start to finish, and the engine found nothing
1210
+ worth saying. That is not true of the compile path, where an empty list only means a
1211
+ short synthetic series reached nothing — see \`GET /strategy/{strategyId}\`.
1212
+
1213
+ Notices are raised on failed and aborted runs too, and those are the ones most worth
1214
+ reading: a run that produced no trades often did so for a reason stated here.
1215
+ `,
1216
+ items: {
1217
+ $ref: "#/components/schemas/Notice",
1218
+ },
1219
+ },
1220
+ noticesTruncated: {
1221
+ type: "integer",
1222
+ description:
1223
+ "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.",
1224
+ example: 3,
1225
+ },
1226
+ pnlTotal: {
1227
+ type: "number",
1228
+ format: "double",
1229
+ description: "Total profit and loss in the output currency",
1230
+ example: 42.75,
1231
+ },
1232
+ pnlTotalPercent: {
1233
+ type: "number",
1234
+ format: "double",
1235
+ description:
1236
+ "Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.",
1237
+ example: 42.75,
1238
+ },
1239
+ totalTrades: {
1240
+ type: "integer",
1241
+ format: "int64",
1242
+ description: "Total number of trades executed by the strategy",
1243
+ example: 156,
1244
+ },
1245
+ winRate: {
1246
+ type: "number",
1247
+ format: "double",
1248
+ description: "Percentage of profitable trades (0-100)",
1249
+ example: 58.33,
1250
+ },
1251
+ sharpeRatio: {
1252
+ type: "number",
1253
+ format: "double",
1254
+ description:
1255
+ "Risk-adjusted return ratio (mean return / standard deviation of returns)",
1256
+ example: 1.245,
1257
+ },
1258
+ sortinoRatio: {
1259
+ type: "number",
1260
+ format: "double",
1261
+ description:
1262
+ "Downside risk-adjusted return ratio (mean return / downside deviation)",
1263
+ example: 1.872,
1264
+ },
1265
+ cagr: {
1266
+ type: "number",
1267
+ format: "double",
1268
+ description: "Compound Annual Growth Rate",
1269
+ example: 0.1534,
1270
+ },
1271
+ maxDrawdown: {
1272
+ type: "number",
1273
+ format: "double",
1274
+ description: "Maximum absolute drawdown in the output currency",
1275
+ example: 12.5,
1276
+ },
1277
+ maxDrawdownPercent: {
1278
+ type: "number",
1279
+ format: "double",
1280
+ description: "Maximum percentage drawdown from peak equity",
1281
+ example: 8.75,
1282
+ },
1283
+ equityCurve: {
1284
+ type: "array",
1285
+ description:
1286
+ "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.",
1287
+ items: {
1288
+ $ref: "#/components/schemas/EquityPoint",
1289
+ },
1290
+ example: [
1291
+ {
1292
+ timestamp: 1700000000000,
1293
+ equity: 100,
900
1294
  },
901
- signalsUpload: {
902
- type: 'string',
903
- enum: ['Done', 'Failed', 'Skipped'],
904
- description: 'Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.',
905
- example: 'Done'
1295
+ {
1296
+ timestamp: 1700000060000,
1297
+ equity: 110.5,
906
1298
  },
907
- signalsUploadedAt: {
908
- type: 'string',
909
- format: 'date-time',
910
- description: 'ISO 8601 timestamp of when the upload completed. Only present when signalsUpload is Done.',
911
- example: '2026-03-18T13:21:48.170Z'
1299
+ {
1300
+ timestamp: 1700000120000,
1301
+ equity: 90.25,
912
1302
  },
913
- signalsUploadReason: {
914
- type: 'string',
915
- description: 'Human-readable reason when signalsUpload is Failed or Skipped.',
916
- example: 'signal file generation failed'
917
- }
918
- }
1303
+ ],
1304
+ },
1305
+ signalCount: {
1306
+ type: "integer",
1307
+ description: "Number of signals emitted during strategy execution",
1308
+ example: 100000,
1309
+ },
1310
+ signalsId: {
1311
+ type: "string",
1312
+ description:
1313
+ "Storage key for the signals file. Treat as opaque; use signalsUrl to download.",
1314
+ example:
1315
+ "00000000-0000-0000-0000-000000000000/exec/binance/3vsndwikcuaatjmb83fjtl",
1316
+ },
1317
+ signalsUrl: {
1318
+ type: "string",
1319
+ format: "uri",
1320
+ description:
1321
+ "HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.",
1322
+ example:
1323
+ "https://storage.qtsurfer.com/00000000-0000-0000-0000-000000000000/exec/binance/3vsndwikcuaatjmb83fjtl.parquet",
1324
+ },
1325
+ signalsUpload: {
1326
+ type: "string",
1327
+ enum: ["Done", "Failed", "Skipped"],
1328
+ description:
1329
+ "Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.",
1330
+ example: "Done",
1331
+ },
1332
+ signalsUploadedAt: {
1333
+ type: "string",
1334
+ format: "date-time",
1335
+ description:
1336
+ "ISO 8601 timestamp of when the upload completed. Only present when signalsUpload is Done.",
1337
+ example: "2026-03-18T13:21:48.170Z",
1338
+ },
1339
+ signalsUploadReason: {
1340
+ type: "string",
1341
+ description:
1342
+ "Human-readable reason when signalsUpload is Failed or Skipped.",
1343
+ example: "signal file generation failed",
1344
+ },
1345
+ },
919
1346
  } as const;
920
1347
 
921
1348
  export const EquityPointSchema = {
922
- type: 'object',
923
- description: 'Single sample of the running equity at a yield event.',
924
- required: ['timestamp', 'equity'],
925
- properties: {
926
- timestamp: {
927
- type: 'integer',
928
- format: 'int64',
929
- description: 'Epoch milliseconds. The first point in an equity curve is anchored at the backtest `from`; subsequent points carry the timestamp of each emitted yield.',
930
- example: 1700000000000
931
- },
932
- equity: {
933
- type: 'number',
934
- format: 'double',
935
- description: 'Running equity at this point (`initialCapital + cumulativePnl`).',
936
- example: 110.5
937
- }
938
- }
1349
+ type: "object",
1350
+ description: "Single sample of the running equity at a yield event.",
1351
+ required: ["timestamp", "equity"],
1352
+ properties: {
1353
+ timestamp: {
1354
+ type: "integer",
1355
+ format: "int64",
1356
+ description:
1357
+ "Epoch milliseconds. The first point in an equity curve is anchored at the backtest `from`; subsequent points carry the timestamp of each emitted yield.",
1358
+ example: 1700000000000,
1359
+ },
1360
+ equity: {
1361
+ type: "number",
1362
+ format: "double",
1363
+ description:
1364
+ "Running equity at this point (`initialCapital + cumulativePnl`).",
1365
+ example: 110.5,
1366
+ },
1367
+ },
939
1368
  } as const;
940
1369
 
941
1370
  export const strategyIdSchema = {
942
- description: 'Unique identifier for a compiled strategy',
943
- type: 'string',
944
- example: '6bsh31ikwkuivhtgcoa6s4'
1371
+ description: `Unique identifier for a compiled strategy, derived from the source itself: the same code
1372
+ always yields the same id, for every caller, whatever its formatting. See
1373
+ \`POST /strategy\` for exactly which rewrites preserve it and which do not.
1374
+ `,
1375
+ type: "string",
1376
+ example: "6bsh31ikwkuivhtgcoa6s4",
1377
+ } as const;
1378
+
1379
+ export const NoticeSchema = {
1380
+ type: "object",
1381
+ description: `A diagnostic the engine raised while the strategy ran. Advisory: it describes something worth
1382
+ knowing about how the strategy is wired, not necessarily an error.
1383
+ `,
1384
+ required: ["level", "code", "message"],
1385
+ properties: {
1386
+ level: {
1387
+ type: "string",
1388
+ description: "Severity as the engine classified it.",
1389
+ example: "WARN",
1390
+ },
1391
+ code: {
1392
+ type: "string",
1393
+ description:
1394
+ "Stable identifier for the kind of finding; safe to match on.",
1395
+ example: "indicator.bar-data-on-ticker-path",
1396
+ },
1397
+ message: {
1398
+ type: "string",
1399
+ description: "Human-readable explanation.",
1400
+ example: "Indicator requires bar data but is on the ticker path",
1401
+ },
1402
+ provenance: {
1403
+ type: "string",
1404
+ enum: ["execute", "compile-dry-run"],
1405
+ description: `Where it came from, which matters because the two silences differ: an empty list from a
1406
+ real run (\`execute\`) is a clean bill of health, while an empty list from
1407
+ \`compile-dry-run\` is only a lower bound over a bounded synthetic series.
1408
+ `,
1409
+ example: "compile-dry-run",
1410
+ },
1411
+ },
1412
+ } as const;
1413
+
1414
+ export const StrategyStateSchema = {
1415
+ type: "object",
1416
+ description: `What is known about a registered strategy: that it compiled, and what validating it found.
1417
+
1418
+ **\`validation: passed\` does not mean the strategy is correct.** It means the class loaded and
1419
+ survived the first event of a short synthetic run — a floor, not a guarantee. When
1420
+ \`dryRunIncomplete\` is true it is a lower floor still, because the run did not finish.
1421
+ `,
1422
+ required: ["strategyId", "validation"],
1423
+ properties: {
1424
+ strategyId: {
1425
+ $ref: "#/components/schemas/strategyId",
1426
+ },
1427
+ validation: {
1428
+ type: "string",
1429
+ enum: ["not_validated", "pending", "passed", "failed"],
1430
+ description: `* \`not_validated\` — registered, never checked. \`POST /strategy/{strategyId}/validate\`
1431
+ checks it.
1432
+ * \`pending\` — a check was asked for and has not answered yet.
1433
+ * \`passed\` — the class loaded and survived its first event.
1434
+ * \`failed\` — it did not; \`detail\` says how.
1435
+ `,
1436
+ example: "passed",
1437
+ },
1438
+ compiledAt: {
1439
+ type: "string",
1440
+ format: "date-time",
1441
+ description: "When the live compilation was produced.",
1442
+ },
1443
+ requiredSources: {
1444
+ type: "array",
1445
+ description: `The market data a strategy needs, read off the compiled class rather than off anything
1446
+ you sent — \`TickerStrategy\`, \`KlineStrategy\` and \`FundingRateStrategy\` each declare one,
1447
+ and a \`MultiSourceStrategy\` declares a set.
1448
+
1449
+ **Absent is not "needs nothing".** A strategy always needs market data, so an absent
1450
+ field never means an empty requirement — it means the platform could not establish the
1451
+ answer without constructing your strategy, which it will not do to fill in a field.
1452
+ That happens for a \`MultiSourceStrategy\`, for a class that overrides
1453
+ \`getMarketDataSource()\`, and for anything registered before this field existed;
1454
+ re-registering the source fills it in.
1455
+ `,
1456
+ items: {
1457
+ type: "string",
1458
+ enum: ["Ticker", "KLine", "FundingRate"],
1459
+ },
1460
+ example: ["Ticker"],
1461
+ },
1462
+ validatedAt: {
1463
+ type: "string",
1464
+ format: "date-time",
1465
+ description: "When the verdict was recorded. Absent until there is one.",
1466
+ },
1467
+ detail: {
1468
+ type: "string",
1469
+ description: `Why validation failed, or why a queued check has not reported. Present on \`failed\`, and
1470
+ alongside \`validationStalled\`.
1471
+ `,
1472
+ },
1473
+ notices: {
1474
+ type: "array",
1475
+ description: `What the run surfaced. An empty or absent list is not a clean bill of health when
1476
+ \`dryRunIncomplete\` is true — see that field.
1477
+ `,
1478
+ items: {
1479
+ $ref: "#/components/schemas/Notice",
1480
+ },
1481
+ },
1482
+ noticesTruncated: {
1483
+ type: "integer",
1484
+ description:
1485
+ "How many notices were dropped past the cap. Absent when none were.",
1486
+ example: 3,
1487
+ },
1488
+ dryRunIncomplete: {
1489
+ type: "boolean",
1490
+ description: `The check did not finish its budget — it ran out of time, was refused because the
1491
+ platform was already holding too many unfinishable runs, or hit a failure attributable to
1492
+ the synthetic instrument rather than to your strategy. The verdict stands as far as it
1493
+ went; it simply reached less than a full run would.
1494
+ `,
1495
+ },
1496
+ validationStalled: {
1497
+ type: "boolean",
1498
+ description: `A queued check has not reported for far longer than one takes. Nothing is disproved about
1499
+ the strategy — the check has not run. Stop waiting and re-request it later.
1500
+ `,
1501
+ },
1502
+ },
1503
+ example: {
1504
+ strategyId: "6bsh31ikwkuivhtgcoa6s4",
1505
+ validation: "passed",
1506
+ compiledAt: "2026-08-04T16:23:04Z",
1507
+ requiredSources: ["Ticker"],
1508
+ validatedAt: "2026-08-04T16:24:11Z",
1509
+ notices: [
1510
+ {
1511
+ level: "WARN",
1512
+ code: "indicator.bar-data-on-ticker-path",
1513
+ message: "Indicator requires bar data but is on the ticker path",
1514
+ provenance: "compile-dry-run",
1515
+ },
1516
+ ],
1517
+ },
945
1518
  } as const;
946
1519
 
947
1520
  export const AuthTokenResponseSchema = {
948
- type: 'object',
949
- required: ['access_token', 'token_type', 'expires_in', 'tier'],
950
- properties: {
951
- access_token: {
952
- type: 'string',
953
- description: 'Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.'
954
- },
955
- token_type: {
956
- type: 'string',
957
- enum: ['Bearer'],
958
- description: 'Always `Bearer`.'
959
- },
960
- expires_in: {
961
- type: 'integer',
962
- description: 'Seconds until the JWT expires (typically 3600).',
963
- example: 3600
964
- },
965
- scopes: {
966
- type: 'array',
967
- description: 'Scopes granted to this token. Reserved for future use; currently always empty.',
968
- items: {
969
- type: 'string'
970
- },
971
- example: []
972
- },
973
- tier: {
974
- type: 'string',
975
- enum: ['free', 'basic', 'pro', 'elite'],
976
- description: 'Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.',
977
- example: 'free'
978
- }
979
- }
1521
+ type: "object",
1522
+ required: ["access_token", "token_type", "expires_in", "tier"],
1523
+ properties: {
1524
+ access_token: {
1525
+ type: "string",
1526
+ description:
1527
+ "Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.",
1528
+ },
1529
+ token_type: {
1530
+ type: "string",
1531
+ enum: ["Bearer"],
1532
+ description: "Always `Bearer`.",
1533
+ },
1534
+ expires_in: {
1535
+ type: "integer",
1536
+ description: "Seconds until the JWT expires (typically 3600).",
1537
+ example: 3600,
1538
+ },
1539
+ scopes: {
1540
+ type: "array",
1541
+ description:
1542
+ "Scopes granted to this token. Reserved for future use; currently always empty.",
1543
+ items: {
1544
+ type: "string",
1545
+ },
1546
+ example: [],
1547
+ },
1548
+ tier: {
1549
+ type: "string",
1550
+ enum: ["free", "basic", "pro", "elite"],
1551
+ description:
1552
+ "Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.",
1553
+ example: "free",
1554
+ },
1555
+ },
980
1556
  } as const;
981
1557
 
982
1558
  export const AuthTokenErrorSchema = {
983
- type: 'object',
984
- required: ['code', 'message'],
985
- description: 'Error envelope returned by `POST /auth/token` when the API key is rejected.',
986
- properties: {
987
- code: {
988
- type: 'string',
989
- enum: ['invalid_apikey', 'apikey_revoked', 'apikey_expired'],
990
- description: 'Machine-readable error reason.'
991
- },
992
- message: {
993
- type: 'string',
994
- description: 'Human-readable description of the failure.'
995
- }
996
- }
997
- } as const;
1559
+ type: "object",
1560
+ required: ["code", "message"],
1561
+ description:
1562
+ "Error envelope returned by `POST /auth/token` when the API key is rejected.",
1563
+ properties: {
1564
+ code: {
1565
+ type: "string",
1566
+ enum: ["invalid_apikey", "apikey_revoked", "apikey_expired"],
1567
+ description: "Machine-readable error reason.",
1568
+ },
1569
+ message: {
1570
+ type: "string",
1571
+ description: "Human-readable description of the failure.",
1572
+ },
1573
+ },
1574
+ } as const;