@qtsurfer/api-client 0.9.0 → 0.11.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.
@@ -104,6 +104,30 @@ export const InstrumentLinksSchema = {
104
104
  },
105
105
  } as const;
106
106
 
107
+ export const StrategyLinksSchema = {
108
+ description: `HAL \`_links\` for a strategy — present on a full \`StrategyState\` body (\`GET
109
+ /strategy/{strategyId}\`, and \`POST /strategy/{strategyId}/validate\`'s already-validated
110
+ \`200\`), absent from that same endpoint's \`202\` — a deliberately partial stub carrying only
111
+ what is known before a check has even started. Following \`code\` can still \`404\` once
112
+ present: it documents its own honest "nothing to return" for a strategy with no source of
113
+ its own (a \`REFERENCE\` marketplace copy, or one resolved only through the platform's shared
114
+ pool). This link says where to look, not that something is there.
115
+ `,
116
+ type: "object",
117
+ required: ["code"],
118
+ properties: {
119
+ code: {
120
+ allOf: [
121
+ {
122
+ $ref: "#/components/schemas/HalLink",
123
+ },
124
+ ],
125
+ description:
126
+ "Link to this strategy's registered source, `GET /strategy/{strategyId}/code`.",
127
+ },
128
+ },
129
+ } as const;
130
+
107
131
  export const HalLinkSchema = {
108
132
  description: "A HAL link object (Hypertext Application Language)",
109
133
  type: "object",
@@ -245,10 +269,36 @@ export const DataSourceTypeSchema = {
245
269
 
246
270
  export const PrepareRequestSchema = {
247
271
  type: "object",
248
- required: ["instrument", "from", "to"],
272
+ description: `Two shapes, chosen by the \`exchangeId\` path segment. Against a managed exchange,
273
+ \`instrument\` is required and \`datasetId\`/\`datasetVersionId\` are ignored. Against the
274
+ reserved \`exchangeId: user\`, send \`datasetId\` instead of \`instrument\` — \`instrument\` is
275
+ ignored there, since it comes from the dataset itself.
276
+ `,
277
+ required: ["from", "to"],
249
278
  properties: {
250
279
  instrument: {
251
- $ref: "#/components/schemas/Instrument",
280
+ allOf: [
281
+ {
282
+ $ref: "#/components/schemas/Instrument",
283
+ },
284
+ ],
285
+ description: `Required unless \`exchangeId\` is the reserved value \`user\`, in which case send
286
+ \`datasetId\` instead.
287
+ `,
288
+ },
289
+ datasetId: {
290
+ type: "string",
291
+ description: `Only for \`exchangeId: user\`: the id of a dataset created via \`POST /datasets\`, in place
292
+ of \`instrument\`. Ignored against a managed exchange.
293
+ `,
294
+ example: "ds_3f9a1c2e7b0d4a5f",
295
+ },
296
+ datasetVersionId: {
297
+ type: "string",
298
+ description: `Only for \`exchangeId: user\`, and optional even then: pins a specific past version of
299
+ the dataset instead of its current one. Defaults to the dataset's current version.
300
+ `,
301
+ example: "dsv_8e2b4f19c6a03d7e",
252
302
  },
253
303
  from: {
254
304
  type: "string",
@@ -276,7 +326,23 @@ resampling and stored alongside the native blob in cache. Coarser-than-
276
326
  source values must be exact multiples of the source cadence — invalid
277
327
  labels return \`400\`.
278
328
  `,
279
- enum: ["1s", "5s", "1m", "5m", "15m", "1h", "4h", "1d"],
329
+ enum: [
330
+ "1s",
331
+ "5s",
332
+ "1m",
333
+ "3m",
334
+ "5m",
335
+ "15m",
336
+ "30m",
337
+ "1h",
338
+ "2h",
339
+ "4h",
340
+ "8h",
341
+ "12h",
342
+ "1d",
343
+ "1w",
344
+ "1q",
345
+ ],
280
346
  default: "1s",
281
347
  },
282
348
  },
@@ -339,12 +405,19 @@ is always terminal (\`Completed\`) — decide from
339
405
  } as const;
340
406
 
341
407
  export const PrepareJobStateSchema = {
342
- description: `State of a single-instrument prepare job — the \`JobState\` shape plus a per-hour
343
- data-coverage summary. A single-instrument prepare is always terminal
344
- (\`status: Completed\`): the client decides what to do from \`coverageRatio\` (e.g.
345
- execute if it is at or above a chosen threshold) rather than polling for missing
346
- hours that may never arrive a missing hour for one instrument usually means low
347
- activity, not missing data.
408
+ description: `State of a single-instrument prepare job — the \`JobState\` shape plus a coverage summary.
409
+ A single-instrument prepare is always terminal (\`status: Completed\`): the client decides
410
+ what to do from \`coverageRatio\` (e.g. execute if it is at or above a chosen threshold)
411
+ rather than polling for missing hours that may never arrive a missing hour for one
412
+ instrument usually means low activity, not missing data.
413
+
414
+ **Two coverage shapes, by exchange vs. dataset.** Against a managed exchange, coverage is
415
+ walked hour by hour: \`totalHours\`/\`hoursWithData\`/\`hoursWithoutData\`. Against a
416
+ dataset-backed prepare (\`exchangeId: user\`), coverage is reported on the dataset's own
417
+ cadence grid instead — hour-walking a daily dataset would report \`1/24\` and read as
418
+ broken — via \`cadence\`/\`gaps\`/\`largestGapSteps\`; \`totalHours\`/\`hoursWithData\`/
419
+ \`hoursWithoutData\` are absent in that case. \`dataFrom\`/\`dataTo\`/\`coverageRatio\` are present
420
+ either way, computed accordingly.
348
421
  `,
349
422
  allOf: [
350
423
  {
@@ -372,25 +445,54 @@ activity, not missing data.
372
445
  format: "double",
373
446
  minimum: 0,
374
447
  maximum: 1,
375
- description: `\`hoursWithData / totalHours\` in \`[0,1]\` (\`1.0\` when \`totalHours\` is 0) — the
376
- fraction of hours in the requested range that have served data.
448
+ description: `Against a managed exchange: \`hoursWithData / totalHours\` in \`[0,1]\` (\`1.0\` when
449
+ \`totalHours\` is 0), the fraction of hours in the requested range that have served
450
+ data. Against a dataset (\`exchangeId: user\`): \`rows / expectedStepsAtCadence\`
451
+ over the dataset version's own range — echoing what ingest computed once, not
452
+ recomputed against a narrower prepare request.
377
453
  `,
378
454
  example: 0.994,
379
455
  },
380
456
  totalHours: {
381
457
  type: "integer",
382
- description: "Number of whole hours in the requested prepare range.",
458
+ description: `Number of whole hours in the requested prepare range. Managed exchanges only —
459
+ absent for a dataset-backed prepare.
460
+ `,
383
461
  example: 168,
384
462
  },
385
463
  hoursWithData: {
386
464
  type: "integer",
387
- description: "Number of hours in the range that have data.",
465
+ description: `Number of hours in the range that have data. Managed exchanges only — absent for
466
+ a dataset-backed prepare.
467
+ `,
388
468
  example: 167,
389
469
  },
470
+ cadence: {
471
+ type: "string",
472
+ description: `The dataset version's own discovered cadence (e.g. \`1m\`, \`1h\`). Only present for a
473
+ dataset-backed prepare (\`exchangeId: user\`).
474
+ `,
475
+ example: "1m",
476
+ },
477
+ gaps: {
478
+ type: "integer",
479
+ description: `Number of gaps in the dataset version at its own cadence, as discovered at ingest
480
+ time. Only present for a dataset-backed prepare.
481
+ `,
482
+ example: 0,
483
+ },
484
+ largestGapSteps: {
485
+ type: "integer",
486
+ description: `The largest gap in the dataset version, in units of its own cadence step. Only
487
+ present for a dataset-backed prepare.
488
+ `,
489
+ example: 0,
490
+ },
390
491
  hoursWithoutData: {
391
492
  type: "array",
392
- description:
393
- "One entry per hour in the range that has no data, with a rationale.",
493
+ description: `One entry per hour in the range that has no data, with a rationale. Managed
494
+ exchanges only absent for a dataset-backed prepare.
495
+ `,
394
496
  items: {
395
497
  type: "object",
396
498
  properties: {
@@ -1417,6 +1519,35 @@ real run (\`execute\`) is a clean bill of health, while an empty list from
1417
1519
  },
1418
1520
  } as const;
1419
1521
 
1522
+ export const StrategySummarySchema = {
1523
+ type: "object",
1524
+ description: `One entry from \`GET /strategies\` — the same provenance a full \`StrategyState\` carries
1525
+ (\`compiledAt\`, \`requiredSources\`), without its validation state, so listing stays cheap
1526
+ regardless of how many strategies you have registered. Check a specific strategy's
1527
+ validation with \`GET /strategy/{strategyId}\`.
1528
+ `,
1529
+ required: ["strategyId"],
1530
+ properties: {
1531
+ strategyId: {
1532
+ $ref: "#/components/schemas/strategyId",
1533
+ },
1534
+ compiledAt: {
1535
+ type: "string",
1536
+ format: "date-time",
1537
+ description: "When the live compilation was produced.",
1538
+ },
1539
+ requiredSources: {
1540
+ type: "array",
1541
+ description: `The market data this strategy needs. Absent, not empty, when it could
1542
+ not be established without constructing the strategy.
1543
+ `,
1544
+ items: {
1545
+ type: "string",
1546
+ },
1547
+ },
1548
+ },
1549
+ } as const;
1550
+
1420
1551
  export const StrategyStateSchema = {
1421
1552
  type: "object",
1422
1553
  description: `What is known about a registered strategy: that it compiled, and what validating it found.
@@ -1505,6 +1636,9 @@ went; it simply reached less than a full run would.
1505
1636
  the strategy — the check has not run. Stop waiting and re-request it later.
1506
1637
  `,
1507
1638
  },
1639
+ _links: {
1640
+ $ref: "#/components/schemas/StrategyLinks",
1641
+ },
1508
1642
  },
1509
1643
  example: {
1510
1644
  strategyId: "6bsh31ikwkuivhtgcoa6s4",
@@ -1520,6 +1654,249 @@ the strategy — the check has not run. Stop waiting and re-request it later.
1520
1654
  provenance: "compile-dry-run",
1521
1655
  },
1522
1656
  ],
1657
+ _links: {
1658
+ code: {
1659
+ href: "/v1/strategy/6bsh31ikwkuivhtgcoa6s4/code",
1660
+ },
1661
+ },
1662
+ },
1663
+ } as const;
1664
+
1665
+ export const DatasetSchema = {
1666
+ type: "object",
1667
+ description: `A dataset's own metadata — not its data. \`currentVersionId\` is what a prepare against
1668
+ \`exchangeId: user\` reads by default; see \`DatasetVersion\` for what a version carries.
1669
+
1670
+ \`from\`/\`to\`/\`cadence\` mirror that current version's own discovered range and cadence, so
1671
+ you don't need a second call to \`GET /datasets/{datasetId}/uploads/{uploadId}\` just to see
1672
+ what a dataset covers. Absent until a version exists.
1673
+ `,
1674
+ required: ["datasetId", "name", "type", "instrument", "createdAt"],
1675
+ properties: {
1676
+ datasetId: {
1677
+ type: "string",
1678
+ description: "Opaque id, returned by `POST /datasets`.",
1679
+ example: "ds_3f9a1c2e7b0d4a5f",
1680
+ },
1681
+ name: {
1682
+ type: "string",
1683
+ description: "Unique among your datasets.",
1684
+ example: "My BTC ticks",
1685
+ },
1686
+ type: {
1687
+ type: "string",
1688
+ enum: ["ticker"],
1689
+ description: "Always `ticker` in v1.",
1690
+ example: "ticker",
1691
+ },
1692
+ instrument: {
1693
+ $ref: "#/components/schemas/Instrument",
1694
+ },
1695
+ createdAt: {
1696
+ type: "string",
1697
+ format: "date-time",
1698
+ description: "When the dataset was created.",
1699
+ example: "2026-08-20T09:00:00Z",
1700
+ },
1701
+ currentVersionId: {
1702
+ type: "string",
1703
+ description: `The id of the most recently finalized, successfully ingested version. Absent until at
1704
+ least one upload has finished ingesting.
1705
+ `,
1706
+ example: "dsv_8e2b4f19c6a03d7e",
1707
+ },
1708
+ updatedAt: {
1709
+ type: "string",
1710
+ format: "date-time",
1711
+ description:
1712
+ "When `currentVersionId` last changed. Absent until it has a value.",
1713
+ example: "2026-08-20T09:04:12Z",
1714
+ },
1715
+ from: {
1716
+ type: "string",
1717
+ format: "date-time",
1718
+ description: `Start of \`currentVersionId\`'s own data range, as discovered at ingest time. Absent
1719
+ until a version exists.
1720
+ `,
1721
+ example: "2026-03-01T00:00:00Z",
1722
+ },
1723
+ to: {
1724
+ type: "string",
1725
+ format: "date-time",
1726
+ description: `End of \`currentVersionId\`'s own data range, as discovered at ingest time. Absent until
1727
+ a version exists.
1728
+ `,
1729
+ example: "2026-03-08T00:00:00Z",
1730
+ },
1731
+ cadence: {
1732
+ type: "string",
1733
+ description: `\`currentVersionId\`'s own discovered bar cadence (e.g. \`1s\`, \`1m\`, \`1h\`). Absent until a
1734
+ version exists.
1735
+ `,
1736
+ example: "1m",
1737
+ },
1738
+ },
1739
+ } as const;
1740
+
1741
+ export const DatasetWithLinksSchema = {
1742
+ description:
1743
+ "A `Dataset` plus a self link. Returned by `GET /datasets/{datasetId}`.",
1744
+ allOf: [
1745
+ {
1746
+ $ref: "#/components/schemas/Dataset",
1747
+ },
1748
+ {
1749
+ type: "object",
1750
+ properties: {
1751
+ _links: {
1752
+ type: "object",
1753
+ properties: {
1754
+ self: {
1755
+ type: "object",
1756
+ properties: {
1757
+ href: {
1758
+ type: "string",
1759
+ example: "/v1/datasets/ds_3f9a1c2e7b0d4a5f",
1760
+ },
1761
+ },
1762
+ },
1763
+ },
1764
+ },
1765
+ },
1766
+ },
1767
+ ],
1768
+ } as const;
1769
+
1770
+ export const DatasetCreatedSchema = {
1771
+ description: `A \`Dataset\` plus the first upload session — the presigned URL to PUT the file to.
1772
+ `,
1773
+ allOf: [
1774
+ {
1775
+ $ref: "#/components/schemas/Dataset",
1776
+ },
1777
+ {
1778
+ type: "object",
1779
+ required: ["uploadId", "upload"],
1780
+ properties: {
1781
+ uploadId: {
1782
+ type: "string",
1783
+ description: `Identifies this upload session. Pass to
1784
+ \`POST /datasets/{datasetId}/uploads/{uploadId}/finalize\` once the PUT completes.
1785
+ `,
1786
+ example: "up_1a2b3c4d5e6f7a8b",
1787
+ },
1788
+ upload: {
1789
+ type: "object",
1790
+ required: ["url", "expiresInMinutes"],
1791
+ properties: {
1792
+ url: {
1793
+ type: "string",
1794
+ description: `Presigned URL. \`PUT\` the raw CSV file here directly — no \`Authorization\`
1795
+ header, no other API credentials.
1796
+ `,
1797
+ example:
1798
+ "https://storage.qtsurfer.com/00000000-.../uploads/up_1a2b3c4d5e6f7a8b/raw.csv?X-Amz-...",
1799
+ },
1800
+ expiresInMinutes: {
1801
+ type: "integer",
1802
+ description: "How long `url` stays valid.",
1803
+ example: 15,
1804
+ },
1805
+ },
1806
+ },
1807
+ },
1808
+ },
1809
+ ],
1810
+ } as const;
1811
+
1812
+ export const DatasetVersionSchema = {
1813
+ type: "object",
1814
+ description: `One successfully ingested upload. Cadence and timestamp unit are discovered from the file,
1815
+ not declared by the caller (D-11).
1816
+ `,
1817
+ required: ["datasetId"],
1818
+ properties: {
1819
+ datasetId: {
1820
+ type: "string",
1821
+ example: "ds_3f9a1c2e7b0d4a5f",
1822
+ },
1823
+ id: {
1824
+ type: "string",
1825
+ description:
1826
+ "The version id. Pass as `datasetVersionId` on `POST .../prepare` to pin it.",
1827
+ example: "dsv_8e2b4f19c6a03d7e",
1828
+ },
1829
+ bytes: {
1830
+ type: "integer",
1831
+ description: "Size of the uploaded file.",
1832
+ example: 4831022,
1833
+ },
1834
+ rows: {
1835
+ type: "integer",
1836
+ description: "Number of data rows.",
1837
+ example: 86400,
1838
+ },
1839
+ cadence: {
1840
+ type: "string",
1841
+ description: "The discovered bar cadence (e.g. `1s`, `1m`, `1h`).",
1842
+ example: "1s",
1843
+ },
1844
+ timestampUnit: {
1845
+ type: "string",
1846
+ enum: ["iso", "s", "ms", "us"],
1847
+ description: `The unit the \`timestamp\` column was uploaded in — ISO-8601, or the epoch band its
1848
+ numeric values fell in (seconds, millis, or micros).
1849
+ `,
1850
+ example: "iso",
1851
+ },
1852
+ gaps: {
1853
+ type: "integer",
1854
+ description: "Number of gaps at the discovered cadence.",
1855
+ example: 0,
1856
+ },
1857
+ largestGapSteps: {
1858
+ type: "integer",
1859
+ description: "The largest gap, in units of the discovered cadence step.",
1860
+ example: 0,
1861
+ },
1862
+ },
1863
+ } as const;
1864
+
1865
+ export const DatasetUploadStateSchema = {
1866
+ type: "object",
1867
+ description: `Progress of one upload, from staged through ingest. Postgres-backed once a version exists,
1868
+ so \`ready\`/\`failed\` are permanent answers; \`uploading\`/\`ingesting\` reflect in-flight state
1869
+ that can itself age out — see the \`404\` case on \`GET .../uploads/{uploadId}\`.
1870
+ `,
1871
+ required: ["uploadId", "status"],
1872
+ properties: {
1873
+ uploadId: {
1874
+ type: "string",
1875
+ example: "up_1a2b3c4d5e6f7a8b",
1876
+ },
1877
+ status: {
1878
+ type: "string",
1879
+ enum: ["uploading", "ingesting", "ready", "failed"],
1880
+ description: `* \`uploading\` — the file was PUT to the presigned URL, but \`finalize\` has not been
1881
+ called yet.
1882
+ * \`ingesting\` — \`finalize\` was called; the worker is parsing and validating the file.
1883
+ * \`ready\` — ingested successfully. \`version\` carries the result.
1884
+ * \`failed\` — ingest rejected the file (e.g. bad CSV contract, mixed timestamp units).
1885
+ `,
1886
+ example: "ready",
1887
+ },
1888
+ jobId: {
1889
+ type: "string",
1890
+ description: "The ingest job id, while `status` is `ingesting`.",
1891
+ },
1892
+ version: {
1893
+ allOf: [
1894
+ {
1895
+ $ref: "#/components/schemas/DatasetVersion",
1896
+ },
1897
+ ],
1898
+ description: "Present when `status` is `ready` or `failed`.",
1899
+ },
1523
1900
  },
1524
1901
  } as const;
1525
1902