Statistics

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

Functions

FunctionGasDescription
geometricMean330sqrt(a · b) — Uniswap V2 invariant
mean6,980 @ 30Arithmetic mean
stdDev15,298 @ 30Sample standard deviation (Bessel-corrected)
weightedAverage15,687 @ 30Σ(v · w) / Σ(w)
historicalVolatility26,135 @ 30Annualized volatility from log returns
sharpeRatio26,273 @ 30Risk-adjusted return
maxDrawdown15,191 @ 30Peak-to-trough decline
valueAtRisk36,752 @ 30Historical VaR (NumPy-compatible)
conditionalValueAtRisk32,917 @ 30Expected shortfall (left-tail mean)

npm install defimath-lib

Conventions

  • Array inputs are 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).
  • intervalSecuint32, sampling interval in seconds (e.g. 1 days for daily prices). Used to annualize.
  • riskFreeRateAnnualuint64, annualized rate, 18-decimal fixed-point.
  • confidenceuint64, the α level for VaR / CVaR, in (0, 1) exclusive (e.g. 0.95e18 for 95%).
  • Return type mirrors sign: int256 for quantities that can be negative (Sharpe, VaR, CVaR); uint256 otherwise.
  • All functions are internal pure.

Quick example

solidity
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);

Important notes

  • Gas scales linearly with array size. The figures in the table are at 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.
  • VaR vs. CVaR. 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).
  • VaR uses NumPy-compatible linear interpolation. Matches 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).

Limits & errors

ConstantValue
MAX_VALUE1e15 (largest allowed value per array element)
MAX_ARRAY_LENGTH1024 (gas-bomb guard on array inputs)
MAX_RATE400% annual (4e18)
ErrorTrigger
ArrayLengthMismatchErrorweightedAverage when values[] and weights[] differ in length
ArrayLengthLowerBoundErrorArray shorter than the minimum required by the function
ArrayLengthUpperBoundErrorArray exceeds MAX_ARRAY_LENGTH
ValueUpperBoundErrorAny element exceeds MAX_VALUE
WeightSumZeroErrorweightedAverage when Σweights == 0
PriceLowerBoundErrorAny price is 0 (can't take log of zero)
IntervalLowerBoundErrorhistoricalVolatility / sharpeRatio when intervalSec == 0
RateUpperBoundErrorsharpeRatio when riskFreeRateAnnual ≥ MAX_RATE
VolatilityZeroErrorsharpeRatio when sample std dev is zero (undefined)
ConfidenceOutOfRangeErrorvalueAtRisk / conditionalValueAtRisk when confidence ∉ (0, 1)