Skip to main content

Appendix A: 27 Standard Metrics (21 Core + 6 Generic Aliases)

All functions are in simudyne.engine.metrics. Each takes a NumPy array and returns a Python float.

Central Tendency & Dispersion​

#FunctionSignatureDescription
1mean(values: ndarray) -> floatArithmetic mean: $\bar{x} = \frac{1}{n}\sum x_i$
2variance(values: ndarray) -> floatSample variance (Bessel-corrected, ddof=1): $s^2 = \frac{1}{n-1}\sum(x_i - \bar{x})^2$
3std(values: ndarray) -> floatSample standard deviation (ddof=1): $s = \sqrt{s^2}$
4coefficient_of_variation(values: ndarray) -> floatDimensionless relative variability: $CV = s / \lvert\bar{x}\rvert$. Returns 0 if mean is zero.

Serial Dependence & Memory​

#FunctionSignatureDescription
5autocorrelation(values: ndarray, lag: int = 1) -> floatAutocorrelation at lag $k$: $\rho(k) = C(k)/C(0)$ where $C(k)$ is autocovariance. Returns 0 if series too short or zero variance.
6hurst_exponent(values: ndarray, max_lag: int = 20) -> floatR/S (rescaled range) analysis. $H > 0.5$: trending (persistent). $H < 0.5$: mean-reverting. $H \approx 0.5$: random walk. Returns 0.5 if series < 20 values.
7ljung_box_statistic(values: ndarray, max_lag: int = 20) -> floatLjung-Box portmanteau test statistic: $Q = n(n+2)\sum_{k=1}^{h}\frac{\rho_k^2}{n-k}$. High values indicate significant serial correlation.
8cross_correlation(x: ndarray, y: ndarray, lag: int = 0) -> floatCross-correlation between two series at given lag: $\rho_{xy}(k) = \text{Cov}(x_t, y_{t+k}) / (\sigma_x \sigma_y)$. Returns 0 if either has zero variance.
9sample_entropy(values: ndarray, m: int = 2, r_fraction: float = 0.2) -> floatSample entropy: $-\ln(A/B)$ where $A$ and $B$ are template match counts at embedding dimensions $m+1$ and $m$. Tolerance $r = r_fraction \times \text{std}$. Lower values indicate more regularity.

Distribution Shape​

#FunctionSignatureDescription
10kurtosis(values: ndarray) -> floatExcess kurtosis (Fisher, 0 for normal): $\kappa = E[((x-\mu)/\sigma)^4] - 3$. Positive = heavy tails (leptokurtic). Needs $\geq 4$ values.
11skewness(values: ndarray) -> floatSample skewness: $\gamma_1 = E[((x-\mu)/\sigma)^3]$. Positive = right tail, negative = left tail. Needs $\geq 3$ values.
12hill_tail_index(values: ndarray, quantile: float = 0.05) -> floatHill estimator of power-law tail exponent: $\alpha = k / \sum \ln(x_i/x_{\min})$ where $k$ is the number of order statistics above the quantile threshold. Typical financial returns: $\alpha \in [2, 5]$.
13entropy(values: ndarray, n_bins: int = 50) -> floatShannon entropy of the histogram: $H = -\sum p_i \log_2 p_i$. Higher values indicate more uniform (less predictable) distributions.
14gini_coefficient(values: ndarray) -> floatGini coefficient from Lorenz curve: 0 = perfect equality, 1 = maximum inequality. Computed as $G = \frac{2\sum i \cdot x_{(i)}}{n \sum x_{(i)}} - \frac{n+1}{n}$ on sorted values.
15tail_ratio(values: ndarray, percentile: float = 5.0) -> floatRatio of upper to lower tail: $\lvert P_{100-p}\rvert / \lvert P_p\rvert$. Values > 1 indicate heavier upper tail; < 1 indicates heavier lower tail.

Risk Metrics​

#FunctionSignatureDescription
16max_drawdown(values: ndarray) -> floatMaximum peak-to-trough decline as a fraction in $[0, 1]$. Tracks running peak and reports largest relative decline. Needs $\geq 2$ values.
17realized_volatility(values: ndarray, window: int = 20, annualize_factor: float = 1.0) -> floatRolling standard deviation over the trailing window values, scaled by $\sqrt{\text{annualize_factor}}$. Uses the final window of the series.
18value_at_risk(values: ndarray, alpha: float = 0.05) -> floatValue at Risk: the $\alpha$-quantile of the distribution. At $\alpha=0.05$, this is the value below which 5% of observations fall.
19conditional_value_at_risk(values: ndarray, alpha: float = 0.05) -> floatExpected Shortfall (CVaR): $E[X \mid X \leq \text{VaR}]$. The mean of all values below the VaR threshold. More sensitive to tail shape than VaR.

Risk-Adjusted Performance​

#FunctionSignatureDescription
20sharpe_ratio(returns: ndarray, risk_free_rate: float = 0.0, annualize_factor: float = 1.0) -> floatSharpe ratio: $SR = \frac{\bar{r} - r_f}{\sigma_r} \times \sqrt{F}$ where $F$ is the annualisation factor (e.g., 252 for daily returns). Returns 0 if std is zero.
21sortino_ratio(returns: ndarray, risk_free_rate: float = 0.0, annualize_factor: float = 1.0) -> floatSortino ratio: $\frac{\bar{r} - r_f}{\sigma_{\text{down}}} \times \sqrt{F}$ where $\sigma_{\text{down}}$ is the standard deviation of negative excess returns only. Penalises downside risk but not upside.

Generic Aliases (6)​

These are domain-neutral aliases that delegate to the core metrics above with appropriate defaults. They provide more intuitive names for common use cases without introducing new computation.

#AliasDelegates ToDescription
22peak_to_troughmax_drawdown(values)Identical to max_drawdown. Provides a domain-neutral name for the maximum peak-to-trough decline metric.
23rolling_stdrealized_volatility(values, window, annualize_factor=1.0)Identical to realized_volatility with annualize_factor=1.0. Provides a generic name when the series is not financial returns.
24lower_quantilevalue_at_risk(values, alpha)Identical to value_at_risk. Provides a domain-neutral name for the lower $\alpha$-quantile (not specific to financial risk).
25lower_tail_meanconditional_value_at_risk(values, alpha)Identical to conditional_value_at_risk. Provides a domain-neutral name for the mean of values below the lower quantile.
26risk_adjusted_meansharpe_ratio(returns, risk_free_rate, annualize_factor)Identical to sharpe_ratio. Provides a generic name for the mean-to-standard-deviation ratio of any series.
27downside_adjusted_meansortino_ratio(returns, risk_free_rate, annualize_factor)Identical to sortino_ratio. Provides a generic name for the mean-to-downside-deviation ratio.

These aliases exist so that non-financial models (epidemiology, supply chain, ecology) can reference metrics by semantically appropriate names without implying a financial context. For example, a supply chain model can use peak_to_trough to measure maximum inventory decline without calling it a "drawdown", and an epidemiology model can use lower_tail_mean to measure expected tail severity without referencing "CVaR".

Usage with MCRunner​

Metrics are referenced by name in the mc_spec:

\{
"seeds": 100,
"steps": 200,
"burn_in": 10,
"metrics_to_compute": [
\{"name": "kurtosis", "field": "price"},
\{"name": "hurst_exponent", "field": "price", "params": \{"max_lag": 50}},
\{"name": "sharpe_ratio", "field": "returns", "params": \{"annualize_factor": 252}},
\{"name": "value_at_risk", "field": "returns", "params": \{"alpha": 0.01}}
]
}

The interpret_metrics_spec() function in simudyne.engine.metrics converts these declarations into callable functions that are applied to the specified trace fields after each MC seed completes. Results are aggregated into MCResult.aggregate_metrics as per-metric dictionaries with keys mean, std, min, max, median, and values (the full array across seeds).