#!/usr/bin/env bash # Tier-1 alpha research runner for a high-CPU MacBook (local only). # # Prerequisites # - git checkout research/earnings-gap-and-sue (or later research branch) # - .env with ALPACA_* (required for OHLCV/ETFs); FMP_* for earnings resume; # optional ALPHA_VANTAGE_* as earnings fallback # - Python venv with project deps installed # - backtest_snapshots/prod.sqlite present (gitignored — copy or rebuild) # # Usage # chmod +x scripts/run_tier1_macbook.sh # ./scripts/run_tier1_macbook.sh # coverage + deep rebuild + harness # ./scripts/run_tier1_macbook.sh --all # earnings resume + full depth pipeline # ./scripts/run_tier1_macbook.sh --earnings-only # multi-day FMP backfill + re-run 2a/2b # ./scripts/run_tier1_macbook.sh --harness-only # skip rebuild; race-guard + IC only # ./scripts/run_tier1_macbook.sh --coverage-only # bars-per-year probe only # # Does NOT touch production Postgres, scheduler, gates, or prod config. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" # --- defaults (override via flags or env) --- PROD_SNAP="${PROD_SNAP:-backtest_snapshots/prod.sqlite}" RESEARCH_SNAP="${RESEARCH_SNAP:-backtest_snapshots/research.sqlite}" HISTORY_DAYS="${HISTORY_DAYS:-5000}" MIN_BARS="${MIN_BARS:-260}" WORKERS="${WORKERS:-8}" ALPACA_SLEEP="${ALPACA_SLEEP:-0.15}" FMP_LIMIT="${FMP_LIMIT:-250}" FMP_SLEEP="${FMP_SLEEP:-0.35}" PYTHON="${PYTHON:-python3}" PHASE="depth" # depth | all | earnings | harness | coverage usage() { sed -n '2,22p' "$0" | sed 's/^# \?//' exit "${1:-0}" } while [[ $# -gt 0 ]]; do case "$1" in --all) PHASE=all; shift ;; --earnings-only) PHASE=earnings; shift ;; --harness-only) PHASE=harness; shift ;; --coverage-only) PHASE=coverage; shift ;; --depth) PHASE=depth; shift ;; --prod-snap) PROD_SNAP="$2"; shift 2 ;; --research-snap) RESEARCH_SNAP="$2"; shift 2 ;; --history-days) HISTORY_DAYS="$2"; shift 2 ;; --workers) WORKERS="$2"; shift 2 ;; --fmp-limit) FMP_LIMIT="$2"; shift 2 ;; --python) PYTHON="$2"; shift 2 ;; -h|--help) usage 0 ;; *) echo "Unknown flag: $1" >&2; usage 1 ;; esac done if [[ -x .venv/bin/python ]]; then PYTHON=".venv/bin/python" elif command -v "$PYTHON" >/dev/null 2>&1; then : else echo "ERROR: no Python found (tried .venv/bin/python and $PYTHON)" >&2 exit 1 fi log() { printf '\n==> %s\n' "$*"; } die() { echo "ERROR: $*" >&2; exit 1; } need_file() { [[ -f "$1" ]] || die "missing $1" } require_prod() { need_file "$PROD_SNAP" } run_earnings() { require_prod log "Earnings backfill (FMP free tier ~${FMP_LIMIT}/day; resume-safe)" "$PYTHON" scripts/backfill_earnings_events.py \ --snapshot "$PROD_SNAP" \ --provider fmp \ --force-symbol \ --limit "$FMP_LIMIT" \ --sleep "$FMP_SLEEP" log "Earnings research 2a+2b (report only; no filters shipped)" "$PYTHON" scripts/run_earnings_research.py \ --snapshot "$PROD_SNAP" \ --workers "$WORKERS" \ --allow-spawn } run_coverage() { require_prod log "Coverage probe (bars per year) on $PROD_SNAP" "$PYTHON" scripts/run_history_depth_research.py \ --phase coverage \ --snapshot "$PROD_SNAP" } run_rebuild() { require_prod log "Deep rebuild $PROD_SNAP → $RESEARCH_SNAP (history-days=$HISTORY_DAYS)" log "SURVIVORSHIP: today's constituents backfilled — relative IC only, not levels" "$PYTHON" scripts/extend_snapshot_universe.py \ --source "$PROD_SNAP" \ --output "$RESEARCH_SNAP" \ --force-copy \ --history-days "$HISTORY_DAYS" \ --min-bars "$MIN_BARS" \ --sleep "$ALPACA_SLEEP" log "Refresh SPY + 11 sector ETFs on research snapshot" "$PYTHON" scripts/fetch_sector_etfs_to_snapshot.py \ --snapshot "$RESEARCH_SNAP" \ --history-days "$HISTORY_DAYS" log "Also deepen sector ETFs on prod snapshot (for local A/B parity)" "$PYTHON" scripts/fetch_sector_etfs_to_snapshot.py \ --snapshot "$PROD_SNAP" \ --history-days "$HISTORY_DAYS" } run_harness() { need_file "$RESEARCH_SNAP" log "Race-guard + full signal harness + era split on $RESEARCH_SNAP" "$PYTHON" scripts/run_history_depth_research.py \ --phase harness \ --snapshot "$RESEARCH_SNAP" \ --workers "$WORKERS" \ --allow-spawn } log "cwd=$ROOT python=$PYTHON phase=$PHASE workers=$WORKERS" case "$PHASE" in coverage) run_coverage ;; earnings) run_earnings ;; harness) run_harness ;; depth) run_coverage run_rebuild run_harness ;; all) run_earnings run_coverage run_rebuild run_harness ;; *) die "unknown phase $PHASE" ;; esac log "Done. Check reports/ and docs/research/history-depth-extension.md" log "Commit reports on this machine if they look good, or copy them back to Windows."