# QREP Backend API

Open, no-auth security comparison analytics: 81 risk and performance metrics per symbol against a benchmark, computed with the open-source QuantStats library.

## Quick start

One `GET`, no API key. Copy this, then change the symbols and the dates:

```
https://qrep-api.tigzig.com/v1/compare?symbols=AAPL,MSFT&start_date=2023-01-01&end_date=2023-12-31
```

## What you send

The public analytics endpoint is `GET /v1/compare`. It takes `?symbols=` with up to 6 comma-separated Yahoo Finance symbols, plus `&start_date=YYYY-MM-DD&end_date=YYYY-MM-DD`. No API key is required. Symbols follow the Yahoo Finance convention, so non-US listings carry an exchange suffix: RELIANCE.NS (India NSE), .BO (BSE), .L (London), .HK (Hong Kong), .T (Tokyo). Indices look like ^NSEI and FX like EURUSD=X. Broker, scheme or company names do not resolve. Dates are YYYY-MM-DD.

## Public endpoints

`/health`, `/v1/compare`. Routes under `/qpulse/` are internal and require an `X-API-Key` header; they are deliberately absent from this spec.

## Attribution (required)

Metrics are computed with the open-source **QuantStats** library (Apache-2.0) by Ran Aroussi and must be attributed as "powered by QuantStats". The full terms are on the `/v1/compare` endpoint below.

## Request limits

- Up to **6 symbols per request**. Sending more returns a clear 400 asking you to split the batch.
- **Report generation is serialised**, so parallel calls queue rather than finish sooner. Batch to the cap and run your batches **sequentially** - firing them at once makes the whole set slower, not faster.
- **Read your budget off the response headers, do not hard-code a number**: `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` are on every response and come out of the running limiter, so they cannot go stale. `GET /` returns the same limits as JSON.

## Errors

Every 4xx and 5xx returns `{error: {code, message}, help: {docs, openapi, catalog}}`.

Where the endpoint is known and the request shape is what failed - a 422, or a wrong method - the body also carries `example`, **a complete working URL for that same endpoint**, built from the parameter examples in this spec. Copy it, run it, then adapt it.

More Tigzig tools: https://api.tigzig.com/.well-known/api-catalog

## Root

`GET /`

What this API is, where the docs are, and your current request limits.

Returns service status, the base URL, links to the docs and the OpenAPI spec, the
MCP endpoint, and a limits block with your per-IP budget and the maximum symbols
per request.

## Health

`GET /health`

Health check endpoint

## Serve Report

`GET /reports/{filename}`

Serve generated report files

## Mcp Compare

`GET /v1/compare`

QREP security comparison. Returns **81 portfolio metrics per symbol**, computed with
the open-source QuantStats library. Lightweight endpoint built for LLM and MCP
consumption: no time series, no chart data, no price data. No API key required.

**Attribution is required.** Metrics come from QuantStats (Apache-2.0) by Ran
Aroussi. Wherever you display, label, cite, chart or summarise them, say
"powered by QuantStats" - for example `Risk metrics (powered by QuantStats)`. Do not
label data, columns, headers or sheets as `QuantStats` alone, do not co-brand as
`QREP / QuantStats`, and do not imply QuantStats endorses or is affiliated with this
service.

## Response structure

- `success` - boolean, overall success
- `benchmark` - the benchmark symbol used
- `start_date` / `end_date` - the date range used
- `risk_free_rate` - the risk-free rate used
- `symbols` - array of per-symbol results, each containing:
    - `symbol` - ticker string
    - `success` - boolean
    - `trading_days` - number of trading days in the period
    - `error` - null on success, an error message on failure
    - `all_metrics` - object with the 81 key-value metric pairs listed below
- `benchmark_metrics` - same structure as a symbol entry, but for the benchmark
  itself. Computed from the benchmark alone, so it does **not** depend on which
  symbols you requested or the order you listed them in. The six benchmark-relative
  metrics are null here, because a benchmark cannot be measured against itself.
- `message` - summary string, e.g. `Compared 3/3 securities successfully`

## What happens to an imperfect request

- **Dates must be exactly `YYYY-MM-DD`.** Anything else is a 400, including
  `03-02-2025`: day-first and month-first cannot be told apart, and guessing would
  silently change the period analysed.
- `start_date` after `end_date` is a 400.
- An `end_date` beyond the last available session is **clamped** to that session, so
  an open-ended "to today" request works.
- **Repeated symbols are collapsed**, so `AAPL,AAPL,AAPL` costs one slot, not three.
- **An unknown symbol does not fail the request.** That entry returns `success:
  false` with its own message and the others return normally. An unknown
  **benchmark** is a 404, because there is then nothing to compare against.

## The 81 metrics

All values are decimals unless noted, and percentages are decimals: `0.31` is 31%.

### Returns and performance

`Cumulative Return`, `CAGR%`, `MTD`, `3M`, `6M`, `YTD`, `1Y`, `3Y (ann.)`,
`5Y (ann.)`, `10Y (ann.)`, `All-time (ann.)`, `Best Day`, `Worst Day`, `Best Month`,
`Worst Month`, `Best Year`, `Worst Year`, `Expected Daily`, `Expected Monthly`,
`Expected Yearly`, `Avg. Return`, `Avg. Win`, `Avg. Loss`, `Avg. Up Month`,
`Avg. Down Month`

### Risk-adjusted ratios

`Sharpe`, `Smart Sharpe`, `Prob. Sharpe Ratio`, `Sortino`, `Smart Sortino`,
`Sortino/sqrt(2)`, `Smart Sortino/sqrt(2)`, `Omega`, `Calmar`,
`Risk-Adjusted Return`, `Risk-Return Ratio`, `Ulcer Performance Index`

### Drawdown

`Max Drawdown`, `Max DD Date`, `Max DD Period Start`, `Max DD Period End`,
`Longest DD Days`, `Avg. Drawdown`, `Avg. Drawdown Days`, `Recovery Factor`,
`Ulcer Index`, `Serenity Index`

The three date fields are `YYYY-MM-DD` strings; the two day-count fields are whole
numbers returned as floats.

### Volatility and distribution

`Volatility (ann.)`, `Skew`, `Kurtosis`

### Benchmark-relative

`Beta`, `Alpha`, `Correlation`, `Treynor Ratio`, `R-squared`, `Information Ratio`

All six are null on the benchmark row.

### Value-at-risk and tail risk

`Daily Value-at-Risk`, `Expected Shortfall (cVaR)`, `Risk of Ruin`,
`Kelly Criterion`, `Tail Ratio`, `Outlier Win Ratio`, `Outlier Loss Ratio`

### Win/loss and trade statistics

`Win Days %`, `Win Month %`, `Win Quarter %`, `Win Year %`, `Win/Loss Ratio`,
`Profit Ratio`, `Payoff Ratio`, `Profit Factor`, `Max Consecutive Wins`,
`Max Consecutive Losses`, `Gain/Pain Ratio`, `Gain/Pain (1M)`,
`Common Sense Ratio`, `CPC Index`

### Time context

`Start Period`, `End Period`, `Risk-Free Rate`, `Time in Market`

`Start Period` and `End Period` are `YYYY-MM-DD`. `Risk-Free Rate` is a **percentage**
here, e.g. `4.5`, unlike the request parameter. `Time in Market` is a decimal where
`1.0` is 100%.

## Notes worth reading before you compare rows

### Missing horizons are null, never estimated

`1Y`, `3Y (ann.)`, `5Y (ann.)` and `10Y (ann.)` are returned **only** when the
requested window actually covers that horizon. Otherwise they are null.

They are not the CAGR under another name, which is what an unguarded library returns
and what makes a ten-month window appear to have a ten-year track record. `CAGR%` and
`All-time (ann.)` are always present, because they claim only what they measure.

### Every row can span a different period

- Each symbol is measured over **its own available history** inside the requested
  window, so a security listed part-way through the period covers a shorter span
  than the others.
- `trading_days` on each row is the number of sessions that row was computed from.
  The benchmark row covers the full requested window, so a security with a shorter
  history will not match it.
- Read `Start Period` and `End Period` on each row before comparing cumulative or
  annualised figures across rows.

### Precision

Metrics this service computes itself carry **5 decimal places**: `Avg. Return`,
`Avg. Win`, `Avg. Loss`, `Payoff Ratio`, `CPC Index`, `Expected Daily`,
`Risk of Ruin`, and `Omega` / `Daily Value-at-Risk` / `Expected Shortfall (cVaR)` /
`Tail Ratio` when their optional parameters are supplied.

The rest come from QuantStats, which rounds to 2 decimals internally and offers no
way to ask for more, so those are 2dp and no further precision exists to give. It
matters most on small-magnitude values: an expected **daily** return is around
`0.0004`, which is a flat `0` at two decimals.

### Drawdown

Measured from the equity curve, so it is reported even when the drawdown has not
recovered and even when the window starts at the peak. `Max DD Period End` stays null
while a drawdown is still open: there is no end date yet, and inventing one would be
worse than the null.

### Two definitions that catch people out

- `Win/Loss Ratio` is **not** average win divided by average loss - `Payoff Ratio` is
  that one. `Win/Loss Ratio` is the library's own definition. If you want avg win over
  avg loss, read `Payoff Ratio` or compute it from `Avg. Win` and `Avg. Loss`.
- `Daily Value-at-Risk` and `Expected Shortfall (cVaR)` are **decimal fractions**
  (`-0.02` is -2%) on every row, whether or not `var_confidence` is supplied. Before
  v1.3.1 they were percentage points when `var_confidence` was sent, so the unit of
  the field depended on an unrelated optional parameter.

### risk_free_rate

Annual and a **decimal fraction** (`0.045` is 4.5%), applied identically to every row
including the benchmark. Values outside `-0.10` to `1.00` are rejected, because at
that distance the only explanation is a percentage sent as a whole number. With the
default of `0` the rate plays no part.

Part of Tigzig: free interactive tools, open-source repos, APIs and MCP servers for
analytics and live data across global and Indian markets, macro indicators and
filings. Catalog: https://api.tigzig.com/.well-known/api-catalog
Guide: https://www.tigzig.com/llms.txt
