Skip to content
VELUMA

Methodology

How every number is computed

No black boxes. Each figure below is one formula over your imported trades and the closes fetched for them, and each names the file that implements it.

Portfolio value

Every other figure is derived from one series: what the open positions were worth at each close.

value(d) = Σ over tickers  shares_held(t, d) × last_close(t, on or before d)
Formula

Trades are replayed in date order to give the net shares held in each ticker on every date, and each holding is valued at its most recent close on or before that date. A ticker with no close yet published carries its previous one forward rather than dropping to zero.

The series begins at your first trade date. Price bars published before it are still loaded, because they are what seeds the carry-forward value on the first day.

Cash is not tracked
This is the market value of open positions, not of an account. Proceeds from a sale leave the series entirely rather than sitting as cash, so a fully liquidated portfolio reads as zero.
Dividends, splits, and fees are not modeled
Closes are taken as published. An unadjusted split will show up as a step change in value, and reinvested dividends will not show up at all.

Implemented in src/lib/analytics/portfolio.ts

Daily returns

Simple period-over-period change, the input to everything below.

r(i) = (value(i) − value(i−1)) / value(i−1)
Formula

Simple rather than logarithmic returns, because the figures built on them — Sharpe, volatility — are conventionally quoted on simple returns and mixing the two produces numbers that cannot be compared against anything published elsewhere.

A period opening at zero or below produces no return at all rather than an infinite one. A portfolio that was fully liquidated and later rebuilt therefore yields a shorter return series than its value series, which is the honest outcome: the percentage change from nothing is undefined, not enormous.

Implemented in src/lib/analytics/math.ts

Maximum drawdown

The deepest fall from a high-water mark, and how long it lasted.

maxDD = max over d  (peak(≤ d) − value(d)) / peak(≤ d)
Formula

A single pass carries the running peak and records the largest proportional fall below it. The result is reported as a positive fraction, together with the index of the peak it fell from and the trough it fell to, so the decline can be annotated on the chart rather than merely stated.

Recovery is measured separately: the number of trading days from the trough until the series first regains the old peak. A portfolio still below that peak reports no recovery length at all — 'still underwater' and 'recovered immediately' are opposite facts and must not render the same.

Close-to-close, not intraday
Only end-of-day prices are fetched, so a fall that reversed within a single session is invisible here. A drawdown measured tick by tick would be deeper.

Implemented in src/lib/analytics/metrics.ts

Sharpe ratio

Excess return per unit of volatility, annualized.

S = mean(r − rf_daily) / stdev(r − rf_daily) × √252
rf_daily = (1 + rf_annual)^(1/252) − 1
Formula

The annual risk-free rate is converted to a daily one geometrically, not by dividing by 252 — over a year those differ by enough to move the ratio's second decimal, and the second decimal is the one people compare.

The standard deviation uses the sample denominator (n − 1), matching the volatility figure reported beside it. Mixing sample and population estimators across one dashboard produces two numbers that quietly disagree.

Fewer than two returns, or a series with no volatility at all, yields zero rather than an error or an infinity. There is no ratio to report, and zero is the reading that does not overstate the case.

The risk-free rate is yours to set
It defaults to 4% and can be changed in settings. The right value moved from near zero to above five percent within a few years, and a Sharpe ratio measured against the wrong era is simply wrong.

Implemented in src/lib/analytics/math.ts

Volatility and annualized return

The size of the swings, and the growth rate that survived them.

σ = stdev(r) × √252
CAGR = (value_last / value_first)^(252 / periods) − 1
Formula

Volatility is the sample standard deviation of daily returns scaled by the square root of 252, the conventional count of trading days in a year.

The annualized return compounds rather than averaging. It reads only the first and last values, which makes it blind to the path between them — a smooth climb and a crash followed by a full recovery produce the same figure, which is exactly why it is shown beside the drawdown rather than instead of it.

Implemented in src/lib/analytics/math.ts

Correlation

Pearson correlation of daily returns, over aligned dates only.

ρ(a,b) = Σ (a_i − ā)(b_i − b̄) / √( Σ(a_i − ā)² · Σ(b_i − b̄)² )
Formula

Every pair is measured over the same dates. The intersection of all holdings' price histories is taken first, and returns are computed within it, because correlating observations from different days is a silent error that produces a completely plausible-looking number.

A ticker bought last month therefore truncates the window for everything it is compared against. Any holding that still has gaps after the intersection is excluded and named, rather than quietly omitted from the matrix.

Below twenty shared observations no matrix is shown at all. A correlation drawn from a handful of days is noise, and presenting it in a colored grid would lend it an authority it has not earned.

Two-pass formulas throughout
Every routine subtracts the mean before summing squares rather than using the algebraically equivalent E[x²] − E[x]² shortcut, which loses precision when values share a large offset and can return a negative variance whose square root is NaN.
No variance means no correlation
A holding whose price never moved has no direction to share, so its pairs report zero rather than dividing by a zero spread.

Implemented in src/lib/analytics/correlation.ts

Cost basis and profit

Average-cost accounting, replayed in date order.

avg_cost = basis / shares
realized += shares_sold × (sell_price − avg_cost)
Formula

The ledger is replayed oldest first. Buys add to shares and to basis; sells reduce both proportionally and bank the difference as realized profit. Order matters — pricing a sell against purchases that had not happened yet would produce a figure that looks fine and is wrong.

A sell larger than the recorded position, which usually means an opening buy was never imported, is accounted for only up to the shares the ledger knows about. Realizing profit on shares with no recorded cost would invent money.

Average cost, not FIFO
Tax lots (FIFO, LIFO, specific identification) produce different realized figures and are what a tax return needs. Average cost answers 'am I up on this position', which is what a risk dashboard is for. Do not file these numbers.

Implemented in src/lib/analytics/positions.ts

Terms used above

Definitions of every figure here, plus the way each one is commonly misread, are on the glossary.