| name | catalyst-event-gate |
| description | Unified forward catalyst timeline + deterministic R3 event-risk gate. Merges the FMP earnings calendar (US-listed) and economic calendar (FOMC/CPI/NFP) into one timeline, then turns imminent binary events into label caps and confidence trims so /expect and daily-briefing don't issue fresh BUY calls right into an earnings print or a Fed decision. Triggers: catalyst, event risk, earnings risk, event gate, upcoming earnings, FOMC, CPI, NFP, macro event, ์ด๋ฒคํธ ๋ฆฌ์คํฌ, ์ค์ ๋ฐํ ์๋ฐ, ์บํธ๋ฆฌ์คํธ. |
Catalyst Event Gate โ Forward Timeline + R3 Event-Risk Gate
Two products from two forward calendars (FMP /stable primary; keyless
yfinance fallback for US earnings dates):
- Merged timeline โ every forward earnings report (per ticker) and every
high-impact macro release (FOMC / CPI / NFP) in one normalized, sorted view.
- R3 gate โ a deterministic verdict that caps the label or trims confidence
when a binary event is too close to safely hold a directional thesis through.
It is a risk gate, not a signal: R3 never raises the BUY bar and never
issues a BUY โ it only caps a would-be BUY to WATCH or shaves confidence.
Why this exists
A strong technical + news + macro setup can still be a bad entry if the next
trading day is an earnings print or a Fed decision: the outcome is a coin-flip
gap, not a thesis. R1 (regime) and R2 (overextension) cannot see dated binary
events. R3 closes that gap.
CLI
bin/stock-cli catalyst timeline NVDA,AMD --market US --days 14
bin/stock-cli catalyst timeline NVDA,AMD --market US --days 14 --include-macro
bin/stock-cli catalyst timeline 005930 --market KR --days 14 --include-macro
bin/stock-cli catalyst gate NVDA,AMD --market US
bin/stock-cli catalyst gate 005930 --market KR
All output is JSON. FMP_API_KEY is preferred but no longer required for US
earnings caps: when the FMP earnings fetch fails (or the key is missing), the
gate falls back to keyless per-ticker yfinance earnings dates. The macro
side (FOMC/CPI/NFP) is FMP-only โ on plans without economic-calendar access it
returns 402 and the gate reports macro_available: false. Any failure โ the
command still exits 0 โ it never breaks the cron (FAIL-OPEN). But fail-open
is now visible: gate_unavailable: true only when NO source produced data;
partial outages are flagged via earnings_source / macro_available +
notes, so a dead feed can never masquerade as "no imminent events".
Merged timeline structure
{
"asof": "2026-06-17",
"market": "US",
"by_ticker": {
"NVDA": [
{
"ticker": "NVDA", "market": "US", "kind": "earnings",
"name": "NVIDIA Corp", "event_date": "2026-06-18",
"timing": "AMC", "impact": "High",
"trading_days_until": 1, "source": "fmp:earnings-calendar"
}
],
"AMD": []
},
"market_wide": [
{
"ticker": null, "market": "GLOBAL", "kind": "macro",
"name": "FOMC Interest Rate Decision", "event_date": "2026-06-18",
"timing": null, "impact": "High",
"trading_days_until": 1, "source": "fmp:economic-calendar"
}
]
}
by_ticker[SYM] โ that ticker's forward earnings events, nearest-first.
market_wide โ high-impact macro events (GLOBAL โ they transmit across
markets via FX / index futures), nearest-first.
trading_days_until โ business-day (Mon-Fri) distance; 0 = today.
Holidays are not excluded (pure stdlib), which only ever makes an event look
~1 day farther away โ the safe direction for a risk gate.
R3 gate rule table
bin/stock-cli catalyst gate returns:
{
"asof": "2026-06-17", "market": "US",
"by_ticker": {
"NVDA": {"cap_label": "WATCH", "confidence_trim": 0.0,
"next_earnings_date": "2026-06-18", "trading_days_until": 1}
},
"macro_trim": 0.05,
"macro_events": [{"name": "FOMC ...", "event_date": "2026-06-18",
"trading_days_until": 1, "impact": "High"}],
"gate_unavailable": false, "notes": [],
"earnings_source": "fmp", "macro_available": true
}
Availability fields (visible fail-open)
| Field | Meaning |
|---|
earnings_source | "fmp" (/stable/earnings-calendar), "yfinance" (keyless fallback), or null (earnings side unavailable โ or KR, which has no feed by design) |
macro_available | false = the macro calendar could not be fetched (e.g. FMP plan lacks economic-calendar access) โ NOT "no macro event imminent"; treat only macro_trim as zero |
gate_unavailable | true only when no source produced data โ treat every cap/trim as advisory-zero |
notes | one entry per degradation (which source failed and why) |
Earnings (per-ticker, US only)
| Trading days to next report | R3 action | Field |
|---|
td <= 2 (EARNINGS_WATCH_DAYS) | cap label โ WATCH (binary risk) | cap_label="WATCH" |
2 < td <= 5 (EARNINGS_TRIM_DAYS) | trim confidence 0.05 | confidence_trim=0.05 |
td > 5 | none | both zero |
Macro (market-wide, US + KR)
| Condition | R3 action | Field |
|---|
Any High-impact FOMC / CPI / NFP within td <= 2 (MACRO_TRIM_DAYS) | trim 0.05 on every pick in that market | macro_trim=0.05 |
| else | none | macro_trim=0.0 |
Thresholds live in mcp-market-data/events.py as module constants.
KR vs US coverage matrix
| Earnings (per-ticker) | Macro (market-wide) |
|---|
| US | โ
FMP /stable/earnings-calendar (US-listed) โ keyless yfinance fallback | โ
FMP /stable/economic-calendar (may 402 on free plans โ macro_available: false) |
| KR | โ no forward KR EPS feed on FMP | โ
consumes the US macro stream (transmits via FX / SOXL) |
KR is macro-only. FMP's earnings calendar is US-listed only and there is
no forward KR EPS feed, so KR tickers never receive a per-ticker earnings cap โ
their cap_label/next_earnings_date are always null. KR still receives the
US macro_trim, because a Fed decision or US CPI moves KOSPI/KOSDAQ through the
won and the semiconductor complex (SOXL/SOXX). The earnings fetch is skipped
entirely for KR to protect the FMP quota.
R1 + R2 + R3 stacking
R3 composes with the existing /expect gates (see expect/SKILL.md Step 7):
- WATCH cap wins. A WATCH from R1 (RISK_OFF), R2 (EXTREME overextension), or
R3 (earnings
td <= 2) caps the label at WATCH โ no BULL logged, full stop.
- Confidence caps/trims take the minimum / stack down. R1/R2 caps
(0.60), the R3 earnings trim (โ0.05), and the R3 macro trim (โ0.05) all pull
confidence down; the effective confidence is the lowest survivor. A pick can
eat both R3 trims (own earnings near + macro near) at once.
- R3 never raises the BUY bar. Only R1 NEUTRAL (+1.0) and R2 ELEVATED (+1.0)
raise the COMPOSITE bar. R3 caps or trims; it does not move the threshold.
Quota note
catalyst gate and catalyst timeline each fetch the earnings + macro windows
once per call (not once per ticker). Pass all candidate tickers in a single
comma-separated call. FMP free tier is 250 calls/day. The yfinance earnings
fallback is per-ticker but only runs over the candidate list, and only when the
FMP earnings fetch failed.
Implementation
- Core + fetchers:
mcp-market-data/events.py
(CatalystEvent, EventGate, trading_days_between, build_timeline,
evaluate_gate, _fetch_earnings_fallback_yf).
- CLI:
stock_cli.py (cmd_catalyst_timeline, cmd_catalyst_gate,
catalyst subparser).
- Tests (offline, stubbed fetchers):
mcp-market-data/tests/test_events.py.