Source code for keeks.metrics
import math
from collections.abc import Sequence
from dataclasses import dataclass
[docs]
@dataclass(frozen=True)
class HistorySummary:
"""
End-of-run statistics for a bankroll history.
Attributes
----------
bets : int
Periods recorded after the starting entry (``len(history) - 1``).
start, end : float
The first and last history entries.
total_return : float or None
``end / start - 1``; ``None`` when ``start`` is not positive.
geometric_growth_per_period : float or None
``(end / start) ** (1 / bets) - 1``; ``None`` when undefined (no
recorded periods, or a nonpositive ``start`` or ``end``).
max_drawdown : float or None
Largest peak-to-trough fractional loss, in ``[0, 1]``, measured
against the running peak; ``None`` when no entry is ever positive.
ruined : bool
Whether the final funds are at or below zero.
"""
bets: int
start: float
end: float
total_return: float | None
geometric_growth_per_period: float | None
max_drawdown: float | None
ruined: bool
[docs]
def summarize_history(history: Sequence[float]) -> HistorySummary:
"""
Summarize a :attr:`BankRoll.history <keeks.bankroll.BankRoll.history>`.
Parameters
----------
history : sequence of float
Bankroll values in time order, starting with the initial funds.
Returns
-------
HistorySummary
Frozen dataclass of drawdown, growth and ruin statistics. Values
that cannot be defined are ``None`` rather than ``0.0``.
Raises
------
ValueError
If the history is empty, or holds a non-finite or negative value.
Examples
--------
>>> summary = summarize_history([1000.0, 1250.0, 1000.0])
>>> summary.bets, summary.total_return, summary.max_drawdown
(2, 0.0, 0.2)
"""
values = [float(value) for value in history]
if not values:
raise ValueError("history must contain at least one entry")
if not all(math.isfinite(value) for value in values):
raise ValueError("history must contain only finite values")
if any(value < 0 for value in values):
raise ValueError("history must contain only nonnegative values")
start, end = values[0], values[-1]
bets = len(values) - 1
total_return = end / start - 1 if start > 0 else None
growth = None
if bets > 0 and start > 0 and end > 0:
growth = (end / start) ** (1 / bets) - 1
peak = 0.0
max_drawdown = None
for value in values:
peak = max(peak, value)
if peak > 0:
drawdown = (peak - value) / peak
if max_drawdown is None or drawdown > max_drawdown:
max_drawdown = drawdown
return HistorySummary(
bets=bets,
start=start,
end=end,
total_return=total_return,
geometric_growth_per_period=growth,
max_drawdown=max_drawdown,
ruined=end <= 0,
)