§ The API
Backtest by URL.
The calculator on this site is a thin shell around a small JSON API. It is open, it has no key, and it speaks the same query parameters you see in the address bar. The methodology is the product — so it is documented in full, set in editorial type, and free to cite.
Read-only. JSON only. Same daily-close granularity as the on-page calculator. Edge-cached for 24 hours, the same window the price providers refresh on.
№ 01
The endpoint
GET https://thestackreport.io/api/v1/dcaOne method, one path, one version. v1 is the current contract and will not break. A v2 will live on a separate path with at least 90 days of overlap before v1 is retired.
№ 02
Parameters
| Name | Required | Type | Description |
|---|---|---|---|
amount | Yes | number | USD per buy. Whole dollars between 1 and 1,000,000. |
freq | Yes | daily | weekly | monthly | Cadence of recurring buys. |
from | Yes | YYYY-MM-DD | Start of the window. Must be on or after 2013-04-28. |
to | Optional | YYYY-MM-DD | End of the window. Defaults to today. Must be after from. |
strat | Optional | comma list of lump, va, dip | Alternative strategies to compute alongside DCA. Lump sum, value averaging, dip-buyer. |
include | Optional | prices | Opt-in payload extras. Pass include=prices to receive the full daily price series. |
№ 03
A worked example
$50 weekly from 1 January 2020 through 31 December 2024 — the window the homepage uses by default.
cURL
curl -s "https://thestackreport.io/api/v1/dca?amount=50&freq=weekly&from=2020-01-01&to=2024-12-31"JavaScript
const res = await fetch(
"https://thestackreport.io/api/v1/dca?amount=50&freq=weekly&from=2020-01-01&to=2024-12-31",
);
const { result, source, meta } = await res.json();
console.log(result.currentValue); // 52306.93
console.log(result.roi); // 300.82
console.log(source); // "cryptocompare"
console.log(meta.version); // "v1"№ 04
The response
A successful call returns the full SimulationResult — the same object that powers the on-page calculator — alongside the upstream price source and a small meta block. The history array carries one entry per executed buy.
{
"result": {
"totalInvested": 13050,
"totalBtc": 0.5601,
"currentValue": 52306.93,
"profit": 39256.93,
"roi": 300.82,
"purchases": 261,
"skippedPurchases": 0,
"firstPurchaseDate": "2020-01-01",
"history": [
{ "date": "2020-01-01", "invested": 50, "value": 50 },
…
],
"lumpValue": 169509.81,
"lumpProfit": 156459.81,
"firstPrice": 7189.94,
"lastPrice": 73599.17,
"startDate": "2020-01-01",
"endDate": "2024-12-31",
"maxDrawdown": {
"pct": 68.87,
"peakDate": "2021-10-19",
"peakValue": 20558,
"troughDate": "2022-11-09",
"troughValue": 6399,
"recovered": true
}
},
"source": "cryptocompare",
"meta": {
"version": "v1",
"params": { "amount": 50, "freq": "weekly", "from": "2020-01-01", "to": "2024-12-31" },
"attribution": "https://thestackreport.io",
"citation": "X-Citation: thestackreport.io"
}
}When ?strat= is set, a strategies object is included, keyed by strategy id. When ?include=prices is set, the full daily series is appended. Both are off by default to keep responses lean.
№ 05
Errors
Every error returns { "error": { "code", "message" } } with one of the codes below. HTTP status is the second authority — use either.
| Code | HTTP | Meaning |
|---|---|---|
missing-required-param | 400 | amount, freq, or from was not supplied. |
invalid-amount | 400 | amount could not be parsed, or fell outside 1–1,000,000. |
invalid-frequency | 400 | freq was not one of daily, weekly, monthly. |
invalid-date | 400 | from or to was not a valid YYYY-MM-DD. |
invalid-range | 400 | from before 2013-04-28, to in the future, or from ≥ to. |
unknown-strategy | 400 | strat contained a token other than lump, va, dip. |
unknown-include | 400 | include contained a token other than prices. |
rate-limited | 429 | Soft per-IP limit hit. Retry-After header is set. |
data-unavailable | 503 | Upstream price providers all failed and the window can't be simulated. |
№ 06
Rate limit
Sixty requests per minute per IP. The limit is soft and best-effort — Vercel's serverless runtime wipes the in-memory counter on cold starts, and multiple instances each carry their own. In practice this means most callers will never see it. The response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset on every reply, and Retry-After on a 429. If you need a higher limit, email editor@thestackreport.io.
№ 07
Caching
Responses carry Cache-Control: public, s-maxage=86400, stale-while-revalidate=604800 — the same window the upstream daily-close providers refresh on. Most requests hit the edge cache and never invoke the function. If you intend to poll, poll the cache, not the function.
№ 08
CORS
Access-Control-Allow-Origin: *. Call us from the browser, from a notebook, from a Cloudflare Worker — whatever. There is no preflight requirement for plain GET with default headers; an OPTIONS handler is provided for the cases that send one.
№ 09
Versioning
The /v1 in the path is the contract. v1 will not break without a 90-day deprecation notice on this page and via the RSS feed. A v2 will live on a separate path; v1 will continue to answer for at least 90 days after.
№ 10
Attribution
Free to use. If you publish something that runs on this API, please link back to thestackreport.io. Every response carries an X-Citation: thestackreport.io header and an equivalent meta.citation field — that's the canonical citation token.
No key. No signup. No SLA. No tracking. The same calculator the site runs on, addressed by URL.
Questions or corrections: editor@thestackreport.io.