Help us shape the future of Go! Take the 2026 Go Developer Survey

pick6

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT

README

a pixel football with little legs

pick6

Terminal war room for fantasy football drafts. Live-syncs Sleeper, tracks tiers, and tells you when the RB run means the value's gone.

status

Milestone 5 of 6. Every subcommand is built: the board renders, live-syncs a Sleeper draft, runs offline off hand-typed picks, and thinks — survival probability, urgency ordering, replacement level, cliff and run alerts. Left is the opponent-aware simulation (milestone 6).

pick6 in a mock draft

A scripted room drafts, the cliff banner fires, the ranking re-sorts under it; a stops the clock, / finds a player — Devonta Smith at 8% to survive, Jaxon Smith-Njigba already gone at 1.10 — and tab drops to every number the engine holds. Every position wears Sleeper's own colour, because the board is read next to Sleeper's draft screen all night. It's recorded from the real thing: docs/demo.tape, vhs docs/demo.tape to regenerate.

install

go install github.com/trisslazaj/pick6/cmd/pick6@latest

That puts the binary in go env GOPATH/bin, which is not on most people's PATH by default. Either add it, or install somewhere that already is:

GOBIN=$HOME/bin go install ./cmd/pick6

No Go toolchain? Every tagged release carries prebuilt darwin and linux binaries on the releases page; pick6 version says which build you're on.

Working on the code instead? go run ./cmd/pick6 <cmd> always runs current source, with nothing to reinstall.

use

pick6 fetch                  # pull data (do this first)
pick6 live <draft_id> -user yourname   # the main event: sync a live sleeper draft
pick6 board                  # same board, no feed: you type the picks as they happen
pick6 mock                   # watch a scripted draft play out on the real board
pick6 mock -auto=false       # step through it yourself with space
pick6 tiers                  # print the current tier board
pick6 mock -snapshot 26      # print one frame, no tui (for screenshots)

pick6 board is the offline path: no draft id, no polling, no Sleeper. Find a player with /, mark him gone with x, take it back with u. Everything downstream is identical — the engine never asks where a pick came from — so it's also the way to run this against an in-person draft, or any platform with no API.

In the TUI, tab flips between the board and a full data table — every available player with value, tier, ADP, spread, survival and format spread on one screen (j/k scrolls, p filters by position). The board abstracts; the table doesn't. / searches from either tab: type a name or a team code, and taken players stay in the results with the pick that took them, because "is he gone?" is the same question.

go run ./cmd/pick6 fetch

Pulls the Sleeper player pool, half-PPR ADP, and player values; matches everything onto Sleeper player ids; derives value tiers; and caches it all under ~/Library/Caches/pick6/. Re-running inside 12 hours is served entirely from disk.

pick6 fetch -format half-ppr -teams 12
pick6 fetch -rankings my-rankings.csv     # your tiers and points win

Bring your own rankings and they take priority over everything fetched. Column order doesn't matter and unknown columns are ignored, so FantasyPros exports and hand-made files both load:

name,position,team,tier,points

the math, in plain english

The whole board answers one question: what does waiting cost?

Survival. Every player has an ADP — the average pick where real drafts take him, measured across about a thousand recent drafts — and a spread, because the market doesn't agree on everyone equally: a locked-in first-rounder goes inside a two-pick window, a late flier goes anywhere across three rounds. The chance he lasts to pick $p$ is a logistic S-curve centred on his ADP, with the width set by his own spread:

$$S(p) = \frac{1}{1 + e^{(p - \mathrm{adp})/\sigma}}$$

ADP already passed → $S$ near 0, probably gone. ADP far ahead → near 1, safe. And at $p = \mathrm{adp}$ it's exactly $\tfrac{1}{2}$ — a coin flip, which is literally what ADP means: half the rooms had taken him by then.

The width comes from the measured standard deviation of his real draft slot (for a logistic distribution $\mathrm{stdev} = \sigma \pi / \sqrt{3}$):

$$\sigma = \frac{\sqrt{3}}{\pi}\thinspace\mathrm{stdev} \approx \frac{\mathrm{stdev}}{1.8138}, \qquad \sigma \ \text{clamped to} \ [0.5,\ 25]$$

So a locked-in star (stdev ≈ 1) gets a near-step-function — one pick past his ADP and he is simply gone — while a volatile flier (stdev ≈ 40) gets a nearly flat curve: stop panicking, he keeps.

Your room, not the market. ADP is a thousand strangers; a home league is a dozen people who already know what each other do. Measured over three completed drafts from this user's leagues, they take the first quarterback at pick 23.0 and the first tight end at 23.3, against 26.1 and 39.5 nationally. So at the top of each position — where that appetite actually lives — the curve is centred on a blend of the two prices instead:

$$\mathrm{adp_{eff}} = w \cdot \mathrm{adp_{room}}(P, k) + (1 - w) \cdot \mathrm{adp}, \qquad w = \frac{n}{n + 2}$$

where $\mathrm{adp_{room}}(P, k)$ is the mean pick at which those drafts took the $k$-th player at position $P$, over the $n$ of them that ever got that deep. Every player the curve reaches is repriced.

And this is the shakiest thing in the tool, so it says so. Those three drafts are three different leagues — 9, 5 and 6 shared managers out of 12 — so the curve is a portrait of casual home leagues, not of one room. It used to reprice only the first five at each position, on the theory that a ranked list runs deeper than any finite draft and the tail is therefore junk. That cutoff won cleanly on the first backtest fold and then lost on both folds that arrived later — on both metrics, on the per-position gate, and at a common board size — so it was removed. Worse, the fold that chose it had a curve built entirely from drafts that happened a year after it, which no live board could ever have; holding out each fold's future leaves that fold with no curve at all. So the argument for a cap is still readable and the measurement never backed it. pick6 calibrate re-derives all of this on every run, and -room=false prices the whole board on the market's numbers.

One honesty adjustment: a player who's on the board right now can only be taken by the picks between now and your turn. So the number shown is survival conditioned on the present:

$$p_{\text{survive}} = \frac{S(\text{nextPick})}{S(\text{pickNo})}$$

Without that, a player the room keeps passing on reads 90% gone when only one team picks before you do. Deep past his ADP the ratio's tail becomes a clean per-pick hazard, $e^{-(\text{nextPick} - \text{pickNo})/\sigma}$ — each pick that passes costs him the same factor, no matter how far he's fallen. (It's computed in log space so the extremes don't flatten; see internal/engine/urgency.go.)

One correction on top of that. Exactly $N$ players come off the board before your turn — that's what a draft is — but treating every survival as independent "expects" $\sum_j (1 - p_j)$ of them to go, and that sum is measurably not $N$. Backtested against three real drafts, the model was pessimistic on all of them. So raise every probability to the single power that reconciles them:

$$\text{find } c \gt 0 \ \text{ with } \ \sum_j \big(1 - p_j^{\thinspace c}\big) = N, \qquad \tilde p_j = p_j^{\thinspace c}$$

It's the gentlest fix there is: $\ln p$ is a player's hazard over those picks, so $p^c$ scales everyone's hazard by the same factor. Certainties stay certain ($1^c = 1$), nobody overtakes anybody, and $\tilde p$ is what every number on screen means.

Urgency. Per position: the best player available now, versus the expected value of whoever is still there at your next pick. Line the position up best-first — the best survivor is #1 if he survives, #2 only if #1 is gone and #2 survives, and so on:

$$\mathbb{E}[\text{best later}] = \sum_j v_j\thinspace \tilde p_j \prod_{i \lt j} \big(1 - \tilde p_i\big)$$

$$\text{urgency} = \Big( v(\text{bestNow}) - \mathbb{E}[\text{best later}] \Big) \times \text{need}$$

— where need is 1.0 for an open starter slot, 0.6 for flex depth, 0.25 for bench, and 0 for kickers and defenses until the last rounds. The expected drop is what waiting costs; need is whether you should care. The board sorts by it. And because it's an expectation rather than a cutoff, nothing jumps: a player crossing 50% doesn't re-sort the board on a rounding error. The wait signal is its own claim now — safe to wait, green, when the best man himself is more likely than not to still be sitting there.

Counting. Tiers come from human rankings; where no file covers, they're derived by breaking the value curve wherever it genuinely drops:

$$\frac{v_{i-1} - v_i}{v_{i-1}} \gt 0.10 \quad\text{and}\quad v_{i-1} - v_i \ \ge\ 0.015 \cdot v_{\max}$$

A tier is a cliff when it probably won't reach you, which is a probability and not a headcount — three players the room is about to eat is a worse cliff than one player nobody wants:

$$p_{\text{hold}} = 1 - \prod_j \big(1 - \tilde p_j\big) \quad \text{over what's left in the tier}$$

Under 50% is amber, under 15% is red, and a tier nobody has drafted out of yet is never either. Four of the last six picks at one position is a run. And a player still available a full spread past his ADP,

$$\frac{\text{pickNo} - \mathrm{adp}}{\sigma} \ \ge\ 1,$$

is falling: the draft moved past his price and he's still here. His numbers turn amber. That's a discount.

The long version is docs/pick6-engine.tex — every formula above derived from its definition, the calibration methodology and the measured backtest results, and the ideas that were tried and didn't work. It's written for someone who likes math but hasn't formally studied it, so every symbol gets introduced before it's used; make paper builds the PDF.

data sources

license

MIT — see LICENSE.

Directories

Path Synopsis
cmd
pick6 command
internal
adp
Package adp fetches ADP from several sources and merges them onto one scale.
Package adp fetches ADP from several sources and merges them onto one scale.
cache
Package cache is a dumb disk cache: fetch a URL, keep the bytes, don't refetch today.
Package cache is a dumb disk cache: fetch a URL, keep the bytes, don't refetch today.
engine
Package engine holds the draft state and the pure math over it: snake arithmetic, roster filling, cliff and run detection.
Package engine holds the draft state and the pure math over it: snake arithmetic, roster filling, cliff and run detection.
rankings
Package rankings handles name normalization and matching source players onto Sleeper player ids.
Package rankings handles name normalization and matching source players onto Sleeper player ids.
sleeper
Package sleeper talks to the Sleeper API.
Package sleeper talks to the Sleeper API.
ui
Package ui holds the shared lipgloss palette and styles.
Package ui holds the shared lipgloss palette and styles.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL