`---
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:
- 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.
- Language-agnostic. JS, Python, Go, whatever — it's just HTTP.
- 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
- 🌐 Docs & live landing: https://swalance-portfolio-analytics.fly.dev/
- 🧰 Ready-made clients (JS / Python / curl): https://github.com/Gab-Swalance/portfolio-analytics-api-examples
- 🔑 Free key: https://rapidapi.com/gabriele-rEc4i6FCa/api/portfolio-analytics
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.
`
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.