Minor webapp update

This commit is contained in:
Pawel Sarkowicz
2026-07-31 11:08:37 -04:00
parent 69a0d51f3c
commit 2a836b1ef6
3 changed files with 33 additions and 11 deletions
+19 -3
View File
@@ -133,9 +133,25 @@ def form_decile_portfolios(
def decile_long_returns(signal_df: pd.DataFrame, ret_df: pd.DataFrame, decile: float = 0.1) -> pd.Series:
"""Top-decile equal-weight long-only monthly returns."""
port = form_decile_portfolios(signal_df, ret_df, decile=decile)
return port["long"] if "long" in port else pd.Series(dtype=float)
"""Top-decile equal-weight long-only monthly returns (signal at t, return at t+1).
Vectorized for speed — the webapp's live stress-test button calls this once
per click. Ranks cross-sectionally per month, takes the top decile, and
averages next-month returns. (Semantically equivalent to the per-month loop
in ``form_decile_portfolios`` but without computing holdings/turnover.)
"""
ranks = signal_df.rank(axis=1, pct=True) # NaN stays NaN
rvals = ranks.values[:-1] # signal at t (T-1, N)
next_ret = ret_df.values[1:] # return at t+1 (T-1, N)
with np.errstate(invalid="ignore", all="ignore"):
thr = np.nanquantile(rvals, 1 - decile, axis=1) # per-row (1-decile) quantile of valid scores
top = (rvals >= thr[:, None]) & ~np.isnan(rvals)
valid = (~np.isnan(rvals)).sum(axis=1)
contrib = np.where(top, next_ret, np.nan)
with np.errstate(invalid="ignore"):
long_ret = np.nanmean(contrib, axis=1)
long_ret = np.where(valid >= 50, long_ret, np.nan)
return pd.Series(long_ret, index=np.arange(1, signal_df.shape[0]), dtype=float)
def _is_datetime_like(index: pd.Index) -> bool: