Vol. I · No. 1

The Stack Report

A periodical for the patient buyer of bitcoin.

§ 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/dca

One 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

NameRequiredTypeDescription
amountYesnumberUSD per buy. Whole dollars between 1 and 1,000,000.
freqYesdaily | weekly | monthlyCadence of recurring buys.
fromYesYYYY-MM-DDStart of the window. Must be on or after 2013-04-28.
toOptionalYYYY-MM-DDEnd of the window. Defaults to today. Must be after from.
stratOptionalcomma list of lump, va, dipAlternative strategies to compute alongside DCA. Lump sum, value averaging, dip-buyer.
includeOptionalpricesOpt-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.

CodeHTTPMeaning
missing-required-param400amount, freq, or from was not supplied.
invalid-amount400amount could not be parsed, or fell outside 1–1,000,000.
invalid-frequency400freq was not one of daily, weekly, monthly.
invalid-date400from or to was not a valid YYYY-MM-DD.
invalid-range400from before 2013-04-28, to in the future, or from ≥ to.
unknown-strategy400strat contained a token other than lump, va, dip.
unknown-include400include contained a token other than prices.
rate-limited429Soft per-IP limit hit. Retry-After header is set.
data-unavailable503Upstream 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.