Part 5: The Implementation Blueprint
TRP — True Relative Price: True Value. Market Pricing. No Noise.
Parts 1–4 established the theory: replacement-level baselines, Z-score normalization, iterative tier refinement, and production-weighted dollar conversion. This part translates that theory into a buildable system.
What follows is a language-agnostic architectural blueprint. The data structures, core functions, and control flow are specified precisely enough that you could code this yourself or hand this document to an LLM and get a working implementation in one shot.
Photo by Mohammad Rahmani on Unsplash
Inputs
The system requires three input files:
1. Projections File (projections.csv)
Player-level projections with the following schema:
1player_id: string (unique identifier)2name: string3team: string4positions: string[] (e.g., ["C", "1B"] or ["SP"])5pa: integer (plate appearances, hitters only)6ab: integer (at-bats, hitters only)7r: float (runs)8hr: float (home runs)9rbi: float (runs batted in)10sb: float (stolen bases)11obp: float (on-base percentage)12slg: float (slugging percentage)13ip: float (innings pitched, pitchers only)14era: float (earned run average)15whip: float (walks + hits per inning pitched)16k9: float (strikeouts per 9 innings)17qs: integer (quality starts, SP only)18svhd: integer (saves + holds, RP only)19role: string ("HITTER" | "SP" | "RP")20wrc_plus: float (hitters only, pre-calculated)21fip: float (pitchers only, pre-calculated)
Note: wrc_plus and fip are pre-calculated composite metrics used for initial player sorting. These come from your projection source or are calculated upstream.
2. League Settings File (league_settings.json)
1{2 "num_teams": 12,3 "budget_per_team": 260,4 "bench_reserve": 5,5 "roster_slots": {6 "C": 1,7 "1B": 1,8 "2B": 1,9 "3B": 1,10 "SS": 1,11 "OF": 3,12 "UTIL": 1,13 "SP": 5,14 "RP": 215 },16 "hitter_categories": ["R", "HR", "RBI", "SB", "OBP", "SLG"],17 "sp_categories": ["IP", "QS", "ERA", "WHIP", "K9"],18 "rp_categories": ["IP", "SVHD", "ERA", "WHIP", "K9"]19}
3. Budget Configuration File (budget_config.json)
1{2 "hitter_pitcher_split": [0.70, 0.30],3 "sp_rp_split": [0.50, 0.50],4 "hitter_category_weights": {5 "OBP": 0.25,6 "SLG": 0.25,7 "R": 0.125,8 "HR": 0.125,9 "RBI": 0.125,10 "SB": 0.12511 },12 "sp_category_weights": {13 "K9": 0.40,14 "IP": 0.15,15 "QS": 0.15,16 "ERA": 0.15,17 "WHIP": 0.1518 },19 "rp_category_weights": {20 "K9": 0.40,21 "IP": 0.15,22 "SVHD": 0.15,23 "ERA": 0.15,24 "WHIP": 0.1525 },26 "pa_weights": {27 "C": 500,28 "default": 60029 },30 "replacement_tier_pct": 0.03,31 "min_replacement_tier_size": 3,32 "max_iterations": 10,33 "convergence_threshold": 034}
Outputs
The system produces two output files:
1. Player Valuations File (valuations.csv)
1player_id: string2name: string3position: string (primary position used for valuation)4role: string ("HITTER" | "SP" | "RP")5total_z: float6dollar_value: float7z_R: float (hitters only)8z_HR: float (hitters only)9z_RBI: float (hitters only)10z_SB: float (hitters only)11z_OBP: float (hitters only)12z_SLG: float (hitters only)13z_IP: float (pitchers only)14z_ERA: float (pitchers only)15z_WHIP: float (pitchers only)16z_K9: float (pitchers only)17z_QS: float (SP only)18z_SVHD: float (RP only)19dollar_R: float (hitters only)20dollar_HR: float (hitters only)21... (dollar value per category)22tier: string ("ROSTERED" | "REPLACEMENT" | "BELOW_REPLACEMENT")
2. Position Summary File (position_summary.csv)
1position: string2role: string3rostered_count: integer4replacement_tier_count: integer5total_budget: float6dollars_per_z_R: float7dollars_per_z_HR: float8... ($/Z for each category)9replacement_baseline_R: float10replacement_baseline_HR: float11... (RLP archetype stats)
Core Data Structures
Player
1{2 id: string3 name: string4 team: string5 positions: string[]6 role: "HITTER" | "SP" | "RP"7 stats: {8 // Raw projection stats9 pa: float10 ab: float11 r: float12 hr: float13 rbi: float14 sb: float15 obp: float16 slg: float17 ip: float18 era: float19 whip: float20 k9: float21 qs: float22 svhd: float23 // Pre-calculated composite metrics24 wrc_plus: float (hitters)25 fip: float (pitchers)26 }27 computed: {28 primary_position: string29 raw_z: { [category]: float }30 normalized_z: { [category]: float }31 total_z: float32 dollar_values: { [category]: float }33 total_dollars: float34 tier: "ROSTERED" | "REPLACEMENT" | "BELOW_REPLACEMENT"35 }36}
PositionPool
1{2 position: string3 role: "HITTER" | "SP" | "RP"4 roster_slots: integer5 rostered_players: Player[]6 replacement_players: Player[]7 rostered_tier_means: { [category]: float }8 rostered_tier_stdevs: { [category]: float }9 rlp_archetype: { [category]: float }10 rlp_raw_z_avg: { [category]: float }11 category_budgets: { [category]: float }12 dollars_per_z: { [category]: float }13 total_pool_z: { [category]: float }14 production_share: { [category]: float }15}
LeagueBudget
1{2 total: float3 hitter_budget: float4 pitcher_budget: float5 sp_budget: float6 rp_budget: float7 category_budgets: {8 hitter: { [category]: float }9 sp: { [category]: float }10 rp: { [category]: float }11 }12}
Core Functions
ASSIGN_PRIMARY_POSITIONS(players, settings)
Multi-position players create a challenge: where do you value them? A player eligible at 2B and SS could anchor either position. TRP assigns each player to their most valuable position — the scarcest one where they’d be rostered.
This function processes positions from scarcest to deepest, assigning players to maximize positional value.
1ASSIGN_PRIMARY_POSITIONS(players, settings):2 // Sort positions by scarcity (fewest roster slots first)3 position_order = SORT_BY(settings.roster_slots, ascending)45 assigned = {}67 FOR each position IN position_order:8 eligible = FILTER(players, position IN player.positions AND player.id NOT IN assigned)9 slots = settings.roster_slots[position] * settings.num_teams1011 // Sort by composite metric (wRC+ for hitters, FIP for pitchers)12 eligible = SORT_BY(eligible, composite_metric, descending)1314 // Assign top N players to this position15 FOR i = 0 TO slots + (slots * 0.5): // Include replacement tier buffer16 IF i = threshold17 )1819 // Enforce minimum tier size20 IF LENGTH(replacement_candidates) 0:21 last_rostered_metric = pool.rostered_players[-1].composite_metric22 threshold = last_rostered_metric * (1 - budget_config.replacement_tier_pct)2324 replacement_candidates = FILTER(25 util_candidates[pool.roster_slots :],26 composite_metric >= threshold27 )2829 IF LENGTH(replacement_candidates) 0:30 pool.dollars_per_z[category] = pool.category_budgets[category] / pool.total_pool_z[category]31 ELSE:32 pool.dollars_per_z[category] = 03334 RETURN pools
CALC_PLAYER_DOLLARS(player, pool)
The final step: multiply each player’s normalized Z by the $/Z rate for their position-category. Sum across categories for total dollar value.
Negative Z-scores produce negative dollar contributions — a player who hurts you in a category is penalized accordingly.
1CALC_PLAYER_DOLLARS(player, pool):2 dollar_values = {}34 FOR each category IN player.computed.normalized_z:5 z = player.computed.normalized_z[category]6 rate = pool.dollars_per_z[category]7 dollar_values[category] = z * rate89 RETURN dollar_values
Helper Functions
IS_INVERTED(category)
1RETURN category IN ["ERA", "WHIP"]
GET_CATEGORIES(role, settings)
1IF role == "HITTER":2 RETURN settings.hitter_categories3ELSE IF role == "SP":4 RETURN settings.sp_categories5ELSE IF role == "RP":6 RETURN settings.rp_categories
CALC_MEANS(players, field)
1values = [player.stats[field] OR player.computed[field] FOR player IN players]2RETURN SUM(values) / LENGTH(values)
CALC_STDEVS(players, field)
1values = [player.stats[field] OR player.computed[field] FOR player IN players]2mean = CALC_MEANS(players, field)3variance = SUM((v - mean)^2 FOR v IN values) / LENGTH(values)4RETURN SQRT(variance)
Validation Checks
Before outputting, validate:
- Budget Balance: Sum of all rostered player dollars ≈ total league budget (±$1)
- No Orphan Players: Every player with projections is assigned to exactly one position pool
- Tier Consistency: Rostered tier size equals roster slots × num teams for each position
- Z-Score Sanity: RLP players should have total normalized Z near 0
- Dollar Sanity: No rostered player should have negative total dollars (below replacement should be rare)
Control Flow
Putting it all together — the complete pipeline from raw projections to dollar values:
1MAIN():2 // Phase 1: Initialize3 projections = LOAD_PROJECTIONS("projections.csv")4 settings = LOAD_SETTINGS("league_settings.json")5 budget_config = LOAD_BUDGET_CONFIG("budget_config.json")67 // Phase 2: Assign primary positions (scarcity-first allocation)8 players = ASSIGN_PRIMARY_POSITIONS(projections, settings)910 // Phase 3: Split by role11 hitters = FILTER(players, role == "HITTER")12 pure_dh_players = FILTER(hitters, positions == ["DH"])13 starters = FILTER(players, role == "SP")14 relievers = FILTER(players, role == "RP")1516 // Phase 4: Build position pools and iterate to convergence17 hitter_pools = BUILD_POSITION_POOLS(hitters, settings, "HITTER")18 hitter_pools = ITERATE_TO_CONVERGENCE(hitter_pools, budget_config)1920 // Phase 5: Build UTIL pool from replacement-tier players + pure DHs21 // This must happen AFTER position pools converge so we know who's below replacement22 util_pool = BUILD_UTIL_POOL(hitter_pools, pure_dh_players, settings)23 util_pool = ITERATE_TO_CONVERGENCE([util_pool], budget_config)[0]24 hitter_pools.APPEND(util_pool)2526 // Phase 6: Build pitcher pools27 sp_pool = BUILD_SINGLE_POOL(starters, settings, "SP")28 sp_pool = ITERATE_TO_CONVERGENCE([sp_pool], budget_config)[0]2930 rp_pool = BUILD_SINGLE_POOL(relievers, settings, "RP")31 rp_pool = ITERATE_TO_CONVERGENCE([rp_pool], budget_config)[0]3233 // Phase 7: Calculate league budget structure34 league_budget = CALC_LEAGUE_BUDGET(settings, budget_config)3536 // Phase 8: Allocate category budgets to positions37 hitter_pools = ALLOCATE_POSITION_BUDGETS(hitter_pools, league_budget, budget_config)38 sp_pool = ALLOCATE_POOL_BUDGET(sp_pool, league_budget.sp_budget, budget_config.sp_category_weights)39 rp_pool = ALLOCATE_POOL_BUDGET(rp_pool, league_budget.rp_budget, budget_config.rp_category_weights)4041 // Phase 9: Convert Z-scores to dollars42 hitter_pools = CALC_DOLLARS_PER_Z(hitter_pools)43 sp_pool = CALC_DOLLARS_PER_Z([sp_pool])[0]44 rp_pool = CALC_DOLLARS_PER_Z([rp_pool])[0]4546 // Phase 10: Value each player47 FOR each pool IN [hitter_pools..., sp_pool, rp_pool]:48 FOR each player IN pool.rostered_players + pool.replacement_players:49 player.computed.dollar_values = CALC_PLAYER_DOLLARS(player, pool)50 player.computed.total_dollars = SUM(player.computed.dollar_values)5152 // Phase 11: Validate and normalize53 total_allocated = SUM(all player.computed.total_dollars WHERE tier == "ROSTERED")54 IF total_allocated != league_budget.total:55 NORMALIZE_TO_BUDGET(all_players, league_budget.total)5657 // Phase 12: Output58 WRITE_VALUATIONS("valuations.csv", all_players)59 WRITE_POSITION_SUMMARY("position_summary.csv", all_pools)
What We’ve Built
This blueprint specifies:
- Input/Output contracts — exactly what data goes in and comes out
- Data structures — Player, PositionPool, and LeagueBudget objects
- Core algorithms — iteration, Z-score calculation, budget allocation, dollar conversion
- UTIL pool construction — collecting replacement-tier players to fill the flex slot
- Validation checks — sanity tests before output
- Control flow — the 12-phase pipeline from raw projections to dollar values
Hand this document to any competent developer or LLM, and they can build a working TRP implementation in their language of choice.
Part 6 extends this foundation to in-season analysis: combining current stats with rest-of-season projections to identify buy-low, sell-high, and waiver opportunities.
TRP is a valuation framework developed within the MTBL (Metaball) ecosystem. It consumes projections from any source and outputs market-calibrated player values for fantasy baseball.
