fix: recalibrate FIP path labels to live equity scale
Deploy / lint (push) Successful in 9s
Deploy / test (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s

Replace inert ±0.25 bands with ~p25/p75 cutoffs from the prod snapshot
(−0.08 / 0.00). Document zero-return dilution and left-skewed distribution.
This commit is contained in:
2026-07-18 19:27:45 +02:00
parent d9c4cd35eb
commit dc08a805a8
3 changed files with 48 additions and 7 deletions
+22 -5
View File
@@ -408,6 +408,17 @@ def compute_pivot_points(
}
# Path labels for display only. Calibrated on the ~505-name prod snapshot
# (2026-07, n=502 with full history): empirical p25 ≈ 0.082, p75 ≈ +0.004,
# mean ≈ 0.043. Paper-style |ID| ≳ 0.25 almost never appears in live equities
# (only ~0.2% of names); real momentum winners cluster around 0.04…−0.12.
# Thresholds are therefore ~quartile cutoffs, not ±0.25 textbook extremes.
# Distribution is left-skewed (bullish sample → more "continuous" than "discrete"),
# so the discrete band is not symmetric.
FIP_PATH_CONTINUOUS_MAX = -0.08 # ~p25: smoother quartile
FIP_PATH_DISCRETE_MIN = 0.00 # ~p75: less-continuous quartile
def compute_fip_id(closes: list[float], as_of_index: int | None = None) -> dict[str, Any]:
"""Da/Gurun/Warachka information discreteness over the 12-1 formation window.
@@ -418,8 +429,13 @@ def compute_fip_id(closes: list[float], as_of_index: int | None = None) -> dict[
ID = sign(PRET) × (%neg %pos)
Lower ID ⇒ smoother / more continuous path (for a winner: many small up days).
Higher ID ⇒ jumpy / discrete path (few large moves). Zero-return days count
in neither numerator but remain in the denominator.
Higher ID ⇒ jumpy / discrete path (few large moves).
Zero-return days count in neither numerator but remain in the denominator
(paper definition). Quirk: a flat series with one big jump can still land
near zero ("mixed") because zeros dilute %pos/%neg — faithful to the paper
and to real equities (exact zero daily returns are rare). Synthetic jump
tests assert ordering vs a steady climber, not the discrete label itself.
"""
i = len(closes) - 1 if as_of_index is None else as_of_index
if i < 252 or closes[i - 252] <= 0 or closes[i - 21] <= 0:
@@ -448,12 +464,13 @@ def compute_fip_id(closes: list[float], as_of_index: int | None = None) -> dict[
else:
sign = 0.0
fip = sign * (pct_neg - pct_pos)
# Map [-1, 1] → score where lower ID (smoother) is higher for display only.
# Map observed ID range (~[-0.3, 0.15]) loosely to 0100 for the card chrome;
# lower ID (smoother) → higher score. Display only.
score = max(0.0, min(100.0, 50.0 * (1.0 - fip)))
if fip <= -0.25:
if fip <= FIP_PATH_CONTINUOUS_MAX:
path = "continuous"
path_label = "smooth grind (continuous information)"
elif fip >= 0.25:
elif fip >= FIP_PATH_DISCRETE_MIN:
path = "discrete"
path_label = "jumpy path (discrete information)"
else: