-
Notifications
You must be signed in to change notification settings - Fork 0
PR #574: League constants 60/40 blend (2026/2025) — May 15 mid-season update #443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,14 +34,16 @@ | |
| # ── 2025 MLB league-average PA outcome rates ────────────────────────────── | ||
| # Source: FanGraphs 2025 season (confirmed via VSiN Feb 2026) | ||
| LEAGUE_RATES: dict[str, float] = { | ||
| "K": 0.228, # strikeout — FG 2026: 22.8% through game 44 | ||
| "BB": 0.083, # walk — FG 2026: 8.3% through game 44 | ||
| "HBP": 0.011, # hit by pitch | ||
| "HR": 0.033, # home run | ||
| "3B": 0.004, # triple | ||
| "2B": 0.047, # double | ||
| "1B": 0.143, # single | ||
| "OUT": 0.452, # field out (K+0.005 BB-0.004 net rounded) | ||
| # Blended 60/40 2026/2025 — May 15 2026 update | ||
| # 2026 actuals: BBRef totals through May 14 (49,542 PA, 30 teams) | ||
| "K": 0.224, # strikeout — blended (2026: 22.2%, 2025: 22.6%) | ||
| "BB": 0.091, # walk — blended (2026: 9.5% ABS effect, 2025: 8.5%) | ||
| "HBP": 0.011, # hit by pitch — unchanged | ||
| "HR": 0.030, # home run — blended (2026: 2.8%, 2025: 3.3%) | ||
| "3B": 0.004, # triple — unchanged | ||
| "2B": 0.044, # double — blended (2026: 4.1%, 2025: 4.7%) | ||
| "1B": 0.141, # single — blended (2026: 14.0%, 2025: 14.3%) | ||
| "OUT": 0.455, # field out — residual to sum=1.000 | ||
| } | ||
| assert abs(sum(LEAGUE_RATES.values()) - 1.0) < 0.01, "LEAGUE_RATES must sum to 1" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -207,15 +207,15 @@ def _get_fg_pitcher(name: str) -> dict: | |
| from fangraphs_layer import get_pitcher # noqa: PLC0415 | ||
| stats = get_pitcher(name) or {} | ||
| return { | ||
| "k_rate": stats.get("k_pct", stats.get("k_rate", 0.223)), | ||
| "bb_rate": stats.get("bb_pct", stats.get("bb_rate", 0.087)), | ||
| "k_rate": stats.get("k_pct", stats.get("k_rate", 0.224)), | ||
| "bb_rate": stats.get("bb_pct", stats.get("bb_rate", 0.091)), | ||
| "era": stats.get("xfip", stats.get("fip", 4.06)), | ||
| "whip": stats.get("whip", 1.28), | ||
| "csw_pct": stats.get("csw_pct", 0.275), | ||
| "swstr_pct": stats.get("swstr_pct", 0.110), | ||
| "xfip": stats.get("xfip", 4.06), | ||
| "siera": stats.get("siera", 4.06), | ||
| "k_bb_pct": stats.get("k_bb_pct", 0.139), | ||
| "k_bb_pct": stats.get("k_bb_pct", 0.133), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While |
||
| } | ||
| except Exception: | ||
| return {} | ||
|
|
@@ -236,14 +236,14 @@ def _get_fg_batter(name: str) -> dict: | |
| stats = get_batter(name) or {} | ||
| return { | ||
| "wrc_plus": stats.get("wrc_plus", 100.0), | ||
| "woba": stats.get("woba", 0.308), | ||
| "iso": stats.get("iso", 0.156), | ||
| "woba": stats.get("woba", 0.310), | ||
| "iso": stats.get("iso", 0.155), | ||
| "babip": stats.get("babip", 0.288), | ||
| "o_swing": stats.get("o_swing", 0.316), | ||
| "o_swing": stats.get("o_swing", 0.287), | ||
| "z_contact": stats.get("z_contact", 0.850), | ||
| "hr_fb_pct": stats.get("hr_fb_pct", 0.105), | ||
| "k_pct": stats.get("k_pct", 0.228), | ||
| "bb_pct": stats.get("bb_pct", 0.083), | ||
| "hr_fb_pct": stats.get("hr_fb_pct", 0.109), | ||
| "k_pct": stats.get("k_pct", 0.224), | ||
| "bb_pct": stats.get("bb_pct", 0.091), | ||
| } | ||
| except Exception: | ||
| return {} | ||
|
|
@@ -550,14 +550,14 @@ def _get_form_adj(player_name: str, prop_type: str, hub: dict) -> float: | |
|
|
||
| def _get_chase_score(opposing_team: str, hub: dict) -> dict: | ||
| default = {"k_prob_adjustment": 0.0, "lineup_difficulty": "NEUTRAL", | ||
| "avg_chase_rate": 0.316, "_opp_o_swing_avg": 0.316} | ||
| "avg_chase_rate": 0.287, "_opp_o_swing_avg": 0.287} | ||
| if not opposing_team: | ||
| return default | ||
| try: | ||
| from lineup_chase_layer import get_lineup_chase_score # noqa: PLC0415 | ||
| lineups = hub.get("context", {}).get("lineups", []) | ||
| result = get_lineup_chase_score(opposing_team, lineups) | ||
| result["_opp_o_swing_avg"] = result.get("avg_chase_rate", 0.316) | ||
| result["_opp_o_swing_avg"] = result.get("avg_chase_rate", 0.287) | ||
| return result | ||
| except Exception as _lcs_err: | ||
| logger.debug("[Enrich] lineup_chase_score failed: %s", _lcs_err) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -589,7 +589,7 @@ def _sigmoid(x: float) -> float: | |
| "S": 0.027, # switch: use favourable side | ||
| } | ||
| _STAT_DEFAULTS_PLATOON = { | ||
| "avg": 0.245, "obp": 0.315, "slg": 0.390, "ops": 0.705, "woba": 0.320 | ||
| "avg": 0.241, "obp": 0.317, "slg": 0.397, "ops": 0.714, "woba": 0.310 | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -3146,15 +3146,15 @@ def _clamp(v, lo=0.0, hi=1.0): | |
|
|
||
| # slot 2: SLG for TB/power props (16% feature importance) | ||
| if _is_tb_prop: | ||
| _slg = float(prop.get("slg", 0.410) or 0.410) | ||
| _slg = float(prop.get("slg", 0.397) or 0.397) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The update of the |
||
| era = _clamp((_slg - 0.250) / 0.400) # 0.250=0, 0.410=avg(0.40), 0.650=elite(1.0) | ||
| else: | ||
| era = _clamp((float(prop.get("babip", 0.288) or 0.288) - 0.200) / 0.200) | ||
|
|
||
| # slot 3: batter bb_pct (plate discipline) | ||
| whip = _clamp(float(prop.get("bb_pct", 0.087) or 0.087) / 0.20) | ||
| whip = _clamp(float(prop.get("bb_pct", 0.091) or 0.091) / 0.20) | ||
| # slot 4: batter K% (inverse contact — higher K = worse contact) | ||
| shadow_whiff = _clamp(float(prop.get("k_pct", 0.223) or 0.223) / 0.35) | ||
| shadow_whiff = _clamp(float(prop.get("k_pct", 0.224) or 0.224) / 0.35) | ||
|
|
||
| # Zone integrity multiplier (pitcher K-props only, 1.0 for batters) | ||
| # Blended with pitcher type cluster: power=+0.05, command=-0.05, neutral=0 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with the other league constants in this file (which use three decimal places),
wobashould be explicitly set to0.310.