QTSurfer beta

Equity curves

Transform, retain, retrieve, and plot backtest and sweep equity curves.

An equity curve describes the account value through a backtest. QTSurfer returns the same EquityCurveResult contract for a plain backtest and for a retained sweep trial, with different delivery rules:

  • A plain backtest returns the curve inline in results.equityCurve and fixes its transform when the execution is submitted.
  • A sweep leaderboard row may carry a pointer in equityCurve.url; fetch it separately and choose the transform at read time.

The first point is an anchor at the backtest from timestamp with the initial capital. Every later point is one sample per emitted yield. equity is account value (initialCapital + cumulativePnl), not a percentage. To chart percentage return, normalize each value as (equity / initialEquity - 1) * 100.

Illustrative equity curve normalized to starting capital, rising to +18.3% with an intervening drawdown

Response shapes

meta.outMode determines which point representation is present.

ARRAY

{
  "points": [
    {"timestamp": 1700000000000, "equity": 100.0},
    {"timestamp": 1700000060000, "equity": 110.5},
    {"timestamp": 1700000120000, "equity": 90.25}
  ],
  "meta": {
    "inputPointCount": 3,
    "outputPointCount": 3,
    "resampled": false,
    "differential": false,
    "outMode": "ARRAY"
  }
}

SHORT

SHORT removes repeated JSON property names by returning parallel arrays. Values at the same index form one point.

{
  "timestamps": [1700000000000, 1700000060000, 1700000120000],
  "equities": [100.0, 110.5, 90.25],
  "meta": {
    "inputPointCount": 3,
    "outputPointCount": 3,
    "resampled": false,
    "differential": false,
    "outMode": "SHORT"
  }
}

Do not infer the shape from the request. A server size guard may force a compact representation; meta.outMode is the source of truth.

Transform pipeline

Transforms run in a fixed order: resampledifferentialoutMode.

OptionType and defaultEffect
resampleinteger ≥ 2; omittedLimits the result to at most this many points. Downsampling preserves the exact first and last points and global extrema. A ceiling above the input size is a valid no-op.
differentialboolean; falseKeeps the first post-resample point absolute and delta-encodes timestamp and equity from the second point onward.
outModeARRAY | SHORT; ARRAYChooses objects or parallel arrays after the preceding stages.

The response metadata reports what actually happened:

MetadataMeaning
inputPointCountNumber of points received by the transform pipeline.
outputPointCountNumber of points after the complete pipeline.
resampledtrue only when resampling changed the point count.
differentialtrue only when delta encoding ran; a zero- or one-point curve has nothing to encode.
outModeActual JSON representation served.

Decoding differential data

The first point remains absolute. Reconstruct each following point by adding its delta to the previous reconstructed value:

{
  "timestamps": [1700000000000, 60000, 60000],
  "equities": [100.0, 10.5, -20.25],
  "meta": {
    "inputPointCount": 3,
    "outputPointCount": 3,
    "resampled": false,
    "differential": true,
    "outMode": "SHORT"
  }
}

This reconstructs timestamps 1700000000000, 1700000060000, 1700000120000 and equities 100.0, 110.5, 90.25. The same rule applies to points in ARRAY mode.

Plain backtests: choose the transform on submit

A plain execution has no later curve endpoint, so its transform is baked into the stored result. Set equityCurve on POST .../execute:

curl -X POST https://api.qtsurfer.net/v1/backtest/binance/ticker/execute 
  -H "Authorization: Bearer $TOKEN" 
  -H "Content-Type: application/json" 
  -d '{
    "prepareJobId": "5ikYAMIO...",
    "strategyId": "2ul144qe9tlwzu5anhwvc6",
    "equityCurve": {"resample": 500, "differential": true, "outMode": "SHORT"}
  }'

Omitting equityCurve means ARRAY, no requested resampling, and no differential encoding. The transform participates in execution idempotency: (prepareJobId, strategyId, storeSignals, equityCurve) → jobId. Changing only the transform creates a different execution rather than reshaping an existing result.

The curve is present with yield metrics after the strategy emits at least one trade. For indicator values and buy/sell markers behind the aggregate curve, request stored signals and read the Parquet file at signalsUrl.

Sweeps: select, retain, and fetch curves

Sweep rows are aggregate outcomes and do not carry time series by default. EquityCurveRequest adds retention controls to the shared transform options:

FieldType and defaultEffect
modeauto | topN | topPct | none; autoChooses which completed trials retain curves. auto delegates retention to server size limits; use topN or topPct when deterministic retention is required.
ninteger ≥ 1Number of ranked trials retained by topN.
maxPctnumber > 0 and ≤ 100Percentage of ranked trials retained by topPct, rounded up with a minimum of one.
resample, differential, outModeshared optionsDefaults used when a later curve GET omits its corresponding query parameter. They do not affect retention.
{
  "strategyId": "2ul144qe9tlwzu5anhwvc6",
  "sweep": {
    "sampler": "grid",
    "objective": "sharpe",
    "params": {"rsiPeriod": {"from": 7, "to": 28, "step": 1}}
  },
  "equityCurve": {
    "mode": "topN",
    "n": 5,
    "resample": 500,
    "outMode": "SHORT"
  }
}

Retention (mode, n, maxPct) contributes to sweep identity. Transform defaults do not: two otherwise identical submissions that differ only in resample, differential, or outMode deduplicate to the same sweepId.

Selected leaderboard rows contain a pointer, not inline points:

{
  "runIx": 12,
  "equityCurve": {
    "meta": {
      "inputPointCount": 118,
      "outputPointCount": 118,
      "resampled": false,
      "differential": false,
      "outMode": "ARRAY"
    },
    "url": "/v1/backtest/binance/ticker/executeSweep/5ikYAMIO.../swp_95e47a7f0966ce11/runs/12/equityCurve"
  }
}

equityCurve is absent, not null, on unselected rows. The row’s metadata is a preview captured when selection occurred; fetch url for the actual, possibly size-guarded curve and authoritative metadata.

Sweep curve query parameters

GET .../runs/{runIx}/equityCurve accepts outMode, resample, and differential. A parameter omitted from the query inherits the sweep submission’s transform default. An explicitly supplied value overrides that default for this read:

curl "https://api.qtsurfer.net$EQUITY_CURVE_URL?outMode=SHORT&resample=500&differential=true" 
  -H "Authorization: Bearer $TOKEN"

The server may still force a smaller representation above its size thresholds. Parse the response according to meta, not according to the submitted defaults or query string.

The endpoint returns 404 when the sweep or runIx is unknown, or when that trial’s curve was not retained. Those cases are intentionally indistinguishable to the caller.

Without a retained sweep curve, reproduce the chosen trial by compiling its winning parameter values as strategy defaults and running a plain execution against the same prepared window.