"""Canonical constants for sg-eqdp-scanner. Single source of truth referenced by analytical code, tests, and docs. Bump deliberately, not accidentally — any change here invalidates downstream results. Cross-referenced by `memory.md` and `docs/STRATEGY.md`. """ from __future__ import annotations from typing import Final import pandas as pd # yfinance ticker convention SGX_SUFFIX: Final[str] = ".SI" STI_TICKER: Final[str] = "^STI" # Programme events. Keys are stable IDs used in abnormal_returns.event_id. EVENTS: Final[dict[str, str]] = { "announcement": "2025-02-21", "tranche_1": "2025-07-21", "tranche_2": "2025-11-19", "expansion": "2026-02-12", } def event_date(event_id: str) -> pd.Timestamp: """Return the canonical event date as a tz-naive Timestamp.""" return pd.Timestamp(EVENTS[event_id]).tz_localize(None) # The nine EQDP-appointed managers across both tranches. EQDP_MANAGERS: Final[tuple[str, ...]] = ( # Tranche 1 (Jul 2025) "Avanda Investment Management", "Fullerton Fund Management", "JPMorgan Asset Management", # Tranche 2 (Nov 2025) "Amova Asset Management", # formerly Nikko AM "AR Capital", "BlackRock", "Eastspring Investments", "Lion Global Investors", "Manulife Investment Management", ) EQDP_MANAGER_TRANCHE: Final[dict[str, int]] = { "Avanda Investment Management": 1, "Fullerton Fund Management": 1, "JPMorgan Asset Management": 1, "Amova Asset Management": 2, "AR Capital": 2, "BlackRock": 2, "Eastspring Investments": 2, "Lion Global Investors": 2, "Manulife Investment Management": 2, } # Candidate-score weights — see docs/METHODOLOGY.md §3. Must sum to 1.0. SCORE_WEIGHTS: Final[dict[str, float]] = { "liquidity_rise": 0.30, "institutional_proxy": 0.20, "index_inclusion": 0.15, "broker_named": 0.15, "filing_present": 0.20, } # CAPM β estimation window — 252 trading days (≈1 year) ending 30 days before event. CAPM_BETA_WINDOW_DAYS: Final[int] = 252 CAPM_GAP_DAYS: Final[int] = 30 # Default event windows (trading-day offsets relative to event date). EVENT_WINDOWS: Final[dict[str, tuple[int, int]]] = { "announcement": (-5, 20), "tranche_1": (-1, 10), "tranche_2": (-1, 10), "expansion": (-1, 20), } # Benchmark options for abnormal-return computation. BENCHMARK_CAPM: Final[str] = "capm" BENCHMARK_FF3: Final[str] = "ff3" BENCHMARK_MARKET: Final[str] = "market_adjusted" BENCHMARKS: Final[tuple[str, ...]] = (BENCHMARK_CAPM, BENCHMARK_FF3, BENCHMARK_MARKET) # Tier identifiers used everywhere in the codebase and DB. TIER_T1: Final[str] = "T1" TIER_T2: Final[str] = "T2" TIER_T3: Final[str] = "T3" TIER_CONTROL: Final[str] = "control" TIER_NONE: Final[str] = "none" TIERS: Final[tuple[str, ...]] = (TIER_T1, TIER_T2, TIER_T3, TIER_CONTROL, TIER_NONE) # Bootstrap configuration — docs/METHODOLOGY.md §10. BOOTSTRAP_REPLICATIONS: Final[int] = 5_000 BOOTSTRAP_BLOCK_LENGTH: Final[int] = 5