@qtsurfer/api-client 0.11.0 → 0.12.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.
@@ -705,9 +705,76 @@ export const ExecuteSweepRequestSchema = {
705
705
  walkForward: {
706
706
  $ref: "#/components/schemas/WalkForwardRequest",
707
707
  },
708
+ equityCurve: {
709
+ $ref: "#/components/schemas/EquityCurveRequest",
710
+ },
711
+ },
712
+ } as const;
713
+
714
+ export const EquityCurveOptionsSchema = {
715
+ type: "object",
716
+ description:
717
+ "Requested equity-curve transform, applied server-side in a fixed pipeline order: `resample` (point count) then `differential` (encoding) then `outMode` (JSON shape) — each stage assumes the previous one already ran. A server-side size guard can still force a smaller/deflated shape above its thresholds regardless of what is requested here — see `EquityCurveMeta` for what actually happened.",
718
+ properties: {
719
+ resample: {
720
+ type: "integer",
721
+ minimum: 2,
722
+ description:
723
+ "Downsample to at most this many points (extrema-preserving — the global max/min and the exact first/last point are always kept). Omit for no downsampling.",
724
+ },
725
+ differential: {
726
+ type: "boolean",
727
+ default: false,
728
+ description:
729
+ "Delta-encode both fields from the second (post-resample) point onward.",
730
+ },
731
+ outMode: {
732
+ allOf: [
733
+ {
734
+ $ref: "#/components/schemas/EquityCurveOutMode",
735
+ },
736
+ ],
737
+ default: "ARRAY",
738
+ description: "Requested JSON shape.",
739
+ },
708
740
  },
709
741
  } as const;
710
742
 
743
+ export const EquityCurveRequestSchema = {
744
+ allOf: [
745
+ {
746
+ $ref: "#/components/schemas/EquityCurveOptions",
747
+ },
748
+ {
749
+ type: "object",
750
+ properties: {
751
+ mode: {
752
+ type: "string",
753
+ enum: ["auto", "topN", "topPct", "none"],
754
+ default: "auto",
755
+ description:
756
+ "Which trials keep their per-point equity curve. `auto` retains curves only while the accumulated size stays within server limits; `topN`/`topPct` retain curves for the best-ranked trials explicitly; `none` retains no curves.",
757
+ },
758
+ n: {
759
+ type: "integer",
760
+ minimum: 1,
761
+ description: "Trial count to retain when mode is topN.",
762
+ },
763
+ maxPct: {
764
+ type: "number",
765
+ format: "double",
766
+ exclusiveMinimum: 0,
767
+ maximum: 100,
768
+ description:
769
+ "Top percentage of trials to retain when mode is topPct.",
770
+ },
771
+ },
772
+ },
773
+ ],
774
+ description:
775
+ "Selection (`mode`/`n`/`maxPct`) plus the transform preference (`resample`/`differential`/`outMode`) applied by `GET .../equityCurve` whenever ITS OWN query params are absent, for a curve this sweep retained. The transform half never affects retention or `sweepId` — a caller can always override it per-request at read time regardless of what was submitted here.",
776
+ } as const;
777
+
711
778
  export const WalkForwardRequestSchema = {
712
779
  type: "object",
713
780
  description:
@@ -949,6 +1016,15 @@ export const SweepRunRowSchema = {
949
1016
  type: "integer",
950
1017
  format: "int64",
951
1018
  },
1019
+ equityCurve: {
1020
+ allOf: [
1021
+ {
1022
+ $ref: "#/components/schemas/EquityCurveResult",
1023
+ },
1024
+ ],
1025
+ description:
1026
+ "Present only when this trial's curve was selected (`equityCurve.mode` was `topN`/`topPct` on the request, and this row ranked among the winners) — absent, not null, otherwise; a run that was never spilled and one that was spilled but not selected look identical here. Always a pointer today (`url` present, no inline points): a sweep's curves live in a spill store, not the response, until fetched separately. `GET` the `url` to fetch the curve itself; its own `meta` there is the real, possibly size-guarded outcome, while this outer `meta` is a raw, untransformed preview from selection time and can differ.",
1027
+ },
952
1028
  },
953
1029
  } as const;
954
1030
 
@@ -1086,6 +1162,7 @@ export const ExecuteSweepResultSchema = {
1086
1162
  "leaderboardSize",
1087
1163
  "truncated",
1088
1164
  "leaderboard",
1165
+ "state",
1089
1166
  ],
1090
1167
  properties: {
1091
1168
  sweepId: {
@@ -1094,6 +1171,8 @@ export const ExecuteSweepResultSchema = {
1094
1171
  status: {
1095
1172
  type: "string",
1096
1173
  enum: ["RUNNING", "COMPLETED", "PARTIAL", "CANCELLED"],
1174
+ description:
1175
+ "The sweep's own status vocabulary — not the same set `state.status` below uses. See `state` for why.",
1097
1176
  },
1098
1177
  objective: {
1099
1178
  type: "string",
@@ -1148,6 +1227,17 @@ First failure wins and later ones are not recorded, so on a sweep where several
1148
1227
  walkForward: {
1149
1228
  $ref: "#/components/schemas/WalkForwardResult",
1150
1229
  },
1230
+ state: {
1231
+ allOf: [
1232
+ {
1233
+ $ref: "#/components/schemas/JobState",
1234
+ },
1235
+ ],
1236
+ description: `The same \`JobState\` shape a single-execute \`BacktestJobResult\` carries — not a sweep-specific lookalike, the actual type, so field names and timestamp formatting match exactly.
1237
+ \`state.status\` uses \`JobState\`'s own vocabulary (\`New\`/\`Started\`/\`Completed\`/ \`Aborted\`/\`Failed\`), mapped from the sweep's \`status\` field above rather than copying it: \`PARTIAL\` and \`CANCELLED\` both map to \`Aborted\`, because a sweep's \`PARTIAL\` is already terminal (some shards finished, some failed, nothing more is coming) unlike a single job's non-terminal \`Partial\`, which has no equivalent here at all.
1238
+ \`state.completed\` is real ticks processed on a plain sweep. On a \`walkForward\` sweep it is currently always \`0\` — the walk-forward fold runner was not wired to count ticks when this shipped, unlike the plain shard path.
1239
+ \`state.size\` is always \`0\` on every path (single execute, plain sweep, walk-forward alike) — nothing populates it anywhere yet. That is a known gap across the whole API, not a sweep-specific omission.`,
1240
+ },
1151
1241
  },
1152
1242
  } as const;
1153
1243
 
@@ -1389,26 +1479,36 @@ reading: a run that produced no trades often did so for a reason stated here.
1389
1479
  example: 8.75,
1390
1480
  },
1391
1481
  equityCurve: {
1392
- type: "array",
1393
- description:
1394
- "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.",
1395
- items: {
1396
- $ref: "#/components/schemas/EquityPoint",
1397
- },
1398
- example: [
1399
- {
1400
- timestamp: 1700000000000,
1401
- equity: 100,
1402
- },
1403
- {
1404
- timestamp: 1700000060000,
1405
- equity: 110.5,
1406
- },
1482
+ allOf: [
1407
1483
  {
1408
- timestamp: 1700000120000,
1409
- equity: 90.25,
1484
+ $ref: "#/components/schemas/EquityCurveResult",
1410
1485
  },
1411
1486
  ],
1487
+ description:
1488
+ "Equity curve over the backtest. `points[0]` (or `timestamps[0]`/`equities[0]` in `SHORT` mode) 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. Always inline for a plain backtest today — never a pointer (`url`); that shape exists only for a sweep row's top-N winners.",
1489
+ example: {
1490
+ points: [
1491
+ {
1492
+ timestamp: 1700000000000,
1493
+ equity: 100,
1494
+ },
1495
+ {
1496
+ timestamp: 1700000060000,
1497
+ equity: 110.5,
1498
+ },
1499
+ {
1500
+ timestamp: 1700000120000,
1501
+ equity: 90.25,
1502
+ },
1503
+ ],
1504
+ meta: {
1505
+ inputPointCount: 3,
1506
+ outputPointCount: 3,
1507
+ resampled: false,
1508
+ differential: false,
1509
+ outMode: "ARRAY",
1510
+ },
1511
+ },
1412
1512
  },
1413
1513
  signalCount: {
1414
1514
  type: "integer",
@@ -1475,6 +1575,104 @@ export const EquityPointSchema = {
1475
1575
  },
1476
1576
  } as const;
1477
1577
 
1578
+ export const EquityCurveOutModeSchema = {
1579
+ type: "string",
1580
+ enum: ["ARRAY", "SHORT"],
1581
+ description:
1582
+ "JSON shape for an equity curve's points. `ARRAY` is `[{timestamp, equity}, ...]`; `SHORT` is `{timestamps: [...], equities: [...]}` (parallel arrays, no repeated key text). The one schema shared by every place `outMode` appears, request or response, so the two cannot drift to different value sets.",
1583
+ } as const;
1584
+
1585
+ export const EquityCurveMetaSchema = {
1586
+ type: "object",
1587
+ description:
1588
+ "What the transform pipeline actually did, computed from the observed outcome — never a copy of what was requested. Lets a caller detect a forced or no-op transform (e.g. a `resample` ceiling already above the curve's size is a legal no-op, reported honestly as `resampled: false`).",
1589
+ required: [
1590
+ "inputPointCount",
1591
+ "outputPointCount",
1592
+ "resampled",
1593
+ "differential",
1594
+ "outMode",
1595
+ ],
1596
+ properties: {
1597
+ inputPointCount: {
1598
+ type: "integer",
1599
+ description: "Size of the curve the transform pipeline received.",
1600
+ example: 100000,
1601
+ },
1602
+ outputPointCount: {
1603
+ type: "integer",
1604
+ description:
1605
+ "Size after the full pipeline (resample, then differential, then outMode).",
1606
+ example: 100,
1607
+ },
1608
+ resampled: {
1609
+ type: "boolean",
1610
+ description:
1611
+ "True only if the resample stage actually changed the point count.",
1612
+ },
1613
+ differential: {
1614
+ type: "boolean",
1615
+ description:
1616
+ "True only if delta-encoding actually ran. Requesting it on a curve of 0 or 1 points has nothing to encode, so it does not run even if asked.",
1617
+ },
1618
+ outMode: {
1619
+ allOf: [
1620
+ {
1621
+ $ref: "#/components/schemas/EquityCurveOutMode",
1622
+ },
1623
+ ],
1624
+ description:
1625
+ "The actual JSON shape served. Present even when the points themselves are not (a pointer curve, `EquityCurveResult.url`) — a caller resolving that URL separately still needs to know how to parse what it gets back before fetching it. May override an explicit request above a server-side size threshold; this field, not the request, is the source of truth for what shape actually came back.",
1626
+ example: "ARRAY",
1627
+ },
1628
+ },
1629
+ } as const;
1630
+
1631
+ export const EquityCurveResultSchema = {
1632
+ type: "object",
1633
+ description:
1634
+ "An equity curve, shaped per `meta.outMode`: `points` when `ARRAY`, `timestamps` + `equities` (parallel arrays) when `SHORT`. Used identically wherever a curve is returned — a plain backtest's inline `equityCurve` and a sweep row's `equityCurve` are the same type. `url` is present *instead of* any points when the curve is served by pointer rather than inline (a sweep row's top-N winners only): `GET` it separately to fetch this exact same shape with the points populated.",
1635
+ required: ["meta"],
1636
+ properties: {
1637
+ meta: {
1638
+ $ref: "#/components/schemas/EquityCurveMeta",
1639
+ },
1640
+ points: {
1641
+ type: "array",
1642
+ description:
1643
+ "Present when `meta.outMode` is `ARRAY` and the curve is inline (not a pointer).",
1644
+ items: {
1645
+ $ref: "#/components/schemas/EquityPoint",
1646
+ },
1647
+ },
1648
+ timestamps: {
1649
+ type: "array",
1650
+ description:
1651
+ "Present when `meta.outMode` is `SHORT` and the curve is inline (not a pointer).",
1652
+ items: {
1653
+ type: "integer",
1654
+ format: "int64",
1655
+ },
1656
+ },
1657
+ equities: {
1658
+ type: "array",
1659
+ description:
1660
+ "Present when `meta.outMode` is `SHORT` and the curve is inline (not a pointer), parallel to `timestamps` (same index, same point).",
1661
+ items: {
1662
+ type: "number",
1663
+ format: "double",
1664
+ },
1665
+ },
1666
+ url: {
1667
+ type: "string",
1668
+ description:
1669
+ "Present only for a sweep row's pointer curve. `GET` this to fetch the curve itself, in this exact `{points|timestamps+equities, meta}` shape — `meta` there is the real, possibly size-guarded outcome; this outer `meta` is a raw, untransformed preview from the moment the sweep selected this trial's curve, and the two can legitimately differ.",
1670
+ example:
1671
+ "/v1/backtest/binance/ticker/executeSweep/req-1/swp_test/runs/3/equityCurve",
1672
+ },
1673
+ },
1674
+ } as const;
1675
+
1478
1676
  export const strategyIdSchema = {
1479
1677
  description: `Unique identifier for a compiled strategy, derived from the source itself: the same code
1480
1678
  always yields the same id, for every caller, whatever its formatting. See
@@ -1484,6 +1682,68 @@ always yields the same id, for every caller, whatever its formatting. See
1484
1682
  example: "6bsh31ikwkuivhtgcoa6s4",
1485
1683
  } as const;
1486
1684
 
1685
+ export const DeclaredPropertySchema = {
1686
+ type: "object",
1687
+ description: `One property name \`POST /strategy\` could establish without constructing the strategy —
1688
+ either declared with \`@StrategyProperty\` on the compiled source, or one of the small set of
1689
+ base properties every strategy carries (\`amnt\`, \`enabled\`, \`multiEntry\`, ...).
1690
+
1691
+ **Best-effort, not exhaustive.** A property registered through an attached risk/backtest
1692
+ config needs a live instance to discover and is not listed here. Use this to catch a typo'd
1693
+ sweep key before submitting, not as the definitive list of what a sweep will accept — a
1694
+ name absent from this list may still be valid.
1695
+ `,
1696
+ required: ["name"],
1697
+ properties: {
1698
+ name: {
1699
+ type: "string",
1700
+ description:
1701
+ "The key a sweep or execute param map uses for this property.",
1702
+ example: "rsi.period",
1703
+ },
1704
+ description: {
1705
+ type: "string",
1706
+ description: "Human-readable label, as declared.",
1707
+ example: "RSI period",
1708
+ },
1709
+ defaultValue: {
1710
+ type: "string",
1711
+ description: `The declared default, as a string, if one was given. Absent, not null, when none was
1712
+ declared.
1713
+ `,
1714
+ example: "14",
1715
+ },
1716
+ reflected: {
1717
+ type: "boolean",
1718
+ description: `Whether a value for this key is injected into the strategy's field (\`true\`) or only
1719
+ available through the property map (\`false\`).
1720
+ `,
1721
+ example: true,
1722
+ },
1723
+ min: {
1724
+ type: "number",
1725
+ format: "double",
1726
+ description:
1727
+ "Suggested sweep/range minimum, if declared. Advisory only, never validated.",
1728
+ example: 2,
1729
+ },
1730
+ max: {
1731
+ type: "number",
1732
+ format: "double",
1733
+ description:
1734
+ "Suggested sweep/range maximum, if declared. Advisory only, never validated.",
1735
+ example: 50,
1736
+ },
1737
+ step: {
1738
+ type: "number",
1739
+ format: "double",
1740
+ description:
1741
+ "Suggested sweep/range step, if declared. Advisory only, never validated.",
1742
+ example: 1,
1743
+ },
1744
+ },
1745
+ } as const;
1746
+
1487
1747
  export const NoticeSchema = {
1488
1748
  type: "object",
1489
1749
  description: `A diagnostic the engine raised while the strategy ran. Advisory: it describes something worth
@@ -1812,7 +2072,7 @@ header, no other API credentials.
1812
2072
  export const DatasetVersionSchema = {
1813
2073
  type: "object",
1814
2074
  description: `One successfully ingested upload. Cadence and timestamp unit are discovered from the file,
1815
- not declared by the caller (D-11).
2075
+ not declared by the caller.
1816
2076
  `,
1817
2077
  required: ["datasetId"],
1818
2078
  properties: {
@@ -58,6 +58,9 @@ import type {
58
58
  GetSweepSensitivityData,
59
59
  GetSweepSensitivityResponse,
60
60
  GetSweepSensitivityError,
61
+ GetSweepRunEquityCurveData,
62
+ GetSweepRunEquityCurveResponse,
63
+ GetSweepRunEquityCurveError,
61
64
  ExecuteBacktestData,
62
65
  ExecuteBacktestResponse,
63
66
  ExecuteBacktestError,
@@ -316,6 +319,10 @@ export const listStrategies = <ThrowOnError extends boolean = false>(
316
319
  * different means are two strategies, because deciding otherwise would mean deciding program
317
320
  * equivalence.
318
321
  *
322
+ * The response also lists `declaredProperties` — the sweep/execute param keys this strategy
323
+ * is known to accept, so a caller can catch a typo'd key before submitting a sweep instead of
324
+ * only learning it from a rejected one. See `DeclaredProperty`: best-effort, not exhaustive.
325
+ *
319
326
  */
320
327
  export const compileStrategy = <ThrowOnError extends boolean = false>(
321
328
  options: Options<CompileStrategyData, ThrowOnError>
@@ -689,6 +696,41 @@ export const getSweepSensitivity = <ThrowOnError extends boolean = false>(
689
696
  });
690
697
  };
691
698
 
699
+ /**
700
+ * Get one sweep trial's equity curve
701
+ * The resource a leaderboard row's `equityCurve.url` points at — only reachable when that
702
+ * trial's curve was actually selected (`equityCurve.mode: topN` or `topPct` on the sweep
703
+ * submission, and this trial ranked among the winners). Returns the exact same
704
+ * `{points|timestamps+equities, meta}` shape a plain backtest's inline `equityCurve` carries.
705
+ *
706
+ * Query params reshape the response the same way a plain backtest's `equityCurve` options do.
707
+ * A param genuinely absent from the query string falls back to the `equityCurve` transform
708
+ * preference the sweep was submitted with — a param
709
+ * present but malformed does not fall back, it degrades the same way it always has. Above a
710
+ * server-side size threshold, the shape is forced regardless of either — `meta.outMode` in
711
+ * the response, not the query string or the submitted default, is the source of truth for
712
+ * what shape actually came back.
713
+ *
714
+ */
715
+ export const getSweepRunEquityCurve = <ThrowOnError extends boolean = false>(
716
+ options: Options<GetSweepRunEquityCurveData, ThrowOnError>
717
+ ) => {
718
+ return (options.client ?? _heyApiClient).get<
719
+ GetSweepRunEquityCurveResponse,
720
+ GetSweepRunEquityCurveError,
721
+ ThrowOnError
722
+ >({
723
+ security: [
724
+ {
725
+ scheme: "bearer",
726
+ type: "http",
727
+ },
728
+ ],
729
+ url: "/backtest/{exchangeId}/{type}/executeSweep/{requestId}/{sweepId}/runs/{runIx}/equityCurve",
730
+ ...options,
731
+ });
732
+ };
733
+
692
734
  /**
693
735
  * Execute a compiled strategy against a prepared dataset
694
736
  * Enqueues an execute task that runs the strategy identified by `strategyId` over the data
@@ -698,8 +740,9 @@ export const getSweepSensitivity = <ThrowOnError extends boolean = false>(
698
740
  * Returns immediately with a `jobId`; poll `GET /backtest/{exchangeId}/{type}/execute/{jobId}`
699
741
  * for the result.
700
742
  *
701
- * The same params (same `prepareJobId`, `strategyId`, `storeSignals`) always return the same
702
- * `jobId` (idempotent).
743
+ * The same params (same `prepareJobId`, `strategyId`, `storeSignals`, `equityCurve`) always
744
+ * return the same `jobId` (idempotent) — a request that omits `equityCurve` dedupes exactly
745
+ * as it did before that field existed.
703
746
  *
704
747
  * Works unchanged for a dataset-backed prepare (`exchangeId: user`) — the request body is
705
748
  * identical either way, since the instrument and range are recovered from `prepareJobId`.