@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 +41 -3
- package/dist/index.d.ts +141 -11
- package/dist/index.js +118 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/auth/session.ts +33 -1
- package/src/client.ts +52 -2
- package/src/index.ts +1 -0
- package/src/internal/preparation.ts +50 -4
- package/src/workflows/backtest.ts +39 -4
- package/src/workflows/strategies.ts +98 -0
- package/src/workflows/sweep.ts +35 -5
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.
|
|
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`
|
|
324
|
-
`QTSurfer` and, identically, on the authenticated
|
|
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
|
|
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
|
|
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
|
|
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: {
|
|
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
|
|
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();
|