keeks 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. keeks-0.3.0/CHANGELOG.md +50 -0
  2. keeks-0.3.0/CLAUDE.md +202 -0
  3. {keeks-0.2.0 → keeks-0.3.0}/Makefile +1 -1
  4. {keeks-0.2.0 → keeks-0.3.0}/PKG-INFO +31 -2
  5. {keeks-0.2.0 → keeks-0.3.0}/README.md +30 -1
  6. {keeks-0.2.0 → keeks-0.3.0}/docs/source/binary_strategies.rst +8 -0
  7. keeks-0.3.0/examples/output/st_petersburg_paradox.csv +5 -0
  8. keeks-0.3.0/examples/output/st_petersburg_paradox.png +0 -0
  9. keeks-0.3.0/examples/output/strategy_comparison.csv +14 -0
  10. keeks-0.3.0/examples/output/strategy_comparison.png +0 -0
  11. keeks-0.3.0/examples/st_petersburg_paradox.py +244 -0
  12. keeks-0.2.0/examples/st_petersburg_comparison.py → keeks-0.3.0/examples/strategy_comparison.py +71 -24
  13. {keeks-0.2.0 → keeks-0.3.0}/keeks/bankroll.py +21 -6
  14. {keeks-0.2.0 → keeks-0.3.0}/keeks/binary_strategies/__init__.py +3 -0
  15. {keeks-0.2.0 → keeks-0.3.0}/keeks/binary_strategies/base.py +51 -0
  16. {keeks-0.2.0 → keeks-0.3.0}/keeks/binary_strategies/kelly.py +126 -0
  17. {keeks-0.2.0 → keeks-0.3.0}/keeks/binary_strategies/simple.py +396 -2
  18. {keeks-0.2.0 → keeks-0.3.0}/keeks/simulators/random_binary.py +19 -11
  19. {keeks-0.2.0 → keeks-0.3.0}/keeks/simulators/random_uncertain_binary.py +22 -14
  20. {keeks-0.2.0 → keeks-0.3.0}/keeks/simulators/repeated_binary.py +18 -11
  21. keeks-0.3.0/keeks/utils.py +146 -0
  22. {keeks-0.2.0 → keeks-0.3.0}/pyproject.toml +1 -1
  23. keeks-0.3.0/tests/test_bankruptcy_protection.py +176 -0
  24. keeks-0.3.0/tests/test_merton_share.py +322 -0
  25. keeks-0.3.0/tests/test_strategy_entry_price.py +366 -0
  26. keeks-0.3.0/tests/test_utility_functions.py +263 -0
  27. keeks-0.2.0/.cursor/rules/project_overview.mdc +0 -133
  28. keeks-0.2.0/.cursor/rules/pytest_standards.mdc +0 -48
  29. keeks-0.2.0/.cursor/rules/python_standards.mdc +0 -68
  30. keeks-0.2.0/.cursor/rules/sphinx_docs_standards.mdc +0 -62
  31. keeks-0.2.0/.cursor/rules/testing.mdc +0 -53
  32. keeks-0.2.0/CHANGELOG.md +0 -19
  33. keeks-0.2.0/examples/README.md +0 -50
  34. keeks-0.2.0/examples/output/st_petersburg_strategies_comparison.csv +0 -11
  35. keeks-0.2.0/examples/output/st_petersburg_strategies_comparison.png +0 -0
  36. keeks-0.2.0/keeks/utils.py +0 -9
  37. {keeks-0.2.0 → keeks-0.3.0}/.github/ISSUE_TEMPLATE.md +0 -0
  38. {keeks-0.2.0 → keeks-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  39. {keeks-0.2.0 → keeks-0.3.0}/.github/workflows/docs.yml +0 -0
  40. {keeks-0.2.0 → keeks-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
  41. {keeks-0.2.0 → keeks-0.3.0}/.github/workflows/test.yml +0 -0
  42. {keeks-0.2.0 → keeks-0.3.0}/.gitignore +0 -0
  43. {keeks-0.2.0 → keeks-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  44. {keeks-0.2.0 → keeks-0.3.0}/LICENSE.md +0 -0
  45. {keeks-0.2.0 → keeks-0.3.0}/docs/Makefile +0 -0
  46. {keeks-0.2.0 → keeks-0.3.0}/docs/README.md +0 -0
  47. {keeks-0.2.0 → keeks-0.3.0}/docs/make.bat +0 -0
  48. {keeks-0.2.0 → keeks-0.3.0}/docs/requirements.txt +0 -0
  49. {keeks-0.2.0 → keeks-0.3.0}/docs/source/bankroll.rst +0 -0
  50. {keeks-0.2.0 → keeks-0.3.0}/docs/source/conf.py +0 -0
  51. {keeks-0.2.0 → keeks-0.3.0}/docs/source/getting_started.rst +0 -0
  52. {keeks-0.2.0 → keeks-0.3.0}/docs/source/index.rst +0 -0
  53. {keeks-0.2.0 → keeks-0.3.0}/docs/source/simulators.rst +0 -0
  54. {keeks-0.2.0 → keeks-0.3.0}/docs/source/utils.rst +0 -0
  55. {keeks-0.2.0 → keeks-0.3.0}/keeks/__init__.py +0 -0
  56. {keeks-0.2.0 → keeks-0.3.0}/keeks/simulators/__init__.py +0 -0
  57. {keeks-0.2.0 → keeks-0.3.0}/tests/__init__.py +0 -0
  58. {keeks-0.2.0 → keeks-0.3.0}/tests/test_Bankroll.py +0 -0
  59. {keeks-0.2.0 → keeks-0.3.0}/tests/test_cppi_strategy.py +0 -0
  60. {keeks-0.2.0 → keeks-0.3.0}/tests/test_drawdown_adjusted_kelly.py +0 -0
  61. {keeks-0.2.0 → keeks-0.3.0}/tests/test_dynamic_bankroll_management.py +0 -0
  62. {keeks-0.2.0 → keeks-0.3.0}/tests/test_fixed_fraction_strategy.py +0 -0
  63. {keeks-0.2.0 → keeks-0.3.0}/tests/test_kelly_criterion.py +0 -0
  64. {keeks-0.2.0 → keeks-0.3.0}/tests/test_naive_strategy.py +0 -0
  65. {keeks-0.2.0 → keeks-0.3.0}/tests/test_optimal_f.py +0 -0
  66. {keeks-0.2.0 → keeks-0.3.0}/version.py +0 -0
@@ -0,0 +1,50 @@
1
+ v0.3.0
2
+ ======
3
+
4
+ **New Features:**
5
+ * Added MertonShare strategy based on Merton's portfolio problem with CRRA utility
6
+ * MertonShare supports configurable risk aversion parameter (gamma)
7
+ * Added CRRA utility functions to keeks.utils: `crra_utility()`, `expected_utility()`, `find_indifference_price()`
8
+ * Added `calculate_max_entry_price()` method to ALL 9 strategies:
9
+ - **Utility-based**: KellyCriterion, MertonShare, OptimalF use CRRA utility
10
+ - **Kelly variants**: FractionalKelly, DrawdownAdjustedKelly scale Kelly's price
11
+ - **Rule-based**: FixedFraction, CPPI, Dynamic apply mechanical rules
12
+ - **Risk-neutral**: NaiveStrategy pays expected value
13
+ * Added St. Petersburg paradox example demonstrating all 9 strategies
14
+ * Updated strategy comparison example to include MertonShare with different risk aversion levels
15
+ * Added comprehensive tests for MertonShare strategy (16 test cases)
16
+ * Added comprehensive tests for utility functions (18 test cases)
17
+ * Added comprehensive tests for strategy entry price methods (17 test cases)
18
+ * Updated documentation to include MertonShare, utility functions, and entry price methods
19
+ * Example outputs now include both CSV and PNG visualizations
20
+
21
+ **Critical Bug Fixes:**
22
+ * Fixed bankruptcy protection - bankrolls can no longer go negative
23
+ * Fixed transaction cost bug in simulators - costs now correctly increase losses instead of decreasing them
24
+ * Fixed transaction costs being charged on zero bets - costs only apply when actually betting
25
+ * Fixed CPPI hardcoded 1% edge threshold - now works with realistic 0.5-2% edges
26
+ * Added bankruptcy checks to all simulators - simulations now stop when bankroll is depleted
27
+ * Fixed drawdown check ordering - bankruptcy is now checked before drawdown limits
28
+ * Fixed ruin rate detection in examples - now properly detects early termination
29
+ * Added 9 comprehensive bankruptcy protection tests
30
+ * Updated example parameters to realistic professional bettor scenario (52% win, 0.95x payoff, 0.4% edge)
31
+
32
+ v0.2.0
33
+ ======
34
+
35
+ * no longer allowing negative bets
36
+
37
+ v0.1.0
38
+ ======
39
+
40
+ * modernizing, added new methods, examples fixes
41
+
42
+ v0.0.2
43
+ ======
44
+
45
+ * adding CI, docs, and testing
46
+
47
+ v0.0.1
48
+ ======
49
+
50
+ * first release
keeks-0.3.0/CLAUDE.md ADDED
@@ -0,0 +1,202 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ Keeks is a Python library for optimal bankroll allocation and betting strategies, focusing on the Kelly Criterion and its variants. The library provides tools for:
8
+ - Bankroll management with risk protection
9
+ - Mathematical betting strategies (Kelly, Fractional Kelly, etc.)
10
+ - Simulation of betting scenarios with different probability distributions
11
+
12
+ ## Development Commands
13
+
14
+ This project uses `uv` as the package manager. All commands are available via the Makefile.
15
+
16
+ ### Setup
17
+ ```bash
18
+ make setup # Install uv and create virtual environment
19
+ make install-dev # Install package with development dependencies
20
+ ```
21
+
22
+ ### Testing
23
+ ```bash
24
+ make test # Run pytest
25
+ make test-cov # Run tests with coverage report
26
+ make test-all # Run tests on all Python versions using tox
27
+
28
+ # Run a single test file
29
+ uv run pytest tests/test_kelly_criterion.py
30
+
31
+ # Run a specific test
32
+ uv run pytest tests/test_kelly_criterion.py::test_basic_functionality
33
+
34
+ # Custom pytest arguments
35
+ make test PYTEST_ARGS="tests/test_bankroll.py -v"
36
+ ```
37
+
38
+ ### Code Quality
39
+ ```bash
40
+ make lint # Run ruff checks
41
+ make lint-fix # Auto-fix linting issues (includes unsafe fixes)
42
+ make format # Format code with ruff
43
+ ```
44
+
45
+ ### Documentation
46
+ ```bash
47
+ make docs # Build Sphinx documentation and open in Chrome
48
+ ```
49
+
50
+ ### Other
51
+ ```bash
52
+ make build # Build package distributions
53
+ make clean # Clean build artifacts
54
+ make examples # Run example scripts (st_petersburg_comparison.py)
55
+ ```
56
+
57
+ ## Architecture
58
+
59
+ ### Four-Component Design
60
+
61
+ The library follows a four-component architecture:
62
+
63
+ 1. **BankRoll** (`keeks/bankroll.py`): Central state manager
64
+ - Tracks total funds and bettable funds (via `percent_bettable`)
65
+ - Maintains transaction history
66
+ - Enforces risk limits via `max_draw_down` parameter
67
+ - Raises `RuinError` when drawdown limit exceeded
68
+ - Key methods: `deposit()`, `withdraw()`, `bet()`, `add_funds()`, `remove_funds()`
69
+
70
+ 2. **Strategies** (`keeks/binary_strategies/`): Decision engines
71
+ - All inherit from `BaseStrategy` abstract class
72
+ - Must implement `evaluate(probability, current_bankroll)` returning bet fraction
73
+ - Strategy constructors take: `payoff`, `loss`, `transaction_cost`
74
+ - Available strategies: `KellyCriterion`, `FractionalKellyCriterion`, `DrawdownAdjustedKelly`, `OptimalF`, `FixedFractionStrategy`, `CPPIStrategy`, `DynamicBankrollManagement`, `MertonShare`, `NaiveStrategy`
75
+
76
+ 3. **Simulators** (`keeks/simulators/`): Test harnesses
77
+ - `RepeatedBinarySimulator`: Fixed probability across all trials
78
+ - `RandomBinarySimulator`: Normally distributed probabilities
79
+ - `RandomUncertainBinarySimulator`: Adds uncertainty to outcomes
80
+ - All simulators have `evaluate_strategy(strategy, bankroll)` method that runs trials and updates bankroll in-place
81
+
82
+ 4. **Utility Functions** (`keeks/utils.py`): Decision theory tools
83
+ - `crra_utility(wealth, risk_aversion)`: Calculate CRRA utility values
84
+ - `expected_utility(outcomes, probabilities, current_wealth, entry_price, risk_aversion)`: Calculate expected utility of a gamble
85
+ - `find_indifference_price(outcomes, probabilities, current_wealth, risk_aversion)`: Find maximum price willing to pay for a gamble
86
+ - Used for one-time decision problems (e.g., St. Petersburg paradox) vs. repeated betting strategies
87
+
88
+ ### Key Design Patterns
89
+
90
+ - **Strategy Pattern**: Betting strategies are interchangeable implementations of `BaseStrategy`
91
+ - **State Management**: `BankRoll` maintains all financial state; strategies are stateless (except for state-dependent variants)
92
+ - **In-Place Updates**: Simulators modify the `BankRoll` object directly rather than returning new values
93
+ - **Safety-First**: Strategies use `get_max_safe_bet()` to prevent negative bankroll scenarios
94
+
95
+ ### Important Constraints
96
+
97
+ - **No negative bets**: All strategies validated in `BaseStrategy.__init__()` to ensure `payoff > 0`, `loss >= 0`, `transaction_cost >= 0`
98
+ - **Drawdown protection**: `BankRoll.max_draw_down` enforces maximum loss per transaction; raises `RuinError` when violated
99
+ - **Test isolation**: NEVER add test-specific handling or special case logic in library code (see python_standards.mdc)
100
+
101
+ ## Code Standards
102
+
103
+ ### Python Style
104
+ - Line length: 88 characters (ruff/Black default)
105
+ - Use snake_case for functions/variables, CamelCase for classes
106
+ - Type hints preferred for public APIs
107
+ - Docstrings: NumPy/Google style with parameters, returns, and examples
108
+
109
+ ### Testing Standards
110
+ - Tests in `tests/` directory, named `test_*.py`
111
+ - Use `pytest.approx()` for floating-point comparisons
112
+ - Random seed (`random.seed(42)`) for reproducible simulations
113
+ - Target 80%+ code coverage
114
+ - Run specific tests: `uv run pytest tests/test_file.py::test_name`
115
+
116
+ ### Import Organization
117
+ 1. Standard library (random, abc)
118
+ 2. Third-party (numpy, matplotlib, pandas, pytest)
119
+ 3. Local imports (keeks modules)
120
+
121
+ ## Common Patterns
122
+
123
+ ### Testing a Strategy
124
+ ```python
125
+ # Pattern used throughout tests
126
+ bankroll = BankRoll(initial_funds=1000, percent_bettable=0.5, max_draw_down=0.3)
127
+ strategy = KellyCriterion(payoff=1.0, loss=1.0, transaction_cost=0.01)
128
+ simulator = RepeatedBinarySimulator(
129
+ payoff=1.0, loss=1.0, transaction_costs=0.01,
130
+ probability=0.55, trials=1000
131
+ )
132
+ simulator.evaluate_strategy(strategy, bankroll)
133
+ ```
134
+
135
+ ### Strategy Implementation
136
+ ```python
137
+ class CustomStrategy(BaseStrategy):
138
+ def evaluate(self, probability: float, current_bankroll: float) -> float:
139
+ # Calculate bet fraction (0.0 to 1.0)
140
+ fraction = ... # your logic
141
+ # Always respect safety limits
142
+ return min(fraction, self.get_max_safe_bet(current_bankroll))
143
+ ```
144
+
145
+ ### Using MertonShare Strategy
146
+ ```python
147
+ # MertonShare uses CRRA utility (Constant Relative Risk Aversion)
148
+ # Formula: f* = μ / (γ × σ²) where:
149
+ # μ = expected return
150
+ # γ = risk aversion coefficient (1=aggressive, 2=moderate, 5=conservative)
151
+ # σ² = variance of returns
152
+
153
+ from keeks.binary_strategies.simple import MertonShare
154
+
155
+ # Moderate risk aversion (γ=2.0 is empirically common)
156
+ strategy = MertonShare(
157
+ payoff=1.0,
158
+ loss=1.0,
159
+ transaction_cost=0.01,
160
+ risk_aversion=2.0, # Higher values = more conservative
161
+ min_probability=0.5,
162
+ max_fraction=1.0
163
+ )
164
+
165
+ # MertonShare is typically more conservative than Kelly Criterion
166
+ # Useful for risk-averse investors or when parameter uncertainty is high
167
+ ```
168
+
169
+ ### Using Utility Functions for One-Time Decisions
170
+ ```python
171
+ # For one-time gamble decisions (not repeated betting)
172
+ from keeks.utils import find_indifference_price
173
+
174
+ # St. Petersburg paradox example: what would you pay to play?
175
+ # Generate outcomes and probabilities for St. Petersburg game
176
+ outcomes = [2**n for n in range(1, 31)] # Payouts: $2, $4, $8, ..., $2^30
177
+ probabilities = [(0.5)**n for n in range(1, 31)] # Probs: 0.5, 0.25, 0.125, ...
178
+
179
+ max_price = find_indifference_price(
180
+ outcomes=outcomes,
181
+ probabilities=probabilities,
182
+ current_wealth=10000,
183
+ risk_aversion=2.0, # Moderate risk aversion
184
+ tolerance=0.01
185
+ )
186
+
187
+ print(f"Maximum willing to pay: ${max_price:.2f}")
188
+ # Output: Maximum willing to pay: $12.80
189
+
190
+ # For comparison, Kelly bettor (γ=1.0) would pay ~$14.24
191
+ # Conservative bettor (γ=5.0) would pay ~$11.26
192
+ ```
193
+
194
+ ## Project-Specific Notes
195
+
196
+ - **Version**: Managed in `pyproject.toml` (currently 0.3.0)
197
+ - **Python Support**: 3.8, 3.9, 3.10, 3.11
198
+ - **Documentation site**: [keeks.mcginniscommawill.com](https://keeks.mcginniscommawill.com)
199
+ - **Educational focus**: Library includes disclaimer about educational use only
200
+ - **Examples**:
201
+ - `strategy_comparison.py`: Compares all strategies with favorable odds
202
+ - `st_petersburg_paradox.py`: True St. Petersburg paradox - shows rational payment amounts
@@ -91,6 +91,6 @@ test-all:
91
91
 
92
92
  # Run example scripts
93
93
  examples:
94
- uv run python examples/st_petersburg_comparison.py
94
+ uv run python examples/strategy_comparison.py
95
95
 
96
96
  all: clean test docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keeks
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A python library for bankroll allocation strategies
5
5
  Project-URL: Homepage, https://github.com/wdm0006/keeks
6
6
  Project-URL: Bug Tracker, https://github.com/wdm0006/keeks/issues
@@ -168,11 +168,40 @@ Keeks implements several betting strategies:
168
168
  dynamic = DynamicBankrollManagement(base_fraction=0.1, payoff=1.0, loss=1.0, window_size=10)
169
169
  ```
170
170
 
171
- 8. **Naive Strategy**: A simple strategy that bets the full amount when expected value is positive
171
+ 8. **Merton Share (CRRA Utility)**: Based on Merton's portfolio problem with constant relative risk aversion
172
+ ```python
173
+ from keeks.binary_strategies.simple import MertonShare
174
+ merton = MertonShare(payoff=1.0, loss=1.0, transaction_cost=0.01, risk_aversion=2.0)
175
+ ```
176
+
177
+ 9. **Naive Strategy**: A simple strategy that bets the full amount when expected value is positive
172
178
  ```python
173
179
  naive = NaiveStrategy(payoff=1.0, loss=1.0, transaction_cost=0.01)
174
180
  ```
175
181
 
182
+ ### Utility Functions
183
+
184
+ For one-time decision problems (e.g., "What should I pay for this opportunity?"), keeks provides CRRA utility functions:
185
+
186
+ ```python
187
+ from keeks.utils import find_indifference_price
188
+
189
+ # Calculate maximum price you'd pay for a gamble
190
+ # Example: St. Petersburg paradox
191
+ outcomes = [2**n for n in range(1, 31)]
192
+ probabilities = [(0.5)**n for n in range(1, 31)]
193
+
194
+ max_price = find_indifference_price(
195
+ outcomes=outcomes,
196
+ probabilities=probabilities,
197
+ current_wealth=10000,
198
+ risk_aversion=2.0 # 1.0=Kelly, 2.0=moderate, 5.0=conservative
199
+ )
200
+ # Returns: ~$12.80 despite infinite expected value!
201
+ ```
202
+
203
+ See [examples/st_petersburg_paradox.py](examples/st_petersburg_paradox.py) for a complete demonstration.
204
+
176
205
  ### Simulators
177
206
 
178
207
  Test your strategies with different simulators:
@@ -133,11 +133,40 @@ Keeks implements several betting strategies:
133
133
  dynamic = DynamicBankrollManagement(base_fraction=0.1, payoff=1.0, loss=1.0, window_size=10)
134
134
  ```
135
135
 
136
- 8. **Naive Strategy**: A simple strategy that bets the full amount when expected value is positive
136
+ 8. **Merton Share (CRRA Utility)**: Based on Merton's portfolio problem with constant relative risk aversion
137
+ ```python
138
+ from keeks.binary_strategies.simple import MertonShare
139
+ merton = MertonShare(payoff=1.0, loss=1.0, transaction_cost=0.01, risk_aversion=2.0)
140
+ ```
141
+
142
+ 9. **Naive Strategy**: A simple strategy that bets the full amount when expected value is positive
137
143
  ```python
138
144
  naive = NaiveStrategy(payoff=1.0, loss=1.0, transaction_cost=0.01)
139
145
  ```
140
146
 
147
+ ### Utility Functions
148
+
149
+ For one-time decision problems (e.g., "What should I pay for this opportunity?"), keeks provides CRRA utility functions:
150
+
151
+ ```python
152
+ from keeks.utils import find_indifference_price
153
+
154
+ # Calculate maximum price you'd pay for a gamble
155
+ # Example: St. Petersburg paradox
156
+ outcomes = [2**n for n in range(1, 31)]
157
+ probabilities = [(0.5)**n for n in range(1, 31)]
158
+
159
+ max_price = find_indifference_price(
160
+ outcomes=outcomes,
161
+ probabilities=probabilities,
162
+ current_wealth=10000,
163
+ risk_aversion=2.0 # 1.0=Kelly, 2.0=moderate, 5.0=conservative
164
+ )
165
+ # Returns: ~$12.80 despite infinite expected value!
166
+ ```
167
+
168
+ See [examples/st_petersburg_paradox.py](examples/st_petersburg_paradox.py) for a complete demonstration.
169
+
141
170
  ### Simulators
142
171
 
143
172
  Test your strategies with different simulators:
@@ -75,3 +75,11 @@ OptimalF
75
75
  :members:
76
76
  :undoc-members:
77
77
  :show-inheritance:
78
+
79
+ Merton Share
80
+ ------------
81
+
82
+ .. autoclass:: keeks.binary_strategies.simple.MertonShare
83
+ :members:
84
+ :undoc-members:
85
+ :show-inheritance:
@@ -0,0 +1,5 @@
1
+ Bankroll,Kelly ($),Kelly (%),Half Kelly ($),Half Kelly (%),Drawdown Kelly ($),Drawdown Kelly (%),Optimal F ($),Optimal F (%),Merton (γ=2.0) ($),Merton (γ=2.0) (%),Merton (γ=5.0) ($),Merton (γ=5.0) (%),Naive ($),Naive (%),Fixed 5% ($),Fixed 5% (%),Dynamic 10% ($),Dynamic 10% (%)
2
+ $1,$0.50,49.609%,$0.25,24.805%,$0.20,19.844%,$0.50,49.609%,$0.50,49.609%,$0.50,49.609%,$1.00,100.000%,$0.05,5.000%,$0.10,10.000%
3
+ $10,$4.97,49.658%,$2.48,24.829%,$1.99,19.863%,$4.97,49.658%,$3.96,39.600%,$3.10,31.006%,$10.00,100.000%,$0.50,5.000%,$1.00,10.000%
4
+ $100,$7.79,7.791%,$3.90,3.896%,$3.12,3.116%,$7.79,7.791%,$6.49,6.491%,$5.20,5.197%,$100.00,100.000%,$5.00,5.000%,$10.00,10.000%
5
+ "$1,000",$10.95,1.095%,$5.48,0.548%,$4.38,0.438%,$10.95,1.095%,$9.55,0.955%,$8.06,0.806%,$1000.00,100.000%,$50.00,5.000%,$100.00,10.000%
@@ -0,0 +1,14 @@
1
+ Strategy,Mean,Median,Min,Max,Std Dev,Ruin Rate
2
+ Merton (γ=1.0),$1301.11,$1255.87,$623.56,$3112.29,$379.23,0.0%
3
+ Kelly Criterion,$1316.52,$1250.61,$539.09,$3052.69,$397.43,0.0%
4
+ Naive Strategy,$1272.19,$1215.58,$566.97,$2641.01,$351.19,0.0%
5
+ Optimal F,$1279.55,$1215.03,$393.22,$2839.17,$415.17,0.0%
6
+ Merton (γ=2.0),$1102.08,$1095.51,$757.45,$1739.15,$154.93,0.0%
7
+ Half Kelly,$1102.39,$1080.04,$721.58,$1767.90,$165.63,0.0%
8
+ Drawdown Kelly,$1065.94,$1064.01,$722.03,$1578.80,$126.19,0.0%
9
+ Quarter Kelly,$1022.51,$1015.90,$787.98,$1276.53,$78.62,0.0%
10
+ Merton (γ=5.0),$1012.83,$1010.68,$869.55,$1243.26,$60.75,0.0%
11
+ CPPI,$961.07,$960.53,$928.36,$996.13,$12.10,0.0%
12
+ Fixed 5%,$13028.93,$72.95,$0.00,$1087677.26,$80321.85,33.0%
13
+ Dynamic,$1875.02,$13.62,$0.00,$145023.91,$9762.06,41.2%
14
+ Fixed 10%,$98.25,$0.00,$0.00,$48451.32,$2164.67,98.8%
@@ -0,0 +1,244 @@
1
+ """
2
+ True St. Petersburg Paradox: What Would You Pay to Play?
3
+
4
+ The St. Petersburg paradox is a probability theory paradox:
5
+ - Flip a fair coin repeatedly until it comes up tails
6
+ - Payout is $2^n where n is the number of flips
7
+ - Expected value is infinite: E[X] = Σ(1/2^n × 2^n) = Σ(1) = ∞
8
+
9
+ The paradox: Despite infinite expected value, no rational person would pay much to play.
10
+
11
+ This example calculates the maximum entry price different betting strategies would pay
12
+ to play one round of the St. Petersburg game at various wealth levels.
13
+ """
14
+
15
+ import os
16
+
17
+ import matplotlib
18
+ matplotlib.use("Agg") # Use non-interactive backend
19
+
20
+ import matplotlib.pyplot as plt
21
+ import numpy as np
22
+ import pandas as pd
23
+
24
+ from keeks.binary_strategies import KellyCriterion
25
+ from keeks.binary_strategies.kelly import DrawdownAdjustedKelly, FractionalKellyCriterion
26
+ from keeks.binary_strategies.simple import (
27
+ CPPIStrategy,
28
+ DynamicBankrollManagement,
29
+ FixedFractionStrategy,
30
+ MertonShare,
31
+ NaiveStrategy,
32
+ OptimalF,
33
+ )
34
+
35
+
36
+ def get_st_petersburg_outcomes(max_flips=30):
37
+ """
38
+ Generate outcomes and probabilities for St. Petersburg game.
39
+
40
+ Parameters
41
+ ----------
42
+ max_flips : int
43
+ Maximum coin flips to consider (practical cap for infinite series)
44
+ Note: Limited to ~1000 due to float64 overflow at 2^1024
45
+
46
+ Returns
47
+ -------
48
+ tuple
49
+ (outcomes, probabilities) arrays
50
+ """
51
+ import numpy as np
52
+
53
+ outcomes = []
54
+ probabilities = []
55
+
56
+ for n in range(1, max_flips + 1):
57
+ prob = (0.5) ** n # Probability of exactly n flips
58
+ payout = 2.0 ** n # Payout is 2^n (use 2.0 to keep as float)
59
+ outcomes.append(payout)
60
+ probabilities.append(prob)
61
+
62
+ return np.array(outcomes, dtype=float), np.array(probabilities, dtype=float)
63
+
64
+
65
+ def main():
66
+ """Calculate maximum payment for St. Petersburg game across different scenarios."""
67
+
68
+ print("=" * 80)
69
+ print("ST. PETERSBURG PARADOX: WHAT WOULD YOU PAY TO PLAY?")
70
+ print("=" * 80)
71
+ print()
72
+ print("The Game:")
73
+ print(" - Flip a fair coin repeatedly until tails")
74
+ print(" - Payout is $2^n where n is the number of flips")
75
+ print(" - Expected value is INFINITE")
76
+ print()
77
+ print("The Question:")
78
+ print(" Given your wealth, what's the maximum you'd rationally pay to play once?")
79
+ print()
80
+ print("=" * 80)
81
+ print()
82
+
83
+ # Different bankroll levels to test
84
+ bankrolls = [1, 10, 100, 1_000]
85
+
86
+ # Create strategy instances - all 9 strategies!
87
+ strategies = {
88
+ # Utility-based strategies
89
+ "Kelly": KellyCriterion(
90
+ payoff=1.0, loss=1.0, transaction_cost=0.0
91
+ ),
92
+ "Half Kelly": FractionalKellyCriterion(
93
+ payoff=1.0, loss=1.0, transaction_cost=0.0, fraction=0.5
94
+ ),
95
+ "Drawdown Kelly": DrawdownAdjustedKelly(
96
+ payoff=1.0, loss=1.0, transaction_cost=0.0, max_acceptable_drawdown=0.2
97
+ ),
98
+ "Optimal F": OptimalF(
99
+ payoff=1.0, loss=1.0, transaction_cost=0.0, win_rate=0.55
100
+ ),
101
+ "Merton (γ=2.0)": MertonShare(
102
+ payoff=1.0, loss=1.0, transaction_cost=0.0, risk_aversion=2.0
103
+ ),
104
+ "Merton (γ=5.0)": MertonShare(
105
+ payoff=1.0, loss=1.0, transaction_cost=0.0, risk_aversion=5.0
106
+ ),
107
+ # Rule-based strategies
108
+ "Naive": NaiveStrategy(
109
+ payoff=1.0, loss=1.0, transaction_cost=0.0
110
+ ),
111
+ "Fixed 5%": FixedFractionStrategy(
112
+ payoff=1.0, loss=1.0, transaction_cost=0.0, fraction=0.05
113
+ ),
114
+ "Dynamic 10%": DynamicBankrollManagement(
115
+ payoff=1.0, loss=1.0, transaction_cost=0.0, base_fraction=0.1, window_size=10
116
+ ),
117
+ }
118
+
119
+ # Get St. Petersburg outcomes
120
+ # Using 1,000 flips gives EV = $1,000, approaching the infinite EV of the true paradox
121
+ # Each flip contributes exactly $1 to expected value: (1/2^n) × 2^n = 1
122
+ # True paradox has infinite flips → infinite EV, but we cap at 1000 due to float64 limits
123
+ outcomes, probabilities = get_st_petersburg_outcomes(max_flips=1_000)
124
+
125
+ print("Calculating optimal entry prices for each strategy...")
126
+ print()
127
+
128
+ # Calculate results
129
+ all_results = []
130
+
131
+ for bankroll in bankrolls:
132
+ row = {"Bankroll": f"${bankroll:,}"}
133
+
134
+ for strategy_name, strategy in strategies.items():
135
+ # Use the strategy's calculate_max_entry_price method
136
+ max_price = strategy.calculate_max_entry_price(
137
+ outcomes=outcomes,
138
+ probabilities=probabilities,
139
+ current_wealth=bankroll
140
+ )
141
+ row[f"{strategy_name}"] = max_price
142
+ row[f"{strategy_name} (%BR)"] = (max_price / bankroll) * 100
143
+
144
+ all_results.append(row)
145
+
146
+ # Create DataFrame
147
+ df_results = pd.DataFrame(all_results)
148
+
149
+ # Display price table
150
+ print("\nMAXIMUM ENTRY PRICE BY STRATEGY AND WEALTH LEVEL:")
151
+ print("=" * 80)
152
+ print()
153
+
154
+ # Create price-only table for display
155
+ price_cols = ["Bankroll"] + list(strategies.keys())
156
+ df_prices = df_results[price_cols].copy()
157
+
158
+ # Format prices as currency
159
+ for col in strategies.keys():
160
+ df_prices[col] = df_results[col].apply(lambda x: f"${x:.2f}")
161
+
162
+ print(df_prices.to_string(index=False))
163
+
164
+ # Create visualization
165
+ print("Creating visualization...")
166
+
167
+ # Single chart: Entry price by strategy by bankroll
168
+ fig, ax = plt.subplots(figsize=(16, 10))
169
+
170
+ # Prepare data for plotting
171
+ bankroll_labels = [f"${b:,}" for b in bankrolls]
172
+ x = np.arange(len(bankroll_labels))
173
+ n_strategies = len(strategies)
174
+ width = 0.08 # Width of bars
175
+
176
+ # Define colors for different strategy types
177
+ colors = {
178
+ 'Kelly': '#1f77b4',
179
+ 'Half Kelly': '#aec7e8',
180
+ 'Drawdown Kelly': '#7fbfe8',
181
+ 'Optimal F': '#4a90c4',
182
+ 'Merton (γ=2.0)': '#ff7f0e',
183
+ 'Merton (γ=5.0)': '#ffbb78',
184
+ 'Naive': '#2ca02c',
185
+ 'Fixed 5%': '#d62728',
186
+ 'Dynamic 10%': '#9467bd',
187
+ }
188
+
189
+ # Plot bars for each strategy
190
+ for i, strategy_name in enumerate(strategies.keys()):
191
+ prices = [all_results[j][strategy_name] for j in range(len(bankrolls))]
192
+ offset = (i - n_strategies / 2 + 0.5) * width
193
+ ax.bar(x + offset, prices, width, label=strategy_name,
194
+ color=colors.get(strategy_name, f'C{i}'))
195
+
196
+ ax.set_xlabel('Bankroll Level', fontsize=12, fontweight='bold')
197
+ ax.set_ylabel('Maximum Entry Price ($, log scale)', fontsize=12, fontweight='bold')
198
+ ax.set_title('St. Petersburg Paradox: Maximum Entry Price by Strategy\n' +
199
+ '(Despite Infinite Expected Value, Agents Pay Finite Amounts)',
200
+ fontsize=14, fontweight='bold', pad=20)
201
+ ax.set_xticks(x)
202
+ ax.set_xticklabels(bankroll_labels, rotation=0)
203
+ ax.set_yscale('log')
204
+ ax.legend(loc='upper left', fontsize=9, ncol=2)
205
+ ax.grid(axis='y', alpha=0.3, linestyle='--', which='both')
206
+
207
+ # Add annotation
208
+ ax.text(0.98, 0.02,
209
+ 'Utility-based strategies (Kelly, Merton, Optimal F) analyze risk\n' +
210
+ 'Rule-based strategies (Fixed, Dynamic) apply mechanical rules\n' +
211
+ 'Naive pays expected value ($1,000 for 1k flips), capped at wealth',
212
+ transform=ax.transAxes, fontsize=9,
213
+ verticalalignment='bottom', horizontalalignment='right',
214
+ bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.3))
215
+
216
+ plt.tight_layout()
217
+
218
+ # Create output directory
219
+ output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output")
220
+ os.makedirs(output_dir, exist_ok=True)
221
+
222
+ # Save figure
223
+ fig_path = os.path.join(output_dir, "st_petersburg_paradox.png")
224
+ plt.savefig(fig_path, dpi=300, bbox_inches='tight')
225
+ print(f"\n✓ Chart saved to: {fig_path}")
226
+
227
+ # Save CSV with better formatting
228
+ csv_data = []
229
+ for result in all_results:
230
+ row = {"Bankroll": result["Bankroll"]}
231
+ for strategy_name in strategies.keys():
232
+ row[f"{strategy_name} ($)"] = f"${result[strategy_name]:.2f}"
233
+ row[f"{strategy_name} (%)"] = f"{result[f'{strategy_name} (%BR)']:.3f}%"
234
+ csv_data.append(row)
235
+
236
+ df_csv = pd.DataFrame(csv_data)
237
+ csv_path = os.path.join(output_dir, "st_petersburg_paradox.csv")
238
+ df_csv.to_csv(csv_path, index=False)
239
+ print(f"✓ Data saved to: {csv_path}")
240
+
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()