This week's tight end touchdown bets: best value, longshots, due for a TD, fades, and how past picks did.
Sports & fitnessHTML · JavaScriptGitHub @mattrudy13OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
NFL tight end stats, usage and touchdown odds, updated through the season
Taken from the repository README
Loading the full README.
Read the full README ↗Estimates each NFL tight end's chance to score a touchdown from usage data, compares it with sportsbook prices, and grades the picks after the games.
Site: https://mattrudy13.github.io/te-tracker/
Best Bets (index.html, the home page): this week's TD picks in six categories: most likely to
score, best value, longshots, due for a TD, fades, and first-TD value. Each pick shows the best price, model %, market %, edge,
expected profit per $10 bet and a one-line reason. Below that is a track record of every past pick, graded after
the games (W–L–void, hit rate, units, ROI, and closing-line value). Players listed as Out, Doubtful or on IR are never picked.
Matchups (matchups.html): this week's games (next week's once Monday night is final) with
kickoff, network, spread, total and implied team points. Expand a game to see both teams' tight ends:
Finished games show the TE box score with the pregame TD price and receptions line, and how each one
landed. ?open=<gameId>,… keeps games expanded.
Leaders (leaders.html): usage and touchdown leaderboard, per game or season totals: targets, target
share, receptions, catch %, yards, yards per target, TDs, red-zone targets, xTD and TD − xTD, plus this
week's receptions line and best anytime-TD price. Filter by team, week range, minimum targets or your
★ watchlist.
Touchdowns (touchdowns.html): this week's anytime-TD or first-TD odds for every TE (an Anytime / 1st TD toggle,
?mkt=first; sorted by model-vs-book edge), red-zone usage
(targets inside the 20 / 10 / 5), expected TDs (xTD) vs actual, and the TE touchdown rate by
field position that the model uses.
Player (player.html?id=<espn id>, click any player name): this week's game with the receptions line
(best over/under, how often the player has gone over it), anytime-TD and first-TD model vs book; season usage; touchdown
profile; charts of targets, receptions against the line, red-zone targets and yards; and a game log.
Filters live in the URL, so any view can be shared. The watchlist is saved in your browser.
No server and no build step: GitHub Actions write JSON into data/, and GitHub Pages serves the HTML plus
that JSON.
update-data.yml every 3 h Thu–Mon + Tue build-data.mjs (ESPN) → grade-picks.mjs
snapshot-odds.yml Thu 11 AM + Sun 4 AM ET build-data.mjs → fetch-odds.mjs (The Odds API) → record-picks.mjs
check-assets.yml on push stamp-assets.mjs --check
↓
data/2026.json · odds-2026.json · picks-2026.json → GitHub Pages
| File | What it is |
|---|---|
index.html, matchups.html, leaders.html, touchdowns.html, player.html | The pages. Each keeps its page logic in an inline script |
model.js | Data and model code (aggregation, TD model, injuries, best bets), shared by the pages and the scripts |
common.js | Browser helpers: data loading, formatting, tables, URL state, watchlist |
style.css | All styles (light/dark) |
scripts/ | build-data, fetch-odds, record-picks, grade-picks, stamp-assets (Node 20+, no dependencies) |
data/ | Generated JSON, committed by the workflows |
scripts/build-data.mjs:
data/positions-<season>.json).Games already in the file are reused, so a rerun only fetches new games. A game that can't be read (an ESPN error, or a game roster that isn't posted yet) is skipped and tried again on the next run. Picks on it aren't graded until it goes through.
.github/workflows/update-data.yml runs every 3 hours Thursday–Monday plus Tuesday morning
(UTC) from September through February. It commits only when the data changed. You can also run it
by hand from the Actions tab ("Run workflow"). Both data workflows build from the latest main, so
re-running an old failed run works, though the next scheduled run catches up on its own.
scripts/fetch-odds.mjs takes a snapshot of tight end props from The Odds API:
anytime-TD and first-TD-scorer prices and the receptions over/under, from US-licensed books only (DraftKings, FanDuel, BetMGM,
Caesars, BetRivers, ESPN BET, Fanatics…). Offshore books (BetOnline.ag, Bovada, BetUS, MyBookie, LowVig) are
dropped before the best price and market median are chosen, so every price on the site can be bet at a
licensed book. It writes data/odds-<season>.json.
.github/workflows/snapshot-odds.yml runs it Thursday at 11 AM ET (before TNF) and Sunday at 4 AM ET. Both are early on purpose: GitHub often
starts scheduled runs hours late.
Games that have already kicked off keep their earlier snapshot. GitHub can start scheduled runs hours
late (the first Thursday run started at 9 PM ET, after TNF kickoff), so TNF picks may come from an earlier snapshot. Each snapshot costs 1 credit per market per
game (3 markets, ~15 games), so about 30–45 credits, ~85 a week and ~375 a month. That leaves ~125 for
manual runs on the free tier (500/month). Each run logs the credits left.
The key is the ODDS_API_KEY repository secret, so it's only used inside the workflow. Set or replace it
with gh secret set ODDS_API_KEY -R mattrudy13/te-tracker. To run locally:
ODDS_API_KEY=... node scripts/fetch-odds.mjs. Without the file, the odds columns just show "–".
First-TD markets carry a 25–40% margin, so the snapshot also stores a de-vigged fairProb: each book's
prices are divided by that book's total implied probability over every first-TD outcome in the game
(books listing fewer than 20 outcomes are skipped, since a partial list understates the margin).
Sportsbooks post most TE props midweek, so a Monday-night or Tuesday run prices only a few players. Thursday's snapshot is the one that counts.
Where the odds show up:
The picks logic is bestBets() in model.js. That file is shared by the site and the Node scripts, so the
Action computes exactly what the page shows.
| Category | Rule (top 5 each) |
|---|---|
| Most likely to score | Highest model TD % |
| Best value | Expected profit > 0 at the best price, price under +400, model ≥ 15%, ≥ 2 books |
| Longshots | Price +400 or longer, expected profit > 0, at least 1 red-zone target, at least 2 targets in the team's last 2 games |
| Due for a TD | TD − xTD ≤ −0.8 with ≥ 3 red-zone targets |
| Fades | Price +250 or shorter and model at least 8 points below the market |
| First TD value | First-TD bet: expected profit > 0 at the best price, model ≥ 4%, ≥ 2 books, ≥ 1 red-zone target, ≥ 2 targets in the team's last 2 games |
scripts/record-picks.mjs runs after each odds snapshot, following a fresh stats/injury build. It saves the
picks to data/picks-<season>.json. Picks lock at kickoff: Sunday's snapshot only replaces picks for games
that haven't started. A replaced pick keeps the price it was first picked at (openPrice, openMarketProb), so
the last snapshot before kickoff serves as the closing line.scripts/grade-picks.mjs runs after every stats build. It grades final games: a TD (receiving or rushing)
wins, a box-score line without a TD loses, and no game row (inactive, or dressed but never took a snap) is void. Profit is stored per $100 at the recorded price; the page shows it per $10 bet.
Fades are graded right/wrong only. First-TD picks win only if the player scored the game's first TD
(firstTd on the game row) and lose otherwise, including games with no TD. Reruns don't change graded picks.model.js.TD_PER_POINT (0.105, about
2.4 TDs on 23 points). If TDs arrive independently, the first one is the player's with chance λ/Λ, and
there is one at all with chance 1 − e^(−Λ): P = λ/Λ · (1 − e^(−Λ)). TD_PER_POINT isn't calibrated against
the first-TD market yet. Over weeks 1–4 a TE scored first in 10 of 64 games (16%); the model's TE share is about 20%.build-data.mjs reads ESPN's scoringPlays and flags the TE row that scored the game's
first TD (firstTd: true). Games processed before this existed were filled in with
node scripts/build-data.mjs --backfill-first-td.GitHub Pages lets browsers cache files for 10 minutes. The pages load model.js, common.js and
style.css with a content-hash stamp (model.js?v=1a2b3c4d), so a new page never runs against a stale
cached script. After editing any of those three files, run node scripts/stamp-assets.mjs before
committing. The "Check asset stamps" workflow fails on a push if you forget.
node scripts/build-data.mjs # current season; or pass a year
python3 -m http.server 8000 # then open http://localhost:8000
Ideas and next steps are in ENHANCEMENTS.md.