fantasyfb 0.3.0__py3-none-any.whl
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.
- fantasyfb/__init__.py +41 -0
- fantasyfb/analysis/__init__.py +1 -0
- fantasyfb/analysis/move_analyzer.py +639 -0
- fantasyfb/analysis/war_calculator.py +165 -0
- fantasyfb/cli.py +202 -0
- fantasyfb/configs.py +166 -0
- fantasyfb/data/__init__.py +1 -0
- fantasyfb/data/nfl_provider.py +102 -0
- fantasyfb/data/nflreadpy_provider.py +367 -0
- fantasyfb/data/player_data_manager.py +440 -0
- fantasyfb/data/yahoo_client.py +466 -0
- fantasyfb/drafts/__init__.py +1 -0
- fantasyfb/drafts/prep.py +314 -0
- fantasyfb/drafts/salary_cap.py +388 -0
- fantasyfb/drafts/snake.py +554 -0
- fantasyfb/drafts/snake_cockpit.py +288 -0
- fantasyfb/drafts/tools.py +638 -0
- fantasyfb/io/__init__.py +1 -0
- fantasyfb/io/excel_exporter.py +252 -0
- fantasyfb/league.py +830 -0
- fantasyfb/projections/__init__.py +1 -0
- fantasyfb/projections/engine.py +312 -0
- fantasyfb/projections/engine_v2.py +321 -0
- fantasyfb/projections/model_fitter.py +227 -0
- fantasyfb/scoring/__init__.py +1 -0
- fantasyfb/scoring/fantasy_scoring.py +202 -0
- fantasyfb/scoring/lineup_optimizer.py +197 -0
- fantasyfb/scoring/matchup_model.py +275 -0
- fantasyfb/sim/__init__.py +1 -0
- fantasyfb/sim/backtest.py +267 -0
- fantasyfb/sim/schedule_manager.py +142 -0
- fantasyfb/sim/season_simulator.py +489 -0
- fantasyfb-0.3.0.dist-info/METADATA +150 -0
- fantasyfb-0.3.0.dist-info/RECORD +38 -0
- fantasyfb-0.3.0.dist-info/WHEEL +5 -0
- fantasyfb-0.3.0.dist-info/entry_points.txt +5 -0
- fantasyfb-0.3.0.dist-info/licenses/LICENSE +21 -0
- fantasyfb-0.3.0.dist-info/top_level.txt +1 -0
fantasyfb/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""fantasyfb: fantasy football league simulation and optimization toolkit.
|
|
2
|
+
|
|
3
|
+
Public API
|
|
4
|
+
----------
|
|
5
|
+
The most commonly used entry points are re-exported here so that
|
|
6
|
+
``import fantasyfb as fb`` continues to work after the package
|
|
7
|
+
restructuring (League, FantasyScorer, projection / matchup models, etc.).
|
|
8
|
+
For lower-level helpers, import directly from the relevant submodule:
|
|
9
|
+
|
|
10
|
+
from fantasyfb.drafts.tools import compute_vorp, assign_tiers
|
|
11
|
+
from fantasyfb.sim.backtest import run_backtest
|
|
12
|
+
from fantasyfb.io.excel_exporter import FantasyExcelExporter
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .league import League
|
|
16
|
+
from .scoring.fantasy_scoring import FantasyScorer
|
|
17
|
+
from .scoring.matchup_model import MatchupModel
|
|
18
|
+
from .scoring.lineup_optimizer import LineupOptimizer
|
|
19
|
+
from .projections.engine_v2 import ProjectionEngineV2
|
|
20
|
+
from .sim.season_simulator import SeasonSimulator
|
|
21
|
+
from .sim.schedule_manager import ScheduleManager
|
|
22
|
+
from .data.nfl_provider import NFLDataProvider
|
|
23
|
+
from .data.nflreadpy_provider import NflreadpyProvider
|
|
24
|
+
from .configs import get_league_config, apply_default_scoring_categories
|
|
25
|
+
|
|
26
|
+
__version__ = "0.3.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"League",
|
|
30
|
+
"FantasyScorer",
|
|
31
|
+
"MatchupModel",
|
|
32
|
+
"LineupOptimizer",
|
|
33
|
+
"ProjectionEngineV2",
|
|
34
|
+
"SeasonSimulator",
|
|
35
|
+
"ScheduleManager",
|
|
36
|
+
"NFLDataProvider",
|
|
37
|
+
"NflreadpyProvider",
|
|
38
|
+
"get_league_config",
|
|
39
|
+
"apply_default_scoring_categories",
|
|
40
|
+
"__version__",
|
|
41
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""WAR calculation, move analysis, and post-hoc evaluators."""
|
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fantasy football roster move analysis.
|
|
3
|
+
|
|
4
|
+
This module analyzes the impact of potential roster moves including
|
|
5
|
+
pickups, adds, drops, and trades on team performance.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MoveAnalyzer:
|
|
13
|
+
"""
|
|
14
|
+
Analyzes the impact of potential roster moves on team performance.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, league):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the move analyzer.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
league: League instance with all the data and simulation methods
|
|
23
|
+
"""
|
|
24
|
+
self.league = league
|
|
25
|
+
|
|
26
|
+
def possible_pickups(
|
|
27
|
+
self,
|
|
28
|
+
focus_on: list = [],
|
|
29
|
+
exclude: list = [],
|
|
30
|
+
limit_per: int = 10,
|
|
31
|
+
team_name: str = None,
|
|
32
|
+
postseason: bool = True,
|
|
33
|
+
verbose: bool = True,
|
|
34
|
+
payouts: list = [800, 300, 100],
|
|
35
|
+
bestball: bool = False,
|
|
36
|
+
min_rostership: float = 0.05,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Simulates the remainder of the season with the current roster and compares it to
|
|
40
|
+
a simulation of the roster after a series of potential add & drop transactions.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
focus_on (list, optional): list of players to include in every potential trade, defaults to [].
|
|
44
|
+
exclude (list, optional): list of players to exclude from every potential trade, defaults to [].
|
|
45
|
+
limit_per (int, optional): number of players per position to analyze, defaults to 10.
|
|
46
|
+
team_name (str, optional): name of team to analyze trades for, defaults to None (and therefore team of interest).
|
|
47
|
+
postseason (bool, optional): whether to analyze postseason gains or just regular season, defaults to True.
|
|
48
|
+
verbose (bool, optional): whether to print out a status report as the code runs, defaults to True.
|
|
49
|
+
payouts (list, optional): list of payout amounts for top three finishers, defaults to [800, 300, 100].
|
|
50
|
+
bestball (bool, optional): whether to use best ball settings during simulation, defaults to False.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
pd.DataFrame: dataframe containing the impact and value of every add & drop combination analyzed.
|
|
54
|
+
"""
|
|
55
|
+
as_of = self.league.season * 100 + self.league.week
|
|
56
|
+
self.league.yahoo_client.refresh_oauth()
|
|
57
|
+
if bestball:
|
|
58
|
+
orig_standings = self.league.bestball_sims(payouts)
|
|
59
|
+
else:
|
|
60
|
+
orig_standings = self.league.season_sims(postseason, payouts)[1]
|
|
61
|
+
added_value = pd.DataFrame(
|
|
62
|
+
columns=[
|
|
63
|
+
"player_to_drop",
|
|
64
|
+
"player_to_add",
|
|
65
|
+
"wins_avg",
|
|
66
|
+
"wins_stdev",
|
|
67
|
+
"points_avg",
|
|
68
|
+
"points_stdev",
|
|
69
|
+
"per_game_avg",
|
|
70
|
+
"per_game_stdev",
|
|
71
|
+
"per_game_fano",
|
|
72
|
+
"playoffs",
|
|
73
|
+
"playoff_bye",
|
|
74
|
+
]
|
|
75
|
+
+ (
|
|
76
|
+
["winner", "runner_up", "third", "earnings"]
|
|
77
|
+
if postseason
|
|
78
|
+
else []
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
if not team_name:
|
|
82
|
+
team_name = [
|
|
83
|
+
team["name"]
|
|
84
|
+
for team in self.league.teams
|
|
85
|
+
if team["team_key"] == self.league.lg.team_key()
|
|
86
|
+
][0]
|
|
87
|
+
players_to_drop = self.league.players.loc[self.league.players.fantasy_team == team_name]
|
|
88
|
+
if players_to_drop.name.isin(focus_on).sum() > 0:
|
|
89
|
+
players_to_drop = players_to_drop.loc[players_to_drop.name.isin(focus_on)]
|
|
90
|
+
if players_to_drop.name.isin(exclude).sum() > 0:
|
|
91
|
+
players_to_drop = players_to_drop.loc[~players_to_drop.name.isin(exclude)]
|
|
92
|
+
available = self.league.players.loc[self.league.players.fantasy_team.isnull() \
|
|
93
|
+
& (self.league.players.until.isnull() | (self.league.players.until < 17)) \
|
|
94
|
+
& (self.league.players.pct_rostered >= min_rostership)].reset_index(drop=True)
|
|
95
|
+
for my_player in players_to_drop.name:
|
|
96
|
+
self.league.yahoo_client.refresh_oauth(55)
|
|
97
|
+
if (
|
|
98
|
+
players_to_drop.loc[players_to_drop.name == my_player, "until"].values[
|
|
99
|
+
0
|
|
100
|
+
]
|
|
101
|
+
>= as_of % 100
|
|
102
|
+
):
|
|
103
|
+
possible = available.loc[~available.name.str.contains("Average_")]
|
|
104
|
+
else:
|
|
105
|
+
possible = available.loc[
|
|
106
|
+
~available.name.str.contains("Average_")
|
|
107
|
+
& (
|
|
108
|
+
available.WAR
|
|
109
|
+
>= self.league.players.loc[
|
|
110
|
+
self.league.players.name == my_player, "WAR"
|
|
111
|
+
].values[0]
|
|
112
|
+
- 0.5
|
|
113
|
+
)
|
|
114
|
+
]
|
|
115
|
+
if available.name.isin(focus_on).sum() > 0:
|
|
116
|
+
possible = possible.loc[possible.name.isin(focus_on)]
|
|
117
|
+
if possible.name.isin(exclude).sum() > 0:
|
|
118
|
+
possible = possible.loc[~possible.name.isin(exclude)]
|
|
119
|
+
if verbose:
|
|
120
|
+
print(my_player + ": " + str(possible.shape[0]) + " better players")
|
|
121
|
+
print(datetime.datetime.now())
|
|
122
|
+
possible = possible.groupby("position").head(limit_per)
|
|
123
|
+
for free_agent in possible.name:
|
|
124
|
+
self.league.players.loc[self.league.players.name == my_player, "fantasy_team"] = None
|
|
125
|
+
self.league.players.loc[
|
|
126
|
+
self.league.players.name == free_agent, "fantasy_team"
|
|
127
|
+
] = team_name
|
|
128
|
+
if bestball:
|
|
129
|
+
new_standings = self.league.bestball_sims(payouts)
|
|
130
|
+
else:
|
|
131
|
+
new_standings = self.league.season_sims(postseason, payouts)[1]
|
|
132
|
+
added_value = pd.concat([added_value,
|
|
133
|
+
new_standings.loc[new_standings.team == team_name]],
|
|
134
|
+
ignore_index=True,
|
|
135
|
+
sort=False,
|
|
136
|
+
)
|
|
137
|
+
added_value.loc[added_value.shape[0] - 1, "player_to_drop"] = my_player
|
|
138
|
+
added_value.loc[added_value.shape[0] - 1, "player_to_add"] = free_agent
|
|
139
|
+
self.league.players.loc[
|
|
140
|
+
self.league.players.name == my_player, "fantasy_team"
|
|
141
|
+
] = team_name
|
|
142
|
+
self.league.players.loc[self.league.players.name == free_agent, "fantasy_team"] = None
|
|
143
|
+
if verbose:
|
|
144
|
+
temp = added_value.iloc[-1 * possible.shape[0] :][
|
|
145
|
+
["player_to_drop", "player_to_add", "earnings"]
|
|
146
|
+
]
|
|
147
|
+
temp["earnings"] -= orig_standings.loc[
|
|
148
|
+
orig_standings.team == team_name, "earnings"
|
|
149
|
+
].values[0]
|
|
150
|
+
if temp.shape[0] > 0:
|
|
151
|
+
print(
|
|
152
|
+
temp.sort_values(by="earnings", ascending=False).to_string(
|
|
153
|
+
index=False
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
del temp
|
|
157
|
+
if added_value.shape[0] > 0:
|
|
158
|
+
for col in [
|
|
159
|
+
"wins_avg",
|
|
160
|
+
"wins_stdev",
|
|
161
|
+
"points_avg",
|
|
162
|
+
"points_stdev",
|
|
163
|
+
"playoffs",
|
|
164
|
+
"playoff_bye",
|
|
165
|
+
] + (
|
|
166
|
+
["winner", "runner_up", "third", "earnings"]
|
|
167
|
+
if postseason
|
|
168
|
+
else []
|
|
169
|
+
):
|
|
170
|
+
added_value[col] -= orig_standings.loc[
|
|
171
|
+
orig_standings.team == team_name, col
|
|
172
|
+
].values[0]
|
|
173
|
+
added_value[col] = round(added_value[col], 4)
|
|
174
|
+
added_value = added_value.sort_values(
|
|
175
|
+
by="winner" if postseason else "playoffs", ascending=False
|
|
176
|
+
)
|
|
177
|
+
return added_value
|
|
178
|
+
|
|
179
|
+
def possible_adds(
|
|
180
|
+
self,
|
|
181
|
+
focus_on: list = [],
|
|
182
|
+
exclude: list = [],
|
|
183
|
+
limit_per: int = 10,
|
|
184
|
+
team_name: str = None,
|
|
185
|
+
postseason: bool = True,
|
|
186
|
+
verbose: bool = True,
|
|
187
|
+
payouts: list = [800, 300, 100],
|
|
188
|
+
bestball: bool = False,
|
|
189
|
+
min_rostership: float = 0.05,
|
|
190
|
+
):
|
|
191
|
+
"""
|
|
192
|
+
Simulates the remainder of the season with the current roster and compares it to
|
|
193
|
+
a simulation of the roster after a series of potential add transactions.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
focus_on (list, optional): list of players to include in every potential trade, defaults to [].
|
|
197
|
+
exclude (list, optional): list of players to exclude from every potential trade, defaults to [].
|
|
198
|
+
limit_per (int, optional): number of players per position to analyze, defaults to 10.
|
|
199
|
+
team_name (str, optional): name of team to analyze trades for, defaults to None (and therefore team of interest).
|
|
200
|
+
postseason (bool, optional): whether to analyze postseason gains or just regular season, defaults to True.
|
|
201
|
+
verbose (bool, optional): whether to print out a status report as the code runs, defaults to True.
|
|
202
|
+
payouts (list, optional): list of payout amounts for top three finishers, defaults to [800, 300, 100].
|
|
203
|
+
bestball (bool, optional): whether to use best ball settings during simulation, defaults to False.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
pd.DataFrame: dataframe containing the impact and value of every possible add analyzed.
|
|
207
|
+
"""
|
|
208
|
+
as_of = self.league.season * 100 + self.league.week
|
|
209
|
+
self.league.yahoo_client.refresh_oauth()
|
|
210
|
+
if bestball:
|
|
211
|
+
orig_standings = self.league.bestball_sims(payouts)
|
|
212
|
+
else:
|
|
213
|
+
orig_standings = self.league.season_sims(postseason, payouts)[1]
|
|
214
|
+
added_value = pd.DataFrame(
|
|
215
|
+
columns=[
|
|
216
|
+
"player_to_add",
|
|
217
|
+
"wins_avg",
|
|
218
|
+
"wins_stdev",
|
|
219
|
+
"points_avg",
|
|
220
|
+
"points_stdev",
|
|
221
|
+
"per_game_avg",
|
|
222
|
+
"per_game_stdev",
|
|
223
|
+
"per_game_fano",
|
|
224
|
+
"playoffs",
|
|
225
|
+
"playoff_bye",
|
|
226
|
+
]
|
|
227
|
+
+ (
|
|
228
|
+
["winner", "runner_up", "third", "earnings"]
|
|
229
|
+
if postseason
|
|
230
|
+
else []
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
if not team_name:
|
|
234
|
+
team_name = [
|
|
235
|
+
team["name"]
|
|
236
|
+
for team in self.league.teams
|
|
237
|
+
if team["team_key"] == self.league.lg.team_key()
|
|
238
|
+
][0]
|
|
239
|
+
available = self.league.players.loc[self.league.players.fantasy_team.isnull() \
|
|
240
|
+
& (self.league.players.until.isnull() | (self.league.players.until < 17)) \
|
|
241
|
+
& (self.league.players.pct_rostered >= min_rostership)].reset_index(drop=True)
|
|
242
|
+
possible = available.loc[~available.name.str.contains("Average_")]
|
|
243
|
+
if possible.name.isin(focus_on).sum() > 0:
|
|
244
|
+
possible = possible.loc[possible.name.isin(focus_on)]
|
|
245
|
+
if possible.name.isin(exclude).sum() > 0:
|
|
246
|
+
possible = possible.loc[~possible.name.isin(exclude)]
|
|
247
|
+
possible = possible.groupby("position").head(limit_per)
|
|
248
|
+
for free_agent in possible.name:
|
|
249
|
+
if verbose:
|
|
250
|
+
print("{}, {}".format(free_agent, datetime.datetime.now()))
|
|
251
|
+
self.league.players.loc[
|
|
252
|
+
self.league.players.name == free_agent, "fantasy_team"
|
|
253
|
+
] = team_name
|
|
254
|
+
if bestball:
|
|
255
|
+
new_standings = self.league.bestball_sims(payouts)
|
|
256
|
+
else:
|
|
257
|
+
new_standings = self.league.season_sims(postseason, payouts)[1]
|
|
258
|
+
added_value = pd.concat([added_value,
|
|
259
|
+
new_standings.loc[new_standings.team == team_name]],
|
|
260
|
+
ignore_index=True,
|
|
261
|
+
sort=False,
|
|
262
|
+
)
|
|
263
|
+
added_value.loc[added_value.shape[0] - 1, "player_to_add"] = free_agent
|
|
264
|
+
added_value.loc[added_value.shape[0] - 1, "position"] = possible.loc[
|
|
265
|
+
possible.name == free_agent, "position"
|
|
266
|
+
].values[0]
|
|
267
|
+
added_value.loc[added_value.shape[0] - 1, "current_team"] = possible.loc[
|
|
268
|
+
possible.name == free_agent, "current_team"
|
|
269
|
+
].values[0]
|
|
270
|
+
self.league.players.loc[self.league.players.name == free_agent, "fantasy_team"] = None
|
|
271
|
+
if added_value.shape[0] > 0:
|
|
272
|
+
for col in [
|
|
273
|
+
"wins_avg",
|
|
274
|
+
"wins_stdev",
|
|
275
|
+
"points_avg",
|
|
276
|
+
"points_stdev",
|
|
277
|
+
"playoffs",
|
|
278
|
+
"playoff_bye",
|
|
279
|
+
] + (
|
|
280
|
+
["winner", "runner_up", "third", "earnings"]
|
|
281
|
+
if postseason
|
|
282
|
+
else []
|
|
283
|
+
):
|
|
284
|
+
added_value[col] -= orig_standings.loc[
|
|
285
|
+
orig_standings.team == team_name, col
|
|
286
|
+
].values[0]
|
|
287
|
+
added_value[col] = round(added_value[col], 4)
|
|
288
|
+
added_value = added_value.sort_values(
|
|
289
|
+
by="winner" if postseason else "playoffs", ascending=False
|
|
290
|
+
)
|
|
291
|
+
if verbose:
|
|
292
|
+
print(
|
|
293
|
+
added_value[["player_to_add", "earnings"]]
|
|
294
|
+
.sort_values(by="earnings", ascending=False)
|
|
295
|
+
.to_string(index=False)
|
|
296
|
+
)
|
|
297
|
+
return added_value
|
|
298
|
+
|
|
299
|
+
def possible_drops(
|
|
300
|
+
self,
|
|
301
|
+
focus_on: list = [],
|
|
302
|
+
exclude: list = [],
|
|
303
|
+
team_name: str = None,
|
|
304
|
+
postseason: bool = True,
|
|
305
|
+
verbose: bool = True,
|
|
306
|
+
payouts: list = [800, 300, 100],
|
|
307
|
+
bestball: bool = False,
|
|
308
|
+
):
|
|
309
|
+
"""
|
|
310
|
+
Simulates the remainder of the season with the current roster and compares it to
|
|
311
|
+
a simulation of the roster after a series of potential drop transactions.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
focus_on (list, optional): list of players to include in every potential trade, defaults to [].
|
|
315
|
+
exclude (list, optional): list of players to exclude from every potential trade, defaults to [].
|
|
316
|
+
team_name (str, optional): name of team to analyze trades for, defaults to None (and therefore team of interest).
|
|
317
|
+
postseason (bool, optional): whether to analyze postseason gains or just regular season, defaults to True.
|
|
318
|
+
verbose (bool, optional): whether to print out a status report as the code runs, defaults to True.
|
|
319
|
+
payouts (list, optional): list of payout amounts for top three finishers, defaults to [800, 300, 100].
|
|
320
|
+
bestball (bool, optional): whether to use best ball settings during simulation, defaults to False.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
pd.DataFrame: dataframe containing the impact and value of every possible drop analyzed.
|
|
324
|
+
"""
|
|
325
|
+
self.league.yahoo_client.refresh_oauth()
|
|
326
|
+
if bestball:
|
|
327
|
+
orig_standings = self.league.bestball_sims(payouts)
|
|
328
|
+
else:
|
|
329
|
+
orig_standings = self.league.season_sims(postseason, payouts)[1]
|
|
330
|
+
reduced_value = pd.DataFrame(
|
|
331
|
+
columns=[
|
|
332
|
+
"player_to_drop",
|
|
333
|
+
"wins_avg",
|
|
334
|
+
"wins_stdev",
|
|
335
|
+
"points_avg",
|
|
336
|
+
"points_stdev",
|
|
337
|
+
"per_game_avg",
|
|
338
|
+
"per_game_stdev",
|
|
339
|
+
"per_game_fano",
|
|
340
|
+
"playoffs",
|
|
341
|
+
"playoff_bye",
|
|
342
|
+
]
|
|
343
|
+
+ (
|
|
344
|
+
["winner", "runner_up", "third", "earnings"]
|
|
345
|
+
if postseason
|
|
346
|
+
else []
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
if not team_name:
|
|
350
|
+
team_name = [
|
|
351
|
+
team["name"]
|
|
352
|
+
for team in self.league.teams
|
|
353
|
+
if team["team_key"] == self.league.lg.team_key()
|
|
354
|
+
][0]
|
|
355
|
+
players_to_drop = self.league.players.loc[self.league.players.fantasy_team == team_name]
|
|
356
|
+
if players_to_drop.name.isin(focus_on).sum() > 0:
|
|
357
|
+
players_to_drop = players_to_drop.loc[players_to_drop.name.isin(focus_on)]
|
|
358
|
+
if players_to_drop.name.isin(exclude).sum() > 0:
|
|
359
|
+
players_to_drop = players_to_drop.loc[~players_to_drop.name.isin(exclude)]
|
|
360
|
+
for my_player in players_to_drop.name:
|
|
361
|
+
self.league.players.loc[self.league.players.name == my_player, "fantasy_team"] = None
|
|
362
|
+
if bestball:
|
|
363
|
+
new_standings = self.league.bestball_sims(payouts)
|
|
364
|
+
else:
|
|
365
|
+
new_standings = self.league.season_sims(postseason, payouts)[1]
|
|
366
|
+
reduced_value = pd.concat([reduced_value,
|
|
367
|
+
new_standings.loc[new_standings.team == team_name]],
|
|
368
|
+
ignore_index=True,
|
|
369
|
+
sort=False,
|
|
370
|
+
)
|
|
371
|
+
reduced_value.loc[reduced_value.shape[0] - 1, "player_to_drop"] = my_player
|
|
372
|
+
self.league.players.loc[self.league.players.name == my_player, "fantasy_team"] = team_name
|
|
373
|
+
if reduced_value.shape[0] > 0:
|
|
374
|
+
for col in [
|
|
375
|
+
"wins_avg",
|
|
376
|
+
"wins_stdev",
|
|
377
|
+
"points_avg",
|
|
378
|
+
"points_stdev",
|
|
379
|
+
"playoffs",
|
|
380
|
+
"playoff_bye",
|
|
381
|
+
] + (
|
|
382
|
+
["winner", "runner_up", "third", "earnings"]
|
|
383
|
+
if postseason
|
|
384
|
+
else []
|
|
385
|
+
):
|
|
386
|
+
reduced_value[col] -= orig_standings.loc[
|
|
387
|
+
orig_standings.team == team_name, col
|
|
388
|
+
].values[0]
|
|
389
|
+
reduced_value[col] = round(reduced_value[col], 4)
|
|
390
|
+
reduced_value = reduced_value.sort_values(
|
|
391
|
+
by="winner" if postseason else "playoffs", ascending=False
|
|
392
|
+
)
|
|
393
|
+
if verbose:
|
|
394
|
+
print(
|
|
395
|
+
reduced_value[["player_to_drop", "earnings"]]
|
|
396
|
+
.sort_values(by="earnings", ascending=False)
|
|
397
|
+
.to_string(index=False)
|
|
398
|
+
)
|
|
399
|
+
return reduced_value
|
|
400
|
+
|
|
401
|
+
def possible_trades(
|
|
402
|
+
self,
|
|
403
|
+
focus_on: list = [],
|
|
404
|
+
exclude: list = [],
|
|
405
|
+
given: list = [],
|
|
406
|
+
limit_per: int = 10,
|
|
407
|
+
team_name: str = None,
|
|
408
|
+
postseason: bool = True,
|
|
409
|
+
verbose: bool = True,
|
|
410
|
+
payouts: list = [800, 300, 100],
|
|
411
|
+
bestball: bool = False,
|
|
412
|
+
):
|
|
413
|
+
"""
|
|
414
|
+
Simulates the remainder of the season with the current roster and compares it to
|
|
415
|
+
a simulation of the roster after a series of potential trade transactions.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
focus_on (list, optional): list of players to include in every potential trade, defaults to [].
|
|
419
|
+
exclude (list, optional): list of players to exclude from every potential trade, defaults to [].
|
|
420
|
+
given (list, optional): list of players to include in the trade in the background, defaults to [].
|
|
421
|
+
limit_per (int, optional): number of players per position to analyze, defaults to 10.
|
|
422
|
+
team_name (str, optional): name of team to analyze trades for, defaults to None (and therefore team of interest).
|
|
423
|
+
postseason (bool, optional): whether to analyze postseason gains or just regular season, defaults to True.
|
|
424
|
+
verbose (bool, optional): whether to print out a status report as the code runs, defaults to True.
|
|
425
|
+
payouts (list, optional): list of payout amounts for top three finishers, defaults to [800, 300, 100].
|
|
426
|
+
bestball (bool, optional): whether to use best ball settings during simulation, defaults to False.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
pd.DataFrame: dataframe containing the impact and value of every possible trade analyzed.
|
|
430
|
+
"""
|
|
431
|
+
self.league.yahoo_client.refresh_oauth()
|
|
432
|
+
if not team_name:
|
|
433
|
+
team_name = [
|
|
434
|
+
team["name"]
|
|
435
|
+
for team in self.league.teams
|
|
436
|
+
if team["team_key"] == self.league.lg.team_key()
|
|
437
|
+
][0]
|
|
438
|
+
my_players = self.league.players.loc[
|
|
439
|
+
(self.league.players.fantasy_team == team_name)
|
|
440
|
+
& ~self.league.players.position.isin(["K", "DEF"])
|
|
441
|
+
]
|
|
442
|
+
if my_players.name.isin(focus_on).sum() > 0:
|
|
443
|
+
my_players = my_players.loc[my_players.name.isin(focus_on)]
|
|
444
|
+
if my_players.name.isin(exclude).sum() > 0:
|
|
445
|
+
my_players = my_players.loc[~my_players.name.isin(exclude)]
|
|
446
|
+
their_players = self.league.players.loc[
|
|
447
|
+
(self.league.players.fantasy_team != team_name)
|
|
448
|
+
& ~self.league.players.position.isin(["K", "DEF"])
|
|
449
|
+
]
|
|
450
|
+
if their_players.name.isin(focus_on).sum() > 0:
|
|
451
|
+
their_players = their_players.loc[their_players.name.isin(focus_on)]
|
|
452
|
+
if their_players.name.isin(exclude).sum() > 0:
|
|
453
|
+
their_players = their_players.loc[~their_players.name.isin(exclude)]
|
|
454
|
+
if bestball:
|
|
455
|
+
orig_standings = self.league.bestball_sims(payouts)
|
|
456
|
+
else:
|
|
457
|
+
orig_standings = self.league.season_sims(postseason, payouts)[1]
|
|
458
|
+
|
|
459
|
+
# Make sure there are two teams and narrow down to that team!!!
|
|
460
|
+
given_check = (
|
|
461
|
+
type(given) == list
|
|
462
|
+
and my_players.name.isin(given).any()
|
|
463
|
+
and their_players.loc[their_players.name.isin(given), "fantasy_team"]
|
|
464
|
+
.unique()
|
|
465
|
+
.shape[0]
|
|
466
|
+
== 1
|
|
467
|
+
)
|
|
468
|
+
if given_check:
|
|
469
|
+
mine = [player for player in given if my_players.name.isin([player]).any()]
|
|
470
|
+
theirs = [
|
|
471
|
+
player for player in given if their_players.name.isin([player]).any()
|
|
472
|
+
]
|
|
473
|
+
their_team = self.league.players.loc[
|
|
474
|
+
self.league.players.name.isin(theirs), "fantasy_team"
|
|
475
|
+
].values[0]
|
|
476
|
+
self.league.players.loc[self.league.players.name.isin(mine), "fantasy_team"] = their_team
|
|
477
|
+
self.league.players.loc[self.league.players.name.isin(theirs), "fantasy_team"] = team_name
|
|
478
|
+
my_players = my_players.loc[~my_players.name.isin(given)]
|
|
479
|
+
their_players = their_players.loc[
|
|
480
|
+
(their_players.fantasy_team == their_team)
|
|
481
|
+
& ~their_players.name.isin(given)
|
|
482
|
+
]
|
|
483
|
+
my_players["WAR"] = 0.0
|
|
484
|
+
their_players["WAR"] = 0.0
|
|
485
|
+
# Make sure there are two teams and narrow down to that teams!!!
|
|
486
|
+
|
|
487
|
+
my_added_value = pd.DataFrame()
|
|
488
|
+
their_added_value = pd.DataFrame()
|
|
489
|
+
for my_player in my_players.name:
|
|
490
|
+
self.league.yahoo_client.refresh_oauth(55)
|
|
491
|
+
if their_players.name.isin(focus_on).any():
|
|
492
|
+
possible = their_players.copy()
|
|
493
|
+
else:
|
|
494
|
+
possible = their_players.loc[
|
|
495
|
+
abs(
|
|
496
|
+
their_players.WAR
|
|
497
|
+
- my_players.loc[my_players.name == my_player, "WAR"].values[0]
|
|
498
|
+
)
|
|
499
|
+
<= 0.5
|
|
500
|
+
]
|
|
501
|
+
# possible = their_players.loc[their_players.WAR - my_players.loc[my_players.name == my_player,'WAR'].values[0] > -1.0]
|
|
502
|
+
if verbose:
|
|
503
|
+
print(my_player + ": " + str(possible.shape[0]) + " comparable players")
|
|
504
|
+
print(datetime.datetime.now())
|
|
505
|
+
possible = possible.groupby("position").head(limit_per)
|
|
506
|
+
for their_player in possible.name:
|
|
507
|
+
their_team = self.league.players.loc[
|
|
508
|
+
self.league.players.name == their_player, "fantasy_team"
|
|
509
|
+
].values[0]
|
|
510
|
+
self.league.players.loc[
|
|
511
|
+
self.league.players.name == my_player, "fantasy_team"
|
|
512
|
+
] = their_team
|
|
513
|
+
self.league.players.loc[
|
|
514
|
+
self.league.players.name == their_player, "fantasy_team"
|
|
515
|
+
] = team_name
|
|
516
|
+
if bestball:
|
|
517
|
+
new_standings = self.league.bestball_sims(payouts)
|
|
518
|
+
else:
|
|
519
|
+
new_standings = self.league.season_sims(postseason, payouts)[1]
|
|
520
|
+
self.league.players.loc[
|
|
521
|
+
self.league.players.name == my_player, "fantasy_team"
|
|
522
|
+
] = team_name
|
|
523
|
+
self.league.players.loc[
|
|
524
|
+
self.league.players.name == their_player, "fantasy_team"
|
|
525
|
+
] = their_team
|
|
526
|
+
new_standings["player_to_trade_away"] = my_player
|
|
527
|
+
new_standings["player_to_trade_for"] = their_player
|
|
528
|
+
my_added_value = pd.concat([my_added_value,
|
|
529
|
+
new_standings.loc[new_standings.team == team_name]],
|
|
530
|
+
ignore_index=True,
|
|
531
|
+
)
|
|
532
|
+
their_added_value = pd.concat([their_added_value,
|
|
533
|
+
new_standings.loc[new_standings.team == their_team]],
|
|
534
|
+
ignore_index=True,
|
|
535
|
+
)
|
|
536
|
+
if verbose and possible.shape[0] > 0:
|
|
537
|
+
me = my_added_value.iloc[-1 * possible.shape[0] :][
|
|
538
|
+
["player_to_trade_away", "player_to_trade_for", "earnings"]
|
|
539
|
+
].rename(columns={"earnings": "my_earnings"})
|
|
540
|
+
them = their_added_value.iloc[-1 * possible.shape[0] :][
|
|
541
|
+
["player_to_trade_away", "player_to_trade_for", "team", "earnings"]
|
|
542
|
+
].rename(columns={"earnings": "their_earnings"})
|
|
543
|
+
me["my_earnings"] -= orig_standings.loc[
|
|
544
|
+
orig_standings.team == team_name, "earnings"
|
|
545
|
+
].values[0]
|
|
546
|
+
for their_team in them.team.unique():
|
|
547
|
+
them.loc[
|
|
548
|
+
them.team == their_team, "their_earnings"
|
|
549
|
+
] -= orig_standings.loc[
|
|
550
|
+
orig_standings.team == their_team, "earnings"
|
|
551
|
+
].values[
|
|
552
|
+
0
|
|
553
|
+
]
|
|
554
|
+
temp = pd.merge(
|
|
555
|
+
left=me,
|
|
556
|
+
right=them,
|
|
557
|
+
how="inner",
|
|
558
|
+
on=["player_to_trade_away", "player_to_trade_for"],
|
|
559
|
+
)
|
|
560
|
+
if temp.shape[0] > 0:
|
|
561
|
+
print(
|
|
562
|
+
temp.sort_values(by="my_earnings", ascending=False).to_string(
|
|
563
|
+
index=False
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
del me, them, temp, their_team
|
|
567
|
+
|
|
568
|
+
if given_check:
|
|
569
|
+
mine = [player for player in given if my_players.name.isin([player]).any()]
|
|
570
|
+
theirs = [
|
|
571
|
+
player for player in given if their_players.name.isin([player]).any()
|
|
572
|
+
]
|
|
573
|
+
their_team = self.league.players.loc[
|
|
574
|
+
self.league.players.name.isin(theirs), "fantasy_team"
|
|
575
|
+
].values[0]
|
|
576
|
+
self.league.players.loc[self.league.players.name.isin(mine), "fantasy_team"] = their_team
|
|
577
|
+
self.league.players.loc[self.league.players.name.isin(theirs), "fantasy_team"] = team_name
|
|
578
|
+
|
|
579
|
+
for col in [
|
|
580
|
+
"wins_avg",
|
|
581
|
+
"wins_stdev",
|
|
582
|
+
"points_avg",
|
|
583
|
+
"points_stdev",
|
|
584
|
+
"per_game_avg",
|
|
585
|
+
"per_game_stdev",
|
|
586
|
+
"per_game_fano",
|
|
587
|
+
"playoffs",
|
|
588
|
+
"playoff_bye",
|
|
589
|
+
] + (["winner", "runner_up", "third", "earnings"] if postseason else []):
|
|
590
|
+
my_added_value[col] -= orig_standings.loc[
|
|
591
|
+
orig_standings.team == team_name, col
|
|
592
|
+
].values[0]
|
|
593
|
+
my_added_value[col] = round(my_added_value[col], 4)
|
|
594
|
+
for their_team in their_added_value.team.unique():
|
|
595
|
+
for col in [
|
|
596
|
+
"wins_avg",
|
|
597
|
+
"wins_stdev",
|
|
598
|
+
"points_avg",
|
|
599
|
+
"points_stdev",
|
|
600
|
+
"per_game_avg",
|
|
601
|
+
"per_game_stdev",
|
|
602
|
+
"per_game_fano",
|
|
603
|
+
"playoffs",
|
|
604
|
+
"playoff_bye",
|
|
605
|
+
] + (["winner", "runner_up", "third", "earnings"] if postseason else []):
|
|
606
|
+
their_added_value.loc[
|
|
607
|
+
their_added_value.team == their_team, col
|
|
608
|
+
] -= orig_standings.loc[orig_standings.team == their_team, col].values[
|
|
609
|
+
0
|
|
610
|
+
]
|
|
611
|
+
their_added_value[col] = round(their_added_value[col], 4)
|
|
612
|
+
for col in [
|
|
613
|
+
"team",
|
|
614
|
+
"wins_avg",
|
|
615
|
+
"wins_stdev",
|
|
616
|
+
"points_avg",
|
|
617
|
+
"points_stdev",
|
|
618
|
+
"per_game_avg",
|
|
619
|
+
"per_game_stdev",
|
|
620
|
+
"per_game_fano",
|
|
621
|
+
"playoffs",
|
|
622
|
+
"playoff_bye",
|
|
623
|
+
] + (["winner", "runner_up", "third", "earnings"] if postseason else []):
|
|
624
|
+
my_added_value = my_added_value.rename(
|
|
625
|
+
index=str, columns={col: "my_" + col}
|
|
626
|
+
)
|
|
627
|
+
their_added_value = their_added_value.rename(
|
|
628
|
+
index=str, columns={col: "their_" + col}
|
|
629
|
+
)
|
|
630
|
+
added_value = pd.merge(
|
|
631
|
+
left=my_added_value,
|
|
632
|
+
right=their_added_value,
|
|
633
|
+
how="inner",
|
|
634
|
+
on=["player_to_trade_away", "player_to_trade_for"],
|
|
635
|
+
)
|
|
636
|
+
added_value = added_value.sort_values(
|
|
637
|
+
by="my_winner" if postseason else "playoffs", ascending=False
|
|
638
|
+
)
|
|
639
|
+
return added_value
|