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 | 26,135 @ 30 | Annualized volatility from log returns |
| sharpeRatio | 26,273 @ 30 | Risk-adjusted return |
| maxDrawdown | 15,191 @ 30 | Peak-to-trough decline |
| valueAtRisk | 36,752 @ 30 | Historical VaR (NumPy-compatible) |
| conditionalValueAtRisk | 32,917 @ 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).| Constant | Value |
|---|---|
| MAX_VALUE | 1e15 (largest allowed value per array element) |
| MAX_ARRAY_LENGTH | 1024 (gas-bomb guard on array inputs) |
| MAX_RATE | 400% annual (4e18) |
| Error | Trigger |
|---|---|
| ArrayLengthMismatchError | weightedAverage when values[] and weights[] differ in length |
| ArrayLengthLowerBoundError | Array shorter than the minimum required by the function |
| ArrayLengthUpperBoundError | Array exceeds MAX_ARRAY_LENGTH |
| ValueUpperBoundError | Any element exceeds MAX_VALUE |
| WeightSumZeroError | weightedAverage when Σweights == 0 |
| PriceLowerBoundError | Any price is 0 (can't take log of zero) |
| IntervalLowerBoundError | historicalVolatility / sharpeRatio when intervalSec == 0 |
| RateUpperBoundError | sharpeRatio when riskFreeRateAnnual ≥ MAX_RATE |
| VolatilityZeroError | sharpeRatio when sample std dev is zero (undefined) |
| ConfidenceOutOfRangeError | valueAtRisk / conditionalValueAtRisk when confidence ∉ (0, 1) |