Issue 1345 — RecessionAlert Recession Fear Indicator¶
Offline, public-safe exploratory research. The checked-in RecessionAlert snapshot describes global Google search volume for recession as a fear gauge, but does not provide source-linked observations, exact query settings, a native span, publication clock, threshold, or buy/sell rule. This notebook never requests the live site, raw media, credentials, or private account data.
from pathlib import Path
import hashlib
import json
import sqlite3
root = Path.cwd()
while root != root.parent and not (root / 'data' / 'recessionalert.sqlite').exists():
root = root.parent
page_url = 'https://recessionalert.com/a-recession-fear-indicator/'
page_sha = 'sha256:2cb8464ee21e8b28af0f8dcbf7bacbd3ddeffc040c53717a198c0b701fa019dd'
manifest = json.loads((root / 'research/artifacts/recessionalert/manifest.json').read_text())
manifest_page = next(item for item in manifest if item.get('redacted_url') == page_url)
with sqlite3.connect(f'file:{root / "data/recessionalert.sqlite"}?mode=ro', uri=True) as connection:
row = connection.execute('SELECT fetched_at, status, sha256, content_length, body FROM pages WHERE url=?', (page_url,)).fetchone()
history = connection.execute('SELECT fetched_at, status, sha256 FROM crawl_log WHERE url=? ORDER BY id', (page_url,)).fetchall()
fetched_at, status, digest, content_length, body = row
assert manifest_page['sha256'] == page_sha == digest == 'sha256:' + hashlib.sha256(body).hexdigest()
assert len(body) == content_length == 83313
assert status == 200 and fetched_at == '2026-08-22T23:39:13Z'
print({'source_sha256': digest, 'bytes': content_length, 'fetched_at': fetched_at, 'same_url_crawl_rows': len(history), 'database_mode': 'ro'})
Disclosed rule and gaps¶
The article says that global Google search volume for recession is a promising recession indicator and fear gauge. It reports a peak within roughly two weeks of the 2008 NBER recession start, mentions an August 2011 spike near a forming S&P 500 bottom, and describes a later SuperIndex fall. These are contextual claims, not an executable market rule. The page gives no query geography/category/normalization, machine-readable observations, first/last dates, recurring release clock, threshold, smoothing, state semantics, target, sizing, rebalance, cost, exit, or re-entry rule. The article-to-archive delay and two-week NBER comparison are not publication lags.
import sys
sys.path.insert(0, str(root))
from research.research_campaign.recessionalert_1345 import run_campaign
campaign = run_campaign(db_path=root / 'data/market.sqlite')
print({'task_id': campaign['task_id'], 'source_fidelity': campaign['source_fidelity'], 'tested_variants': len(campaign['variants']), 'macro_window': campaign['window']['macro_signal']})
for variant in campaign['variants']:
ief = variant['metrics_spy_ief']
bil = variant['metrics_spy_bil']
print({
'variant': variant['id'],
'lookback': variant['lookback'],
'threshold': variant['threshold'],
'lag_bars': variant['lag_bars'],
'risk_off_fraction': round(variant['risk_off_fraction'], 4),
'ief_total_vs_spy': (round(ief['total_return'], 6), round(ief['benchmark_total_return'], 6)),
'bil_total_vs_spy': (round(bil['total_return'], 6), round(bil['benchmark_total_return'], 6)),
})
Best-effort diagnostics¶
The inferred proxy uses FRED UNRATE because it is the longest recession-relevant public series in the tracked cache. SPY is the risk-on benchmark and IEF/BIL are descriptive defensive sleeves; these are not a RecessionAlert allocation. Lag 1 is the causal baseline, lag 0 is research-only, and lag 2 is a conservative sensitivity.
from research.research_campaign.recessionalert_1345 import run_diagnostics
diagnostics = run_diagnostics(db_path=root / 'data/market.sqlite')
print('Timing sensitivity:')
for row in diagnostics['timing_sensitivity']:
print({k: row[k] for k in ('variant', 'lag_bars', 'risk_off_fraction', 'risk_off_events', 'same_close_status')}, 'IEF_total=', round(row['metrics_spy_ief']['total_return'], 6), 'BIL_total=', round(row['metrics_spy_bil']['total_return'], 6))
print('Context windows:')
for row in diagnostics['regime_results']:
print({k: row[k] for k in ('name', 'macro_mean_unrate', 'macro_max_unrate', 'risk_off_fraction')})
print('Failure modes:', [row['id'] for row in diagnostics['failure_modes']])
Conclusion¶
Six inferred variants were executed on the longest available offline proxy history. They all underperformed their aligned SPY total-return benchmark in the IEF and BIL descriptive overlays. This is an exploratory negative result, not a validation of the source claim. The native Google Trends series, exact publication lag, and disclosed buy/sell rule remain unavailable. Publish the derived charts and trial ledger as Other / Research evidence only; do not register an official indicator or present an inferred setting as disclosed.