@qtsurfer/sdk 0.8.0 → 0.10.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.
package/README.md CHANGED
@@ -306,9 +306,43 @@ A verdict also describes the bytecode that existed when it was recorded. If `com
306
306
  than `validatedAt`, the strategy was recompiled afterwards and the verdict no longer describes what
307
307
  would run — ask for validation again.
308
308
 
309
+ ## Managing registered strategies
310
+
311
+ ```ts
312
+ // Every strategy you've registered and not deleted, most recently compiled first.
313
+ // Never 404s — an empty array means you have none.
314
+ const summaries = await qts.strategies();
315
+
316
+ // The exact source last submitted for an id, whitespace and comments included.
317
+ const code = await qts.strategyCode(strategyId);
318
+
319
+ // Release a registration.
320
+ await qts.deleteStrategy(strategyId);
321
+ ```
322
+
323
+ `strategies()` deliberately omits `validation` on every entry — that is what keeps it cheap
324
+ regardless of how many strategies you have registered. Check a specific one's verdict with
325
+ `strategy(strategyId)`.
326
+
327
+ `strategyCode()`'s `404` covers two cases the response cannot tell apart: the id was never
328
+ registered by you, or it resolves only through a shared/marketplace reference that carries no
329
+ source of its own.
330
+
331
+ `deleteStrategy()` resolves with nothing. It removes the strategy from both `strategy()` and
332
+ `strategies()`, but does not undo anything already run: backtests you ran against it beforehand are
333
+ unaffected, and re-submitting the exact same source afterwards registers a **new** strategy with a
334
+ **new** id rather than undeleting this one. Deleting your own copy of a strategy never affects
335
+ anyone else's copy of the same source (e.g. a shared/marketplace listing).
336
+
337
+ A full `StrategyState` — from `strategy()`, and from `validateStrategy()`'s already-validated `200`
338
+ — carries an optional `_links.code` discovery link pointing at the same source `strategyCode()`
339
+ fetches by id. It is absent from `validateStrategy()`'s `queued: true` (`202`) outcome, which is a
340
+ deliberately partial stub. The SDK does not follow this link for you; it passes through unmodified
341
+ from api-client, so read it off `StrategyState` directly if you want it.
342
+
309
343
  ## API coverage
310
344
 
311
- Measured against **API spec 0.107.0**: 18 operations, all 18 reachable from this SDK.
345
+ Measured against **API spec 0.109.2**: 21 operations, all 21 reachable from this SDK.
312
346
 
313
347
  It exists because the generated `@qtsurfer/api-client` tracks the spec automatically and this
314
348
  hand-written layer does not, so an operation the platform serves could otherwise have no way in
@@ -320,8 +354,9 @@ deliberately does not wrap it, the row says why.
320
354
  There are two ways an operation is reached:
321
355
 
322
356
  - **Direct** — callable on its own, without running a workflow. The client methods below
323
- (`exchanges`, `instruments`, `tickers`, `klines`, `validateStrategy`, `strategy`) exist on
324
- `QTSurfer` and, identically, on the authenticated session. The remaining direct rows are reached
357
+ (`exchanges`, `instruments`, `tickers`, `klines`, `validateStrategy`, `strategy`, `strategies`,
358
+ `deleteStrategy`, `strategyCode`) exist on `QTSurfer` and, identically, on the authenticated
359
+ session. The remaining direct rows are reached
325
360
  otherwise: `authenticate()` is a top-level export rather than a method on either class; the two
326
361
  `Sweep.*` entries live on the handle `sweep()` hands back and, being handle-scoped, sit outside
327
362
  the session's refresh-on-401 policy; and the two cancels are an option you pass in rather than a
@@ -341,9 +376,12 @@ There are two ways an operation is reached:
341
376
  | `listSegmentInstruments` | Direct — `instruments(exchangeId, segment)` |
342
377
  | `downloadTickers` | Direct — `tickers(...)` |
343
378
  | `downloadKlines` | Direct — `klines(...)` |
379
+ | `listStrategies` | Direct — `strategies()` |
344
380
  | `compileStrategy` | Via workflow — inside `backtest(...)` / `sweep(...)`; no standalone method, unlike the Java SDK |
345
381
  | `validateStrategy` | Direct — `validateStrategy(strategyId)` |
346
382
  | `getStrategy` | Direct — `strategy(strategyId)` |
383
+ | `deleteStrategy` | Direct — `deleteStrategy(strategyId)` |
384
+ | `getStrategyCode` | Direct — `strategyCode(strategyId)` |
347
385
  | `prepareBacktest` | Via workflow |
348
386
  | `getPrepareStatus` | Via workflow |
349
387
  | `executeBacktest` | Via workflow — `backtest(...)` |
package/dist/index.d.ts CHANGED
@@ -1,12 +1,43 @@
1
- import { ResultMap, Exchange as Exchange$1, InstrumentDetail as InstrumentDetail$1, StrategyState as StrategyState$1, SweepProgress as SweepProgress$1, ExecuteSweepAccepted, ExecuteSweepResult, SweepSensitivity as SweepSensitivity$1, SweepHeatmap as SweepHeatmap$1, SweepHeatmapCell as SweepHeatmapCell$1, SweepMarginal as SweepMarginal$1, SweepMarginalPoint as SweepMarginalPoint$1, SweepRunRow as SweepRunRow$1, WalkForwardFold as WalkForwardFold$1, WalkForwardResult as WalkForwardResult$1, AuthTokenResponse } from '@qtsurfer/api-client';
1
+ import { ResultMap, Exchange as Exchange$1, InstrumentDetail as InstrumentDetail$1, StrategyState as StrategyState$1, StrategySummary as StrategySummary$1, SweepProgress as SweepProgress$1, ExecuteSweepAccepted, ExecuteSweepResult, SweepSensitivity as SweepSensitivity$1, SweepHeatmap as SweepHeatmap$1, SweepHeatmapCell as SweepHeatmapCell$1, SweepMarginal as SweepMarginal$1, SweepMarginalPoint as SweepMarginalPoint$1, SweepRunRow as SweepRunRow$1, WalkForwardFold as WalkForwardFold$1, WalkForwardResult as WalkForwardResult$1, AuthTokenResponse } from '@qtsurfer/api-client';
2
2
 
3
+ /**
4
+ * ```ts
5
+ * const request: BacktestRequest = {
6
+ * strategy: source,
7
+ * exchangeId: 'binance',
8
+ * instrument: 'BTC/USDT',
9
+ * from: '2026-01-01T00:00:00Z',
10
+ * to: '2026-02-01T00:00:00Z',
11
+ * };
12
+ * ```
13
+ *
14
+ * Backtest against a dataset you uploaded instead of an exchange instrument by
15
+ * replacing `instrument` with `datasetId` (and `exchangeId: 'user'`):
16
+ *
17
+ * ```ts
18
+ * const request: BacktestRequest = {
19
+ * strategy: source,
20
+ * exchangeId: 'user',
21
+ * datasetId: 'ds_123',
22
+ * from: '2026-01-01T00:00:00Z',
23
+ * to: '2026-02-01T00:00:00Z',
24
+ * };
25
+ * ```
26
+ */
3
27
  interface BacktestRequest {
4
28
  /** Strategy source code (Java) */
5
29
  strategy: string;
6
- /** Exchange id, e.g. `binance` */
30
+ /** Exchange id, e.g. `binance`, or the reserved value `user` when backtesting against `datasetId`. */
7
31
  exchangeId: string;
8
- /** Instrument symbol, e.g. `BTC/USDT` */
9
- instrument: string;
32
+ /** Instrument symbol, e.g. `BTC/USDT`. Exactly one of `instrument`/`datasetId` is required. */
33
+ instrument?: string;
34
+ /**
35
+ * Id of a dataset you uploaded, in place of `instrument`. Exactly one of
36
+ * `instrument`/`datasetId` is required. Pairs with `exchangeId: 'user'`.
37
+ */
38
+ datasetId?: string;
39
+ /** Optional specific version of `datasetId`; omit to use its current version. Requires `datasetId`. */
40
+ datasetVersionId?: string;
10
41
  /** Date range start (ISO-8601, ISO DATE or BASIC ISO DATE) */
11
42
  from: string;
12
43
  /** Date range end (same formats as `from`) */
@@ -96,6 +127,15 @@ type DownloadFormat = 'lastra' | 'parquet';
96
127
  * A verdict describes the bytecode that existed when it was recorded:
97
128
  * `compiledAt` newer than `validatedAt` means the strategy was recompiled
98
129
  * afterwards and the verdict no longer describes what would run.
130
+ *
131
+ * `_links.code`, when present, is a discovery link to this strategy's raw
132
+ * source (`GET /strategy/{strategyId}/code` — the same thing
133
+ * {@link QTSurfer.strategyCode} fetches by id, so there is no need to follow
134
+ * the link yourself). It is present on a full `StrategyState` body — this
135
+ * function's result, and {@link QTSurfer.validateStrategy}'s already-validated
136
+ * `200` — and **absent** from that same operation's `queued: true` (`202`)
137
+ * outcome, which is a deliberately partial stub. This field passes through
138
+ * unmodified from api-client, so it needs no unwrapping on the SDK's part.
99
139
  */
100
140
  type StrategyState = StrategyState$1;
101
141
  /**
@@ -120,6 +160,19 @@ type StrategyValidation = {
120
160
  strategyId: string;
121
161
  state?: undefined;
122
162
  };
163
+ /**
164
+ * One entry in {@link QTSurfer.strategies}'s result: the same provenance
165
+ * {@link QTSurfer.strategy} reports — `compiledAt`, `requiredSources` — but
166
+ * never `validation`, which is what keeps listing cheap no matter how many
167
+ * strategies you have registered. Check a specific strategy's verdict with
168
+ * {@link QTSurfer.strategy}.
169
+ *
170
+ * Note: the spec types this endpoint's `requiredSources` as a plain
171
+ * `string[]`, not the `'Ticker' | 'KLine' | 'FundingRate'` union that
172
+ * {@link StrategyState}'s own `requiredSources` carries — narrow it yourself
173
+ * if you need the literal type. Alias for api-client's `StrategySummary`.
174
+ */
175
+ type StrategySummary = StrategySummary$1;
123
176
 
124
177
  /**
125
178
  * The metric a sweep optimizes, and the one its leaderboard and its
@@ -351,14 +404,35 @@ type WalkForwardFold = WalkForwardFold$1;
351
404
  * objective: 'sharpe',
352
405
  * };
353
406
  * ```
407
+ *
408
+ * Sweep against a dataset you uploaded instead of an exchange instrument by
409
+ * replacing `instrument` with `datasetId` (and `exchangeId: 'user'`):
410
+ *
411
+ * ```ts
412
+ * const request: SweepRequest = {
413
+ * strategy: source,
414
+ * exchangeId: 'user',
415
+ * datasetId: 'ds_123',
416
+ * from: '2026-01-01T00:00:00Z',
417
+ * to: '2026-02-01T00:00:00Z',
418
+ * params: { rsiPeriod: { from: 7, to: 28, step: 1 } },
419
+ * };
420
+ * ```
354
421
  */
355
422
  interface SweepRequest {
356
423
  /** Strategy source code (Java), compiled once and reused by every trial. */
357
424
  strategy: string;
358
- /** Exchange id, e.g. `binance`. */
425
+ /** Exchange id, e.g. `binance`, or the reserved value `user` when sweeping against `datasetId`. */
359
426
  exchangeId: string;
360
- /** Instrument symbol, e.g. `BTC/USDT`. */
361
- instrument: string;
427
+ /** Instrument symbol, e.g. `BTC/USDT`. Exactly one of `instrument`/`datasetId` is required. */
428
+ instrument?: string;
429
+ /**
430
+ * Id of a dataset you uploaded, in place of `instrument`. Exactly one of
431
+ * `instrument`/`datasetId` is required. Pairs with `exchangeId: 'user'`.
432
+ */
433
+ datasetId?: string;
434
+ /** Optional specific version of `datasetId`; omit to use its current version. Requires `datasetId`. */
435
+ datasetVersionId?: string;
362
436
  /** Range start (ISO-8601, ISO DATE, or BASIC ISO DATE). */
363
437
  from: string;
364
438
  /** Range end (same formats as `from`; must be later than `from`). */
@@ -691,7 +765,7 @@ interface DownloadHourArgs {
691
765
  * Thin, stateless wrapper over `@qtsurfer/api-client` that exposes the SDK's
692
766
  * workflow methods (`backtest`, `sweep`, `tickers`, `klines`), the platform catalog
693
767
  * (`exchanges`, `instruments`) and the strategy surface (`validateStrategy`,
694
- * `strategy`). Constructing an
768
+ * `strategy`, `strategies`, `deleteStrategy`, `strategyCode`). Constructing an
695
769
  * instance reconfigures the underlying api-client singleton, so avoid
696
770
  * holding two `QTSurfer`s with different `baseUrl`s or tokens alive in the
697
771
  * same process — they will race. Prefer the `authenticate()` helper over
@@ -724,7 +798,8 @@ declare class QTSurfer {
724
798
  * if a stage exceeds `timeoutMs`, or {@link QTSCanceledError} if the caller's
725
799
  * signal fires before the sweep is accepted. A plain {@link QTSError} means
726
800
  * the request itself is malformed (an empty grid, a non-positive `step`, a
727
- * walk-forward block with fewer than two folds) and never reached the network.
801
+ * walk-forward block with fewer than two folds, or naming both/neither of
802
+ * `instrument`/`datasetId`) and never reached the network.
728
803
  *
729
804
  * What the sweep *found* arrives through {@link Sweep.result}, which is also
730
805
  * where the semantics of the leaderboard are documented. Acceptance already
@@ -825,6 +900,42 @@ declare class QTSurfer {
825
900
  * It is never a stale or expired answer.
826
901
  */
827
902
  strategy(strategyId: string): Promise<StrategyState>;
903
+ /**
904
+ * List every strategy you have registered and not deleted, most recently
905
+ * compiled first. Never `404`s — an empty array means you have none.
906
+ * Each entry deliberately omits `validation`; check a specific strategy's
907
+ * verdict with {@link QTSurfer.strategy}. See {@link StrategySummary}.
908
+ *
909
+ * @throws QTSError on any non-2xx response, with the HTTP status on
910
+ * `status`.
911
+ */
912
+ strategies(): Promise<StrategySummary[]>;
913
+ /**
914
+ * Release a registered strategy: removes it from both {@link
915
+ * QTSurfer.strategy} and {@link QTSurfer.strategies}.
916
+ *
917
+ * Backtests already run against this strategy are unaffected, and
918
+ * re-submitting the same source afterwards registers a **new** strategy
919
+ * with a **new** id rather than undeleting this one. Deleting your own
920
+ * copy of a strategy never affects anyone else's copy of the same source
921
+ * (e.g. a shared/marketplace listing).
922
+ *
923
+ * @param strategyId the id returned when the strategy was compiled
924
+ * @throws QTSError on any non-2xx response; a `404` (carried on `status`)
925
+ * means no such registered strategy for this caller.
926
+ */
927
+ deleteStrategy(strategyId: string): Promise<void>;
928
+ /**
929
+ * Read back the exact source last submitted for a strategy id, whitespace
930
+ * and comments included.
931
+ *
932
+ * A `404` (carried on `status`) covers two indistinguishable cases: the id
933
+ * was never registered by you, or it resolves only through a shared/
934
+ * marketplace reference that carries no source of its own.
935
+ *
936
+ * @param strategyId the id returned when the strategy was compiled
937
+ */
938
+ strategyCode(strategyId: string): Promise<string>;
828
939
  }
829
940
 
830
941
  /**
@@ -1035,6 +1146,24 @@ declare class AuthenticatedClient {
1035
1146
  * a guarantee.
1036
1147
  */
1037
1148
  strategy(strategyId: string): Promise<StrategyState>;
1149
+ /**
1150
+ * List every strategy you have registered and not deleted, most recently
1151
+ * compiled first. Refreshes the token once on `401` before retrying. See
1152
+ * {@link QTSurfer.strategies}.
1153
+ */
1154
+ strategies(): Promise<StrategySummary[]>;
1155
+ /**
1156
+ * Release a registered strategy. Refreshes the token once on `401` before
1157
+ * retrying. See {@link QTSurfer.deleteStrategy} for what this does and
1158
+ * does not undo.
1159
+ */
1160
+ deleteStrategy(strategyId: string): Promise<void>;
1161
+ /**
1162
+ * Read back a strategy's exact registered source. Refreshes the token
1163
+ * once on `401` before retrying. See {@link QTSurfer.strategyCode} for
1164
+ * what its `404` covers.
1165
+ */
1166
+ strategyCode(strategyId: string): Promise<string>;
1038
1167
  }
1039
1168
  /**
1040
1169
  * Exchange a long-lived API key for an authenticated session.
@@ -1043,10 +1172,11 @@ declare class AuthenticatedClient {
1043
1172
  * environment. The returned {@link AuthenticatedClient} caches the JWT,
1044
1173
  * refreshes it on 401, and exposes the same surface as `QTSurfer`
1045
1174
  * (`backtest`, `sweep`, `tickers`, `klines`, `exchanges`, `instruments`,
1046
- * `validateStrategy`, `strategy`).
1175
+ * `validateStrategy`, `strategy`, `strategies`, `deleteStrategy`,
1176
+ * `strategyCode`).
1047
1177
  *
1048
1178
  * @throws {QTSAuthError} if no apikey is supplied or available in env.
1049
1179
  */
1050
1180
  declare function authenticate(apikey?: string, opts?: AuthOptions): Promise<AuthenticatedClient>;
1051
1181
 
1052
- export { type AuthOptions, AuthenticatedClient, type BacktestOptions, type BacktestProgress, type BacktestRequest, type BacktestResult, type BacktestStage, type DownloadFormat, type DownloadHourArgs, type Exchange, InMemoryTokenStore, type InstrumentDetail, type InstrumentSegment, type ParamAxis, QTSAuthError, QTSCanceledError, QTSDownloadError, QTSError, QTSExecutionError, QTSPreparationError, QTSStrategyCompileError, QTSTimeoutError, QTSurfer, type QTSurferOptions, type StrategyState, type StrategyValidation, type Sweep, type SweepAccepted, type SweepHeatmap, type SweepHeatmapCell, type SweepMarginal, type SweepMarginalPoint, type SweepObjective, type SweepOptions, type SweepOrder, type SweepProgress, type SweepProgressEvent, type SweepRanking, type SweepRequest, type SweepResult, type SweepRunRow, type SweepSampler, type SweepSensitivity, type SweepState, type SweepWalkForward, type TokenStore, type WalkForwardFold, type WalkForwardResult, authenticate };
1182
+ export { type AuthOptions, AuthenticatedClient, type BacktestOptions, type BacktestProgress, type BacktestRequest, type BacktestResult, type BacktestStage, type DownloadFormat, type DownloadHourArgs, type Exchange, InMemoryTokenStore, type InstrumentDetail, type InstrumentSegment, type ParamAxis, QTSAuthError, QTSCanceledError, QTSDownloadError, QTSError, QTSExecutionError, QTSPreparationError, QTSStrategyCompileError, QTSTimeoutError, QTSurfer, type QTSurferOptions, type StrategyState, type StrategySummary, type StrategyValidation, type Sweep, type SweepAccepted, type SweepHeatmap, type SweepHeatmapCell, type SweepMarginal, type SweepMarginalPoint, type SweepObjective, type SweepOptions, type SweepOrder, type SweepProgress, type SweepProgressEvent, type SweepRanking, type SweepRequest, type SweepResult, type SweepRunRow, type SweepSampler, type SweepSensitivity, type SweepState, type SweepWalkForward, type TokenStore, type WalkForwardFold, type WalkForwardResult, authenticate };
package/dist/index.js CHANGED
@@ -145,10 +145,31 @@ async function compileStrategySource(source, signal) {
145
145
  }
146
146
  return data.strategyId;
147
147
  }
148
+ function validatePrepareTarget(workflow, target) {
149
+ const hasInstrument = target.instrument !== void 0;
150
+ const hasDataset = target.datasetId !== void 0;
151
+ if (hasInstrument && hasDataset) {
152
+ throw new QTSError(`${workflow}: exactly one of instrument/datasetId is required, got both`);
153
+ }
154
+ if (!hasInstrument && !hasDataset) {
155
+ throw new QTSError(
156
+ `${workflow}: exactly one of instrument/datasetId is required, got neither`
157
+ );
158
+ }
159
+ if (target.datasetVersionId !== void 0 && !hasDataset) {
160
+ throw new QTSError(`${workflow}: datasetVersionId requires datasetId`);
161
+ }
162
+ }
148
163
  async function prepareDataset(target, policy, run = {}) {
149
164
  const { data, error } = await prepareBacktest({
150
165
  path: { exchangeId: target.exchangeId, type: TICKER },
151
- body: { instrument: target.instrument, from: target.from, to: target.to },
166
+ body: {
167
+ ...target.instrument !== void 0 ? { instrument: target.instrument } : {},
168
+ ...target.datasetId !== void 0 ? { datasetId: target.datasetId } : {},
169
+ ...target.datasetVersionId !== void 0 ? { datasetVersionId: target.datasetVersionId } : {},
170
+ from: target.from,
171
+ to: target.to
172
+ },
152
173
  ...run.signal ? { signal: run.signal } : {}
153
174
  });
154
175
  if (error) throw new QTSPreparationError("Prepare submission failed", error);
@@ -188,6 +209,7 @@ async function prepareDataset(target, policy, run = {}) {
188
209
  var DEFAULT_POLL_INTERVAL_MS = 500;
189
210
  var DEFAULT_MAX_POLL_INTERVAL_MS = 5e3;
190
211
  async function backtest(req, opts = {}) {
212
+ validatePrepareTarget("backtest", req);
191
213
  const policy = buildStagePolicy(opts, DEFAULT_POLL_INTERVAL_MS, DEFAULT_MAX_POLL_INTERVAL_MS);
192
214
  opts.onProgress?.({ stage: "compiling" });
193
215
  const strategyId = await compileStrategySource(req.strategy, opts.signal);
@@ -200,7 +222,9 @@ function prepareData(req, policy, opts) {
200
222
  return prepareDataset(
201
223
  {
202
224
  exchangeId: req.exchangeId,
203
- instrument: req.instrument,
225
+ ...req.instrument !== void 0 ? { instrument: req.instrument } : {},
226
+ ...req.datasetId !== void 0 ? { datasetId: req.datasetId } : {},
227
+ ...req.datasetVersionId !== void 0 ? { datasetVersionId: req.datasetVersionId } : {},
204
228
  from: req.from,
205
229
  to: req.to
206
230
  },
@@ -360,7 +384,10 @@ function describe2(error) {
360
384
  // src/workflows/strategies.ts
361
385
  import {
362
386
  getStrategy as apiGetStrategy,
363
- validateStrategy as apiValidateStrategy
387
+ validateStrategy as apiValidateStrategy,
388
+ listStrategies as apiListStrategies,
389
+ deleteStrategy as apiDeleteStrategy,
390
+ getStrategyCode as apiGetStrategyCode
364
391
  } from "@qtsurfer/api-client";
365
392
  async function validateStrategy(strategyId) {
366
393
  const { data, error, response } = await apiValidateStrategy({ path: { strategyId } });
@@ -375,6 +402,22 @@ async function getStrategy(strategyId) {
375
402
  if (!data) throw new QTSError("Empty strategy response");
376
403
  return data;
377
404
  }
405
+ async function listStrategies() {
406
+ const { data, error, response } = await apiListStrategies();
407
+ if (error) throw requestFailed("strategies list", error, response?.status);
408
+ if (!data) throw new QTSError("Empty strategies response");
409
+ return data.strategies;
410
+ }
411
+ async function deleteStrategy(strategyId) {
412
+ const { error, response } = await apiDeleteStrategy({ path: { strategyId } });
413
+ if (error) throw requestFailed("strategy delete", error, response?.status);
414
+ }
415
+ async function getStrategyCode(strategyId) {
416
+ const { data, error, response } = await apiGetStrategyCode({ path: { strategyId } });
417
+ if (error) throw requestFailed("strategy code lookup", error, response?.status);
418
+ if (!data) throw new QTSError("Empty strategy code response");
419
+ return data.code;
420
+ }
378
421
 
379
422
  // src/workflows/sweep.ts
380
423
  import {
@@ -403,6 +446,7 @@ async function sweep(req, opts = {}) {
403
446
  return createHandle(req, opts, policy, data, requestId, strategyId);
404
447
  }
405
448
  function validateRequest(req) {
449
+ validatePrepareTarget("sweep", req);
406
450
  const names = Object.keys(req.params ?? {});
407
451
  if (names.length === 0) {
408
452
  throw new QTSError("sweep: params must hold at least one axis");
@@ -432,7 +476,9 @@ function prepareData2(req, policy, opts) {
432
476
  return prepareDataset(
433
477
  {
434
478
  exchangeId: req.exchangeId,
435
- instrument: req.instrument,
479
+ ...req.instrument !== void 0 ? { instrument: req.instrument } : {},
480
+ ...req.datasetId !== void 0 ? { datasetId: req.datasetId } : {},
481
+ ...req.datasetVersionId !== void 0 ? { datasetVersionId: req.datasetVersionId } : {},
436
482
  from: req.from,
437
483
  to: req.to
438
484
  },
@@ -593,7 +639,8 @@ var QTSurfer = class {
593
639
  * if a stage exceeds `timeoutMs`, or {@link QTSCanceledError} if the caller's
594
640
  * signal fires before the sweep is accepted. A plain {@link QTSError} means
595
641
  * the request itself is malformed (an empty grid, a non-positive `step`, a
596
- * walk-forward block with fewer than two folds) and never reached the network.
642
+ * walk-forward block with fewer than two folds, or naming both/neither of
643
+ * `instrument`/`datasetId`) and never reached the network.
597
644
  *
598
645
  * What the sweep *found* arrives through {@link Sweep.result}, which is also
599
646
  * where the semantics of the leaderboard are documented. Acceptance already
@@ -708,6 +755,48 @@ var QTSurfer = class {
708
755
  strategy(strategyId) {
709
756
  return getStrategy(strategyId);
710
757
  }
758
+ /**
759
+ * List every strategy you have registered and not deleted, most recently
760
+ * compiled first. Never `404`s — an empty array means you have none.
761
+ * Each entry deliberately omits `validation`; check a specific strategy's
762
+ * verdict with {@link QTSurfer.strategy}. See {@link StrategySummary}.
763
+ *
764
+ * @throws QTSError on any non-2xx response, with the HTTP status on
765
+ * `status`.
766
+ */
767
+ strategies() {
768
+ return listStrategies();
769
+ }
770
+ /**
771
+ * Release a registered strategy: removes it from both {@link
772
+ * QTSurfer.strategy} and {@link QTSurfer.strategies}.
773
+ *
774
+ * Backtests already run against this strategy are unaffected, and
775
+ * re-submitting the same source afterwards registers a **new** strategy
776
+ * with a **new** id rather than undeleting this one. Deleting your own
777
+ * copy of a strategy never affects anyone else's copy of the same source
778
+ * (e.g. a shared/marketplace listing).
779
+ *
780
+ * @param strategyId the id returned when the strategy was compiled
781
+ * @throws QTSError on any non-2xx response; a `404` (carried on `status`)
782
+ * means no such registered strategy for this caller.
783
+ */
784
+ deleteStrategy(strategyId) {
785
+ return deleteStrategy(strategyId);
786
+ }
787
+ /**
788
+ * Read back the exact source last submitted for a strategy id, whitespace
789
+ * and comments included.
790
+ *
791
+ * A `404` (carried on `status`) covers two indistinguishable cases: the id
792
+ * was never registered by you, or it resolves only through a shared/
793
+ * marketplace reference that carries no source of its own.
794
+ *
795
+ * @param strategyId the id returned when the strategy was compiled
796
+ */
797
+ strategyCode(strategyId) {
798
+ return getStrategyCode(strategyId);
799
+ }
711
800
  // Future surface:
712
801
  // TTL cache for exchanges / instruments
713
802
  // jobs: { cancel, stream, result }
@@ -896,6 +985,30 @@ var AuthenticatedClient = class {
896
985
  strategy(strategyId) {
897
986
  return this.withRefreshOn401(() => getStrategy(strategyId));
898
987
  }
988
+ /**
989
+ * List every strategy you have registered and not deleted, most recently
990
+ * compiled first. Refreshes the token once on `401` before retrying. See
991
+ * {@link QTSurfer.strategies}.
992
+ */
993
+ strategies() {
994
+ return this.withRefreshOn401(() => listStrategies());
995
+ }
996
+ /**
997
+ * Release a registered strategy. Refreshes the token once on `401` before
998
+ * retrying. See {@link QTSurfer.deleteStrategy} for what this does and
999
+ * does not undo.
1000
+ */
1001
+ deleteStrategy(strategyId) {
1002
+ return this.withRefreshOn401(() => deleteStrategy(strategyId));
1003
+ }
1004
+ /**
1005
+ * Read back a strategy's exact registered source. Refreshes the token
1006
+ * once on `401` before retrying. See {@link QTSurfer.strategyCode} for
1007
+ * what its `404` covers.
1008
+ */
1009
+ strategyCode(strategyId) {
1010
+ return this.withRefreshOn401(() => getStrategyCode(strategyId));
1011
+ }
899
1012
  };
900
1013
  async function authenticate(apikey, opts = {}) {
901
1014
  const resolved = apikey ?? readEnvApikey();