Equity Rank Backtest API
The backtest API from Equity Rank — 2 operation(s) for backtest.
The backtest API from Equity Rank — 2 operation(s) for backtest.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/equity-rank-com:equity-rank-com-backtest-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Equity Rank Com Backtest API
version: 0.1.0
description: 'Operations tagged backtest across 2 of this provider''s published API definitions: openapi.json, equity-rank-com-openapi.yml. Each path carries the servers of the definition it was published in.'
tags:
- name: backtest
paths:
/api/backtest/summary:
get:
tags:
- backtest
summary: Get Backtest Summary
description: 'Return the latest BacktestRunner result.
Public — no auth. The runner self-caches for 6 hours; the response
is built from whatever the runner returns (including the empty-shell
`sufficient_data=False` result when there''s < 30 valuation_history rows).'
operationId: get_backtest_summary_api_backtest_summary_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/app__routers__backtest__BacktestSummaryResponse'
/api/backtest/track-record:
get:
tags:
- backtest
summary: Get Track Record
description: 'Published, point-in-time track record (AWL-08).
Public — no auth. Built from two real sources and nothing else:
- hit_rates: methodology._fetch_realized_outcomes over
signal_outcomes, every bucket carrying n / ticker_count / window /
horizon; a bucket below the honest minimum is emitted with
sufficient=False and hit_rate=None, never a borrowed number.
- backtest: this file''s own /summary simulation result, with its
compliance caveats passed through verbatim.
Degrades to `degraded=True` + empty hit_rates on any Supabase failure —
never falls through to a synthetic value.'
operationId: get_track_record_api_backtest_track_record_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/TrackRecordResponse'
components:
schemas:
BenchmarkBlock:
properties:
total_return:
anyOf:
- type: number
- type: 'null'
title: Total Return
max_drawdown:
anyOf:
- type: number
- type: 'null'
title: Max Drawdown
type: object
title: BenchmarkBlock
description: 'Per-benchmark figures over the SAME measured steps as the strategy.
MDD-01e (2026-10-09): `max_drawdown` is published exactly when the
strategy''s is (n_rebalances >= 24 measured steps) so the strategy drawdown
is never shown without its comparator; None otherwise, and None for SPY
when the feed degraded.'
TrackRecordBacktestBlock:
properties:
sufficient_data:
type: boolean
title: Sufficient Data
real_data_since:
anyOf:
- type: string
- type: 'null'
title: Real Data Since
period_start:
anyOf:
- type: string
- type: 'null'
title: Period Start
period_end:
anyOf:
- type: string
- type: 'null'
title: Period End
n_rebalances:
type: integer
title: N Rebalances
default: 0
max_drawdown_suppressed:
type: boolean
title: Max Drawdown Suppressed
default: false
coverage:
$ref: '#/components/schemas/CoverageBlock'
strategy:
$ref: '#/components/schemas/BacktestMetricsBlock'
spy:
$ref: '#/components/schemas/BenchmarkBlock'
qqq:
$ref: '#/components/schemas/BenchmarkBlock'
validation:
$ref: '#/components/schemas/ValidationGateBlock'
caveats:
$ref: '#/components/schemas/BacktestComplianceBlock'
type: object
required:
- sufficient_data
title: TrackRecordBacktestBlock
description: 'The existing /backtest simulation, passed through with its
compliance caveats verbatim (byte-identical to what /summary serves).'
BacktestMetricsBlock:
properties:
cagr:
anyOf:
- type: number
- type: 'null'
title: Cagr
sharpe:
anyOf:
- type: number
- type: 'null'
title: Sharpe
max_drawdown:
anyOf:
- type: number
- type: 'null'
title: Max Drawdown
total_return:
anyOf:
- type: number
- type: 'null'
title: Total Return
alpha_vs_spy:
anyOf:
- type: number
- type: 'null'
title: Alpha Vs Spy
alpha_vs_qqq:
anyOf:
- type: number
- type: 'null'
title: Alpha Vs Qqq
type: object
title: BacktestMetricsBlock
description: 'Headline metrics — None when sufficient_data=False.
P0-3: cagr / sharpe / alpha_vs_spy are additionally None until
n_rebalances >= 24 (annualizing a ~4-month window is statistically
meaningless — df=3). MDD-01 (2026-10-09): max_drawdown is gated the same
way (a sample maximum is most biased on a short panel); the response''s
`max_drawdown_suppressed` says so. `alpha_vs_spy` is a CAGR DIFFERENCE, not
risk-adjusted alpha; surfaces must label it "CAGR gap vs SPY".'
CoverageBlock:
properties:
min_snapshot_universe:
type: integer
title: Min Snapshot Universe
default: 0
min_priced_fraction:
type: number
title: Min Priced Fraction
default: 0.0
min_basket_names:
type: integer
title: Min Basket Names
default: 0
steps_attempted:
type: integer
title: Steps Attempted
default: 0
steps_measured:
type: integer
title: Steps Measured
default: 0
steps_abstained:
type: integer
title: Steps Abstained
default: 0
measured_steps_contiguous:
type: boolean
title: Measured Steps Contiguous
default: true
abstained:
items:
$ref: '#/components/schemas/AbstainedStep'
type: array
title: Abstained
type: object
title: CoverageBlock
description: 'MDD-01 (2026-10-09): per-step coverage floor and what it removed.
A step is measured only when both of its monthly snapshots hold at least
`min_snapshot_universe` distinct scored symbols AND at least
max(`min_basket_names`, `min_priced_fraction` x basket) of the basket is
priced at both ends. Any other step abstains: it is absent from the curves
and from every figure (never published as a flat 0.00% month) and is
listed in `abstained`. `measured_steps_contiguous` is False when an
abstained step sits BETWEEN measured ones, i.e. the curve joins
non-adjacent periods.'
ValidationGateBlock:
properties:
dsr:
anyOf:
- type: number
- type: 'null'
title: Dsr
pbo:
anyOf:
- type: number
- type: 'null'
title: Pbo
pbo_applicable:
anyOf:
- type: boolean
- type: 'null'
title: Pbo Applicable
pbo_n_candidates:
anyOf:
- type: integer
- type: 'null'
title: Pbo N Candidates
passes_gates:
anyOf:
- type: boolean
- type: 'null'
title: Passes Gates
validation_status:
type: string
title: Validation Status
default: not_evaluated
type: object
title: ValidationGateBlock
description: 'D3 / VALGATE hard gates (item 37, .planning/PRICE-PREDICTION-TIMING-PLAN-2026-09-03.md).
Deflated Sharpe Ratio + Probability of Backtest Overfitting (Bailey &
López de Prado 2014 — computed once in
app/services/factor_ic_significance.py and never re-derived here) applied to
the persisted strategy result. `dsr`/`pbo`/`passes_gates` are all None and
`validation_status` stays ''not_evaluated'' until the annualized Sharpe itself
is populated (fewer than MIN_REBALANCES_FOR_ANNUALIZED periods — nothing to
deflate yet). `passes_gates` is true only when dsr >= 0.95 AND, where PBO is
applicable, pbo < 0.35; a False or None value here means this result must
NOT be read as validated, regardless of how the headline metrics look.
`pbo_applicable` (2026-10-08): PBO measures selection among >= 2 candidate
configurations. This strategy is one fixed configuration, so `pbo` is null
and `pbo_applicable` is false: no overfitting-of-selection test was run,
and the gate rests on the DSR alone. It is never published as 0.0.
`pbo_n_candidates` is the number of configurations compared.
`dsr` IS A PROBABILITY IN [0, 1] AS OF 2026-10-07, NOT A RATIO. It estimates
P(true Sharpe > the Sharpe the best of n_trials null configurations would be
expected to show by luck), so 0.95 is a one-sided 95% confidence level. The
field previously carried a ratio-valued statistic whose documented 1.5
threshold was unreachable at any trial count of 4 or more (DSR-01); it was
never non-null on this surface, because the live result has had fewer than
MIN_REBALANCES_FOR_ANNUALIZED rebalances throughout, so no published value
changed. Both fields stay None rather than 0.0 so "not measurable" can never
read as "measured".'
app__routers__backtest__BacktestSummaryResponse:
properties:
schema_version:
type: string
title: Schema Version
default: '2.0'
sufficient_data:
type: boolean
title: Sufficient Data
real_data_since:
anyOf:
- type: string
- type: 'null'
title: Real Data Since
period_start:
anyOf:
- type: string
- type: 'null'
title: Period Start
period_end:
anyOf:
- type: string
- type: 'null'
title: Period End
n_rebalances:
type: integer
title: N Rebalances
default: 0
annualized_metrics_suppressed:
type: boolean
title: Annualized Metrics Suppressed
default: false
max_drawdown_suppressed:
type: boolean
title: Max Drawdown Suppressed
default: false
benchmark_degraded:
type: boolean
title: Benchmark Degraded
default: false
price_basis:
anyOf:
- type: string
- type: 'null'
title: Price Basis
coverage:
$ref: '#/components/schemas/CoverageBlock'
equity_curve:
$ref: '#/components/schemas/EquityCurveBlock'
strategy:
$ref: '#/components/schemas/BacktestMetricsBlock'
spy:
$ref: '#/components/schemas/BenchmarkBlock'
qqq:
$ref: '#/components/schemas/BenchmarkBlock'
universe:
$ref: '#/components/schemas/BenchmarkBlock'
validation:
$ref: '#/components/schemas/ValidationGateBlock'
compliance:
$ref: '#/components/schemas/BacktestComplianceBlock'
type: object
required:
- sufficient_data
title: BacktestSummaryResponse
description: Response model for GET /api/backtest/summary.
TrackRecordResponse:
properties:
live_tracking_started:
type: string
title: Live Tracking Started
hit_rates:
items:
$ref: '#/components/schemas/TrackRecordHitRate'
type: array
title: Hit Rates
backtest:
$ref: '#/components/schemas/TrackRecordBacktestBlock'
limitations:
items:
type: string
type: array
title: Limitations
total_realized_outcomes:
type: integer
title: Total Realized Outcomes
default: 0
total_ticker_count:
type: integer
title: Total Ticker Count
default: 0
degraded:
type: boolean
title: Degraded
default: false
type: object
required:
- live_tracking_started
- backtest
title: TrackRecordResponse
description: 'Response model for GET /api/backtest/track-record.
`total_realized_outcomes` / `total_ticker_count` are the aggregate live
counts (across every evaluated signal_outcomes row, not just the
published buckets) — the exact numbers the UI-SPEC''s mandatory
insufficient-data copy substitutes into "{n} realized outcomes across
{ticker_count} tickers". Kept as explicit numeric fields (not just prose
inside `limitations`) so the frontend never has to parse text to render
the mandated string.'
BacktestComplianceBlock:
properties:
framing:
type: string
title: Framing
default: Historical model performance under stated assumptions. Not a forecast, recommendation, or guarantee of future returns.
look_ahead_controls:
type: string
title: Look Ahead Controls
default: At each rebalance date the strategy uses ONLY data already in valuation_history as of that date. No forward-looking model values used to make past selections.
live_history_disclaimer:
type: string
title: Live History Disclaimer
default: Model live since March 2026. Track record covers a limited period. Historical output does not predict future returns.
survivorship_bias_note:
type: string
title: Survivorship Bias Note
default: 'IMPORTANT — Survivorship bias: this backtest selects from stocks currently in the Equity Rank universe, which excludes companies that were delisted, acquired, or went bankrupt during the historical period. This inflates displayed results by an amount that varies with the window and cannot currently be stated precisely, but which can be large relative to the figures shown — prior internal point-in-time work on an earlier window estimated the effect in the tens of percentage points. A point-in-time universe reconstruction is planned to bound it for the current window. Past model performance is not indicative of future results; provided for informational purposes only.'
type: object
title: BacktestComplianceBlock
description: 'Compliance copy surfaced to the frontend (BACK-05).
All language locks to "historical model performance" framing and
explicitly negates "forecast" / "recommendation" / "guarantee".'
TrackRecordHitRate:
properties:
signal:
type: string
title: Signal
horizon_days:
type: integer
title: Horizon Days
n:
type: integer
title: N
ticker_count:
type: integer
title: Ticker Count
window_start:
type: string
title: Window Start
window_end:
type: string
title: Window End
hit_rate:
anyOf:
- type: number
- type: 'null'
title: Hit Rate
base_rate:
anyOf:
- type: number
- type: 'null'
title: Base Rate
excess_hit_rate:
anyOf:
- type: number
- type: 'null'
title: Excess Hit Rate
excess_n:
type: integer
title: Excess N
default: 0
excess_ci_low:
anyOf:
- type: number
- type: 'null'
title: Excess Ci Low
excess_ci_high:
anyOf:
- type: number
- type: 'null'
title: Excess Ci High
sufficient:
type: boolean
title: Sufficient
type: object
required:
- signal
- horizon_days
- n
- ticker_count
- window_start
- window_end
- sufficient
title: TrackRecordHitRate
description: 'One realized-outcome bucket, always carrying its full sample context.
`hit_rate` is None (never a synthetic or borrowed number) whenever
`sufficient` is False — i.e. the bucket has fewer than the honest
minimum (`_OUTCOME_BUCKET_MIN_N`) evaluated outcomes.'
EquityCurveBlock:
properties:
dates:
items:
type: string
type: array
title: Dates
strategy:
items:
type: number
type: array
title: Strategy
spy:
items:
type: number
type: array
title: Spy
qqq:
items:
type: number
type: array
title: Qqq
universe:
items:
type: number
type: array
title: Universe
type: object
title: EquityCurveBlock
description: 'Per-series equity curve segment — normalized to 100.0 at index 0.
P0-3 (2026-08-03): `universe` is the equal-weight mean of every scored
symbol over the same rebalance dates — the like-for-like comparator.
`qqq` is retired (always empty, kept for response-shape compatibility).
An empty `spy` with `benchmark_degraded=True` on the response means the
external feed failed — never rendered as a flat 0% line.'
AbstainedStep:
properties:
entry_date:
type: string
title: Entry Date
exit_date:
type: string
title: Exit Date
reason:
anyOf:
- type: string
- type: 'null'
title: Reason
entry_universe:
type: integer
title: Entry Universe
default: 0
exit_universe:
type: integer
title: Exit Universe
default: 0
basket_size:
type: integer
title: Basket Size
default: 0
priced:
type: integer
title: Priced
default: 0
type: object
required:
- entry_date
- exit_date
title: AbstainedStep
description: One rebalance step left out of every curve and figure (MDD-01a/b).
securitySchemes:
HTTPBearer:
type: http
scheme: bearer
x-refined-from:
- openapi.json
- equity-rank-com-openapi.yml