新闻详情

新闻详情

首页 / 资讯中心 / 详情

用Python构建个性化Fantasy Football模拟选秀:规则外置与VORP估值

发布时间:2026/9/4 23:50:51来源:尧图网络
用Python构建个性化Fantasy Football模拟选秀:规则外置与VORP估值
最近在 Hacker News 的 Show HN 区域看到一个项目名字叫 “Realer Mock Draft”它的卖点一句话就能说清楚不再用“全网通用默认设置”来模拟线上选秀而是通过一份 skill file 把你的真实联赛规则告诉模拟器让 Mock Draft 尽量贴近你自己 league 的实际选秀环境。很多玩 Fantasy Football美式橄榄球梦幻联赛的同学都有这种经历开赛前想去平台练两把 Draft结果模拟器永远是用同一套默认规则——12 支队伍、1 QB / 2 RB / 2 WR、half PPR。可你自己加入的联赛明明是 10 人、带 Superflex、三个 WR 首发位、还有 TE Premium。这两套规则下球员价值排序可能完全不一样练出来的 Mock Draft 和正式选秀根本是两种游戏。这篇文章不是去复刻 Realer Mock Draft 的源码而是把这套思路拆成一个可以自己动手实现的工程方案如何把 league 规则固化成一份可校验、可版本化的规则文件再基于它重新计算球员估值最后写一个能真正模拟“你的联赛选秀”的 Python 引擎。整个过程适合刚接触 Python、又想自己做点数据或游戏工具的朋友也适合对 Fantasy Sports 有兴趣的后端开发者。1. Mock Draft 和“按你联赛来模拟”到底解决了什么问题1.1 Mock Draft 是什么为什么赛前都要练Fantasy Football 是一种由真实 NFL 球员数据驱动的虚拟球队经营游戏。赛季开始前所有参与者要聚在一起进行一次“球员选秀”用有限名额把各位置球员选进自己的队伍。现实里你不可能把所有球员都试一遍于是平台提供了 Mock Draft用 AI 模拟其他玩家陪你完整走一遍选秀流程让你提前熟悉策略、观察球员行情。Mock Draft 的核心价值是“练习”。比如你打算在第 2 轮拿一个跑卫那就要训练自己在第 1 轮面对什么阵容时敢拿外接手、什么时候必须抢跑卫。如果模拟环境里的球员顺序和你真实参加的联赛差别太大这种练习反而会误导你做出错误判断。1.2 通用模拟器哪里不够“真实”多数通用 Mock Draft 使用的是平台默认参数或者是全站用户综合出来的平均选秀顺位ADP。ADP 本身没问题问题是它抹掉了“联赛规则差异”这个关键变量。举例来说在标准的 12 人 1 QB 联赛里四分卫普遍要到第 4、5 轮才开始被连续抢走。但在 Superflex 或 2 QB 联赛里四分卫的战略价值直线上升第一轮就出现 Patrick Mahomes 和 Josh Allen 并不奇怪。同样一个外接手在 full PPR每次接球得 1 分和 standard接球不得分规则下的价值能差开一到两个轮次。TE Premium 联赛会让近端锋的整体行情提前不少。阵容里是 2 个 WR 首发还是 3 个 WR 首发也会改变外接手的稀缺程度。这些差异都建立在“同一个联赛的不同规则”上。用一个静态 AD P列表去模拟全世界所有 league结果必然失真。Realer Mock Draft 想做的就是让用户先把“自己的联赛规则”描述清楚再让模拟器基于这套规则生成选秀。核心思路不是 AI 多聪明而是“输入参数”终于对了。1.3 Skill File 这个设计想表达什么Skill file 这个名字在不同牛客论坛和项目里指代不一样。有的团队里“skill” 指语音助手的技能包有的指 LLM Agent 的领域能力描述文件。但单纯从 Realer Mock Draft 这个标题来理解它更像是在表达把“模拟一次选秀所需的环境整体”打包成一份文件这份文件可以随你的 league 变化而替换。工程上我们可以把这种设计统称为“规则外置”。也就是说代码里不硬编码“12 人里 1 QB、2 RB、2 WR……”这些内容而是让规则文件成为唯一事实来源。这样一来不同 league 可以共用同一套引擎规则变更走配置评审而不是改代码文件本身可以被版本管理、校验、分享。本文后面的整个项目就是围绕“规则外置”这个理念展开的。我们先写一份自己的 skill file然后让引擎去读取它、计算它、最终用它完成整个 Mock Draft 模拟。2. 整体设计与环境准备2.1 核心流程规则固化 → 得分反算 → 位置价值修正 → 选秀模拟一个“按真实联赛规则跑”的 Mock Draft 模拟器最少要经过四层处理规则文件层用 YAML 描述队伍数量、阵容位置、计分规则。得分反算层把每个球员的历史统计或赛季预测换算成你这个 league 规则下的 Fantasy Points。位置价值修正层只比较总分还不够。不同位置的可替代性差异很大我们需要用 VORPValue Over Replacement Player超出替换球员的价值等方式把“位置稀缺性”算进球员估值里。选秀引擎层按照蛇形选秀snake draft顺序让每支 AI 队伍在“自己还需要的位置”和“当前分值最高的球员”之间做权衡。简单来说规则文件决定了第 2、3、4 层的一切输出。同一个球员在不同规则文件下会得到不同的得分和不同的选秀优先级这正是“基于 YOUR league”的关键所在。为了能实际运行本文会实现一个最小可运行的 Python 版本。注意球员预测数据只用于演示 engine 逻辑不代表任何真实赛季的预测结果真实项目中你应该接入自己信任的赛季预测数据源。2.2 项目目录与环境版本项目目录建议这样组织realer-mock/ ├── config/ │ └── league_demo.yaml # 我们的 skill file ├── data/ │ └── sample_players.py # 演示用球员数据模拟 ├── src/ │ ├── __init__.py │ ├── scoring.py # 计分与 VORP 计算 │ ├── draft_engine.py # 选秀引擎与 AI 策略 │ └── main.py # 入口 └── requirements.txt # pyyaml版本方面不需要太纠结本文示例以常见环境为例Python 3.9 及以上PyYAML用于解析规则文件纯标准库完成模拟逻辑不依赖 pandas。安装依赖pip install pyyaml如果你的项目环境版本不同只需要保证 YAML 解析正常核心代码逻辑可以原样迁移。后面代码文件路径都按照上面的目录结构说明方便你对照创建。3. 编写你的第一份 Skill File 联赛规则文件3.1 YAML 配置结构先看一份可运行的联赛规则文件。为了控制示例篇幅这里用 3 支队伍演示真实项目中改成 12 支队伍即可。关注结构不要被队伍数量限制住。文件路径config/league_demo.yamlleague: name: 3队演示联赛 team_count: 3 draft_type: snake roster: QB: 1 RB: 1 WR: 2 TE: 1 FLEX: 1 BENCH: 4 scoring: pass_yd_per_pt: 25 pass_td: 4 int: -2 rush_yd_per_pt: 10 rush_td: 6 rec_yd_per_pt: 10 rec_td: 6 ppr: 1 flex_positions: - RB - WR - TE strategy: need_boost: 1.2这份文件就是我们要演示的“skill file”。它把一次选秀需要的联赛级参数都放在了一起。3.2 配置字段逐一拆解roster表示阵容总共需要的球员数量。一支队伍需要 1 个 QB、1 个 RB、2 个 WR、1 个 TE、1 个 FLEX、4 个替补。rounds即所有位置数量之和可以由程序自动计算也可以手写。scoring是计分规则的核心。不同平台有不同的叫法常见包括pass_yd_per_pt每多少传球码数得 1 分例如 25 表示 25 码 1 分pass_td每次传球达阵得分int每次被抄截扣分rush_yd_per_pt每多少冲球码数得 1 分rush_td冲球达阵得分rec_yd_per_pt接球码数换算比例rec_td接球达阵得分ppr每次接球单独加的分数full PPR 为 1half PPR 为 0.5standard 为 0。flex_positions表示 FLEX 位可以放哪些位置。普通联赛一般是 RB、WR、TE如果你是 Superflex 或 2 QB 联赛可以把QB加进去。strategy.need_boost是模拟器 AI 的一个权重系数。它表示当某个位置仍然缺少首发球员时选秀引擎会给该位置球员多少额外加分。这套机制属于模拟器策略层不是联赛规则本身。放在 skill file 里只是便于调试不同风格的 AI。3.3 为什么用独立文件而不是写死在代码里把规则抽成独立文件的收益在项目变复杂后会越来越明显。第一是避免硬编码。选秀引擎、计分函数如果全部依赖“1 QB、2 RB、2 WR”的假设换一种联赛就要改代码很容易改出一堆 bug。第二是可校验、可回滚。规则文件可以被 Load 后进行 schema 校验发现team_count为 0 时直接启动失败而不是到第 5 轮模拟时才暴露问题。第三是便于分享和自动化测试。你可以保存不同年份、不同 league 的规则文件跑回归测试确保新引擎逻辑不会破坏旧规则下的输出。第四是职责分离。联赛管理者和引擎开发者可以各自维护自己关心的部分。规则文件更像 DSL领域特定语言帮助我们用一种可读的方式描述“你的 league 长什么样”。4. 实现得分计算与 VORP 估值4.1 生成演示球员数据为了让代码开箱即用同时避免手动录入几百名球员本文用一个带随机种子的函数生成演示数据。每个位置有若干位球员顺序靠前表示基础水平更高。具体的统计数值只是为了演示计算过程不要当作真实预测。文件路径data/sample_players.py# data/sample_players.py import random SAMPLES { QB: [ Patrick Mahomes, Josh Allen, Jalen Hurts, Lamar Jackson, Joe Burrow, C.J. Stroud, Dak Prescott, Justin Herbert ], RB: [ Christian McCaffrey, Bijan Robinson, Breece Hall, Jahmyr Gibbs, Bijan Robinson, Derrick Henry, Saquon Barkley, Jonathan Taylor, Josh Jacobs, Rhamondre Stevenson ], WR: [ Tyreek Hill, Justin Jefferson, CeeDee Lamb, Amon-Ra St. Brown, JaMarr Chase, A.J. Brown, Puka Nacua, Marvin Harrison Jr., Garrett Wilson, DeVonta Smith, Jaylen Waddle, Tee Higgins ], TE: [ Travis Kelce, Sam LaPorta, Mark Andrews, Trey McBride, Dalton Kincaid, Kyle Pitts ], } BASELINES { QB: {pass_yd: 4200, pass_td: 30, int: 12, rush_yd: 250, rush_td: 3}, RB: {rush_yd: 900, rush_td: 7, rec: 40, rec_yd: 300, rec_td: 2}, WR: {rec: 85, rec_yd: 1050, rec_td: 7}, TE: {rec: 60, rec_yd: 650, rec_td: 5}, } def make_players(seed2024): rng random.Random(seed) players [] for pos, names in SAMPLES.items(): base BASELINES[pos] for idx, name in enumerate(names): # idx 越大代表该位置排名越靠后 factor 1.0 - idx * 0.03 rng.uniform(-0.02, 0.02) stats {key: int(value * factor) for key, value in base.items()} players.append({name: name, pos: pos, stats: stats}) return players这段代码的真实目的不是生成完美数据而是让后面的计分和选秀逻辑有一个可持续运行的输入源。你在生产项目里可以把make_players()替换成从 API 或 CSV 读取真实预测数据。4.2 联赛规则反算 Fantasy Points拿到球员的底层统计之后第一步是按照 skill file 中的 scoring 规则计算每个球员的总得分。关键是不要只算“某一种默认规则下的分数”而是让函数完全由规则参数驱动。文件路径src/scoring.py# src/scoring.py from data.sample_players import make_players def calc_points(stats, rules): 根据规则文件中的 scoring 配置计算 fantasy points。 pts 0.0 if pass_yd in stats: pts stats[pass_yd] / rules.get(pass_yd_per_pt, 25) pts stats[pass_td] * rules.get(pass_td, 4) pts stats[int] * rules.get(int, -2) if rush_yd in stats: pts stats[rush_yd] / rules.get(rush_yd_per_pt, 10) pts stats[rush_td] * rules.get(rush_td, 6) if rec in stats: pts stats[rec] * rules.get(ppr, 0) pts stats[rec_yd] / rules.get(rec_yd_per_pt, 10) pts stats[rec_td] * rules.get(rec_td, 6) return round(pts, 1) def compute_league_points(players, rules): 为一批球员计算各自 league 下的总得分。 for player in players: player[points] calc_points(player[stats], rules) return players为什么要把规则参数从配置里读出来而不是直接写pts yards / 25因为如果规则文件把pass_yd_per_pt改成 20代码不需要任何变化引擎自动就会使用新比例。这种解耦在自定义联赛规则时非常重要。4.3 替换水平与 VORP 修正只看绝对总分是不够的。例如 TE 球员即使再强总体得分通常也低于外接手但 TE 位置首发人数少市场上可替代的优质 TE 更少。因此我们在选秀时要看“这个球员比该位置的替换水平高多少”也就是 VORP。替换水平的简单估算方法是根据联赛设置算出某个位置总共需要多少首发名额然后取该位置排名恰好落在最后一个首发名额处的球员作为替换水平。def compute_replacement_baseline(players, pos, starts_needed): same_pos [p for p in players if p[pos] pos] same_pos.sort(keylambda p: p[points], reverseTrue) idx min(starts_needed, len(same_pos)) if idx 0: return 0.0 return same_pos[idx - 1][points] def add_vorp(players, roster, team_count): 为每个球员计算 VORP。 roster 示例: {QB: 1, RB: 1, WR: 2, TE: 1, ...} for pos, per_team_starts in roster.items(): if pos in (FLEX, K, DEF, BENCH): # FLEX/BENCH 不参与位置淘汰线计算 continue starts_needed per_team_starts * team_count baseline compute_replacement_baseline(players, pos, starts_needed) for player in players: if player[pos] pos: player[vorp] round(player[points] - baseline, 1) return players这里有意把 K、DEF、BENCH 跳过。真实平台里 K 和 DEF 也有自己的策略但初学者入门时可以先聚焦核心技能位。VORP 算出来之后选秀引擎就有了一个跨位置可比较的量它不是“谁总得分高谁先走”而是“谁比该位置替补水平高出更多谁先走”。5. 实现 Mock Draft 模拟引擎5.1 蛇形选秀顺序Fantasy Football 最常用的选秀方式是 snake draft第一轮按 1 到 N 顺序选第二轮按 N 到 1 倒序选第三轮再反过来。这样能保证第一轮第一位选的玩家在第二轮最后才选整体相对公平。def snake_schedule(team_count, rounds): 生成蛇形选秀顺序列表每个元素是队伍编号。 order [] for r in range(rounds): if r % 2 0: order.extend(range(team_count)) else: order.extend(range(team_count - 1, -1, -1)) return order如果你配置了 3 支队伍、每队 10 个选秀名额那么顺序就是0 1 2 2 1 0 0 1 2 2 1 0 ...5.2 队伍状态与位置限制每支队伍需要维护自己已经选了哪些球员以及每个位置还能不能继续选。这里考虑最基础的限制一支队伍不能超过roster中某个位置的上限。例如 roster 里 QB 只有 1 个那么队伍一旦选了 QB后面的轮次就不能再选 QB 了。如果 BENCH 还可以容纳球员其他位置的球员依然可以选择。class Team: def __init__(self, tid, name, roster): self.tid tid self.name name self.roster roster self.picks [] def can_take(self, pos): 该位置是否还有名额。 max_count self.roster.get(pos, 0) have sum(1 for p in self.picks if p[pos] pos) return have max_count def need(self, pos): 该位置距离首发名额还缺几个。 starter_count self.roster.get(pos, 0) have sum(1 for p in self.picks if p[pos] pos) return max(0, starter_count - have) def summarize(self): return [p[pos] p[name] for p in self.picks]这里的need只统计了 roster 中的固定首发位置。FLEX 是额外位置可以根据 skill file 里的flex_positions灵活处理。为了入门我们先把 FLEX 当作“只要没选满 FLEX 名额就算该位置还有需求”。选秀越到后面每位经理面临的限制条件越复杂首发还没凑齐的要先补首发首发齐了的可以开始选替补或者囤积高潜力球员。这种限制正是真实选秀最有意思的部分也是模拟器比分差容易最大的地方。5.3 AI 策略位置需求与 VORP 加权现在开始写核心选秀策略。每一轮AI 从剩余球员里选出“最适合自己”的一个。简单的做法是先过滤掉当前队伍不能选的位置计算每个候选球员的得分基础分是 VORP如果这个位置还没凑齐首发就给一个需求加成选择得分最高的球员。def evaluate_pick(team, player, strategy): score player[vorp] need team.need(player[pos]) # 如果首发位置还缺人提高该位置的优先级 if need 0: score * strategy[need_boost] return score def pick_for(team, available_players, strategy): candidates [ p for p in available_players if team.can_take(p[pos]) and p[pos] in [QB, RB, WR, TE] ] if not candidates: return None return max(candidates, keylambda p: evaluate_pick(team, p, strategy))这个策略优点是简单、可解释。缺点是它没有考虑 FLEX 位具体放什么位置收益最大也没有引入“未来轮次的供需预判”。真实项目里可以把它升级成更复杂的策略例如给近几轮做简单 mock 回溯、在 QB/WR 之间用动态替换阈值判断。下面的流程函数把前面所有模块串起来def run_draft(cfg): team_count cfg[team_count] roster cfg[roster] rules cfg[scoring] strategy cfg.get(strategy, {need_boost: 1.2}) from data.sample_players import make_players from scoring import compute_league_points, add_vorp players make_players() players compute_league_points(players, rules) players add_vorp(players, roster, team_count) teams [ Team(i, fTeam {chr(65 i)}, roster) for i in range(team_count) ] rounds sum(roster.values()) schedule snake_schedule(team_count, rounds) available
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Linux 内核高危漏洞解析:bridge 桥接模块组播快速离开路径 UAF 风险 2026/9/5 4:30:40

Linux 内核高危漏洞解析:bridge 桥接模块组播快速离开路径 UAF 风险

2026‑08‑15,NVD 披露 CVE‑2026‑74480 严重级别漏洞,CVSS 评分 9.8,归属 net/bridge 桥接子系统,为组播快速离开逻辑引发的释放后使用漏洞。该缺陷代码最早在 2017 年 1 月提交引入,覆盖此后大量内核版本。目前已有…

阅读更多 →
STM32开发环境优化:VSCode+OpenOCD组合替代CubeIDE的实践指南 2026/9/5 4:30:40

STM32开发环境优化:VSCode+OpenOCD组合替代CubeIDE的实践指南

1. 为什么我放弃纯CubeIDE,改用VSCode这组合如果你用STM32开发超过半年,大概率会经历这样一个过程:刚开始用Keil,后来被ST官方生态吸引转到CubeIDE,用了一阵子觉得代码提示和编辑器体验实在跟不上,于是开始…

阅读更多 →
Cortex-M走向何方:指令集、工具链与AI落地的全面演进 2026/9/5 4:30:40

Cortex-M走向何方:指令集、工具链与AI落地的全面演进

一、Cortex-M 家族这条产品线,为什么会让人越看越迷茫 Cortex-M 这个名字,在嵌入式圈子里几乎是"单片机"的代名词。我接触的很多工程师,手里的活儿从 STM32F103 干到 GD32、国民技术、沁恒,折腾来折腾去,架构…

阅读更多 →
无人机视角航拍河道水面塑料垃圾检测数据集VOC+YOLO格式1320张1类别有增强 2026/9/5 4:30:40

无人机视角航拍河道水面塑料垃圾检测数据集VOC+YOLO格式1320张1类别有增强

注意数据集存在大量增强,原图110张,其他都是通过改变亮度对比度加噪声旋转等形成图片数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件)图片数量(jpg文件个…

阅读更多 →
AI生成头发质感优化:从死气沉沉到生动逼真的技术实践 2026/9/5 4:30:40

AI生成头发质感优化:从死气沉沉到生动逼真的技术实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
备战2027国自然,用什么工具才能弯道超车? 2026/9/5 4:27:39

备战2027国自然,用什么工具才能弯道超车?

每一年国自然放榜结束,都会拉开新一轮科研人的差距。有人沉浸在今年落选的遗憾里原地内耗,有人早已抓住放榜黄金窗口期,提前布局来年申报。绝大多数常年陪跑的科研人,并非科研实力不足、实验积累薄弱,而是输在信息闭塞…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞