Simulation¶
Monte Carlo season simulation and the backtest harness used to compare projection engines.
SeasonSimulator¶
fantasyfb.sim.season_simulator.SeasonSimulator ¶
Simulates fantasy football seasons using Monte Carlo methods.
Handles regular season play, playoff brackets, and outcome probabilities for any fantasy football league structure.
Initialize simulator with league-specific settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
league_settings
|
Dict
|
Dictionary containing: - playoff_start_week: Week when playoffs begin - num_playoff_teams: Number of teams making playoffs - uses_playoff_reseeding: Whether to reseed after each round - num_teams: Total teams in league |
required |
roster_spots
|
Roster-spots DataFrame (position/count columns) or
{slot: count} dict. Required when |
None
|
simulate_season ¶
simulate_season(player_projections: DataFrame, schedule_df: DataFrame, num_sims: int = 10000, best_ball: bool = False, include_playoffs: bool = True, payouts: List[float] = [800, 300, 100], fixed_winner: Optional[List] = None) -> Tuple[pd.DataFrame, pd.DataFrame]
Simulate a complete fantasy season with playoffs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
player_projections
|
DataFrame
|
DataFrame with columns [fantasy_team, week, points_avg, points_stdev].
When |
required |
schedule_df
|
DataFrame
|
DataFrame with columns [week, team_1, team_2, score_1, score_2] |
required |
num_sims
|
int
|
Number of Monte Carlo simulations |
10000
|
best_ball
|
bool
|
When True, compute weekly team scores using the optimal lineup
from each team's full player pool (best ball mode). Requires
|
False
|
include_playoffs
|
bool
|
Whether to simulate playoffs |
True
|
payouts
|
List[float]
|
Prize amounts for [1st, 2nd, 3rd, ...] |
[800, 300, 100]
|
fixed_winner
|
Optional[List]
|
Optional [week, team_name] to force a specific outcome |
None
|
Returns:
| Type | Description |
|---|---|
Tuple[DataFrame, DataFrame]
|
Tuple of (schedule_results, standings_results) DataFrames |
ScheduleManager¶
fantasyfb.sim.schedule_manager.ScheduleManager ¶
Manages fantasy league schedule operations including regular season and postseason.
Initialize the schedule manager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
FantasyPlatformClient
|
FantasyPlatformClient instance |
required |
teams
|
List of team dictionaries |
required | |
settings
|
League settings dictionary |
required | |
lg_id
|
League ID |
required | |
latest_season
|
The most recent NFL season year |
required |
get_schedule ¶
Pulls the fantasy schedule for the season in question as well as scores for all matchups up to the week in question.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
season
|
Season year |
required | |
week
|
Current week |
required | |
current_week
|
Retained for API compatibility; no longer used
(schedule treats the as_of week as "start of week N" for any
value of |
required | |
team_key
|
Team key for identifying "me" column |
None
|
Returns:
| Type | Description |
|---|---|
|
DataFrame with fantasy schedule |
Backtest¶
fantasyfb.sim.backtest ¶
Backtest harness for the V2 projection engine.
Walks forward through a held-out test period and, for each (player, week) that actually played, computes:
V2_neutral = V2 engine's points_rate (no matchup adjustment) V2_default = V2 engine's points_rate * MatchupModel(default weights) V2_fitted = V2 engine's points_rate * MatchupModel(LS-fitted weights) baseline = position average
Then reports per-position MAE and RMSE.
Walk-forward construction guards against look-ahead bias: predictions for week W only use stats from games played before W. The fitted weights are trained on the season prior to the test season and applied to the test set.
When a provider is passed to run_backtest, V2_default/V2_fitted use
that week's real historical depth-chart strings (via
get_depth_charts(season, week)) instead of hardcoding string=1.0 for
every player, so MatchupModel's string-penalty term is actually
exercised by the walk-forward fit rather than going untested.
_DEFAULT_WEIGHTS
module-attribute
¶
_DEFAULT_WEIGHTS: Dict[str, _PositionWeights] = {'QB': _PositionWeights(alpha=0.2, beta=0.15, gamma=0.5), 'RB': _PositionWeights(alpha=0.15, beta=0.2, gamma=0.55), 'WR': _PositionWeights(alpha=0.15, beta=0.2, gamma=0.4), 'TE': _PositionWeights(alpha=0.1, beta=0.2, gamma=0.4), 'K': _PositionWeights(alpha=0.1, beta=0.0, gamma=0.0), 'DEF': _PositionWeights(alpha=-0.3, beta=0.15, gamma=0.0)}
NFLDataProvider ¶
Bases: ABC
Abstract base for NFL data backends.
The legacy column name player_id_sr is preserved as the canonical
cross-table player identifier. Concrete providers are free to populate
it with whatever ID system they natively use (PFR id, GSIS id, etc.) as
long as the same value joins stats, rosters, and depth charts.
get_player_stats
abstractmethod
¶
Per-game player + team-defense stats for the YYYYWW range.
Required columns
player_id_sr, name, position, team, opponent, season, week, game_id, points_allowed, rush_yds, rush_att, rush_td, rush_first_down, rec, rec_yds, rec_td, rec_first_down, pass_yds, pass_cmp, pass_td, pass_first_down, pass_int, fumbles_lost, kick_ret_yds, punt_ret_yds, kick_ret_td, punt_ret_td, xpm, fgm, sacks, def_int, fumbles_rec, def_int_td, fumbles_rec_td
Defenses are emitted as one row per team-week with position == "DEF" and player_id_sr set to the team abbreviation.
get_schedule
abstractmethod
¶
NFL schedule with one row per team-game (two rows per game).
Required columns
season, week, date, team, home_away, opp_team, elo_diff, opp_elo
elo_diff and opp_elo carry forward the legacy "opponent strength" signal. Providers without true Elo ratings should map a strength proxy (e.g. Vegas spread) into elo_diff with consistent sign: positive elo_diff means the team is favored.
get_rosters
abstractmethod
¶
Active rosters for the year range.
Required columns
name, current_team, position, player_id_sr
Optional columns (populated when available): yahoo_id -- enables direct Yahoo->NFL ID linkage
get_depth_charts
abstractmethod
¶
Depth charts, current-week by default or a historical snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
season
|
int | None
|
if given, look up this season's depth chart instead of the live/current one. Historical lookups are only meaningful for seasons before the provider's rolling current-snapshot feed took over (nflreadpy: pre-2025). |
None
|
week
|
int | None
|
if given alongside |
None
|
Required columns
name, current_team, position, string
string follows the legacy convention: 1.0 = starter, 2.0 = backup,
3.0 = third string, etc.
get_draft
abstractmethod
¶
Draft picks for the given year.
Required columns
name, current_team, player_id_sr
team_aliases
abstractmethod
¶
Translation table between Yahoo and provider-native team codes.
Required columns
yahoo -- value Yahoo emits as editorial_team_abbr
real_abbrev -- provider-native team code (matches the team
column returned by get_player_stats and
get_schedule)
The column names mirror the legacy team_abbrevs.csv schema so that
existing callers reading nfl_teams.real_abbrev keep working; the
values now point at the active backend rather than at PFR.
MatchupModel ¶
Per-game multiplicative matchup factor.
Build via :meth:from_history, which precomputes the league-wide
distributions of implied total and allowed-vs-position used to
z-score each input. Then call :meth:apply_factors to attach a
matchup_factor column to a players DataFrame for a given week.
from_history
classmethod
¶
from_history(stats_df: DataFrame, schedule_df: DataFrame, weights: Optional[Dict[str, _PositionWeights]] = None) -> 'MatchupModel'
Fit the league-average baselines from historical data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stats_df
|
DataFrame
|
per-game player stats with |
required |
schedule_df
|
DataFrame
|
per-team-game schedule with |
required |
weights
|
Optional[Dict[str, _PositionWeights]]
|
optional per-position coefficient overrides. |
None
|
factor ¶
factor(position: str, team_implied_total: float, opp_implied_total: float, opp_team: str, string: float = 1.0) -> float
Per-player matchup multiplier centered at 1.0.
apply_factors ¶
Attach a matchup_factor column to a players-shaped DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
players
|
DataFrame
|
must have |
required |
schedule
|
DataFrame
|
provider schedule output (one row per team-week). |
required |
as_of
|
int
|
YYYYWW of the week we're projecting. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A copy of |
DataFrame
|
inputs (team implied total, opp_team, opp allowed) joined on |
DataFrame
|
for debuggability. |
_PositionWeights
dataclass
¶
Coefficient bundle for one position's matchup factor.
factor = 1 + alpha * z(team_implied_total) + beta * z(opp_allowed_vs_position) - gamma * string_penalty(string)
where z(x) = (x - league_avg) / league_std, and string_penalty is a
position-aware function of depth-chart string (see
_string_penalty_slots) that is 0 for genuinely-starting depth and
only grows once a player is beyond the slots a position actually
plays.
ProjectionEngineV2 ¶
ProjectionEngineV2(time_decay: float = _DEFAULT_TIME_DECAY, shrinkage_n: Optional[Dict[str, int]] = None)
Volume x efficiency projection engine.
Returns the same points_rate/points_stdev/num_games schema as
V1 so the rest of fantasyfb (lineup optimizer, season simulator,
move analyzer) keeps working unchanged. Per-week matchup adjustment
happens downstream in lineup_optimizer, same as V1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
time_decay
|
float
|
Per-week exponential decay rate applied when aggregating a player's volume and efficiency over their game history. 0 = no decay (treat all games equally), higher = trust recent games more. |
_DEFAULT_TIME_DECAY
|
shrinkage_n
|
Optional[Dict[str, int]]
|
Bayesian shrinkage strength per position, expressed as equivalent prior games. Defaults to _DEFAULT_SHRINKAGE. |
None
|
calculate_projections ¶
calculate_projections(stats_df: DataFrame, earliest_weeks: Dict[str, int], current_week: int, nfl_schedule: Optional[DataFrame] = None) -> pd.DataFrame
Project per-game fantasy points for every player in stats_df.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stats_df
|
DataFrame
|
Historical game stats, must include |
required |
earliest_weeks
|
Dict[str, int]
|
Per-position YYYYWW cutoff -- games older than this are excluded. |
required |
current_week
|
int
|
Training cutoff in YYYYWW. Games on or after this week are excluded (no look-ahead). |
required |
nfl_schedule
|
Optional[DataFrame]
|
Accepted for V1 API parity; unused in Stage 1. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
DataFrame
|
DataFrame with one row per player plus one |
|
DataFrame
|
per position (used as the rookie fallback by fantasyfb.py). |
|
Columns |
DataFrame
|
player_id_sr, position, points_rate, points_stdev, |
DataFrame
|
num_games, volume_rate, efficiency_rate. |
BacktestRow
dataclass
¶
BacktestRow(player_id_sr: str, position: str, season: int, week: int, actual: float, v2_neutral: float, v2_default: float, v2_fitted: float, baseline: float)
Per-(player, week) prediction record across all model variants.
_historical_strings ¶
Depth-chart strings as of a specific historical week.
Returns a player_id_sr -> string lookup, empty if no provider was
given or the provider has no data for that week (e.g. depth charts
older than nflreadpy's legacy-schema coverage). Callers should treat
a miss the same as an unlisted player: fall back to string=1.0
(nflreadpy depth charts only include the top handful of strings per
team, so most bench/inactive players legitimately don't appear).
_v2_predictions ¶
_v2_predictions(engine: ProjectionEngineV2, matchup: Optional[MatchupModel], stats: DataFrame, schedule: DataFrame, cutoff: int, strings: Optional[DataFrame] = None) -> pd.DataFrame
V2 engine output, optionally multiplied by a matchup factor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
strings
|
Optional[DataFrame]
|
|
None
|
run_backtest ¶
run_backtest(stats: DataFrame, schedule: DataFrame, test_season: int, test_weeks: List[int], fitted_weights: Optional[Dict[str, _PositionWeights]] = None, provider: Optional[NFLDataProvider] = None) -> pd.DataFrame
Per-(player, week) predictions for every model variant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stats
|
DataFrame
|
per-game stats with |
required |
schedule
|
DataFrame
|
provider schedule with Vegas fields. |
required |
test_season
|
int
|
season to backtest in. |
required |
test_weeks
|
List[int]
|
weeks within test_season to evaluate. |
required |
fitted_weights
|
Optional[Dict[str, _PositionWeights]]
|
LS-fit weights for V2_fitted. If None, V2_fitted falls back to position-average predictions. |
None
|
provider
|
Optional[NFLDataProvider]
|
if given, used to pull each test week's real historical
depth-chart strings (via |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Long DataFrame, one row per (player, week, variant). Columns: |
DataFrame
|
player_id_sr, position, season, week, variant, prediction, actual. |