Skip to content

loss_avoidance_pattern fires 0% on S&P 500 — Burgstahler-Dichev 1997 thresholds too tight for large-cap universe #103

@dackclup

Description

@dackclup

Summary

The loss_avoidance_pattern flag added in PR #97 (Phase 4.5d) uses Burgstahler-Dichev 1997 JAE cohort thresholds:

  • LOSS_AVOID_NI_CEILING = 5_000_000.0 ($5M absolute NI floor)
  • LOSS_AVOID_EPS_CEILING = 0.05 ($0.05 EPS floor)
  • Both for 3+ consecutive fiscal years

Production verification run #50 (commit c3b29af4, 2026-05-17) shows 0 / 502 tickers fire on the S&P 500 universe — under the predicted 1-3% by the entire range. Re-verified on run #51 (b1588b2a): still 0/502.

Root cause

S&P 500 large-caps simply don't sit in the tiny-positive cohort that Burgstahler-Dichev 1997 studied. The paper used the broader Compustat universe (1976-1994, all firms with NI / EPS data), where the tiny-positive band captures meaningful management-cohort behavior. For S&P 500:

  • Smallest constituent NI > $5M (even the smallest-cap stocks have hundreds of millions in NI)
  • Smallest constituent EPS > $0.05 (most are well above $1.00)

The thresholds are calibration constants that depend on the universe, not universal physical limits.

Options

Option A — S&P-500-scaled thresholds

Scale the floors by universe median market cap or NI:

# Roughly 10x the BD-1997 thresholds, calibrated to S&P 500 median NI
LOSS_AVOID_NI_CEILING_SP500 = 25_000_000.0   # $25M (was $5M)
LOSS_AVOID_EPS_CEILING_SP500 = 0.25          # $0.25 (was $0.05)

Trade-off: loses the direct comparability to the original paper but captures management-cohort behavior at the universe's actual scale. Validate against AAER cohort recall vs the current zero-fire baseline.

Option B — Accept zero-fire as entered_top5 floor-only guard

Keep the original thresholds; treat zero-fire as the safe-by-default state. The flag becomes useful only when QuantRank expands to Phase 8's S&P 1500 mid-cap universe where the original cohort thresholds become meaningful again.

Pro: preserves exact paper-citation provenance.
Con: defends nothing at v1.0-v1.2 universe scale.

Option C — Composite-scoring deferral (Phase 4.5f follow-up)

The 4.5f manipulation_index rollup (PR #100) already gives loss_avoidance_pattern a 5-point weight — but since the flag fires 0 times, the weight contributes nothing. The composite math is correct; the calibration is wrong. Adjusting the thresholds (Option A) lights up this weight; keeping them (Option B) wastes the slot.

Recommendation

Pursue Option A in a Phase 4.5d follow-up sub-PR (e.g., 4.5d.1) once the AAER backtest harness (PR 4b §2 PBO/DSR, #75) is wired into a calibration loop. Risk-free to attempt — it's annotate-only, so even a tighter or looser calibration won't suppress any entered_top5 slot.

If 4.5e (Form 4 insider clustering) lands before this gets attention, ship that first.

Acceptance criteria

  • Universe-aware threshold (LOSS_AVOID_NI_CEILING_BY_UNIVERSE / LOSS_AVOID_EPS_CEILING_BY_UNIVERSE) wired via compute/config.py
  • AAER cohort recall >= baseline (no false-negative regression)
  • Production fire rate lands in the 1-5% band on S&P 500
  • Existing 13 offline tests still pass (threshold abstracted via module-level constants per SKILL.md Rule 17 chore(phase-0): mark complete #2)
  • manipulation_index distribution shifts visibly when this lights up (validation that the 5-pt weight was correctly waiting for non-zero fire)

References

  • compute/scoring/earnings_quality.py:78-85 — current threshold constants
  • compute/scoring/manipulation_index.py:73LOSS_AVOIDANCE_WEIGHT = 5.0 (currently wasted slot)
  • Burgstahler-Dichev 1997 JAE 24(1), 99-126 — original cohort thresholds
  • Phase 4.5d release annotation: production fire rate 0/502 documented at tag v1.2.0-phase4.5

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions