@qtsurfer/api-client 0.1.1 → 0.2.1

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.
package/LICENSE CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2026 QTSurfer
189
+ Copyright 2026 Wualabs LTD
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -41,14 +41,39 @@ if (error) throw error;
41
41
  console.log(exchanges);
42
42
  ```
43
43
 
44
+ ### API key → JWT
45
+
46
+ Every endpoint above expects a short-lived JWT in `Authorization: Bearer …`.
47
+ Exchange a long-lived API key for one via `auth`:
48
+
49
+ ```ts
50
+ import { auth } from '@qtsurfer/api-client';
51
+
52
+ const { data, error } = await auth({
53
+ baseUrl: 'https://api.qtsurfer.com/v1',
54
+ headers: { 'X-API-Key': process.env.QTSURFER_APIKEY! },
55
+ });
56
+ if (error) throw error;
57
+
58
+ const { access_token: jwt } = data; // feed to client.setConfig() for the rest
59
+ ```
60
+
61
+ For production use, prefer the [`@qtsurfer/sdk`](https://github.com/QTSurfer/sdk-ts)
62
+ `auth(apikey)` helper — it returns a session that refreshes the JWT
63
+ transparently, reads `QTSURFER_APIKEY` from the environment, and supports
64
+ pluggable token stores so callers don't reinvent that plumbing.
65
+
44
66
  ## API surface
45
67
 
46
68
  All operations are exported as standalone functions; every operation accepts an `Options` object and returns `{ data, error, response }`.
47
69
 
48
70
  | Function | Method | Path | Purpose |
49
71
  | -------- | ------ | ---- | ------- |
72
+ | `auth` | POST | `/auth/token` | Exchange an API key for a short-lived JWT |
50
73
  | `getExchanges` | GET | `/exchanges` | List available exchanges |
51
74
  | `getInstruments` | GET | `/exchange/{exchangeId}/instruments` | List instruments for an exchange |
75
+ | `getExchangeTickersHour` | GET | `/exchange/{exchangeId}/tickers/{base}/{quote}` | Download one hour of tickers as Lastra/Parquet |
76
+ | `getExchangeKlinesHour` | GET | `/exchange/{exchangeId}/klines/{base}/{quote}` | Download one hour of klines as Lastra/Parquet |
52
77
  | `postStrategy` | POST | `/strategy` | Compile a strategy |
53
78
  | `getStrategyStatus` | GET | `/strategy/{strategyId}` | Poll strategy compilation status |
54
79
  | `prepareBacktesting` | POST | `/backtesting/prepare` | Start a data preparation job |
package/dist/index.d.ts CHANGED
@@ -69,7 +69,7 @@ type Exchange = {
69
69
  description?: string;
70
70
  };
71
71
  /**
72
- * Managed exchange data sources available for backtesting. Currently only ticker is supported; kline and funding rate support is planned.
72
+ * Managed exchange data sources available for backtesting.
73
73
  */
74
74
  type DataSourceType = 'ticker';
75
75
  /**
@@ -77,13 +77,17 @@ type DataSourceType = 'ticker';
77
77
  */
78
78
  type JobState = {
79
79
  /**
80
- * Unique context ID for the job
80
+ * Opaque context identifier for the job
81
81
  */
82
82
  contextId: string;
83
83
  /**
84
- * Current status of the job
84
+ * Current status of the job. `Partial` (prepare only) means some
85
+ * hours are still being produced asynchronously — keep polling.
86
+ * Treat `Completed | Aborted | Failed` as terminal; anything else
87
+ * means keep polling.
88
+ *
85
89
  */
86
- status: 'New' | 'Started' | 'Completed' | 'Aborted' | 'Failed';
90
+ status: 'New' | 'Started' | 'Partial' | 'Completed' | 'Aborted' | 'Failed';
87
91
  /**
88
92
  * Detailed status information, if available
89
93
  */
@@ -124,11 +128,11 @@ type BacktestJobResult = {
124
128
  state: JobState;
125
129
  };
126
130
  /**
127
- * Execution result map. Always includes core fields (hostName, iops, strategyId, instrument). Yield metrics (pnlTotal, totalTrades, winRate, etc.) are present when the strategy emitted at least one trade. When signal storage is enabled, includes signal fields described below.
131
+ * 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.
128
132
  */
129
133
  type ResultMap = {
130
134
  /**
131
- * Hostname of the worker node that executed the strategy
135
+ * Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.
132
136
  */
133
137
  hostName?: string;
134
138
  /**
@@ -136,7 +140,7 @@ type ResultMap = {
136
140
  */
137
141
  iops?: number;
138
142
  /**
139
- * Redis key of the compiled strategy used for execution. Format: strategy:{userId}:ticker:{compilationId}
143
+ * Identifier of the compiled strategy that produced this result
140
144
  */
141
145
  strategyId: string;
142
146
  /**
@@ -147,6 +151,10 @@ type ResultMap = {
147
151
  * Total profit and loss in the output currency
148
152
  */
149
153
  pnlTotal?: number;
154
+ /**
155
+ * Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.
156
+ */
157
+ pnlTotalPercent?: number;
150
158
  /**
151
159
  * Total number of trades executed by the strategy
152
160
  */
@@ -175,20 +183,24 @@ type ResultMap = {
175
183
  * Maximum percentage drawdown from peak equity
176
184
  */
177
185
  maxDrawdownPercent?: number;
186
+ /**
187
+ * 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.
188
+ */
189
+ equityCurve?: Array<EquityPoint>;
178
190
  /**
179
191
  * Number of signals emitted during strategy execution
180
192
  */
181
193
  signalCount?: number;
182
194
  /**
183
- * Storage key for the signal Parquet file. Format: {userId}/exec/{exchange}/{jobId}
195
+ * Storage key for the signals file. Treat as opaque; use signalsUrl to download.
184
196
  */
185
197
  signalsId?: string;
186
198
  /**
187
- * Public HTTPS URL to the Parquet file. Computed at setup time from publicBaseUrl + signalsId + .parquet. Available even before upload completes.
199
+ * HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.
188
200
  */
189
201
  signalsUrl?: string;
190
202
  /**
191
- * Upload status. Done = file uploaded to R2. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted or storage not configured.
203
+ * Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.
192
204
  */
193
205
  signalsUpload?: 'Done' | 'Failed' | 'Skipped';
194
206
  /**
@@ -200,10 +212,82 @@ type ResultMap = {
200
212
  */
201
213
  signalsUploadReason?: string;
202
214
  };
215
+ /**
216
+ * Single sample of the running equity at a yield event.
217
+ */
218
+ type EquityPoint = {
219
+ /**
220
+ * Epoch milliseconds. The first point in an equity curve is anchored at the backtest `from`; subsequent points carry the timestamp of each emitted yield.
221
+ */
222
+ timestamp: number;
223
+ /**
224
+ * Running equity at this point (`initialCapital + cumulativePnl`).
225
+ */
226
+ equity: number;
227
+ };
203
228
  /**
204
229
  * Unique identifier for a compiled strategy
205
230
  */
206
231
  type StrategyId = string;
232
+ type AuthTokenResponse = {
233
+ /**
234
+ * Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.
235
+ */
236
+ access_token: string;
237
+ /**
238
+ * Always `Bearer`.
239
+ */
240
+ token_type: 'Bearer';
241
+ /**
242
+ * Seconds until the JWT expires (typically 3600).
243
+ */
244
+ expires_in: number;
245
+ /**
246
+ * Scopes granted to this token. Reserved for future use; currently always empty.
247
+ */
248
+ scopes?: Array<string>;
249
+ /**
250
+ * Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.
251
+ */
252
+ tier: 'free' | 'basic' | 'pro' | 'elite';
253
+ };
254
+ /**
255
+ * Error envelope returned by `POST /auth/token` when the API key is rejected.
256
+ */
257
+ type AuthTokenError = {
258
+ /**
259
+ * Machine-readable error reason.
260
+ */
261
+ code: 'invalid_apikey' | 'apikey_revoked' | 'apikey_expired';
262
+ /**
263
+ * Human-readable description of the failure.
264
+ */
265
+ message: string;
266
+ };
267
+ type AuthData = {
268
+ body?: never;
269
+ path?: never;
270
+ query?: never;
271
+ url: '/auth/token';
272
+ };
273
+ type AuthErrors = {
274
+ /**
275
+ * API key is invalid, revoked, or expired.
276
+ */
277
+ 401: AuthTokenError;
278
+ /**
279
+ * Rate limit exceeded for this API key.
280
+ */
281
+ 429: unknown;
282
+ };
283
+ type AuthError = AuthErrors[keyof AuthErrors];
284
+ type AuthResponses = {
285
+ /**
286
+ * API key accepted; JWT returned.
287
+ */
288
+ 200: AuthTokenResponse;
289
+ };
290
+ type AuthResponse = AuthResponses[keyof AuthResponses];
207
291
  type GetExchangesData = {
208
292
  body?: never;
209
293
  path?: never;
@@ -242,6 +326,122 @@ type GetInstrumentsResponses = {
242
326
  200: Array<InstrumentDetail>;
243
327
  };
244
328
  type GetInstrumentsResponse = GetInstrumentsResponses[keyof GetInstrumentsResponses];
329
+ type GetExchangeTickersHourData = {
330
+ body?: never;
331
+ path: {
332
+ /**
333
+ * ID of the exchange (e.g. `binance`).
334
+ */
335
+ exchangeId: string;
336
+ /**
337
+ * Base asset symbol (first leg of the pair).
338
+ */
339
+ base: string;
340
+ /**
341
+ * Quote asset symbol (second leg of the pair).
342
+ */
343
+ quote: string;
344
+ };
345
+ query: {
346
+ /**
347
+ * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
348
+ * `[HH:00:00Z, HH+1:00:00Z)`.
349
+ *
350
+ */
351
+ hour: string;
352
+ /**
353
+ * Response wire format. `lastra` (default) returns raw Lastra bytes.
354
+ * `parquet` returns Parquet via on-the-fly conversion using
355
+ * [lastra-convert](https://github.com/QTSurfer/lastra-convert).
356
+ *
357
+ */
358
+ format?: 'lastra' | 'parquet';
359
+ };
360
+ url: '/exchange/{exchangeId}/tickers/{base}/{quote}';
361
+ };
362
+ type GetExchangeTickersHourErrors = {
363
+ /**
364
+ * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
365
+ */
366
+ 400: ResponseError;
367
+ /**
368
+ * No Lastra segment exists for the requested instrument/hour.
369
+ */
370
+ 404: ResponseError;
371
+ /**
372
+ * Unexpected I/O error serving the file.
373
+ */
374
+ 500: ResponseError;
375
+ };
376
+ type GetExchangeTickersHourError = GetExchangeTickersHourErrors[keyof GetExchangeTickersHourErrors];
377
+ type GetExchangeTickersHourResponses = {
378
+ /**
379
+ * One hour of tickers for the instrument. `Content-Type` is
380
+ * `application/vnd.lastra` by default or
381
+ * `application/vnd.apache.parquet` when `format=parquet` was
382
+ * requested.
383
+ *
384
+ */
385
+ 200: Blob | File;
386
+ };
387
+ type GetExchangeTickersHourResponse = GetExchangeTickersHourResponses[keyof GetExchangeTickersHourResponses];
388
+ type GetExchangeKlinesHourData = {
389
+ body?: never;
390
+ path: {
391
+ /**
392
+ * ID of the exchange (e.g. `binance`).
393
+ */
394
+ exchangeId: string;
395
+ /**
396
+ * Base asset symbol.
397
+ */
398
+ base: string;
399
+ /**
400
+ * Quote asset symbol.
401
+ */
402
+ quote: string;
403
+ };
404
+ query: {
405
+ /**
406
+ * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
407
+ * `[HH:00:00Z, HH+1:00:00Z)`.
408
+ *
409
+ */
410
+ hour: string;
411
+ /**
412
+ * Response wire format. `lastra` (default) returns raw Lastra bytes.
413
+ * `parquet` returns Parquet via on-the-fly conversion.
414
+ *
415
+ */
416
+ format?: 'lastra' | 'parquet';
417
+ };
418
+ url: '/exchange/{exchangeId}/klines/{base}/{quote}';
419
+ };
420
+ type GetExchangeKlinesHourErrors = {
421
+ /**
422
+ * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
423
+ */
424
+ 400: ResponseError;
425
+ /**
426
+ * No Lastra segment exists for the requested instrument/hour.
427
+ */
428
+ 404: ResponseError;
429
+ /**
430
+ * Unexpected I/O error serving the file.
431
+ */
432
+ 500: ResponseError;
433
+ };
434
+ type GetExchangeKlinesHourError = GetExchangeKlinesHourErrors[keyof GetExchangeKlinesHourErrors];
435
+ type GetExchangeKlinesHourResponses = {
436
+ /**
437
+ * One hour of klines for the instrument. `Content-Type` is
438
+ * `application/vnd.lastra` by default or
439
+ * `application/vnd.apache.parquet` when `format=parquet`.
440
+ *
441
+ */
442
+ 200: Blob | File;
443
+ };
444
+ type GetExchangeKlinesHourResponse = GetExchangeKlinesHourResponses[keyof GetExchangeKlinesHourResponses];
245
445
  type PostStrategyData = {
246
446
  /**
247
447
  * Raw strategy Java source code
@@ -335,6 +535,15 @@ type PrepareBacktestingData = {
335
535
  *
336
536
  */
337
537
  to: string;
538
+ /**
539
+ * Output bar cadence for the prepared range. Defaults to the publisher's
540
+ * native cadence (`1s`); coarser cadences are produced on demand via
541
+ * resampling and stored alongside the native blob in cache. Coarser-than-
542
+ * source values must be exact multiples of the source cadence — invalid
543
+ * labels return `400`.
544
+ *
545
+ */
546
+ cadence?: '1s' | '5s' | '1m' | '5m' | '15m' | '1h' | '4h' | '1d';
338
547
  };
339
548
  path: {
340
549
  /**
@@ -548,6 +757,19 @@ type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean
548
757
  */
549
758
  meta?: Record<string, unknown>;
550
759
  };
760
+ /**
761
+ * Exchange API key for a short-lived JWT
762
+ * Exchanges a long-lived API key for a short-lived JWT used by every other
763
+ * endpoint. This is the only endpoint that accepts an API key directly —
764
+ * callers should obtain a JWT here, then send it as `Authorization: Bearer
765
+ * <token>` to all other operations.
766
+ *
767
+ * The returned JWT carries the caller's subscription `tier` as a claim and
768
+ * expires after `expires_in` seconds. Callers should refresh the token
769
+ * before expiry (or on a `401` response) by calling this endpoint again.
770
+ *
771
+ */
772
+ declare const auth: <ThrowOnError extends boolean = false>(options?: Options<AuthData, ThrowOnError>) => _hey_api_client_fetch.RequestResult<AuthTokenResponse, unknown, ThrowOnError>;
551
773
  /**
552
774
  * Get a list of available exchanges
553
775
  */
@@ -556,6 +778,52 @@ declare const getExchanges: <ThrowOnError extends boolean = false>(options?: Opt
556
778
  * Get a list of Instruments from a specific exchange
557
779
  */
558
780
  declare const getInstruments: <ThrowOnError extends boolean = false>(options: Options<GetInstrumentsData, ThrowOnError>) => _hey_api_client_fetch.RequestResult<InstrumentDetail[], ResponseError, ThrowOnError>;
781
+ /**
782
+ * Download one hour of tickers for an instrument as a Lastra segment
783
+ * Serves exactly one hour of raw ticker data for the given instrument on the
784
+ * requested exchange. The payload is a native [Lastra](https://github.com/QTSurfer/lastra-java)
785
+ * file — QTSurfer's columnar format for tick-precision timeseries — with
786
+ * no JSON envelope.
787
+ *
788
+ * One segment = one hour, aligned to UTC. The `hour` query parameter selects
789
+ * the segment and must match `YYYY-MM-DDTHH` (no minutes/seconds, no timezone
790
+ * suffix). Example: `hour=2026-01-15T10` returns `h10.lastra` for
791
+ * 2026-01-15, covering `[10:00:00Z, 11:00:00Z)`. Hours not yet available
792
+ * return `404`.
793
+ *
794
+ * A `format=parquet` query parameter switches the response to on-the-fly
795
+ * Parquet conversion via [lastra-convert](https://github.com/QTSurfer/lastra-convert)
796
+ * for clients that don't yet read Lastra. Lastra is the primary format
797
+ * and cheaper when the client can consume it.
798
+ *
799
+ * Clients:
800
+ * - [lastra-java](https://github.com/QTSurfer/lastra-java) — reference
801
+ * Java reader/writer with per-column codecs (ALP, Gorilla, delta-varint,
802
+ * ZSTD) and CRC32 integrity.
803
+ * - [lastra-ts](https://github.com/QTSurfer/lastra-ts) — TypeScript reader
804
+ * (~4 kB bundle, browser + Node.js).
805
+ * - [duckdb-lastra](https://github.com/QTSurfer/duckdb-lastra) — DuckDB
806
+ * extension for ad-hoc SQL over Lastra files.
807
+ * - [lastra-convert](https://github.com/QTSurfer/lastra-convert) — CLI + Java
808
+ * API for converting to/from Parquet, Reef, and CSV.
809
+ * - `curl -OJ` for offline dumps (the `Content-Disposition` header sets a
810
+ * descriptive filename).
811
+ *
812
+ */
813
+ declare const getExchangeTickersHour: <ThrowOnError extends boolean = false>(options: Options<GetExchangeTickersHourData, ThrowOnError>) => _hey_api_client_fetch.RequestResult<Blob | File, ResponseError, ThrowOnError>;
814
+ /**
815
+ * Download one hour of klines for an instrument as a Lastra segment
816
+ * Same shape and semantics as `/exchange/{exchangeId}/tickers/{base}/{quote}`,
817
+ * but serves klines (aggregated bars) instead of raw ticks. One
818
+ * [Lastra](https://github.com/QTSurfer/lastra-java) segment = one hour of
819
+ * klines at the exchange's native kline cadence, aligned to UTC.
820
+ *
821
+ * Klines use the same columnar layout as tickers — readers that handle one
822
+ * format read the other with the same code. Use this endpoint when a
823
+ * per-tick payload would be too large for the window of interest.
824
+ *
825
+ */
826
+ declare const getExchangeKlinesHour: <ThrowOnError extends boolean = false>(options: Options<GetExchangeKlinesHourData, ThrowOnError>) => _hey_api_client_fetch.RequestResult<Blob | File, ResponseError, ThrowOnError>;
559
827
  /**
560
828
  * Compile a strategy from source code
561
829
  * Submits raw strategy source for compilation. By default the call is synchronous and returns
@@ -633,4 +901,4 @@ declare const getExecutionResult: <ThrowOnError extends boolean = false>(options
633
901
 
634
902
  declare const client: _hey_api_client_fetch.Client;
635
903
 
636
- export { type AcceptedJob, type BacktestJobResult, type CancelExecutionData, type CancelExecutionError, type CancelExecutionErrors, type CancelExecutionResponse, type CancelExecutionResponses, type ClientOptions, type DataSourceType, type Exchange, type ExecuteBacktestingData, type ExecuteBacktestingError, type ExecuteBacktestingErrors, type ExecuteBacktestingResponse, type ExecuteBacktestingResponses, type GetExchangesData, type GetExchangesResponse, type GetExchangesResponses, type GetExecutionResultData, type GetExecutionResultError, type GetExecutionResultErrors, type GetExecutionResultResponse, type GetExecutionResultResponses, type GetInstrumentsData, type GetInstrumentsError, type GetInstrumentsErrors, type GetInstrumentsResponse, type GetInstrumentsResponses, type GetPreparationStatusData, type GetPreparationStatusError, type GetPreparationStatusErrors, type GetPreparationStatusResponse, type GetPreparationStatusResponses, type GetStrategyStatusData, type GetStrategyStatusError, type GetStrategyStatusErrors, type GetStrategyStatusResponse, type GetStrategyStatusResponses, type Instrument, type InstrumentDetail, type JobState, type Options, type PostStrategyData, type PostStrategyError, type PostStrategyErrors, type PostStrategyResponse, type PostStrategyResponses, type PrepareBacktestingData, type PrepareBacktestingError, type PrepareBacktestingErrors, type PrepareBacktestingResponse, type PrepareBacktestingResponses, type ResponseError, type ResultMap, type StrategyId, cancelExecution, client, executeBacktesting, getExchanges, getExecutionResult, getInstruments, getPreparationStatus, getStrategyStatus, postStrategy, prepareBacktesting };
904
+ export { type AcceptedJob, type AuthData, type AuthError, type AuthErrors, type AuthResponse, type AuthResponses, type AuthTokenError, type AuthTokenResponse, type BacktestJobResult, type CancelExecutionData, type CancelExecutionError, type CancelExecutionErrors, type CancelExecutionResponse, type CancelExecutionResponses, type ClientOptions, type DataSourceType, type EquityPoint, type Exchange, type ExecuteBacktestingData, type ExecuteBacktestingError, type ExecuteBacktestingErrors, type ExecuteBacktestingResponse, type ExecuteBacktestingResponses, type GetExchangeKlinesHourData, type GetExchangeKlinesHourError, type GetExchangeKlinesHourErrors, type GetExchangeKlinesHourResponse, type GetExchangeKlinesHourResponses, type GetExchangeTickersHourData, type GetExchangeTickersHourError, type GetExchangeTickersHourErrors, type GetExchangeTickersHourResponse, type GetExchangeTickersHourResponses, type GetExchangesData, type GetExchangesResponse, type GetExchangesResponses, type GetExecutionResultData, type GetExecutionResultError, type GetExecutionResultErrors, type GetExecutionResultResponse, type GetExecutionResultResponses, type GetInstrumentsData, type GetInstrumentsError, type GetInstrumentsErrors, type GetInstrumentsResponse, type GetInstrumentsResponses, type GetPreparationStatusData, type GetPreparationStatusError, type GetPreparationStatusErrors, type GetPreparationStatusResponse, type GetPreparationStatusResponses, type GetStrategyStatusData, type GetStrategyStatusError, type GetStrategyStatusErrors, type GetStrategyStatusResponse, type GetStrategyStatusResponses, type Instrument, type InstrumentDetail, type JobState, type Options, type PostStrategyData, type PostStrategyError, type PostStrategyErrors, type PostStrategyResponse, type PostStrategyResponses, type PrepareBacktestingData, type PrepareBacktestingError, type PrepareBacktestingErrors, type PrepareBacktestingResponse, type PrepareBacktestingResponses, type ResponseError, type ResultMap, type StrategyId, auth, cancelExecution, client, executeBacktesting, getExchangeKlinesHour, getExchangeTickersHour, getExchanges, getExecutionResult, getInstruments, getPreparationStatus, getStrategyStatus, postStrategy, prepareBacktesting };
package/dist/index.js CHANGED
@@ -5,6 +5,18 @@ var client = createClient(createConfig({
5
5
  }));
6
6
 
7
7
  // src/generated/sdk.gen.ts
8
+ var auth = (options) => {
9
+ return (options?.client ?? client).post({
10
+ security: [
11
+ {
12
+ name: "X-API-Key",
13
+ type: "apiKey"
14
+ }
15
+ ],
16
+ url: "/auth/token",
17
+ ...options
18
+ });
19
+ };
8
20
  var getExchanges = (options) => {
9
21
  return (options?.client ?? client).get({
10
22
  url: "/exchanges",
@@ -17,6 +29,18 @@ var getInstruments = (options) => {
17
29
  ...options
18
30
  });
19
31
  };
32
+ var getExchangeTickersHour = (options) => {
33
+ return (options.client ?? client).get({
34
+ url: "/exchange/{exchangeId}/tickers/{base}/{quote}",
35
+ ...options
36
+ });
37
+ };
38
+ var getExchangeKlinesHour = (options) => {
39
+ return (options.client ?? client).get({
40
+ url: "/exchange/{exchangeId}/klines/{base}/{quote}",
41
+ ...options
42
+ });
43
+ };
20
44
  var postStrategy = (options) => {
21
45
  return (options.client ?? client).post({
22
46
  bodySerializer: null,
@@ -115,9 +139,12 @@ var getExecutionResult = (options) => {
115
139
  });
116
140
  };
117
141
  export {
142
+ auth,
118
143
  cancelExecution,
119
144
  client,
120
145
  executeBacktesting,
146
+ getExchangeKlinesHour,
147
+ getExchangeTickersHour,
121
148
  getExchanges,
122
149
  getExecutionResult,
123
150
  getInstruments,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generated/client.gen.ts","../src/generated/sdk.gen.ts"],"sourcesContent":["// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { ClientOptions } from './types.gen';\nimport { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch';\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions>({\n baseUrl: 'https://api.staging.qtsurfer.com/v1'\n}));","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';\nimport type { GetExchangesData, GetExchangesResponse, GetInstrumentsData, GetInstrumentsResponse, GetInstrumentsError, PostStrategyData, PostStrategyResponse, PostStrategyError, GetStrategyStatusData, GetStrategyStatusResponse, GetStrategyStatusError, PrepareBacktestingData, PrepareBacktestingResponse, PrepareBacktestingError, GetPreparationStatusData, GetPreparationStatusResponse, GetPreparationStatusError, ExecuteBacktestingData, ExecuteBacktestingResponse, ExecuteBacktestingError, CancelExecutionData, CancelExecutionResponse, CancelExecutionError, GetExecutionResultData, GetExecutionResultResponse, GetExecutionResultError } from './types.gen';\nimport { client as _heyApiClient } from './client.gen';\n\nexport type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {\n /**\n * You can provide a client instance returned by `createClient()` instead of\n * individual options. This might be also useful if you want to implement a\n * custom client.\n */\n client?: Client;\n /**\n * You can pass arbitrary values through the `meta` object. This can be\n * used to access values that aren't defined as part of the SDK function.\n */\n meta?: Record<string, unknown>;\n};\n\n/**\n * Get a list of available exchanges\n */\nexport const getExchanges = <ThrowOnError extends boolean = false>(options?: Options<GetExchangesData, ThrowOnError>) => {\n return (options?.client ?? _heyApiClient).get<GetExchangesResponse, unknown, ThrowOnError>({\n url: '/exchanges',\n ...options\n });\n};\n\n/**\n * Get a list of Instruments from a specific exchange\n */\nexport const getInstruments = <ThrowOnError extends boolean = false>(options: Options<GetInstrumentsData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetInstrumentsResponse, GetInstrumentsError, ThrowOnError>({\n url: '/exchange/{exchangeId}/instruments',\n ...options\n });\n};\n\n/**\n * Compile a strategy from source code\n * Submits raw strategy source for compilation. By default the call is synchronous and returns\n * the `strategyId` once compilation succeeds. Set the header `X-Compile-Async: true` to enqueue\n * the compile task and return immediately with a `jobId` — poll\n * `GET /strategy/{strategyId}` to check status.\n *\n * The `strategyId` is deterministic: the same source for the same user always produces the\n * same id.\n *\n */\nexport const postStrategy = <ThrowOnError extends boolean = false>(options: Options<PostStrategyData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).post<PostStrategyResponse, PostStrategyError, ThrowOnError>({\n bodySerializer: null,\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/strategy',\n ...options,\n headers: {\n 'Content-Type': 'text/plain',\n ...options?.headers\n }\n });\n};\n\n/**\n * Get the status of an async compile task\n * Polls the status of a strategy compilation. Useful when the strategy was submitted with\n * `X-Compile-Async: true`. Returns the resolved `strategyId` once compilation completes.\n *\n */\nexport const getStrategyStatus = <ThrowOnError extends boolean = false>(options: Options<GetStrategyStatusData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetStrategyStatusResponse, GetStrategyStatusError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/strategy/{strategyId}',\n ...options\n });\n};\n\n/**\n * Prepare backtesting data\n * Enqueues a prepare task over the requested date range. Returns immediately with a `jobId`;\n * poll `GET /backtest/{exchangeId}/{type}/prepare/{jobId}` for completion.\n *\n * The same params always return the same `jobId` (idempotent). Repeated calls with identical\n * params do not enqueue duplicate work — they reuse the existing job.\n *\n */\nexport const prepareBacktesting = <ThrowOnError extends boolean = false>(options: Options<PrepareBacktestingData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).post<PrepareBacktestingResponse, PrepareBacktestingError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/prepare',\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n ...options?.headers\n }\n });\n};\n\n/**\n * Get the status of a prepare job\n * Retrieves the current state of the prepare job identified by `jobId`.\n * Poll until `status` is `Completed`, `Failed`, or `Aborted`.\n *\n */\nexport const getPreparationStatus = <ThrowOnError extends boolean = false>(options: Options<GetPreparationStatusData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetPreparationStatusResponse, GetPreparationStatusError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/prepare/{jobId}',\n ...options\n });\n};\n\n/**\n * Execute a compiled strategy against a prepared dataset\n * Enqueues an execute task that runs the strategy identified by `strategyId` over the data\n * prepared by the prepare job identified by `prepareJobId`. The instrument and date range are\n * recovered from the prepare job — they do not need to be sent again.\n *\n * Returns immediately with a `jobId`; poll `GET /backtest/{exchangeId}/{type}/execute/{jobId}`\n * for the result.\n *\n * The same params (same `prepareJobId`, `strategyId`, `storeSignals`) always return the same\n * `jobId` (idempotent).\n *\n */\nexport const executeBacktesting = <ThrowOnError extends boolean = false>(options: Options<ExecuteBacktestingData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).post<ExecuteBacktestingResponse, ExecuteBacktestingError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/execute',\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n ...options?.headers\n }\n });\n};\n\n/**\n * Cancel a running backtest execution\n * Requests cancellation of the specified execution. The execution\n * status will transition to `Aborted` once the cancellation is\n * processed. Cancellation is asynchronous — poll the GET endpoint\n * to confirm the final status.\n *\n */\nexport const cancelExecution = <ThrowOnError extends boolean = false>(options: Options<CancelExecutionData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).delete<CancelExecutionResponse, CancelExecutionError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/execute/{jobId}',\n ...options\n });\n};\n\n/**\n * Get the result of a backtest execution job\n * Retrieves the current state and results of the execute job identified by `jobId`.\n * Poll until `state.status` is `Completed`, `Failed`, or `Aborted`.\n *\n */\nexport const getExecutionResult = <ThrowOnError extends boolean = false>(options: Options<GetExecutionResultData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetExecutionResultResponse, GetExecutionResultError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/execute/{jobId}',\n ...options\n });\n};"],"mappings":";AAGA,SAAkE,cAAc,oBAAoB;AAY7F,IAAM,SAAS,aAAa,aAA4B;AAAA,EAC3D,SAAS;AACb,CAAC,CAAC;;;ACMK,IAAM,eAAe,CAAuC,YAAsD;AACrH,UAAQ,SAAS,UAAU,QAAe,IAAiD;AAAA,IACvF,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAKO,IAAM,iBAAiB,CAAuC,YAAuD;AACxH,UAAQ,QAAQ,UAAU,QAAe,IAA+D;AAAA,IACpG,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAaO,IAAM,eAAe,CAAuC,YAAqD;AACpH,UAAQ,QAAQ,UAAU,QAAe,KAA4D;AAAA,IACjG,gBAAgB;AAAA,IAChB,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACL,gBAAgB;AAAA,MAChB,GAAG,SAAS;AAAA,IAChB;AAAA,EACJ,CAAC;AACL;AAQO,IAAM,oBAAoB,CAAuC,YAA0D;AAC9H,UAAQ,QAAQ,UAAU,QAAe,IAAqE;AAAA,IAC1G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAWO,IAAM,qBAAqB,CAAuC,YAA2D;AAChI,UAAQ,QAAQ,UAAU,QAAe,KAAwE;AAAA,IAC7G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACL,gBAAgB;AAAA,MAChB,GAAG,SAAS;AAAA,IAChB;AAAA,EACJ,CAAC;AACL;AAQO,IAAM,uBAAuB,CAAuC,YAA6D;AACpI,UAAQ,QAAQ,UAAU,QAAe,IAA2E;AAAA,IAChH,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAeO,IAAM,qBAAqB,CAAuC,YAA2D;AAChI,UAAQ,QAAQ,UAAU,QAAe,KAAwE;AAAA,IAC7G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACL,gBAAgB;AAAA,MAChB,GAAG,SAAS;AAAA,IAChB;AAAA,EACJ,CAAC;AACL;AAUO,IAAM,kBAAkB,CAAuC,YAAwD;AAC1H,UAAQ,QAAQ,UAAU,QAAe,OAAoE;AAAA,IACzG,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAQO,IAAM,qBAAqB,CAAuC,YAA2D;AAChI,UAAQ,QAAQ,UAAU,QAAe,IAAuE;AAAA,IAC5G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;","names":[]}
1
+ {"version":3,"sources":["../src/generated/client.gen.ts","../src/generated/sdk.gen.ts"],"sourcesContent":["// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { ClientOptions } from './types.gen';\nimport { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch';\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions>({\n baseUrl: 'https://api.staging.qtsurfer.com/v1'\n}));","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';\nimport type { AuthData, AuthResponse, AuthError, GetExchangesData, GetExchangesResponse, GetInstrumentsData, GetInstrumentsResponse, GetInstrumentsError, GetExchangeTickersHourData, GetExchangeTickersHourResponse, GetExchangeTickersHourError, GetExchangeKlinesHourData, GetExchangeKlinesHourResponse, GetExchangeKlinesHourError, PostStrategyData, PostStrategyResponse, PostStrategyError, GetStrategyStatusData, GetStrategyStatusResponse, GetStrategyStatusError, PrepareBacktestingData, PrepareBacktestingResponse, PrepareBacktestingError, GetPreparationStatusData, GetPreparationStatusResponse, GetPreparationStatusError, ExecuteBacktestingData, ExecuteBacktestingResponse, ExecuteBacktestingError, CancelExecutionData, CancelExecutionResponse, CancelExecutionError, GetExecutionResultData, GetExecutionResultResponse, GetExecutionResultError } from './types.gen';\nimport { client as _heyApiClient } from './client.gen';\n\nexport type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {\n /**\n * You can provide a client instance returned by `createClient()` instead of\n * individual options. This might be also useful if you want to implement a\n * custom client.\n */\n client?: Client;\n /**\n * You can pass arbitrary values through the `meta` object. This can be\n * used to access values that aren't defined as part of the SDK function.\n */\n meta?: Record<string, unknown>;\n};\n\n/**\n * Exchange API key for a short-lived JWT\n * Exchanges a long-lived API key for a short-lived JWT used by every other\n * endpoint. This is the only endpoint that accepts an API key directly —\n * callers should obtain a JWT here, then send it as `Authorization: Bearer\n * <token>` to all other operations.\n *\n * The returned JWT carries the caller's subscription `tier` as a claim and\n * expires after `expires_in` seconds. Callers should refresh the token\n * before expiry (or on a `401` response) by calling this endpoint again.\n *\n */\nexport const auth = <ThrowOnError extends boolean = false>(options?: Options<AuthData, ThrowOnError>) => {\n return (options?.client ?? _heyApiClient).post<AuthResponse, AuthError, ThrowOnError>({\n security: [\n {\n name: 'X-API-Key',\n type: 'apiKey'\n }\n ],\n url: '/auth/token',\n ...options\n });\n};\n\n/**\n * Get a list of available exchanges\n */\nexport const getExchanges = <ThrowOnError extends boolean = false>(options?: Options<GetExchangesData, ThrowOnError>) => {\n return (options?.client ?? _heyApiClient).get<GetExchangesResponse, unknown, ThrowOnError>({\n url: '/exchanges',\n ...options\n });\n};\n\n/**\n * Get a list of Instruments from a specific exchange\n */\nexport const getInstruments = <ThrowOnError extends boolean = false>(options: Options<GetInstrumentsData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetInstrumentsResponse, GetInstrumentsError, ThrowOnError>({\n url: '/exchange/{exchangeId}/instruments',\n ...options\n });\n};\n\n/**\n * Download one hour of tickers for an instrument as a Lastra segment\n * Serves exactly one hour of raw ticker data for the given instrument on the\n * requested exchange. The payload is a native [Lastra](https://github.com/QTSurfer/lastra-java)\n * file — QTSurfer's columnar format for tick-precision timeseries — with\n * no JSON envelope.\n *\n * One segment = one hour, aligned to UTC. The `hour` query parameter selects\n * the segment and must match `YYYY-MM-DDTHH` (no minutes/seconds, no timezone\n * suffix). Example: `hour=2026-01-15T10` returns `h10.lastra` for\n * 2026-01-15, covering `[10:00:00Z, 11:00:00Z)`. Hours not yet available\n * return `404`.\n *\n * A `format=parquet` query parameter switches the response to on-the-fly\n * Parquet conversion via [lastra-convert](https://github.com/QTSurfer/lastra-convert)\n * for clients that don't yet read Lastra. Lastra is the primary format\n * and cheaper when the client can consume it.\n *\n * Clients:\n * - [lastra-java](https://github.com/QTSurfer/lastra-java) — reference\n * Java reader/writer with per-column codecs (ALP, Gorilla, delta-varint,\n * ZSTD) and CRC32 integrity.\n * - [lastra-ts](https://github.com/QTSurfer/lastra-ts) — TypeScript reader\n * (~4 kB bundle, browser + Node.js).\n * - [duckdb-lastra](https://github.com/QTSurfer/duckdb-lastra) — DuckDB\n * extension for ad-hoc SQL over Lastra files.\n * - [lastra-convert](https://github.com/QTSurfer/lastra-convert) — CLI + Java\n * API for converting to/from Parquet, Reef, and CSV.\n * - `curl -OJ` for offline dumps (the `Content-Disposition` header sets a\n * descriptive filename).\n *\n */\nexport const getExchangeTickersHour = <ThrowOnError extends boolean = false>(options: Options<GetExchangeTickersHourData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetExchangeTickersHourResponse, GetExchangeTickersHourError, ThrowOnError>({\n url: '/exchange/{exchangeId}/tickers/{base}/{quote}',\n ...options\n });\n};\n\n/**\n * Download one hour of klines for an instrument as a Lastra segment\n * Same shape and semantics as `/exchange/{exchangeId}/tickers/{base}/{quote}`,\n * but serves klines (aggregated bars) instead of raw ticks. One\n * [Lastra](https://github.com/QTSurfer/lastra-java) segment = one hour of\n * klines at the exchange's native kline cadence, aligned to UTC.\n *\n * Klines use the same columnar layout as tickers — readers that handle one\n * format read the other with the same code. Use this endpoint when a\n * per-tick payload would be too large for the window of interest.\n *\n */\nexport const getExchangeKlinesHour = <ThrowOnError extends boolean = false>(options: Options<GetExchangeKlinesHourData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetExchangeKlinesHourResponse, GetExchangeKlinesHourError, ThrowOnError>({\n url: '/exchange/{exchangeId}/klines/{base}/{quote}',\n ...options\n });\n};\n\n/**\n * Compile a strategy from source code\n * Submits raw strategy source for compilation. By default the call is synchronous and returns\n * the `strategyId` once compilation succeeds. Set the header `X-Compile-Async: true` to enqueue\n * the compile task and return immediately with a `jobId` — poll\n * `GET /strategy/{strategyId}` to check status.\n *\n * The `strategyId` is deterministic: the same source for the same user always produces the\n * same id.\n *\n */\nexport const postStrategy = <ThrowOnError extends boolean = false>(options: Options<PostStrategyData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).post<PostStrategyResponse, PostStrategyError, ThrowOnError>({\n bodySerializer: null,\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/strategy',\n ...options,\n headers: {\n 'Content-Type': 'text/plain',\n ...options?.headers\n }\n });\n};\n\n/**\n * Get the status of an async compile task\n * Polls the status of a strategy compilation. Useful when the strategy was submitted with\n * `X-Compile-Async: true`. Returns the resolved `strategyId` once compilation completes.\n *\n */\nexport const getStrategyStatus = <ThrowOnError extends boolean = false>(options: Options<GetStrategyStatusData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetStrategyStatusResponse, GetStrategyStatusError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/strategy/{strategyId}',\n ...options\n });\n};\n\n/**\n * Prepare backtesting data\n * Enqueues a prepare task over the requested date range. Returns immediately with a `jobId`;\n * poll `GET /backtest/{exchangeId}/{type}/prepare/{jobId}` for completion.\n *\n * The same params always return the same `jobId` (idempotent). Repeated calls with identical\n * params do not enqueue duplicate work — they reuse the existing job.\n *\n */\nexport const prepareBacktesting = <ThrowOnError extends boolean = false>(options: Options<PrepareBacktestingData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).post<PrepareBacktestingResponse, PrepareBacktestingError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/prepare',\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n ...options?.headers\n }\n });\n};\n\n/**\n * Get the status of a prepare job\n * Retrieves the current state of the prepare job identified by `jobId`.\n * Poll until `status` is `Completed`, `Failed`, or `Aborted`.\n *\n */\nexport const getPreparationStatus = <ThrowOnError extends boolean = false>(options: Options<GetPreparationStatusData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetPreparationStatusResponse, GetPreparationStatusError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/prepare/{jobId}',\n ...options\n });\n};\n\n/**\n * Execute a compiled strategy against a prepared dataset\n * Enqueues an execute task that runs the strategy identified by `strategyId` over the data\n * prepared by the prepare job identified by `prepareJobId`. The instrument and date range are\n * recovered from the prepare job — they do not need to be sent again.\n *\n * Returns immediately with a `jobId`; poll `GET /backtest/{exchangeId}/{type}/execute/{jobId}`\n * for the result.\n *\n * The same params (same `prepareJobId`, `strategyId`, `storeSignals`) always return the same\n * `jobId` (idempotent).\n *\n */\nexport const executeBacktesting = <ThrowOnError extends boolean = false>(options: Options<ExecuteBacktestingData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).post<ExecuteBacktestingResponse, ExecuteBacktestingError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/execute',\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n ...options?.headers\n }\n });\n};\n\n/**\n * Cancel a running backtest execution\n * Requests cancellation of the specified execution. The execution\n * status will transition to `Aborted` once the cancellation is\n * processed. Cancellation is asynchronous — poll the GET endpoint\n * to confirm the final status.\n *\n */\nexport const cancelExecution = <ThrowOnError extends boolean = false>(options: Options<CancelExecutionData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).delete<CancelExecutionResponse, CancelExecutionError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/execute/{jobId}',\n ...options\n });\n};\n\n/**\n * Get the result of a backtest execution job\n * Retrieves the current state and results of the execute job identified by `jobId`.\n * Poll until `state.status` is `Completed`, `Failed`, or `Aborted`.\n *\n */\nexport const getExecutionResult = <ThrowOnError extends boolean = false>(options: Options<GetExecutionResultData, ThrowOnError>) => {\n return (options.client ?? _heyApiClient).get<GetExecutionResultResponse, GetExecutionResultError, ThrowOnError>({\n security: [\n {\n scheme: 'bearer',\n type: 'http'\n }\n ],\n url: '/backtest/{exchangeId}/{type}/execute/{jobId}',\n ...options\n });\n};"],"mappings":";AAGA,SAAkE,cAAc,oBAAoB;AAY7F,IAAM,SAAS,aAAa,aAA4B;AAAA,EAC3D,SAAS;AACb,CAAC,CAAC;;;ACeK,IAAM,OAAO,CAAuC,YAA8C;AACrG,UAAQ,SAAS,UAAU,QAAe,KAA4C;AAAA,IAClF,UAAU;AAAA,MACN;AAAA,QACI,MAAM;AAAA,QACN,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAKO,IAAM,eAAe,CAAuC,YAAsD;AACrH,UAAQ,SAAS,UAAU,QAAe,IAAiD;AAAA,IACvF,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAKO,IAAM,iBAAiB,CAAuC,YAAuD;AACxH,UAAQ,QAAQ,UAAU,QAAe,IAA+D;AAAA,IACpG,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAkCO,IAAM,yBAAyB,CAAuC,YAA+D;AACxI,UAAQ,QAAQ,UAAU,QAAe,IAA+E;AAAA,IACpH,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAcO,IAAM,wBAAwB,CAAuC,YAA8D;AACtI,UAAQ,QAAQ,UAAU,QAAe,IAA6E;AAAA,IAClH,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAaO,IAAM,eAAe,CAAuC,YAAqD;AACpH,UAAQ,QAAQ,UAAU,QAAe,KAA4D;AAAA,IACjG,gBAAgB;AAAA,IAChB,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACL,gBAAgB;AAAA,MAChB,GAAG,SAAS;AAAA,IAChB;AAAA,EACJ,CAAC;AACL;AAQO,IAAM,oBAAoB,CAAuC,YAA0D;AAC9H,UAAQ,QAAQ,UAAU,QAAe,IAAqE;AAAA,IAC1G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAWO,IAAM,qBAAqB,CAAuC,YAA2D;AAChI,UAAQ,QAAQ,UAAU,QAAe,KAAwE;AAAA,IAC7G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACL,gBAAgB;AAAA,MAChB,GAAG,SAAS;AAAA,IAChB;AAAA,EACJ,CAAC;AACL;AAQO,IAAM,uBAAuB,CAAuC,YAA6D;AACpI,UAAQ,QAAQ,UAAU,QAAe,IAA2E;AAAA,IAChH,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAeO,IAAM,qBAAqB,CAAuC,YAA2D;AAChI,UAAQ,QAAQ,UAAU,QAAe,KAAwE;AAAA,IAC7G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACL,gBAAgB;AAAA,MAChB,GAAG,SAAS;AAAA,IAChB;AAAA,EACJ,CAAC;AACL;AAUO,IAAM,kBAAkB,CAAuC,YAAwD;AAC1H,UAAQ,QAAQ,UAAU,QAAe,OAAoE;AAAA,IACzG,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;AAQO,IAAM,qBAAqB,CAAuC,YAA2D;AAChI,UAAQ,QAAQ,UAAU,QAAe,IAAuE;AAAA,IAC5G,UAAU;AAAA,MACN;AAAA,QACI,QAAQ;AAAA,QACR,MAAM;AAAA,MACV;AAAA,IACJ;AAAA,IACA,KAAK;AAAA,IACL,GAAG;AAAA,EACP,CAAC;AACL;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qtsurfer/api-client",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Auto-generated TypeScript API client for the QTSurfer API (from OpenAPI 3.1 spec)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -96,7 +96,7 @@ export const ExchangeSchema = {
96
96
 
97
97
  export const DataSourceTypeSchema = {
98
98
  type: 'string',
99
- description: 'Managed exchange data sources available for backtesting. Currently only ticker is supported; kline and funding rate support is planned.',
99
+ description: 'Managed exchange data sources available for backtesting.',
100
100
  enum: ['ticker'],
101
101
  example: 'ticker'
102
102
  } as const;
@@ -108,13 +108,17 @@ export const JobStateSchema = {
108
108
  properties: {
109
109
  contextId: {
110
110
  type: 'string',
111
- description: 'Unique context ID for the job',
112
- example: 'jctx:ticker:4a627755-7f5a-4297-b647-8dddd8aee416:binance:1rabbrpk5r4kkegs83w6qr:btc/usdt:2o8heaioicr0edvx5ybcap'
111
+ description: 'Opaque context identifier for the job',
112
+ example: 'ctx_2o8heaioicr0edvx5ybcap'
113
113
  },
114
114
  status: {
115
115
  type: 'string',
116
- description: 'Current status of the job',
117
- enum: ['New', 'Started', 'Completed', 'Aborted', 'Failed'],
116
+ description: `Current status of the job. \`Partial\` (prepare only) means some
117
+ hours are still being produced asynchronously — keep polling.
118
+ Treat \`Completed | Aborted | Failed\` as terminal; anything else
119
+ means keep polling.
120
+ `,
121
+ enum: ['New', 'Started', 'Partial', 'Completed', 'Aborted', 'Failed'],
118
122
  example: 'Completed'
119
123
  },
120
124
  statusDetail: {
@@ -181,13 +185,13 @@ export const BacktestJobResultSchema = {
181
185
 
182
186
  export const ResultMapSchema = {
183
187
  type: 'object',
184
- description: 'Execution result map. Always includes core fields (hostName, iops, strategyId, instrument). Yield metrics (pnlTotal, totalTrades, winRate, etc.) are present when the strategy emitted at least one trade. When signal storage is enabled, includes signal fields described below.',
188
+ 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.',
185
189
  required: ['strategyId', 'instrument'],
186
190
  properties: {
187
191
  hostName: {
188
192
  type: 'string',
189
- description: 'Hostname of the worker node that executed the strategy',
190
- example: 'backtesting-job-worker-84b569dff9-dr4lb'
193
+ description: 'Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.',
194
+ example: 'executor10'
191
195
  },
192
196
  iops: {
193
197
  type: 'number',
@@ -197,8 +201,8 @@ export const ResultMapSchema = {
197
201
  },
198
202
  strategyId: {
199
203
  type: 'string',
200
- description: 'Redis key of the compiled strategy used for execution. Format: strategy:{userId}:ticker:{compilationId}',
201
- example: 'strategy:4a627755-7f5a-4297-b647-8dddd8aee416:ticker:2iyvtenlzh9dabqtxn7nbv'
204
+ description: 'Identifier of the compiled strategy that produced this result',
205
+ example: 'strategy:00000000-0000-0000-0000-000000000000:ticker:2iyvtenlzh9dabqtxn7nbv'
202
206
  },
203
207
  instrument: {
204
208
  type: 'string',
@@ -211,6 +215,12 @@ export const ResultMapSchema = {
211
215
  description: 'Total profit and loss in the output currency',
212
216
  example: 42.75
213
217
  },
218
+ pnlTotalPercent: {
219
+ type: 'number',
220
+ format: 'double',
221
+ description: 'Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.',
222
+ example: 42.75
223
+ },
214
224
  totalTrades: {
215
225
  type: 'integer',
216
226
  format: 'int64',
@@ -253,6 +263,27 @@ export const ResultMapSchema = {
253
263
  description: 'Maximum percentage drawdown from peak equity',
254
264
  example: 8.75
255
265
  },
266
+ equityCurve: {
267
+ type: 'array',
268
+ 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.",
269
+ items: {
270
+ '$ref': '#/components/schemas/EquityPoint'
271
+ },
272
+ example: [
273
+ {
274
+ timestamp: 1700000000000,
275
+ equity: 100
276
+ },
277
+ {
278
+ timestamp: 1700000060000,
279
+ equity: 110.5
280
+ },
281
+ {
282
+ timestamp: 1700000120000,
283
+ equity: 90.25
284
+ }
285
+ ]
286
+ },
256
287
  signalCount: {
257
288
  type: 'integer',
258
289
  description: 'Number of signals emitted during strategy execution',
@@ -260,19 +291,19 @@ export const ResultMapSchema = {
260
291
  },
261
292
  signalsId: {
262
293
  type: 'string',
263
- description: 'Storage key for the signal Parquet file. Format: {userId}/exec/{exchange}/{jobId}',
264
- example: '4a627755-7f5a-4297-b647-8dddd8aee416/exec/binance/3vsndwikcuaatjmb83fjtl'
294
+ description: 'Storage key for the signals file. Treat as opaque; use signalsUrl to download.',
295
+ example: '00000000-0000-0000-0000-000000000000/exec/binance/3vsndwikcuaatjmb83fjtl'
265
296
  },
266
297
  signalsUrl: {
267
298
  type: 'string',
268
299
  format: 'uri',
269
- description: 'Public HTTPS URL to the Parquet file. Computed at setup time from publicBaseUrl + signalsId + .parquet. Available even before upload completes.',
270
- example: 'https://storage.qtsurfer.com/4a627755-7f5a-4297-b647-8dddd8aee416/exec/binance/3vsndwikcuaatjmb83fjtl.parquet'
300
+ description: "HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.",
301
+ example: 'https://storage.qtsurfer.com/00000000-0000-0000-0000-000000000000/exec/binance/3vsndwikcuaatjmb83fjtl.parquet'
271
302
  },
272
303
  signalsUpload: {
273
304
  type: 'string',
274
305
  enum: ['Done', 'Failed', 'Skipped'],
275
- description: 'Upload status. Done = file uploaded to R2. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted or storage not configured.',
306
+ description: 'Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.',
276
307
  example: 'Done'
277
308
  },
278
309
  signalsUploadedAt: {
@@ -284,7 +315,27 @@ export const ResultMapSchema = {
284
315
  signalsUploadReason: {
285
316
  type: 'string',
286
317
  description: 'Human-readable reason when signalsUpload is Failed or Skipped.',
287
- example: 'parquet file empty or missing after close'
318
+ example: 'signal file generation failed'
319
+ }
320
+ }
321
+ } as const;
322
+
323
+ export const EquityPointSchema = {
324
+ type: 'object',
325
+ description: 'Single sample of the running equity at a yield event.',
326
+ required: ['timestamp', 'equity'],
327
+ properties: {
328
+ timestamp: {
329
+ type: 'integer',
330
+ format: 'int64',
331
+ 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.',
332
+ example: 1700000000000
333
+ },
334
+ equity: {
335
+ type: 'number',
336
+ format: 'double',
337
+ description: 'Running equity at this point (`initialCapital + cumulativePnl`).',
338
+ example: 110.5
288
339
  }
289
340
  }
290
341
  } as const;
@@ -293,4 +344,56 @@ export const strategyIdSchema = {
293
344
  description: 'Unique identifier for a compiled strategy',
294
345
  type: 'string',
295
346
  example: '6bsh31ikwkuivhtgcoa6s4'
347
+ } as const;
348
+
349
+ export const AuthTokenResponseSchema = {
350
+ type: 'object',
351
+ required: ['access_token', 'token_type', 'expires_in', 'tier'],
352
+ properties: {
353
+ access_token: {
354
+ type: 'string',
355
+ description: 'Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.'
356
+ },
357
+ token_type: {
358
+ type: 'string',
359
+ enum: ['Bearer'],
360
+ description: 'Always `Bearer`.'
361
+ },
362
+ expires_in: {
363
+ type: 'integer',
364
+ description: 'Seconds until the JWT expires (typically 3600).',
365
+ example: 3600
366
+ },
367
+ scopes: {
368
+ type: 'array',
369
+ description: 'Scopes granted to this token. Reserved for future use; currently always empty.',
370
+ items: {
371
+ type: 'string'
372
+ },
373
+ example: []
374
+ },
375
+ tier: {
376
+ type: 'string',
377
+ enum: ['free', 'basic', 'pro', 'elite'],
378
+ description: 'Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.',
379
+ example: 'free'
380
+ }
381
+ }
382
+ } as const;
383
+
384
+ export const AuthTokenErrorSchema = {
385
+ type: 'object',
386
+ required: ['code', 'message'],
387
+ description: 'Error envelope returned by `POST /auth/token` when the API key is rejected.',
388
+ properties: {
389
+ code: {
390
+ type: 'string',
391
+ enum: ['invalid_apikey', 'apikey_revoked', 'apikey_expired'],
392
+ description: 'Machine-readable error reason.'
393
+ },
394
+ message: {
395
+ type: 'string',
396
+ description: 'Human-readable description of the failure.'
397
+ }
398
+ }
296
399
  } as const;
@@ -1,7 +1,7 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
2
 
3
3
  import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
4
- import type { GetExchangesData, GetExchangesResponse, GetInstrumentsData, GetInstrumentsResponse, GetInstrumentsError, PostStrategyData, PostStrategyResponse, PostStrategyError, GetStrategyStatusData, GetStrategyStatusResponse, GetStrategyStatusError, PrepareBacktestingData, PrepareBacktestingResponse, PrepareBacktestingError, GetPreparationStatusData, GetPreparationStatusResponse, GetPreparationStatusError, ExecuteBacktestingData, ExecuteBacktestingResponse, ExecuteBacktestingError, CancelExecutionData, CancelExecutionResponse, CancelExecutionError, GetExecutionResultData, GetExecutionResultResponse, GetExecutionResultError } from './types.gen';
4
+ import type { AuthData, AuthResponse, AuthError, GetExchangesData, GetExchangesResponse, GetInstrumentsData, GetInstrumentsResponse, GetInstrumentsError, GetExchangeTickersHourData, GetExchangeTickersHourResponse, GetExchangeTickersHourError, GetExchangeKlinesHourData, GetExchangeKlinesHourResponse, GetExchangeKlinesHourError, PostStrategyData, PostStrategyResponse, PostStrategyError, GetStrategyStatusData, GetStrategyStatusResponse, GetStrategyStatusError, PrepareBacktestingData, PrepareBacktestingResponse, PrepareBacktestingError, GetPreparationStatusData, GetPreparationStatusResponse, GetPreparationStatusError, ExecuteBacktestingData, ExecuteBacktestingResponse, ExecuteBacktestingError, CancelExecutionData, CancelExecutionResponse, CancelExecutionError, GetExecutionResultData, GetExecutionResultResponse, GetExecutionResultError } from './types.gen';
5
5
  import { client as _heyApiClient } from './client.gen';
6
6
 
7
7
  export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
@@ -18,6 +18,31 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
18
18
  meta?: Record<string, unknown>;
19
19
  };
20
20
 
21
+ /**
22
+ * Exchange API key for a short-lived JWT
23
+ * Exchanges a long-lived API key for a short-lived JWT used by every other
24
+ * endpoint. This is the only endpoint that accepts an API key directly —
25
+ * callers should obtain a JWT here, then send it as `Authorization: Bearer
26
+ * <token>` to all other operations.
27
+ *
28
+ * The returned JWT carries the caller's subscription `tier` as a claim and
29
+ * expires after `expires_in` seconds. Callers should refresh the token
30
+ * before expiry (or on a `401` response) by calling this endpoint again.
31
+ *
32
+ */
33
+ export const auth = <ThrowOnError extends boolean = false>(options?: Options<AuthData, ThrowOnError>) => {
34
+ return (options?.client ?? _heyApiClient).post<AuthResponse, AuthError, ThrowOnError>({
35
+ security: [
36
+ {
37
+ name: 'X-API-Key',
38
+ type: 'apiKey'
39
+ }
40
+ ],
41
+ url: '/auth/token',
42
+ ...options
43
+ });
44
+ };
45
+
21
46
  /**
22
47
  * Get a list of available exchanges
23
48
  */
@@ -38,6 +63,64 @@ export const getInstruments = <ThrowOnError extends boolean = false>(options: Op
38
63
  });
39
64
  };
40
65
 
66
+ /**
67
+ * Download one hour of tickers for an instrument as a Lastra segment
68
+ * Serves exactly one hour of raw ticker data for the given instrument on the
69
+ * requested exchange. The payload is a native [Lastra](https://github.com/QTSurfer/lastra-java)
70
+ * file — QTSurfer's columnar format for tick-precision timeseries — with
71
+ * no JSON envelope.
72
+ *
73
+ * One segment = one hour, aligned to UTC. The `hour` query parameter selects
74
+ * the segment and must match `YYYY-MM-DDTHH` (no minutes/seconds, no timezone
75
+ * suffix). Example: `hour=2026-01-15T10` returns `h10.lastra` for
76
+ * 2026-01-15, covering `[10:00:00Z, 11:00:00Z)`. Hours not yet available
77
+ * return `404`.
78
+ *
79
+ * A `format=parquet` query parameter switches the response to on-the-fly
80
+ * Parquet conversion via [lastra-convert](https://github.com/QTSurfer/lastra-convert)
81
+ * for clients that don't yet read Lastra. Lastra is the primary format
82
+ * and cheaper when the client can consume it.
83
+ *
84
+ * Clients:
85
+ * - [lastra-java](https://github.com/QTSurfer/lastra-java) — reference
86
+ * Java reader/writer with per-column codecs (ALP, Gorilla, delta-varint,
87
+ * ZSTD) and CRC32 integrity.
88
+ * - [lastra-ts](https://github.com/QTSurfer/lastra-ts) — TypeScript reader
89
+ * (~4 kB bundle, browser + Node.js).
90
+ * - [duckdb-lastra](https://github.com/QTSurfer/duckdb-lastra) — DuckDB
91
+ * extension for ad-hoc SQL over Lastra files.
92
+ * - [lastra-convert](https://github.com/QTSurfer/lastra-convert) — CLI + Java
93
+ * API for converting to/from Parquet, Reef, and CSV.
94
+ * - `curl -OJ` for offline dumps (the `Content-Disposition` header sets a
95
+ * descriptive filename).
96
+ *
97
+ */
98
+ export const getExchangeTickersHour = <ThrowOnError extends boolean = false>(options: Options<GetExchangeTickersHourData, ThrowOnError>) => {
99
+ return (options.client ?? _heyApiClient).get<GetExchangeTickersHourResponse, GetExchangeTickersHourError, ThrowOnError>({
100
+ url: '/exchange/{exchangeId}/tickers/{base}/{quote}',
101
+ ...options
102
+ });
103
+ };
104
+
105
+ /**
106
+ * Download one hour of klines for an instrument as a Lastra segment
107
+ * Same shape and semantics as `/exchange/{exchangeId}/tickers/{base}/{quote}`,
108
+ * but serves klines (aggregated bars) instead of raw ticks. One
109
+ * [Lastra](https://github.com/QTSurfer/lastra-java) segment = one hour of
110
+ * klines at the exchange's native kline cadence, aligned to UTC.
111
+ *
112
+ * Klines use the same columnar layout as tickers — readers that handle one
113
+ * format read the other with the same code. Use this endpoint when a
114
+ * per-tick payload would be too large for the window of interest.
115
+ *
116
+ */
117
+ export const getExchangeKlinesHour = <ThrowOnError extends boolean = false>(options: Options<GetExchangeKlinesHourData, ThrowOnError>) => {
118
+ return (options.client ?? _heyApiClient).get<GetExchangeKlinesHourResponse, GetExchangeKlinesHourError, ThrowOnError>({
119
+ url: '/exchange/{exchangeId}/klines/{base}/{quote}',
120
+ ...options
121
+ });
122
+ };
123
+
41
124
  /**
42
125
  * Compile a strategy from source code
43
126
  * Submits raw strategy source for compilation. By default the call is synchronous and returns
@@ -72,7 +72,7 @@ export type Exchange = {
72
72
  };
73
73
 
74
74
  /**
75
- * Managed exchange data sources available for backtesting. Currently only ticker is supported; kline and funding rate support is planned.
75
+ * Managed exchange data sources available for backtesting.
76
76
  */
77
77
  export type DataSourceType = 'ticker';
78
78
 
@@ -81,13 +81,17 @@ export type DataSourceType = 'ticker';
81
81
  */
82
82
  export type JobState = {
83
83
  /**
84
- * Unique context ID for the job
84
+ * Opaque context identifier for the job
85
85
  */
86
86
  contextId: string;
87
87
  /**
88
- * Current status of the job
88
+ * Current status of the job. `Partial` (prepare only) means some
89
+ * hours are still being produced asynchronously — keep polling.
90
+ * Treat `Completed | Aborted | Failed` as terminal; anything else
91
+ * means keep polling.
92
+ *
89
93
  */
90
- status: 'New' | 'Started' | 'Completed' | 'Aborted' | 'Failed';
94
+ status: 'New' | 'Started' | 'Partial' | 'Completed' | 'Aborted' | 'Failed';
91
95
  /**
92
96
  * Detailed status information, if available
93
97
  */
@@ -131,11 +135,11 @@ export type BacktestJobResult = {
131
135
  };
132
136
 
133
137
  /**
134
- * Execution result map. Always includes core fields (hostName, iops, strategyId, instrument). Yield metrics (pnlTotal, totalTrades, winRate, etc.) are present when the strategy emitted at least one trade. When signal storage is enabled, includes signal fields described below.
138
+ * 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.
135
139
  */
136
140
  export type ResultMap = {
137
141
  /**
138
- * Hostname of the worker node that executed the strategy
142
+ * Identifier of the worker that executed the strategy. Useful when reporting issues so support can correlate with logs.
139
143
  */
140
144
  hostName?: string;
141
145
  /**
@@ -143,7 +147,7 @@ export type ResultMap = {
143
147
  */
144
148
  iops?: number;
145
149
  /**
146
- * Redis key of the compiled strategy used for execution. Format: strategy:{userId}:ticker:{compilationId}
150
+ * Identifier of the compiled strategy that produced this result
147
151
  */
148
152
  strategyId: string;
149
153
  /**
@@ -154,6 +158,10 @@ export type ResultMap = {
154
158
  * Total profit and loss in the output currency
155
159
  */
156
160
  pnlTotal?: number;
161
+ /**
162
+ * Total PnL as a percentage of the initial capital (`backtestFunding`). Zero when `backtestFunding` is 0.
163
+ */
164
+ pnlTotalPercent?: number;
157
165
  /**
158
166
  * Total number of trades executed by the strategy
159
167
  */
@@ -182,20 +190,24 @@ export type ResultMap = {
182
190
  * Maximum percentage drawdown from peak equity
183
191
  */
184
192
  maxDrawdownPercent?: number;
193
+ /**
194
+ * 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.
195
+ */
196
+ equityCurve?: Array<EquityPoint>;
185
197
  /**
186
198
  * Number of signals emitted during strategy execution
187
199
  */
188
200
  signalCount?: number;
189
201
  /**
190
- * Storage key for the signal Parquet file. Format: {userId}/exec/{exchange}/{jobId}
202
+ * Storage key for the signals file. Treat as opaque; use signalsUrl to download.
191
203
  */
192
204
  signalsId?: string;
193
205
  /**
194
- * Public HTTPS URL to the Parquet file. Computed at setup time from publicBaseUrl + signalsId + .parquet. Available even before upload completes.
206
+ * HTTPS URL to download the signals Parquet file. Use signalsUpload to know when it's ready.
195
207
  */
196
208
  signalsUrl?: string;
197
209
  /**
198
- * Upload status. Done = file uploaded to R2. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted or storage not configured.
210
+ * Upload status. Done = signal file is available at signalsUrl. Failed = upload error (see signalsUploadReason). Skipped = no signals emitted.
199
211
  */
200
212
  signalsUpload?: 'Done' | 'Failed' | 'Skipped';
201
213
  /**
@@ -208,11 +220,91 @@ export type ResultMap = {
208
220
  signalsUploadReason?: string;
209
221
  };
210
222
 
223
+ /**
224
+ * Single sample of the running equity at a yield event.
225
+ */
226
+ export type EquityPoint = {
227
+ /**
228
+ * Epoch milliseconds. The first point in an equity curve is anchored at the backtest `from`; subsequent points carry the timestamp of each emitted yield.
229
+ */
230
+ timestamp: number;
231
+ /**
232
+ * Running equity at this point (`initialCapital + cumulativePnl`).
233
+ */
234
+ equity: number;
235
+ };
236
+
211
237
  /**
212
238
  * Unique identifier for a compiled strategy
213
239
  */
214
240
  export type StrategyId = string;
215
241
 
242
+ export type AuthTokenResponse = {
243
+ /**
244
+ * Short-lived HS256 JWT. Send as `Authorization: Bearer <token>` on all other endpoints.
245
+ */
246
+ access_token: string;
247
+ /**
248
+ * Always `Bearer`.
249
+ */
250
+ token_type: 'Bearer';
251
+ /**
252
+ * Seconds until the JWT expires (typically 3600).
253
+ */
254
+ expires_in: number;
255
+ /**
256
+ * Scopes granted to this token. Reserved for future use; currently always empty.
257
+ */
258
+ scopes?: Array<string>;
259
+ /**
260
+ * Subscription tier this token was issued for. Drives rate limits and feature flags on downstream endpoints.
261
+ */
262
+ tier: 'free' | 'basic' | 'pro' | 'elite';
263
+ };
264
+
265
+ /**
266
+ * Error envelope returned by `POST /auth/token` when the API key is rejected.
267
+ */
268
+ export type AuthTokenError = {
269
+ /**
270
+ * Machine-readable error reason.
271
+ */
272
+ code: 'invalid_apikey' | 'apikey_revoked' | 'apikey_expired';
273
+ /**
274
+ * Human-readable description of the failure.
275
+ */
276
+ message: string;
277
+ };
278
+
279
+ export type AuthData = {
280
+ body?: never;
281
+ path?: never;
282
+ query?: never;
283
+ url: '/auth/token';
284
+ };
285
+
286
+ export type AuthErrors = {
287
+ /**
288
+ * API key is invalid, revoked, or expired.
289
+ */
290
+ 401: AuthTokenError;
291
+ /**
292
+ * Rate limit exceeded for this API key.
293
+ */
294
+ 429: unknown;
295
+ };
296
+
297
+ export type AuthError = AuthErrors[keyof AuthErrors];
298
+
299
+ export type AuthResponses = {
300
+ /**
301
+ * API key accepted; JWT returned.
302
+ */
303
+ 200: AuthTokenResponse;
304
+ };
305
+
306
+ export type AuthResponse = AuthResponses[keyof AuthResponses];
307
+
216
308
  export type GetExchangesData = {
217
309
  body?: never;
218
310
  path?: never;
@@ -259,6 +351,132 @@ export type GetInstrumentsResponses = {
259
351
 
260
352
  export type GetInstrumentsResponse = GetInstrumentsResponses[keyof GetInstrumentsResponses];
261
353
 
354
+ export type GetExchangeTickersHourData = {
355
+ body?: never;
356
+ path: {
357
+ /**
358
+ * ID of the exchange (e.g. `binance`).
359
+ */
360
+ exchangeId: string;
361
+ /**
362
+ * Base asset symbol (first leg of the pair).
363
+ */
364
+ base: string;
365
+ /**
366
+ * Quote asset symbol (second leg of the pair).
367
+ */
368
+ quote: string;
369
+ };
370
+ query: {
371
+ /**
372
+ * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
373
+ * `[HH:00:00Z, HH+1:00:00Z)`.
374
+ *
375
+ */
376
+ hour: string;
377
+ /**
378
+ * Response wire format. `lastra` (default) returns raw Lastra bytes.
379
+ * `parquet` returns Parquet via on-the-fly conversion using
380
+ * [lastra-convert](https://github.com/QTSurfer/lastra-convert).
381
+ *
382
+ */
383
+ format?: 'lastra' | 'parquet';
384
+ };
385
+ url: '/exchange/{exchangeId}/tickers/{base}/{quote}';
386
+ };
387
+
388
+ export type GetExchangeTickersHourErrors = {
389
+ /**
390
+ * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
391
+ */
392
+ 400: ResponseError;
393
+ /**
394
+ * No Lastra segment exists for the requested instrument/hour.
395
+ */
396
+ 404: ResponseError;
397
+ /**
398
+ * Unexpected I/O error serving the file.
399
+ */
400
+ 500: ResponseError;
401
+ };
402
+
403
+ export type GetExchangeTickersHourError = GetExchangeTickersHourErrors[keyof GetExchangeTickersHourErrors];
404
+
405
+ export type GetExchangeTickersHourResponses = {
406
+ /**
407
+ * One hour of tickers for the instrument. `Content-Type` is
408
+ * `application/vnd.lastra` by default or
409
+ * `application/vnd.apache.parquet` when `format=parquet` was
410
+ * requested.
411
+ *
412
+ */
413
+ 200: Blob | File;
414
+ };
415
+
416
+ export type GetExchangeTickersHourResponse = GetExchangeTickersHourResponses[keyof GetExchangeTickersHourResponses];
417
+
418
+ export type GetExchangeKlinesHourData = {
419
+ body?: never;
420
+ path: {
421
+ /**
422
+ * ID of the exchange (e.g. `binance`).
423
+ */
424
+ exchangeId: string;
425
+ /**
426
+ * Base asset symbol.
427
+ */
428
+ base: string;
429
+ /**
430
+ * Quote asset symbol.
431
+ */
432
+ quote: string;
433
+ };
434
+ query: {
435
+ /**
436
+ * Hour selector in `YYYY-MM-DDTHH` (UTC). The returned segment covers
437
+ * `[HH:00:00Z, HH+1:00:00Z)`.
438
+ *
439
+ */
440
+ hour: string;
441
+ /**
442
+ * Response wire format. `lastra` (default) returns raw Lastra bytes.
443
+ * `parquet` returns Parquet via on-the-fly conversion.
444
+ *
445
+ */
446
+ format?: 'lastra' | 'parquet';
447
+ };
448
+ url: '/exchange/{exchangeId}/klines/{base}/{quote}';
449
+ };
450
+
451
+ export type GetExchangeKlinesHourErrors = {
452
+ /**
453
+ * Missing or malformed parameters (e.g. `hour` not `YYYY-MM-DDTHH`).
454
+ */
455
+ 400: ResponseError;
456
+ /**
457
+ * No Lastra segment exists for the requested instrument/hour.
458
+ */
459
+ 404: ResponseError;
460
+ /**
461
+ * Unexpected I/O error serving the file.
462
+ */
463
+ 500: ResponseError;
464
+ };
465
+
466
+ export type GetExchangeKlinesHourError = GetExchangeKlinesHourErrors[keyof GetExchangeKlinesHourErrors];
467
+
468
+ export type GetExchangeKlinesHourResponses = {
469
+ /**
470
+ * One hour of klines for the instrument. `Content-Type` is
471
+ * `application/vnd.lastra` by default or
472
+ * `application/vnd.apache.parquet` when `format=parquet`.
473
+ *
474
+ */
475
+ 200: Blob | File;
476
+ };
477
+
478
+ export type GetExchangeKlinesHourResponse = GetExchangeKlinesHourResponses[keyof GetExchangeKlinesHourResponses];
479
+
262
480
  export type PostStrategyData = {
263
481
  /**
264
482
  * Raw strategy Java source code
@@ -362,6 +580,15 @@ export type PrepareBacktestingData = {
362
580
  *
363
581
  */
364
582
  to: string;
583
+ /**
584
+ * Output bar cadence for the prepared range. Defaults to the publisher's
585
+ * native cadence (`1s`); coarser cadences are produced on demand via
586
+ * resampling and stored alongside the native blob in cache. Coarser-than-
587
+ * source values must be exact multiples of the source cadence — invalid
588
+ * labels return `400`.
589
+ *
590
+ */
591
+ cadence?: '1s' | '5s' | '1m' | '5m' | '15m' | '1h' | '4h' | '1d';
365
592
  };
366
593
  path: {
367
594
  /**