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
References
compute/scoring/earnings_quality.py:78-85 — current threshold constants
compute/scoring/manipulation_index.py:73 — LOSS_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
Summary
The
loss_avoidance_patternflag 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)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:
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:
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_top5floor-only guardKeep 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_indexrollup (PR #100) already givesloss_avoidance_patterna 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_top5slot.If 4.5e (Form 4 insider clustering) lands before this gets attention, ship that first.
Acceptance criteria
LOSS_AVOID_NI_CEILING_BY_UNIVERSE/LOSS_AVOID_EPS_CEILING_BY_UNIVERSE) wired viacompute/config.pymanipulation_indexdistribution 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 constantscompute/scoring/manipulation_index.py:73—LOSS_AVOIDANCE_WEIGHT = 5.0(currently wasted slot)v1.2.0-phase4.5