Three numbers can summarize months of training:

  • CTL — Chronic Training Load. Your fitness.
  • ATL — Acute Training Load. Your recent fatigue.
  • TSB — Training Stress Balance, CTL − ATL. Your form.

Plotted over time, they form the Performance Manager Chart (PMC) — the single most useful long-term view of training a cyclist or runner has. TrainingPeaks paywalls it for $240/yr. Strava paywalls it inside Premium.

The math is two exponentially weighted moving averages and one subtraction. Here it is.

The model

Tim Banister's impulse-response model, from the 1970s, says training does two things simultaneously:

  • It builds fitness, which decays slowly.
  • It accumulates fatigue, which decays quickly.

Performance on any given day is fitness minus fatigue. Train too hard, fatigue dominates, performance drops. Rest, fatigue clears faster than fitness, performance rebounds.

CTL/ATL/TSB are the standard implementation:

  • CTL = exponentially weighted moving average of TSS over 42 days
  • ATL = exponentially weighted moving average of TSS over 7 days
  • TSB = today's CTL − today's ATL

The 42- and 7-day windows aren't arbitrary; they're empirically calibrated to the time constants of fitness and fatigue in trained athletes.

CTL — fitness

CTL_today = CTL_yesterday + (TSS_today - CTL_yesterday) / 42

Each day's CTL is a small step from yesterday's, weighted by how today's training compared to your trailing baseline. If TSS is higher than CTL, fitness rises. If lower, it falls.

A rule of thumb: CTL changes by about TSS/42 per day in steady state. Riding 100 TSS/day every day pushes CTL toward 100. Riding 50 TSS/day pushes it toward 50. The ramp is gradual — gaining 10 CTL takes weeks of consistent effort, which is why fitness is hard to build and easy to keep.

ATL — fatigue

ATL_today = ATL_yesterday + (TSS_today - ATL_yesterday) / 7

Same shape, faster window. ATL spikes after a hard day and clears within a week of rest. A four-hour endurance ride (TSS ≈ 220) might push ATL up by 25 points in a single day.

TSB — form

TSB_today = CTL_yesterday - ATL_yesterday

Note: the standard convention uses yesterday's values to make the chart predictive (today's TSB tells you how today's effort starts, not ends).

Reading TSB:

  • TSB > +25 — deeply rested or detrained. Race-ready or under-trained, depending on context.
  • TSB +5 to +25 — fresh. Good for a hard test or race.
  • TSB −10 to +5 — neutral. Productive training zone.
  • TSB −30 to −10 — fatigued. Where most of the training stimulus happens.
  • TSB < −30 — severely fatigued. Easy to slip into overtraining here.

The peak-and-taper strategy for an "A race" is: ramp CTL for months, hold it through a high-fatigue training block (TSB strongly negative), then taper for 2–3 weeks (TSS down → ATL drops faster than CTL → TSB climbs into positive territory). On race day you've shed fatigue without losing meaningful fitness.

The implementation

type Day = { date: string; tss: number };

export function computePMC(days: Day[]) {
  const sorted = [...days].sort((a, b) => a.date.localeCompare(b.date));
  let ctl = 0;
  let atl = 0;
  return sorted.map(d => {
    const yCTL = ctl;
    const yATL = atl;
    ctl = yCTL + (d.tss - yCTL) / 42;
    atl = yATL + (d.tss - yATL) / 7;
    return {
      date: d.date,
      tss: d.tss,
      ctl,
      atl,
      tsb: yCTL - yATL,
    };
  });
}

20 lines. That's the whole thing. Source.

To compute it correctly you need TSS for every day in the range, including zeros for rest days. Skipping rest days inflates CTL because the EWMA stops decaying.

Common misreadings

  • "My CTL is 90, that means I'm fit." CTL is unitless. 90 for one athlete is moderate; 90 for another is heroic. It's only useful relative to your history.
  • "TSB > 0, so I'm fresh." Recovery isn't binary. A TSB of +5 after weeks of negative numbers feels electric. A TSB of +5 in detraining season feels flat. Context matters.
  • "My CTL dropped, I'm losing fitness." A 5-point drop over 2 weeks of light training is normal and often productive — it's the recovery that consolidates adaptations from the prior block.
  • "I should always train at TSB −10." No. Long stretches at any one TSB are how plateaus and overuse injuries happen. Cycling between accumulation (TSB negative) and recovery (TSB positive) is the entire game.

What you need to compute it yourself

  • Daily TSS values for as far back as you care to look.
  • A spreadsheet, a notebook, or 20 lines of code.

That's it. There's no proprietary data. There's no special algorithm. There is no infrastructure cost — a year of daily PMC values is 365 floats per athlete. You can store it in 3KB.

The reason this is paywalled is because the people who want it will pay. pacelore exists, in part, because that's not a good enough reason to lock up math from the 1970s.

Try it

Live demo — interactive PMC over a sample athlete's full history.

Source — fork, audit, run your own.

Further reading