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).

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.