Portfolio and performance analytics on-chain — mean, std dev, historical volatility, Sharpe, max drawdown, VaR, CVaR. Array-based functions scale linearly with input size; gas figures below are quoted at N = 30.
Contract: Stats.sol
| Function | Gas | Description |
|---|---|---|
| geometricMean | 330 | sqrt(a · b) — Uniswap V2 invariant |
| mean | 6,980 @ 30 | Arithmetic mean |
| stdDev | 15,298 @ 30 | Sample standard deviation (Bessel-corrected) |
| weightedAverage | 15,687 @ 30 | Σ(v · w) / Σ(w) |
| historicalVolatility | 25,915 @ 30 | Annualized volatility from log returns |
| sharpeRatio | 26,053 @ 30 | Risk-adjusted return |
| maxDrawdown | 15,470 @ 30 | Peak-to-trough decline |
| valueAtRisk | 34,531 @ 30 | Historical VaR (NumPy-compatible) |
| conditionalValueAtRisk | 31,889 @ 30 | Expected shortfall (left-tail mean) |
npm install defimath-lib
uint256[] calldata, 18-decimal fixed-point. Each element must fit within MAX_VALUE.prices arrays are oldest-first; returns are derived internally as ln(pi / pi−1).intervalSec — uint32, sampling interval in seconds (e.g. 1 days for daily prices). Used to annualize.riskFreeRateAnnual — uint64, annualized rate, 18-decimal fixed-point.confidence — uint64, the α level for VaR / CVaR, in (0, 1) exclusive (e.g. 0.95e18 for 95%).int256 for quantities that can be negative (Sharpe, VaR, CVaR); uint256 otherwise.internal pure.import "defimath-lib/contracts/finance/Stats.sol";
// 30 daily closing prices, 18-decimal fixed-point.
uint256[] memory prices = loadPriceSeries();
// Sample mean and std dev of the raw values.
uint256 mu = DeFiMathStats.mean(prices);
uint256 sigma = DeFiMathStats.stdDev(prices);
// Annualized volatility from log returns (1-day interval).
uint256 vol = DeFiMathStats.historicalVolatility(prices, 1 days);
// Sharpe ratio at a 2% risk-free rate.
int256 sharpe = DeFiMathStats.sharpeRatio(prices, 1 days, 0.02e18);N = 30 (a typical month of daily prices). Doubling the array roughly doubles the gas; arrays larger than MAX_ARRAY_LENGTH (1024) revert.historicalVolatility annualizes internally. Pass raw prices and the sampling interval in seconds — the function computes log returns, takes the sample std dev, and scales by √(SECONDS_IN_YEAR / interval). Don't pre-annualize.sharpeRatio is annualized. The function uses annualized return minus annualized risk-free rate over annualized volatility. Pass the risk-free rate as a yearly value regardless of price-series interval.valueAtRisk returns the return threshold at the chosen confidence — a probability statement. conditionalValueAtRisk returns the expected return conditional on being beyond that threshold (the left-tail mean). CVaR ≤ VaR (more conservative).numpy.quantile(..., method="linear") and simple-statistics.quantile. Useful when reconciling on-chain risk numbers with off-chain notebooks.stdDev is sample (Bessel-corrected). Divides by n − 1, not n. Matches numpy.std(..., ddof=1).Every function reverts on out-of-bounds inputs with a named error — see the per-function pages for limits and error specifics.
Hardhat correctness layer. 114 tests across 9 function groups. valueAtRisk / conditionalValueAtRisk validated against simple-statistics.quantile (NumPy-compatible linear interpolation); volatility, Sharpe ratio, and max drawdown validated against inline JS references over series of 30–100 prices. Limits coverage at boundary inputs (single-element arrays, max array length, MAX_VALUE per element, confidence approaching 0 and 1).
Foundry property-fuzz layer. 12 mathematical properties × 32,000 random runs each = 384,000 random executions per CI run.
| Category | Count | What they check |
|---|---|---|
| Monotonicity | 1 | geometricMean ↑ in first argument |
| Identities | 7 | geometricMean(a, a) = a, mean(constant) = constant, stdDev(constant) = 0, maxDrawdown(increasing) = 0, weightedAverage(equal_weights) = mean, mean scales linearly (mean(2·v) = 2·mean(v)), stdDev scales linearly |
| Output bounds | 3 | min ≤ mean ≤ max, min(a, b) ≤ geometricMean ≤ max(a, b), maxDrawdown ∈ [0, 1] |
| Symmetries | 1 | geometricMean(a, b) = geometricMean(b, a) — argument-order independence |
Sources: test/Stats.test.mjs · test/foundry/Stats.t.sol