Issue 1335 — RecessionAlert WLEI second-derivative warning¶
Offline exploratory reconstruction. The cached source is a conceptual warning article, not a fully specified executable RecessionAlert signal. Raw source HTML, chart/workbook bytes, credentials, cookies, and private account data are excluded.
from pathlib import Path
import json
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display, Markdown
root = Path.cwd()
while root != root.parent and not (root / 'data' / 'recessionalert.sqlite').exists():
root = root.parent
ledger = json.loads((root / 'research/experiments/1335-state-space.json').read_text())
diagnostics = json.loads((root / 'research/experiments/1335-diagnostics.json').read_text())
synthesis = json.loads((root / 'research/experiments/1335-synthesis.json').read_text())
lab = json.loads((root / 'research/campaigns/recessionalert-wlei-2nd-derivative/lab-series.json').read_text())
expected_sha = 'sha256:9a47b636c5273dbe77dc525e0fdaafc9e1e0d8927ea71d86b3f7b23fa3a663a2'
assert ledger['source_boundary']['canonical_page_sha256'] == expected_sha
assert diagnostics['source_sha256'] == expected_sha
assert len(ledger['variants']) >= 3
print('Canonical page SHA256:', expected_sha)
print('Native WLEI available:', ledger['source_boundary']['native_wlei_series_available'])
print('Macro proxy span:', ledger['proxy_data']['macro']['span'])
print('Market comparison span:', diagnostics['inputs']['common_market_span'])
print('Tested variants:', len(ledger['variants']))
1. Documented rule and causal boundary¶
The article describes WLEI as a first-derivative/rate-of-change indicator and says a second derivative can be represented by the historical percentage of WLEI readings above the current reading. It calls the result WLEI Diffusion, discusses divergence/catch-up, and frames ECRI WLI plus WLEI as first and second recession warnings.
It does not disclose source-linked observations, the rank window or denominator, a numeric threshold/state machine, a buy/sell/hold mapping, or a release/vintage calendar. All executable candidates below are inferred INDPRO proxies. Primary results use an available-bar lag; zero lag is timing sensitivity only; same-close execution is unsupported.
source_summary = pd.DataFrame([
{'evidence': 'Source rule', 'status': 'documented conceptual', 'detail': 'Historical percentage/rank of WLEI readings above current reading; WLEI Diffusion context'},
{'evidence': 'Native span', 'status': 'undisclosed', 'detail': 'One dated HTML article; no source-linked WLEI observation series'},
{'evidence': 'Publication lag', 'status': 'unknown', 'detail': 'Underlying WLEI/WLEI Diffusion/ECRI WLI release clocks and vintages absent'},
{'evidence': 'Executable rule', 'status': 'inferred', 'detail': 'Rolling rank second difference of FRED INDPRO proxy'},
{'evidence': 'Trade mapping', 'status': 'inferred overlay', 'detail': 'SPY risk-on versus IEF risk-off; not a disclosed action'},
])
display(source_summary)
2. Tested proxy variants¶
INDPRO spans 1919–2026 in the checked-in datastore. Market metrics use the actual common SPY/IEF overlap from 2002-08 through 2026-06. Every variant is retained, including the negative stricter/slow trial.
metrics = pd.DataFrame([{
'variant': v['id'], 'lookback': v['lookback'], 'lag': v['lag_bars'],
'threshold': v['threshold'], 'holding_months': v['holding_months'],
'warning_triggers': v['warning_triggers'], 'market_months': v['market_months'],
'overlay_cumulative': v['overlay_cumulative_return'], 'SPY_cumulative': v['spy_cumulative_return'],
'overlay_max_dd': v['overlay_max_drawdown'], 'SPY_max_dd': v['spy_max_drawdown'],
} for v in ledger['variants']]).set_index('variant')
display(metrics.style.format('{:.4f}'))
3. Signal chart and diagnostics¶
The lower panel shows inferred rolling-rank second differences, not native WLEI values. The summary table reports proxy contraction-event coverage and lag sensitivity.
dates = pd.to_datetime(lab['dates'])
asset = pd.Series(lab['asset'], index=dates, dtype='float64')
fig, axes = plt.subplots(2, 1, figsize=(13, 8), sharex=True, constrained_layout=True)
axes[0].plot(dates, asset / asset.dropna().iloc[0], color='black', label='SPY normalized benchmark')
axes[0].set_title('Offline benchmark context (not RecessionAlert output)')
axes[0].set_ylabel('Normalized price')
axes[0].legend(loc='upper left')
for variant in lab['variant_signals']:
signal = pd.Series(variant['signal'], index=dates, dtype='float64')
axes[1].plot(dates, signal, linewidth=0.8, label=variant['id'])
axes[1].axhline(0.0, color='grey', linewidth=0.8)
axes[1].set_title('Inferred INDPRO rolling-rank second-difference variants')
axes[1].set_ylabel('Proxy second difference')
axes[1].legend(loc='upper left', fontsize=7)
plt.show()
diag_summary = pd.DataFrame([
{'diagnostic': 'Prior-six-month hit rate', 'value': diagnostics['event_results']['prior_six_month_hit_rate']},
{'diagnostic': 'Active-warning forward SPY six-month return', 'value': diagnostics['event_results']['forward_six_month_spy_return_after_target_active_mean']},
{'diagnostic': 'Clear-state forward SPY six-month return', 'value': diagnostics['event_results']['forward_six_month_spy_return_after_target_clear_mean']},
{'diagnostic': 'Lag-1 overlay cumulative return', 'value': diagnostics['timing_sensitivity'][1]['overlay_cumulative_return']},
{'diagnostic': 'Lag-1 overlay maximum drawdown', 'value': diagnostics['timing_sensitivity'][1]['overlay_max_drawdown']},
])
display(diag_summary)
print('Conclusion: exploratory proxy evidence only; native RecessionAlert signal not promoted.')