nba-edge 0.1.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.
@@ -0,0 +1,39 @@
1
+ name: Bug report
2
+ description: Create a report to help us improve.
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for taking the time to fill out this bug report!
9
+ - type: textarea
10
+ id: description
11
+ attributes:
12
+ label: Bug Description
13
+ description: A clear and concise description of what the bug is.
14
+ validations:
15
+ required: true
16
+ - type: textarea
17
+ id: reproduction
18
+ attributes:
19
+ label: Steps to Reproduce
20
+ description: Steps to reproduce the behavior.
21
+ placeholder: |
22
+ 1. Go to '...'
23
+ 2. Call function '...'
24
+ 3. See error
25
+ validations:
26
+ required: true
27
+ - type: textarea
28
+ id: expected
29
+ attributes:
30
+ label: Expected Behavior
31
+ description: A clear and concise description of what you expected to happen.
32
+ validations:
33
+ required: true
34
+ - type: textarea
35
+ id: logs
36
+ attributes:
37
+ label: Relevant Log Output
38
+ description: Please copy and paste any relevant log output or stack traces here.
39
+ render: shell
@@ -0,0 +1,28 @@
1
+ name: Feature request
2
+ description: Suggest an idea for this project.
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: Is your feature request related to a problem?
9
+ description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: solution
14
+ attributes:
15
+ label: Describe the solution you'd like
16
+ description: A clear and concise description of what you want to happen.
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: alternatives
21
+ attributes:
22
+ label: Describe alternatives you've considered
23
+ description: A clear and concise description of any alternative solutions or features you've considered.
24
+ - type: textarea
25
+ id: additional
26
+ attributes:
27
+ label: Additional context
28
+ description: Add any other context or screenshots about the feature request here.
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ - package-ecosystem: github-actions
8
+ directory: /
9
+ schedule:
10
+ interval: weekly
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - run: pip install -e ".[dev]"
21
+ - run: ruff check nba_edge tests
22
+ - run: pytest -q
@@ -0,0 +1,17 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ dist/
6
+ *.egg-info/
7
+
8
+ # Local secrets and machine-specific config
9
+ .env
10
+ .env.local
11
+ .envrc
12
+ .env.*
13
+ !.env.example
14
+ !.env.local.example
15
+ !env.example
16
+ *.pem
17
+ *.key
@@ -0,0 +1,41 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best for the overall community, not just the individual
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and unwelcome sexual attention or advances
22
+ * Trolling, insulting or derogatory comments, and personal or political attacks
23
+ * Public or private harassment
24
+ * Publishing others' private information, such as a physical or email address, without their explicit permission
25
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, questions, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32
+
33
+ ## Scope
34
+
35
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36
+
37
+ ## Enforcement
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders at **ian@allowayllc.com**. All complaints will be reviewed and investigated promptly and fairly.
40
+
41
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
@@ -0,0 +1,30 @@
1
+ # Contributing to nba-ratings
2
+
3
+ First off, thank you for considering contributing to `nba-ratings`!
4
+
5
+ ## Code of Conduct
6
+
7
+ By participating in this project, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md).
8
+
9
+ ## How Can I Contribute?
10
+
11
+ ### Reporting Bugs
12
+
13
+ If you find a bug, please create a new issue using our Bug Report template. Please include:
14
+ * Your Python version
15
+ * A minimal reproducible example
16
+ * The expected vs actual behavior
17
+
18
+ ### Suggesting Enhancements
19
+
20
+ We welcome new feature suggestions! Please open an issue using the Feature Request template to discuss the change before implementing it.
21
+
22
+ ### Pull Requests
23
+
24
+ To submit a contribution:
25
+ 1. Fork the repository
26
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
27
+ 3. Install development dependencies (`pip install -e ".[dev]"`)
28
+ 4. Make your changes and ensure tests pass (`pytest`)
29
+ 5. Commit your changes with descriptive messages
30
+ 6. Push to your branch and open a Pull Request!
nba_edge-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ian Alloway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: nba-edge
3
+ Version: 0.1.0
4
+ Summary: Power ratings, win probability, and Kelly sizing primitives for NBA-style moneylines
5
+ Project-URL: Homepage, https://github.com/ianalloway/nba-ratings
6
+ Author: Ian Alloway
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: kelly-criterion,nba,ratings,sports-analytics
10
+ Requires-Python: >=3.10
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == 'dev'
13
+ Requires-Dist: ruff>=0.3; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # nba-edge
17
+
18
+ > Repo: `nba-ratings` ยท Package: `nba-edge`
19
+
20
+ [![CI](https://github.com/ianalloway/nba-ratings/actions/workflows/ci.yml/badge.svg)](https://github.com/ianalloway/nba-ratings/actions/workflows/ci.yml)
21
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
22
+
23
+ Reusable Elo, win-probability, and Kelly-sizing primitives for NBA-style models.
24
+
25
+ ## Why This Repo Matters
26
+
27
+ This is the library layer of the sports ML stack:
28
+
29
+ - reusable rating logic instead of notebook snippets
30
+ - portable win-probability helpers for downstream services
31
+ - Kelly and implied-probability helpers for decision support
32
+ - small, dependency-light package design
33
+
34
+ Pairs well with [`nba-clv-dashboard`](https://github.com/ianalloway/nba-clv-dashboard) for evaluation UI. Employer one-pager: [case study](https://ianalloway.xyz/papers/sports-ml-evaluation-case-study.html).
35
+
36
+ ## What It Includes
37
+
38
+ - Elo updates, including a margin-of-victory-weighted variant
39
+ - Logistic win probability
40
+ - Kelly fraction sizing & multi-leg parlay sizing
41
+ - Bidirectional odds format conversions (American, Decimal, Implied Probability)
42
+ - Bookmaker vig-removal tools (Proportional and Equal Margin methods)
43
+ - Model evaluation metrics (Brier Score, Log Loss)
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install -e . # local; not yet on PyPI
49
+ ```
50
+
51
+ The library has zero runtime dependencies (see `pyproject.toml`). The
52
+ root-level `requirements.txt` is unrelated to the library โ€” it only exists so
53
+ Streamlit Community Cloud can find the demo app's dependencies
54
+ (`demo/requirements.txt`, i.e. `streamlit` + `pandas`).
55
+
56
+ ## Example
57
+
58
+ ```python
59
+ from nba_edge import (
60
+ logistic_win_prob,
61
+ update_elo,
62
+ kelly_fraction,
63
+ american_to_decimal,
64
+ american_to_implied_prob,
65
+ remove_vig,
66
+ parlay_odds,
67
+ kelly_parlay,
68
+ brier_score,
69
+ log_loss,
70
+ )
71
+
72
+ # 1. Ratings & win probability
73
+ p = logistic_win_prob(rating_diff=120)
74
+ new_h, new_a = update_elo(1600, 1580, 1.0)
75
+
76
+ # 2. Odds conversions & Vig Removal
77
+ dec = american_to_decimal(-110) # Convert American to Decimal
78
+ p_implied = american_to_implied_prob(-110) # Convert American to Implied Probability
79
+ p_fair_h, p_fair_a = remove_vig(-110, -110, method="proportional") # Remove vig
80
+
81
+ # 3. Bet sizing & Parlays
82
+ stake = kelly_fraction(p, -110, fraction=0.25)
83
+ # Compute combined parlay odds/joint probability for independent legs
84
+ parlay = parlay_odds([-110, +130])
85
+ # Parlay Kelly sizing (fraction = 0.25 for quarter-Kelly)
86
+ parlay_stake = kelly_parlay([0.60, 0.55], [-110, +130], fraction=0.25)
87
+
88
+ # 4. Model evaluation
89
+ predictions = [0.75, 0.40, 0.65]
90
+ outcomes = [1.0, 0.0, 1.0]
91
+ bs = brier_score(predictions, outcomes)
92
+ ll = log_loss(predictions, outcomes)
93
+ ```
94
+
95
+ ### Margin-of-victory Elo
96
+
97
+ Plain Elo treats every win the same, but a 30-point blowout is a stronger
98
+ signal than a 2-point nail-biter. `update_elo_with_margin` applies a
99
+ FiveThirtyEight-style multiplier so ratings move further on lopsided games
100
+ and less when a team that was already heavily favored piles on:
101
+
102
+ ```python
103
+ from nba_edge import update_elo_with_margin
104
+
105
+ new_h, new_a = update_elo_with_margin(1600, 1580, 1.0, margin=22)
106
+ ```
107
+
108
+ ## Publish
109
+
110
+ ```bash
111
+ pip install build twine
112
+ python -m build
113
+ twine upload dist/*
114
+ ```
115
+
116
+ ## Non-goals
117
+
118
+ - No bundled NBA database or scrapers
119
+ - Not a tipster product
120
+ - Not a full modeling workflow by itself
121
+
122
+ ## CI
123
+
124
+ `pytest` + `ruff` on Python 3.10-3.12.
125
+
126
+ ## Related Repos
127
+
128
+ - [`sports-betting-ml`](https://github.com/ianalloway/sports-betting-ml): applied modeling demo
129
+ - [`nba-clv-dashboard`](https://github.com/ianalloway/nba-clv-dashboard): evaluation dashboard
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,118 @@
1
+ # nba-edge
2
+
3
+ > Repo: `nba-ratings` ยท Package: `nba-edge`
4
+
5
+ [![CI](https://github.com/ianalloway/nba-ratings/actions/workflows/ci.yml/badge.svg)](https://github.com/ianalloway/nba-ratings/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ Reusable Elo, win-probability, and Kelly-sizing primitives for NBA-style models.
9
+
10
+ ## Why This Repo Matters
11
+
12
+ This is the library layer of the sports ML stack:
13
+
14
+ - reusable rating logic instead of notebook snippets
15
+ - portable win-probability helpers for downstream services
16
+ - Kelly and implied-probability helpers for decision support
17
+ - small, dependency-light package design
18
+
19
+ Pairs well with [`nba-clv-dashboard`](https://github.com/ianalloway/nba-clv-dashboard) for evaluation UI. Employer one-pager: [case study](https://ianalloway.xyz/papers/sports-ml-evaluation-case-study.html).
20
+
21
+ ## What It Includes
22
+
23
+ - Elo updates, including a margin-of-victory-weighted variant
24
+ - Logistic win probability
25
+ - Kelly fraction sizing & multi-leg parlay sizing
26
+ - Bidirectional odds format conversions (American, Decimal, Implied Probability)
27
+ - Bookmaker vig-removal tools (Proportional and Equal Margin methods)
28
+ - Model evaluation metrics (Brier Score, Log Loss)
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install -e . # local; not yet on PyPI
34
+ ```
35
+
36
+ The library has zero runtime dependencies (see `pyproject.toml`). The
37
+ root-level `requirements.txt` is unrelated to the library โ€” it only exists so
38
+ Streamlit Community Cloud can find the demo app's dependencies
39
+ (`demo/requirements.txt`, i.e. `streamlit` + `pandas`).
40
+
41
+ ## Example
42
+
43
+ ```python
44
+ from nba_edge import (
45
+ logistic_win_prob,
46
+ update_elo,
47
+ kelly_fraction,
48
+ american_to_decimal,
49
+ american_to_implied_prob,
50
+ remove_vig,
51
+ parlay_odds,
52
+ kelly_parlay,
53
+ brier_score,
54
+ log_loss,
55
+ )
56
+
57
+ # 1. Ratings & win probability
58
+ p = logistic_win_prob(rating_diff=120)
59
+ new_h, new_a = update_elo(1600, 1580, 1.0)
60
+
61
+ # 2. Odds conversions & Vig Removal
62
+ dec = american_to_decimal(-110) # Convert American to Decimal
63
+ p_implied = american_to_implied_prob(-110) # Convert American to Implied Probability
64
+ p_fair_h, p_fair_a = remove_vig(-110, -110, method="proportional") # Remove vig
65
+
66
+ # 3. Bet sizing & Parlays
67
+ stake = kelly_fraction(p, -110, fraction=0.25)
68
+ # Compute combined parlay odds/joint probability for independent legs
69
+ parlay = parlay_odds([-110, +130])
70
+ # Parlay Kelly sizing (fraction = 0.25 for quarter-Kelly)
71
+ parlay_stake = kelly_parlay([0.60, 0.55], [-110, +130], fraction=0.25)
72
+
73
+ # 4. Model evaluation
74
+ predictions = [0.75, 0.40, 0.65]
75
+ outcomes = [1.0, 0.0, 1.0]
76
+ bs = brier_score(predictions, outcomes)
77
+ ll = log_loss(predictions, outcomes)
78
+ ```
79
+
80
+ ### Margin-of-victory Elo
81
+
82
+ Plain Elo treats every win the same, but a 30-point blowout is a stronger
83
+ signal than a 2-point nail-biter. `update_elo_with_margin` applies a
84
+ FiveThirtyEight-style multiplier so ratings move further on lopsided games
85
+ and less when a team that was already heavily favored piles on:
86
+
87
+ ```python
88
+ from nba_edge import update_elo_with_margin
89
+
90
+ new_h, new_a = update_elo_with_margin(1600, 1580, 1.0, margin=22)
91
+ ```
92
+
93
+ ## Publish
94
+
95
+ ```bash
96
+ pip install build twine
97
+ python -m build
98
+ twine upload dist/*
99
+ ```
100
+
101
+ ## Non-goals
102
+
103
+ - No bundled NBA database or scrapers
104
+ - Not a tipster product
105
+ - Not a full modeling workflow by itself
106
+
107
+ ## CI
108
+
109
+ `pytest` + `ruff` on Python 3.10-3.12.
110
+
111
+ ## Related Repos
112
+
113
+ - [`sports-betting-ml`](https://github.com/ianalloway/sports-betting-ml): applied modeling demo
114
+ - [`nba-clv-dashboard`](https://github.com/ianalloway/nba-clv-dashboard): evaluation dashboard
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,16 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Do **not** open a public issue for security vulnerabilities.
6
+
7
+ Email **ian@allowayllc.com** with:
8
+ - Description and reproduction steps
9
+ - Impact assessment
10
+ - Suggested fix (optional)
11
+
12
+ We aim to acknowledge reports within **48 hours**.
13
+
14
+ ## ## Supported versions
15
+
16
+ Only the default branch receives security fixes.
@@ -0,0 +1,2 @@
1
+ streamlit>=1.59.1
2
+ pandas>=2.3.3
@@ -0,0 +1,130 @@
1
+ """Interactive demo of the nba_edge package (Elo, win probability, Kelly).
2
+
3
+ Run locally: streamlit run demo/streamlit_app.py
4
+ Deployed on Streamlit Community Cloud; imports the real package from this repo.
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pandas as pd
11
+ import streamlit as st
12
+
13
+ # Import the actual package from the repo root (no pip install needed).
14
+ sys.path.insert(0, str(Path(__file__).parent.parent))
15
+
16
+ from nba_edge import (
17
+ american_to_implied_prob,
18
+ expected_margin,
19
+ kelly_fraction,
20
+ logistic_win_prob,
21
+ update_elo,
22
+ )
23
+
24
+ st.set_page_config(page_title="nba-edge demo", page_icon="๐Ÿ“ˆ", layout="wide")
25
+
26
+ st.title("๐Ÿ“ˆ nba-edge โ€” live demo")
27
+ st.caption(
28
+ "Every number on this page is computed by the real `nba_edge` package "
29
+ "([source](https://github.com/ianalloway/nba-ratings)) โ€” not a re-implementation."
30
+ )
31
+
32
+ tab_prob, tab_elo, tab_kelly, tab_season = st.tabs(
33
+ ["Win probability", "Elo update", "Kelly sizing", "Season simulation"]
34
+ )
35
+
36
+ # ---------------------------------------------------------------- win prob
37
+ with tab_prob:
38
+ st.subheader("Rating difference โ†’ win probability")
39
+ col1, col2 = st.columns([1, 2])
40
+ with col1:
41
+ diff = st.slider("Home Elo โˆ’ Away Elo", -400, 400, 80, step=10)
42
+ scale = st.select_slider("Logistic scale", [200, 300, 400, 500], value=400)
43
+ p = logistic_win_prob(diff, scale=scale)
44
+ st.metric("P(home wins)", f"{p:.1%}")
45
+ st.metric("Expected margin", f"{expected_margin(diff):+.1f} pts")
46
+ with col2:
47
+ xs = list(range(-400, 401, 10))
48
+ curve = pd.DataFrame(
49
+ {"rating diff": xs, "P(home wins)": [logistic_win_prob(x, scale=scale) for x in xs]}
50
+ ).set_index("rating diff")
51
+ st.line_chart(curve, height=320)
52
+ st.caption("`logistic_win_prob(diff, scale)` โ€” the classic Elo logistic curve.")
53
+
54
+ # ---------------------------------------------------------------- elo update
55
+ with tab_elo:
56
+ st.subheader("One-game Elo update")
57
+ c1, c2, c3 = st.columns(3)
58
+ ra = c1.number_input("Team A rating", 1000.0, 2000.0, 1550.0, step=10.0)
59
+ rb = c2.number_input("Team B rating", 1000.0, 2000.0, 1480.0, step=10.0)
60
+ k = c3.slider("K-factor", 5, 50, 20)
61
+ result = st.radio("Result", ["A wins", "Draw", "B wins"], horizontal=True)
62
+ score = {"A wins": 1, "Draw": 0.5, "B wins": 0}[result]
63
+ new_a, new_b = update_elo(ra, rb, score, k=k)
64
+ pre = logistic_win_prob(ra - rb)
65
+ c1.metric("New A rating", f"{new_a:.1f}", f"{new_a - ra:+.1f}")
66
+ c2.metric("New B rating", f"{new_b:.1f}", f"{new_b - rb:+.1f}")
67
+ c3.metric("Pre-game P(A wins)", f"{pre:.1%}")
68
+ st.caption(
69
+ "Upsets move ratings more: the update is K ร— (actual โˆ’ expected). "
70
+ "`update_elo(ra, rb, score_a, k=K)`"
71
+ )
72
+
73
+ # ---------------------------------------------------------------- kelly
74
+ with tab_kelly:
75
+ st.subheader("Edge โ†’ stake size (fractional Kelly)")
76
+ c1, c2 = st.columns(2)
77
+ with c1:
78
+ win_prob = st.slider("Your model's win probability", 0.30, 0.80, 0.58, 0.01)
79
+ odds = st.number_input("American odds offered", -500, 500, -110, step=5)
80
+ frac = st.select_slider("Kelly fraction", [0.1, 0.25, 0.5, 1.0], value=0.25)
81
+ bankroll = st.number_input("Bankroll ($)", 100, 100000, 1000, step=100)
82
+ with c2:
83
+ implied = american_to_implied_prob(odds)
84
+ stake = kelly_fraction(win_prob, odds, fraction=frac)
85
+ edge = win_prob - implied
86
+ st.metric("Market implied probability", f"{implied:.1%}")
87
+ st.metric("Your edge", f"{edge:+.1%}")
88
+ st.metric("Recommended stake", f"${bankroll * stake:,.2f} ({stake:.2%} of bankroll)")
89
+ if stake == 0:
90
+ st.info("No bet: the market price already exceeds your estimated probability.")
91
+ st.caption(
92
+ "`kelly_fraction` caps stakes at 25% of bankroll and returns 0 on negative edge โ€” "
93
+ "guardrails matter more than the formula."
94
+ )
95
+
96
+ # ---------------------------------------------------------------- season sim
97
+ with tab_season:
98
+ st.subheader("Elo convergence over a simulated season")
99
+ import random
100
+
101
+ n_games = st.slider("Games per team", 20, 200, 82, step=2)
102
+ seed = st.number_input("Random seed", 0, 9999, 42)
103
+ rng = random.Random(int(seed))
104
+
105
+ true_strength = {"Sharks": 1650, "Wolves": 1550, "Comets": 1450, "Drifters": 1350}
106
+ ratings = {t: 1500.0 for t in true_strength}
107
+ history = {t: [1500.0] for t in true_strength}
108
+ teams = list(true_strength)
109
+
110
+ for _ in range(n_games * len(teams) // 2):
111
+ a, b = rng.sample(teams, 2)
112
+ p_true = logistic_win_prob(true_strength[a] - true_strength[b])
113
+ score = 1 if rng.random() < p_true else 0
114
+ ratings[a], ratings[b] = update_elo(ratings[a], ratings[b], score)
115
+ for t in teams:
116
+ history[t].append(ratings[t])
117
+
118
+ chart = pd.DataFrame(history)
119
+ chart.index.name = "game"
120
+ st.line_chart(chart, height=360)
121
+ st.caption(
122
+ "Four teams start at 1500; games are simulated from hidden true strengths "
123
+ "(1650/1550/1450/1350). Watch `update_elo` recover the truth from results alone."
124
+ )
125
+
126
+ st.divider()
127
+ st.markdown(
128
+ "Built from [`ianalloway/nba-ratings`](https://github.com/ianalloway/nba-ratings) ยท "
129
+ "part of the demo suite at [ianalloway.xyz/demos](https://ianalloway.xyz/demos)"
130
+ )
@@ -0,0 +1,43 @@
1
+ """Installable primitives for NBA-style edge models."""
2
+
3
+ from nba_edge.kelly import (
4
+ american_to_decimal,
5
+ american_to_implied_prob,
6
+ decimal_to_american,
7
+ decimal_to_implied_prob,
8
+ implied_prob_to_american,
9
+ implied_prob_to_decimal,
10
+ kelly_fraction,
11
+ kelly_parlay,
12
+ parlay_odds,
13
+ remove_vig,
14
+ )
15
+ from nba_edge.metrics import brier_score, log_loss
16
+ from nba_edge.ratings import (
17
+ expected_margin,
18
+ logistic_win_prob,
19
+ mov_multiplier,
20
+ update_elo,
21
+ update_elo_with_margin,
22
+ )
23
+
24
+ __all__ = [
25
+ "american_to_decimal",
26
+ "american_to_implied_prob",
27
+ "decimal_to_american",
28
+ "decimal_to_implied_prob",
29
+ "implied_prob_to_american",
30
+ "implied_prob_to_decimal",
31
+ "kelly_fraction",
32
+ "kelly_parlay",
33
+ "parlay_odds",
34
+ "remove_vig",
35
+ "brier_score",
36
+ "log_loss",
37
+ "expected_margin",
38
+ "logistic_win_prob",
39
+ "mov_multiplier",
40
+ "update_elo",
41
+ "update_elo_with_margin",
42
+ ]
43
+ __version__ = "0.1.0"