← Quantery Blog

How to test a cyclical value thesis

October 15, 2026 · 9 min readthesis-buildingcyclical-stocksbacktesting

A cyclical value thesis should value a business on earnings it can sustain through a cycle, then test whether the apparent discount survives weaker margins. Start with a multi-period operating margin, apply it to current revenue, and compare the resulting normalized operating income with enterprise value. Add a balance-sheet guard before testing the thesis on point-in-time filings.

That stops one good year from doing all the work. It doesn't reveal a company's "true" earnings, because no formula can. It gives you a declared estimate. Change it, challenge it, and backtest it.

Why can a cyclical stock look cheapest near the peak?

A cyclical business has revenue or margins that move with a recurring economic force such as commodity prices, freight rates, construction demand, or semiconductor inventories. Its latest income statement may describe an unusually good or bad point in that cycle.

The familiar price-to-earnings ratio uses current equity value and recent net income. When demand is strong, fixed costs are spread over more units, prices may rise faster than costs, and net income jumps. The denominator expands, so the multiple falls. The stock can look statistically cheap at the moment its earnings are least repeatable.

Reverse the conditions and the ratio may look expensive near a trough. That doesn't make every low multiple a trap or every high multiple an opportunity. It means the latest multiple and the business cycle can point in opposite directions.

This problem reaches beyond P/E. A thesis using trailing free cash flow yield can be flattered by a working-capital release or deferred capital spending. One using trailing EBITDA can mistake a temporary spread for normal profitability. Free cash flow is harder to fake, but a real cash windfall can still be cyclical.

The first question isn't whether the latest yield clears your cutoff. Ask what operating conditions produced it.

What does normalized earnings mean?

Normalized earnings are an estimate of what a business might earn under representative conditions instead of the latest reported conditions. The definition must name its window and its accounting measure. "Mid-cycle earnings" without those choices is a story with arithmetic added later.

One workable version uses operating margin:

normalized operating income = current TTM revenue × multi-period operating margin

Trailing twelve months, or TTM, means the latest four reported quarters. Operating margin is operating income divided by revenue. A multi-period margin pools several years of filed results, then asks what current revenue would earn if that longer-window margin returned.

Why keep current revenue? Capacity and the size of the business may have changed. Applying an old dollar profit to a larger operation can be as misleading as accepting the latest peak. Why use operating income? It sits before interest and tax, so the estimate can be compared with enterprise value, which includes both equity and debt claims. The enterprise-value guide explains that numerator-and-denominator match.

This is a blunt model. Acquisitions can make old and current margins incomparable. A mine can deplete, a shipping fleet can expand, and a chipmaker's product mix can change. Inflation can alter both prices and costs. If the business changed shape, a long average may describe a company that no longer exists.

Don't hide that judgment inside a strangely precise coefficient. Choose a window before reading the backtest. Explain why it spans the kind of cycle you claim to study. Then inspect the filing history behind companies that qualify.

How should the screen separate price from cycle position?

A useful cyclical thesis asks two different questions. Is the business inexpensive on normalized operating income? Is the latest margin already far above its longer record?

Those questions need separate features. Combining them into one adjusted multiple makes it hard to see why a company passed.

The shape below is illustrative, and the bundled templates are the reference for exact fields. The full Quantery thesis documentation covers the DSL. This example uses a twelve-quarter operating margin, current TTM revenue, and simple enterprise value. Its thresholds are proposed parameters, not claims about universal fair value.

params:
  yield_strong: 0.08
  yield_ok: 0.05
  peak_gap_max: 0.05
  leverage_max: 3.0

features:
  current_revenue_complete: not is_null(lag(revenue, 0)) and
    not is_null(lag(revenue, 1)) and not is_null(lag(revenue, 2)) and
    not is_null(lag(revenue, 3))
  current_operating_complete: not is_null(lag(operating_income, 0)) and
    not is_null(lag(operating_income, 1)) and
    not is_null(lag(operating_income, 2)) and
    not is_null(lag(operating_income, 3))
  current_ttm_complete: current_revenue_complete and current_operating_complete
  revenue_ttm: if(current_revenue_complete, ttm(revenue), null)
  operating_income_ttm: if(current_operating_complete,
    ttm(operating_income), null)
  revenue_history_complete: not is_null(lag(revenue, 0)) and
    not is_null(lag(revenue, 1)) and not is_null(lag(revenue, 2)) and
    not is_null(lag(revenue, 3)) and not is_null(lag(revenue, 4)) and
    not is_null(lag(revenue, 5)) and not is_null(lag(revenue, 6)) and
    not is_null(lag(revenue, 7)) and not is_null(lag(revenue, 8)) and
    not is_null(lag(revenue, 9)) and not is_null(lag(revenue, 10)) and
    not is_null(lag(revenue, 11))
  operating_history_complete: not is_null(lag(operating_income, 0)) and
    not is_null(lag(operating_income, 1)) and
    not is_null(lag(operating_income, 2)) and
    not is_null(lag(operating_income, 3)) and
    not is_null(lag(operating_income, 4)) and
    not is_null(lag(operating_income, 5)) and
    not is_null(lag(operating_income, 6)) and
    not is_null(lag(operating_income, 7)) and
    not is_null(lag(operating_income, 8)) and
    not is_null(lag(operating_income, 9)) and
    not is_null(lag(operating_income, 10)) and
    not is_null(lag(operating_income, 11))
  full_cycle_history: revenue_history_complete and operating_history_complete
  revenue_12q: sum(revenue, 12)
  operating_income_12q: sum(operating_income, 12)
  cycle_margin: if(full_cycle_history and revenue_12q > 0,
    operating_income_12q / revenue_12q, null)
  current_margin: if(current_ttm_complete and revenue_ttm > 0,
    operating_income_ttm / revenue_ttm, null)
  margin_gap: if(is_null(current_margin) or is_null(cycle_margin),
    null, current_margin - cycle_margin)
  normalized_ebit: if(current_ttm_complete and revenue_ttm > 0 and
    not is_null(cycle_margin), revenue_ttm * cycle_margin, null)

  debt_now: newest(total_debt)
  cash_now: newest(cash)
  enterprise_value: if(market_cap > 0 and not is_null(debt_now),
    market_cap + debt_now - coalesce(cash_now, 0), null)
  normalized_yield: if(enterprise_value > 0,
    normalized_ebit / enterprise_value, null)
  net_debt: if(is_null(debt_now) or is_null(cash_now),
    null, debt_now - cash_now)
  normalized_leverage: if(normalized_ebit > 0 and not is_null(net_debt),
    net_debt / normalized_ebit, null)

criteria:
  history:
    rules:
      - { when: "full_cycle_history", score: 1 }
      - { else: 0, flag: incomplete_cycle_history }
  valuation:
    rules:
      - { when: "is_null(normalized_yield)", score: 0, flag: no_yield }
      - { when: "normalized_yield >= $yield_strong", score: 2 }
      - { when: "normalized_yield >= $yield_ok", score: 1 }
      - { else: 0 }
  cycle_position:
    rules:
      - { when: "is_null(margin_gap)", score: 0, flag: no_comparison }
      - { when: "margin_gap <= 0", score: 2 }
      - { when: "margin_gap <= $peak_gap_max", score: 1 }
      - { else: 0, flag: margin_far_above_cycle }
  balance_sheet:
    rules:
      - { when: "is_null(normalized_leverage)", score: 0, flag: no_leverage }
      - { when: "normalized_leverage <= 0", score: 2 }
      - { when: "normalized_leverage <= $leverage_max", score: 1 }
      - { else: 0 }

gate:
  mode: strict

The current-window guards require revenue and operating income in each of the latest four positions before either TTM value can feed the calculation. The history rule requires both fields in every position of the twelve-quarter window, so a partial field history can't masquerade as a complete estimate. It doesn't prove the window contains a full economic cycle. The cycle-position rule then refuses full credit when the latest operating margin sits well above the pooled margin. The balance-sheet rule evaluates debt against normalized operating income because peak EBITDA can make leverage look safest just before earnings fall.

Use newest() for cash and debt so both values come from the same latest filing. Missing debt makes enterprise value null. Filling a missing cash value with zero there is conservative, but the leverage feature still demands both fields. That difference is visible in the flags instead of disappearing into the score.

Which normalization choices deserve separate tests?

There isn't one privileged window. Treat the choice as a model assumption and make it carry its own test.

Start with the shortest window that plausibly includes both strong and weak conditions for the industry. Then rerun a longer window. Keep every other rule fixed. If a thesis works only when one particular starting quarter drops into the average, the window is choosing the result.

Test another definition too. A pooled margin, total operating income divided by total revenue, weights high-revenue periods more heavily. The simple average of quarterly margins gives every quarter equal weight. A median margin resists one extreme period but requires a calculation the illustrative DSL above doesn't express directly. None is neutral.

You can also stress the normalized margin itself. Reduce it by a round amount before computing earnings, or raise the valuation hurdle. Don't tune both at once. A result that survives harsher profitability and valuation assumptions is easier to interpret than one rescued by several moving parts.

Keep sector logic tight. The same cycle proxy won't make sense for lenders, miners, homebuilders, and software vendors. A broad universe can turn your normalized-value score into an industry classifier. Test one economically coherent group first, then run a sector-concentration check.

What should the backtest prove?

A backtest should compare the normalized thesis with the naive version it is meant to improve. Build a small test ladder:

  1. Use the latest TTM operating yield with the same balance-sheet rule.
  2. Replace latest operating income with normalized operating income.
  3. Add the cycle-position criterion.
  4. Stress the normalization window and margin assumption one at a time.

Keep dates, universe, benchmark, rebalance cadence, and exit rule unchanged. Every result is hypothetical and excludes costs. The comparison asks whether normalization changes the selected companies and whether that change held up afterward. It doesn't establish that your margin estimate caused the outcome.

Read membership before returns. Which companies vanish when the peak-margin guard arrives? Which enter because a depressed current margin no longer dominates the valuation? Open their filings and check whether the arithmetic matches the business history. If normalization mostly swaps one cyclical industry for another, your "improvement" may be exposure drift.

Next inspect entry cohorts. Cyclical signals can cluster around recessions, commodity shocks, or inventory corrections, so a long list of events may contain only a few independent episodes. Clustered backtest signals need cohort-level reading. Compare median and mean outcomes, remove the strongest cohort, and look at drawdowns. One boom-to-bust reversal shouldn't grade the whole thesis.

Finally, preserve a holdout period. Choosing the window, margin haircut, leverage ceiling, and valuation bands creates many possible configurations. Research on backtest overfitting shows why testing more variants raises the chance of selecting a pattern that fails out of sample. Decide the final rules, then stop editing before the holdout run. The broader backtest discipline guide covers filing-date alignment and other leakage traps.

When should you reject the thesis?

Reject it when the result needs peak earnings after all. If the normalized version loses its apparent edge, that is useful. The low multiple was describing the cycle instead of a durable discount.

Reject it when reasonable windows reverse the conclusion, when one industry or one entry cohort carries the result, or when balance-sheet failures dominate the near misses. Reject it when the companies that pass have changed so much that their historical margins no longer belong in the model.

A surviving thesis still isn't a forecast. It says that a declared way of normalizing earnings selected a group with a repeatable historical pattern under the tested assumptions. Open the rules. Change the window. Tighten the debt guard. Make the thesis keep earning its conclusion.

Want to try this on your own rules? Quantery is free for 14 days: the full app, no card required.

← All articles