Module Algostream_performance.Metrics

Risk-adjusted performance metrics — the canonical implementations.

Consolidation notice. The tree carries four max-drawdown implementations and three Sharpe implementations, using three different formulas — two of them under the same field name:

The last of those was found by make metrics-dup-lint on its first run, which is a fair illustration of why the lint exists. None of the four is annualized and only one subtracts a risk-free rate, so none is a Sharpe ratio in the conventional sense. Those functions are left in place — Risk_metrics is load-bearing for Risk_management.Var.Historical — but are marked superseded in their own doc comments. This module is where new code should look, and the make metrics-dup-lint target exists to stop a fourth implementation appearing.

Conventions, stated because this is exactly where implementations silently disagree:

type t = {
  1. n_periods : int;
  2. periods_per_year : float;
  3. total_return : float;
    (*

    fractional over the whole sample, not annualized

    *)
  4. cagr : float;
    (*

    geometric annual growth rate

    *)
  5. ann_return : float;
    (*

    arithmetic mean × periods_per_year

    *)
  6. ann_volatility : float;
    (*

    sample stddev × sqrt periods_per_year

    *)
  7. ann_downside_deviation : float;
  8. sharpe : float;
    (*

    (ann_return - risk_free) / ann_volatility

    *)
  9. sortino : float;
    (*

    (ann_return - mar) / ann_downside_deviation

    *)
  10. calmar : float;
    (*

    cagr / |max_drawdown|

    *)
  11. omega : float;
    (*

    Σ gains above MAR / Σ losses below MAR

    *)
  12. ulcer_index : float;
  13. martin_ratio : float;
    (*

    (ann_return - risk_free) / ulcer_index

    *)
  14. tail_ratio : float;
    (*

    |p95| / |p5| of the return distribution

    *)
  15. max_drawdown : float;
    (*

    fractional, positive

    *)
  16. max_drawdown_duration_ns : int64;
  17. skewness : float;
  18. excess_kurtosis : float;
  19. var_95 : float;
    (*

    positive = loss, per period

    *)
  20. cvar_95 : float;
  21. var_99 : float;
  22. cvar_99 : float;
  23. best_period : float;
  24. worst_period : float;
  25. hit_rate : float;
    (*

    fraction of periods with a positive return

    *)
  26. win_loss_ratio : float;
    (*

    mean gain / |mean loss|

    *)
  27. time_in_market : float;
    (*

    fraction of periods with a non-zero return

    *)
}
val empty : t
val of_returns : returns:float array -> periods_per_year:float -> ?risk_free_rate_ann:float -> ?mar_ann:float -> unit -> t

Compute from a return series. periods_per_year comes from Returns.periods_per_year. risk_free_rate_ann and mar_ann are annual rates, converted internally to per-period.

Drawdown fields are derived from the equity curve implied by compounding returns; pass ~nav to of_nav instead when the true NAV curve is available, which gives exact drawdown timings.

val of_nav : nav:(int64 * float) array -> ?kind:Returns.kind -> ?days_per_year:float -> ?hours_per_day:float -> ?risk_free_rate_ann:float -> ?mar_ann:float -> unit -> t

Compute from a NAV curve. Preferred over of_returns: the sampling interval is inferred from the timestamps, and drawdown durations are measured in real event time rather than in periods.

val to_assoc : t -> (string * float) array

Flatten to name/value pairs — the vector a Monte Carlo worker returns instead of a whole equity curve. Field order is stable across calls.

val to_string : t -> string