Backcaster
Backcaster is a Python CLI that produces Marcel-style MLB projections from the prior three seasons of data using local Lahman CSV files and pandas.
It supports:
- Single player projections (
player) - Single team projections (
team) - League-wide projections (
hitters,pitchers, orbatch) - Output as terminal table, CSV, or HTML
Installation
Requirements
- Python 3.13+
- An extracted Lahman CSV database covering the three prior seasons
- Internet access for package installation only; projections run offline
Update an installed package
Upgrade to the latest release available on PyPI:
python -m pip install --upgrade backcaster
To install a specific release, such as 0.4.1:
python -m pip install --upgrade backcaster==0.4.1
Run the command using the same Python environment where you run Backcaster. If you use a virtual environment, activate it first. Check the installed version with:
python -m pip show backcaster
Install from source (recommended for contributors)
# from repo root
uv sync
This installs runtime dependencies and the backcaster CLI entrypoint defined in pyproject.toml.
Install in editable/dev mode
uv sync --extra dev
This adds test/lint/type-check/build tooling (pytest, pytest-cov, ruff, mypy, pandas-stubs, build).
Build/install as a package
python -m build
pip install dist/backcaster-*.whl
Quickstart CLI usage
All commands require --year, the target projection year.
The player, team, and batch commands also require --kind batting|pitching.
The hitters and pitchers commands choose their kind automatically.
Subcommand-specific required options:
playerrequires--nameteamrequires--teambatch,hitters, andpitchersrequire no player or team option
backcaster <player|team|batch> [subcommand-options] --year <YEAR> --kind <batting|pitching> [--format cli|csv|html] [--out PATH]
backcaster <hitters|pitchers> --year <YEAR> [--data-dir PATH] [--format cli|csv|html] [--out PATH]
Pre-offseason league projections
Once the latest regular season is present in your local Lahman files, generate both populations without roster updates or team filters. For terminal output:
backcaster hitters --year 2026
backcaster pitchers --year 2026
To save files for comparison with Baseball Reference:
backcaster hitters --year 2026 --format csv --out hitters_2026.csv
backcaster pitchers --year 2026 --format csv --out pitchers_2026.csv
--year 2026 projects 2026 from 2023–2025; it never reads 2026 statistics.
Both commands support --data-dir, --format cli|csv|html, and --out.
They are shortcuts for batch --kind batting and batch --kind pitching.
For a different data directory, add --data-dir "C:\Baseball\lahman".
For HTML, use --format html --out hitters_2026.html (or a pitcher output filename).
File formats require --out; the CLI format prints to the terminal.
Players qualify for selection by meeting the existing PA/IP threshold in at least one of those three seasons. This is a historical eligibility list, not an active roster; pitchers who never reach the qualifying innings threshold (including most relievers) remain excluded. Qualification selects players without removing their other seasons from the calculation. Trades are combined into one player-season before qualification, and all teams' statistics contribute once. The output is one row per selected player, ready for comparison with reference projections.
1) Player projection
backcaster player --name "Mookie Betts" --year 2026 --kind batting
Offline projections with Lahman CSVs
All projections use local Lahman CSVs. On Windows, the default directory is
C:\Baseball\lahman; on other systems it is ~/Baseball/lahman.
Set the LAHMAN_DATA_DIR environment variable to change the default, or pass
--data-dir to override it for one command. No network requests or online
fallbacks are made. The directory must contain People.csv, Teams.csv, and
Batting.csv or Pitching.csv for the selected kind. Both kinds require valid
birthYear values in People.csv; batters additionally require Appearances.csv. Filenames are matched
without regard to case. The database is not bundled with Backcaster.
For the default Windows directory, no additional flag is needed:
backcaster player --name "Mookie Betts" --year 2024 --kind batting
See Lahman database notes for the supplied 1871–2025 edition's coverage, attribution, license, and historical-data caveats, with its original README.
backcaster player --name "Mookie Betts" --year 2022 --kind batting --data-dir "C:\Baseball\lahman"
backcaster team --team LAN --year 2022 --kind batting --data-dir "C:\Baseball\lahman"
backcaster batch --year 2022 --kind pitching --data-dir "C:\Baseball\lahman" --format csv --out pitchers.csv
Use Lahman's teamID values, such as LAN for the Dodgers (rather than the
FanGraphs abbreviation LAD). A projection year needs the preceding three seasons:
2022 requires 2019–2021. Missing seasons produce an error explaining which year
is unavailable. If the directory or required files are missing, the command
reports the missing file and directory; it never attempts an online fetch.
Offline qualification uses a consistent modern rule across historical seasons:
at least 3.1 PA per game for hitters and one inning per game for pitchers.
The season's game count is the maximum G in that year's Teams.csv records,
so shortened seasons have lower thresholds. The PA threshold is rounded to the
nearest integer (502 PA for 162 games, 186 PA for 60 games). This is an analytical filter, not a
reconstruction of historical award eligibility or batting-title exceptions.
Relievers generally do not qualify under this pitching rule.
Player stints are summed before qualification. Plate appearances are calculated
as AB + BB + HBP + SF + SH; missing/blank HBP, SF, and SH values count as zero.
Blank numeric counting-stat cells are also treated as zero; use a dataset with
complete records for the seasons you intend to project.
Pitching IPouts is divided by three for decimal innings, and BFP supplies
batters faced. Lahman player IDs keep different players with the same name separate;
ambiguous name lookups report the matching IDs.
Batter league regression and rebaselining use all non-pitcher player-seasons; qualification selects the displayed players. Pitching retains its qualified-only baseline, with real-age performance adjustments documented in the pitcher model. See the batter model for the six-stage calculation, real-age adjustment, PA formula, and explicit rebaselining choices. Eligibility means qualifying in at least one of the three preceding seasons. For every selected player, projections use all available seasons in that window, including nonqualified seasons and seasons spent with other teams. For team selection, a traded player's full-season totals are assigned to the team in their latest stint for that season; this is not a current-roster forecast.
2) Team projection
backcaster team --team LAN --year 2026 --kind pitching
3) Batch projection (all eligible players)
backcaster batch --year 2026 --kind batting
File outputs
backcaster player --name "Mookie Betts" --year 2026 --kind batting --format csv --out ./out/mookie_2026.csv
backcaster team --team LAN --year 2026 --kind pitching --format html --out ./out/lan_pitching_2026.html
--outis required forcsvandhtmlformats.
Output examples
CLI table output (--format cli, default)
Selected columns from a 2025 Mookie Betts projection (actual output includes additional counting statistics, reliability, and age):
Name Year PlayerID Age PA AB H HR BA OBP SLG OPS
Mookie Betts 2025 bettsmo01 33 527 461 128 24 0.278 0.362 0.507 0.869
Number formatting and baseball innings
Terminal, CSV, and HTML outputs use the same display rules:
- Counting statistics are rounded to the nearest whole number, with halves rounded up. Age and year are displayed as whole numbers too.
- BA, OBP, SLG, and OPS always show three decimal places, including
1.048OPS. BA is the canonical batter output label (previously AVG). - ERA shows two decimals; WHIP shows three. W-L% shows a three-decimal
fraction, such as
0.524. H9, HR9, BB9, and SO9 show one decimal; SO/W shows two. W-L% without any decisions and SO/W without walks are undefined and display blank. - IP is rounded to the nearest out and displayed in baseball notation:
161.0is 161 innings,161.1is 161 innings plus one out, and161.2is 161 innings plus two outs. Three outs carry into the next whole inning.
Calculations and Python projection DataFrames retain full precision. Rates are computed from unrounded totals, then formatted; they are not recalculated from rounded displayed counts. Independently rounded totals may therefore not add up exactly. Reliability formatting is unchanged.
CSV IP uses baseball notation too. Convert 161.1 to 161 + 1/3 before doing
arithmetic; treating it as 161.1 decimal innings gives incorrect results. Internally,
Backcaster uses decimal innings (IPouts / 3), so ERA and WHIP calculations use
true innings rather than the displayed notation.
Tracked statistics and order
All outputs put available Name, Year, Player ID, and Age metadata first, followed by these statistics in order and then Reliability:
Batters: PA,AB,R,H,2B,3B,HR,RBI,SB,CS,BB,SO,BA,OBP,SLG,OPS,TB,GDP,HBP,SH,SF,IBB
Pitchers: W,L,W-L%,ERA,SV,IP,H,R,ER,HR,BB,IBB,SO,HBP,BK,WP,BF,WHIP,H9,HR9,BB9,SO9,SO/W
Lahman's batting GIDP is exported as GDP. The expanded counting-stat columns
must be present in the appropriate local CSVs; missing columns produce an error.
TB is derived as H + 2B + 2×3B + 3×HR; stolen bases do not add total bases.
BA is H/AB, SLG is TB/AB, OBP is (H+BB+HBP)/(AB+BB+HBP+SF), and OPS is OBP+SLG.
IBB is already part of BB and is never added again to OBP or PA. Runs, RBI, SB,
CS, and GDP are contextual counts rather than additional PA outcomes.
Pitcher per-nine rates use unrounded counts and decimal IP: 9 × count / IP.
SO/W is SO/BB; W-L% is W/(W+L). Runs allowed (R) and earned runs (ER) are
separate projections; ERA uses ER, while WHIP uses H+BB without adding IBB again.
CSV output (--format csv)
A single-row CSV for player; multi-row CSV for team, batch, hitters, and pitchers.
Both populations include PlayerID for matching records across files.
HTML output (--format html)
A standalone report with embedded CSS and JavaScript, ready to open in a browser or share as one file. No server, CDN, fonts, or internet connection are required.
backcaster hitters --year 2025 --format html --out hitters_2025.html
backcaster pitchers --year 2025 --format html --out pitchers_2025.html
The first four columns are Name, Year, Player ID, Age, followed by counting statistics and batting/pitching rates in a consistent order. The report includes:
- A player-count, projection-year, and projection-type summary.
- Search across player names, IDs, and statistics.
- Sortable column headings, with numeric sorting for statistics.
- A sticky name column/header and horizontal scrolling on narrow screens.
- A reset control and print layout for browser printing or saving as PDF.
Your display precision rules apply, including baseball innings notation. Rates retain their trailing decimal places and OPS values above 1.000 display normally. Search filters the visible rows; printing respects that current view. Without JavaScript, the complete table is still readable. See HTML report customization for theme and column constants.
Local data updates
Backcaster reads the local CSV files for each command and does not use the old FanGraphs season cache. To refresh the data, replace the CSVs with a complete Lahman distribution from the same edition. Existing online caches are ignored.
Dependency notes
Runtime dependencies are intentionally minimal:
pandasfor tabular data processing and projection outputnumpyfor numerical operations and rebaselining convergence checks
Development dependencies (--extra dev) include:
pytest,pytest-covruffmypy,pandas-stubs
Lockfile policy (uv.lock)
uv.lock pins transitive versions for reproducible environments when working from a Git checkout.
Release/source ZIP archives may not include uv.lock. If it is missing in your environment, regenerate it locally:
uv lock
Do not hand-edit uv.lock; regenerate it when dependencies change.
What this projection does and does not claim
What it does
- Implements a Marcel-style weighted/regressed projection over prior seasons.
- Uses configurable reliability/regression/playing-time assumptions in code defaults.
- Produces transparent, table-based outputs for downstream analysis.
What it does not claim
- It is not an official MLB/club forecasting system.
- It does not model injuries, role changes, park effects, coaching strategy, or transaction context in a comprehensive way.
- It does not guarantee predictive superiority versus commercial/public projection systems.
- It should be treated as a baseline projection tool, not a betting or financial decision engine.
Development and quality checks
For release preparation and PyPI Trusted Publishing, see RELEASING.md. Changes for the next release are recorded in CHANGELOG.md.
Run tests
pytest
Coverage + JUnit reports
pytest --junitxml=pytest.xml --cov=src --cov-report=xml:coverage.xml
Lint and types
ruff check .
mypy
SonarScanner
sonar-scanner -Dsonar.projectVersion=$(PYTHONPATH=src python -c "from backcaster import __version__; print(__version__)")
Sonar consumes:
coverage.xmlpytest.xml
Acknowledgments
Thank you to Tom Tango for developing and publishing the Marcel system, and to Ben Dilday for marcelR, whose open R implementation provides a valuable methodological reference for our Python work. We document the reviewed formulas, source revision, and implementation differences in our reference notes. The complete marcelR MIT notice is preserved in THIRD_PARTY_NOTICES.md and shipped with our distributions.
Thanks also to SABR, Sean Lahman, and the database contributors. See Lahman attribution and licensing. These acknowledgments do not imply endorsement or exact agreement with another implementation's projections.
Security and responsible use
Please review SECURITY.md for reporting guidance. Backcaster is an analytical utility and should be validated independently before use in production pipelines.
Metadata
Release files for backcaster 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| backcaster-0.4.1.tar.gz | 45.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| backcaster-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 75.1 kB
Release files / backcaster-0.4.1.tar.gz
| Download URL | backcaster-0.4.1.tar.gz |
|---|---|
| Size | 45.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c41b483b4785dfb81718987e998c6647ba9229f92878925b243a05e594ed301b
|
|
BLAKE2b-256 checksum How to use checksums |
481e5b831b43a1fe7dd5e57c8486a1941504cbe650d8950b7c8ff2b0e7059985
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / backcaster-0.4.1-py3-none-any.whl
| Download URL | backcaster-0.4.1-py3-none-any.whl |
|---|---|
| Size | 29.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f539ca0003c5a071d10dad9f2f75afad38d044d67574e2406ab929bcdef3eeb7
|
|
BLAKE2b-256 checksum How to use checksums |
e7a79a2537562499553714fb979022d9c223e01310de6ab0bae5be89474566ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for backcaster 0.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for backcaster-0.4.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.