Utilities¶
The utils module provides utility functions and classes used throughout the keeks package.
- keeks.utils.crra_utility(wealth: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], risk_aversion: float = 1.0) float | ndarray[source]¶
Calculate CRRA (Constant Relative Risk Aversion) utility.
For γ=1 (risk_aversion=1.0), uses log utility. For γ≠1, uses power utility: U(W) = W^(1-γ) / (1-γ)
- Parameters:
- Returns:
The utility value(s)
- Return type:
float or array-like
Notes
CRRA utility exhibits constant relative risk aversion, meaning the fraction of wealth an agent is willing to risk remains constant as wealth changes.
- keeks.utils.expected_utility(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, entry_price: float, risk_aversion: float = 1.0) float[source]¶
Calculate expected utility of a gamble.
- Parameters:
outcomes (array-like) – The possible payoffs from the gamble
probabilities (array-like) – The probability of each outcome (must sum to no more than 1, within
PROBABILITY_SUM_TOLERANCE). Any omitted mass is treated as a zero-payout outcome.current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.
entry_price (float) – Price to pay to participate in the gamble. Must be finite; its sign is unconstrained, because a negative price models being paid to take the gamble.
risk_aversion (float, default=1.0) – Coefficient of relative risk aversion (γ). Must be finite and greater than 0.
- Returns:
The expected utility from participating in the gamble
- Return type:
- Raises:
ValueError – If the gamble arrays are malformed, or if any scalar argument falls outside the ranges documented above.
- keeks.utils.find_indifference_price(outcomes: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], current_wealth: float, risk_aversion: float = 1.0, tolerance: float = 0.01, max_search_fraction: float = 0.5) float[source]¶
Find maximum price willing to pay for a gamble using binary search.
This function finds the entry price where the expected utility from participating equals the utility of not participating (indifference price).
- Parameters:
outcomes (array-like) – The possible payoffs from the gamble
probabilities (array-like) – The probability of each outcome (must sum to no more than 1, within
PROBABILITY_SUM_TOLERANCE). Any omitted mass is treated as a zero-payout outcome.current_wealth (float) – Current wealth before the gamble. Must be finite and greater than 0.
risk_aversion (float, default=1.0) – Coefficient of relative risk aversion (γ). Must be finite and greater than 0.
tolerance (float, default=0.01) – Convergence tolerance for binary search. Must be finite and greater than 0.
max_search_fraction (float, default=0.5) – Maximum fraction of wealth to consider as upper bound. Must be finite and non-negative; values above 1.0 are allowed and search beyond current wealth.
- Returns:
Maximum price willing to pay for the gamble
- Return type:
- Raises:
ValueError – If the gamble arrays are malformed, or if any scalar control falls outside the ranges documented above.
- Warns:
RuntimeWarning – If the gamble is still worth buying at the top of the search range,
current_wealth * max_search_fraction. The search cannot look past that bound, so the returned price is the bound itself rather than a solved indifference price, and the true price is at or above it. Raisemax_search_fractionto search further.
Examples
>>> # A favourable gamble: 60% chance to win $200, 40% chance to lose $100 >>> outcomes = [200, -100] >>> probabilities = [0.6, 0.4] >>> max_price = find_indifference_price(outcomes, probabilities, ... current_wealth=1000, risk_aversion=2.0) >>> print(f"Willing to pay: ${max_price:.2f}") Willing to pay: $57.56
- keeks.utils.validate_probabilities(probabilities: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]) ndarray[source]¶
Validate a probability vector and return it as a float array.
A vector is valid when every probability is finite and nonnegative and the probabilities sum to no more than one within
PROBABILITY_SUM_TOLERANCE. Mass below one is left as-is: completing a partial vector (padding it with an implicit outcome, or rescaling a within-tolerance excess) is a gamble-modeling decision, soexpected_utility()andfind_indifference_price()make it themselves and treat the omitted mass as a zero-payout outcome.- Parameters:
probabilities (array-like) – The probability of each outcome. Must be a non-empty one-dimensional sequence of finite, nonnegative numbers whose sum is at most
1 + PROBABILITY_SUM_TOLERANCE.- Returns:
The validated probabilities as a one-dimensional float array.
- Return type:
- Raises:
ValueError – If the probabilities are not a non-empty one-dimensional sequence of finite nonnegative numbers, or if they sum to more than
1 + PROBABILITY_SUM_TOLERANCE.
Examples
>>> validate_probabilities([0.25, 0.75]) array([0.25, 0.75])
A sum above one by more than the tolerance is rejected:
>>> validate_probabilities([0.5, 0.6]) Traceback (most recent call last): ... ValueError: Probabilities must sum to no more than one
Exceptions¶
- exception keeks.utils.RuinError[source]¶
Bases:
ExceptionException raised when a bankroll safeguard refuses a removal.
Raised by the BankRoll class in two cases: a removal would take the funds below zero (bankruptcy), or a removal exceeds the configured
max_transaction_lossfraction of current funds. The message names the attempted amount, the configured limit, and the current funds; the simulators re-report it as aUserWarningwhen a refusal stops a run.
Checking stated probabilities¶
Every strategy sizes from a stated probability, so before replaying a recorded
log it is worth asking whether those probabilities were calibrated.
keeks.calibration.calibration_report() scores them with the Brier score
and log loss and compares stated and observed frequencies in equal-width bins
(empty bins are omitted). It is an educational diagnostic, not advice. Inputs
are validated first: probabilities must be finite in [0, 1], outcomes must
be bool or 0/1 (not 1.0), and at least one observation is
required.
from keeks import calibration_report
report = calibration_report([0.2, 0.2, 0.8, 0.8], [0, 1, 1, 1])
print(round(report.brier_score, 6)) # 0.19
for b in report.bins:
print(b.lower, b.mean_predicted, b.observed_frequency)
- keeks.calibration.calibration_report(probabilities: Sequence[float], outcomes: Sequence[bool | int], n_bins: int = 10) CalibrationReport[source]¶
Check whether recorded win probabilities were calibrated.
Strategies size from a stated probability, so a miscalibrated one is the main way Kelly-family sizing loses money. This is an educational diagnostic of past data, not advice: a good score on a short record does not show the probabilities will stay calibrated.
- Parameters:
probabilities (sequence of float) – Stated win probability of each event, each finite and in
[0, 1].outcomes (sequence of bool or int) – Whether each event won:
boolor0/1only (floats such as1.0are rejected), same length asprobabilities.n_bins (int, default 10) – Number of equal-width bins over
[0, 1].
- Returns:
Frozen dataclass of Brier score, log loss, base rate and per-bin stated-versus-observed frequencies.
- Return type:
- Raises:
ValueError – If an input is invalid, the lengths differ, there are no observations, or
n_binsis not a positive integer.
Examples
>>> report = calibration_report([0.2, 0.2, 0.8, 0.8], [0, 1, 1, 1]) >>> report.n, round(report.brier_score, 6), report.base_rate (4, 0.19, 0.75) >>> [(b.lower, b.count, b.observed_frequency) for b in report.bins] [(0.2, 2, 0.5), (0.8, 2, 1.0)]
- class keeks.calibration.CalibrationReport(n: int, brier_score: float, log_loss: float, min_clipped: bool, max_clipped: bool, base_rate: float, bins: tuple[CalibrationBin, ...])[source]¶
Bases:
objectCalibration diagnostics for recorded probabilities and outcomes.
- log_loss¶
Mean negative log-likelihood, with probabilities clipped to
[1e-12, 1 - 1e-12].- Type:
- min_clipped, max_clipped
Whether any probability fell below / above the clip bounds, so the log loss reflects the bound rather than the stated value.
- Type:
- bins¶
Non-empty bins in ascending order; empty bins are omitted.
- Type:
Loading a recorded bet log¶
keeks.bet_log.load_bet_log() reads a CSV with a header row and returns
the parallel (probabilities, outcomes) lists that calibration_report,
HistoricalBinarySimulator and compare_strategies take. Rows keep their
file order. Probabilities must be finite and in [0, 1]; outcomes may be
0, 1, true or false (case-insensitive). Errors are
ValueErrors naming the column and the 1-based data row.
from keeks import calibration_report, load_bet_log
probabilities, outcomes = load_bet_log("my_bets.csv")
report = calibration_report(probabilities, outcomes)
- keeks.bet_log.load_bet_log(path: str | PathLike[str], *, probability_column: str = 'probability', outcome_column: str = 'outcome') tuple[list[float], list[int]][source]¶
Read chronological probabilities and 0/1 outcomes from a CSV file.
Row order is preserved. Probabilities must be finite and in
[0, 1]; outcomes may be0,1,trueorfalse(case-insensitive). Any problem raisesValueErrornaming the file column and the 1-based data row (the header is not counted).- Parameters:
path (str or os.PathLike) – CSV file with a header row.
probability_column (str) – Header names of the two columns to read.
outcome_column (str) – Header names of the two columns to read.
- Returns:
(probabilities, outcomes), ready forcalibration_report,HistoricalBinarySimulatororcompare_strategies.- Return type:
Examples
>>> import tempfile >>> from pathlib import Path >>> with tempfile.TemporaryDirectory() as folder: ... log = Path(folder) / "log.csv" ... _ = log.write_text("probability,outcome\n0.6,1\n0.4,false\n") ... load_bet_log(log) ([0.6, 0.4], [1, 0])