DEV Community

Gabriele Paris
Gabriele Paris

Posted on

Compute portfolio risk metrics (Sharpe, beta, correlation) via a free API - in JS and Python

`---
title: "Compute portfolio risk metrics (Sharpe, beta, correlation) via a free API — in JS and Python"
published: false
description: "Stop re-deriving Sharpe, Sortino, beta, alpha, drawdown, correlation and rebalancing. Send your price series to one endpoint and get the numbers back — with copy-paste JavaScript and Python."

tags: javascript, python, api, tutorial

If you've ever built anything that touches a portfolio — a robo-advisor, a crypto tracker, a backtester, a personal-finance dashboard — you've hit the same wall: the analytics math is fiddly and easy to get subtly wrong.

Sharpe looks trivial until you realize you annualized volatility with the wrong factor. Beta needs a benchmark and a covariance that lines up on dates. Max drawdown has an off-by-one that silently reports the wrong trough. Correlation matrices are fine until you forget to convert prices to returns first.

I kept rewriting this code, so I turned it into an API. This post shows how to use it in JavaScript and Python, with runnable snippets.

Informational only — not investment advice. It's a compute service, not a recommendation engine.

What it does (and doesn't)

It's a compute-as-a-service API. You bring your own price data — the API never fetches or redistributes market data. You POST a price series, it returns the metrics. No data-license constraints, no PII.

Four endpoints:

Endpoint Returns
POST /v1/metrics total & annualized return, volatility, Sharpe, Sortino, Calmar, max drawdown, beta, alpha, information ratio
POST /v1/correlation Pearson correlation matrix (2–50 instruments) + insights
POST /v1/diversification asset-class / geography / sector spread + HHI concentration score
POST /v1/rebalancing buy/sell/hold deltas vs a target allocation

The metrics in one line each

  • Sharpe — return per unit of total risk.
  • Sortino — Sharpe, but only penalizing downside volatility.
  • Calmar — annualized return divided by max drawdown.
  • Beta — how much you move relative to a benchmark.
  • Alpha — return beyond what beta explains.
  • Max drawdown — worst peak-to-trough drop.
  • Information ratio — active return per unit of tracking error.

Step 1 — Get a free key

Grab a key on RapidAPI (free tier: 1,000 requests/month, no card):
👉 https://rapidapi.com/gabriele-rEc4i6FCa/api/portfolio-analytics

bash
export RAPIDAPI_KEY="your-key"

Step 2 — Call it from JavaScript (zero dependencies)

Node 18+ has global fetch, so no packages needed:

`js
const HOST = 'portfolio-analytics.p.rapidapi.com';

async function metrics(history, benchmark) {
const res = await fetch(https://${HOST}/v1/metrics, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-RapidAPI-Host': HOST,
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
},
body: JSON.stringify({ history, benchmark }),
});
if (!res.ok) throw new Error(API ${res.status});
return res.json();
}

const price = (arr) => arr.map((close, i) => ({ date: 2024-01-${String(i + 1).padStart(2, '0')}, close }));

const { metrics: m } = await metrics(
price([100, 101, 100.5, 102.8, 103.4, 102.1, 104.9]),
price([100, 100.3, 100.1, 100.9, 101.2, 101.0, 101.6]),
);

console.log(Sharpe ${m.sharpeRatio} · Beta ${m.beta} · Max DD ${m.maxDrawdown}%);
`

Step 3 — Same thing in Python (standard library only)

No requests needed — urllib is enough:

`python
import json, os, urllib.request

HOST = "portfolio-analytics.p.rapidapi.com"

def metrics(history, benchmark):
body = json.dumps({"history": history, "benchmark": benchmark}).encode()
req = urllib.request.Request(
f"https://{HOST}/v1/metrics",
data=body,
headers={
"Content-Type": "application/json",
"X-RapidAPI-Host": HOST,
"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
},
method="POST",
)
with urllib.request.urlopen(req) as r:
return json.load(r)

def price(arr):
return [{"date": f"2024-01-{i+1:02d}", "close": c} for i, c in enumerate(arr)]

res = metrics(
price([100, 101, 100.5, 102.8, 103.4, 102.1, 104.9]),
price([100, 100.3, 100.1, 100.9, 101.2, 101.0, 101.6]),
)
m = res["metrics"]
print(f"Sharpe {m['sharpeRatio']} · Beta {m['beta']} · Max DD {m['maxDrawdown']}%")
`

Step 4 — Beyond single-portfolio metrics

Correlation — pass 2–50 instruments, get an NxN Pearson matrix + insights:

json
POST /v1/correlation
{ "instruments": [
{ "name": "S&P 500", "ticker": "SPY", "history": [ ... ] },
{ "name": "Gold", "ticker": "GLD", "history": [ ... ] }
] }

Rebalancing — send current positions + target weights, get buy/sell/hold deltas:

json
POST /v1/rebalancing
{
"positions": [{ "name": "VWCE", "category": "etf", "value": 7000 }, { "name": "AAPL", "category": "stock", "value": 3000 }],
"targets": [{ "category": "etf", "targetPercent": 50 }, { "category": "stock", "targetPercent": 30 }, { "category": "bond", "targetPercent": 20 }]
}

Why an API instead of a library?

Fair question. Three reasons it earned its place in my stack:

  1. Correctness, once. The annualization, downside-deviation and drawdown edge cases are solved and tested in one place, not re-derived per project or per language.
  2. Language-agnostic. JS, Python, Go, whatever — it's just HTTP.
  3. No data liability. You keep your prices; the service only does math. Nothing to license, nothing sensitive leaving your control beyond anonymous number arrays.

If you'd rather vendor the math, that's valid too — but for quick projects, one HTTP call beats porting formulas.

Try it

If you build something with it, I'd love to hear what — drop a comment.

Disclaimer: informational purposes only, not investment advice. No warranty of accuracy.
`

Top comments (0)