Insights
Backtest Corporate-Default Event Windows With an API
Alphanume Team · August 15, 2026
To backtest corporate-default event windows with an API, freeze every disclosed default row first, retain names that later delist, and keep the filing date separate from the underlying default date.
Alphanume's GET /v1/corporate-default-events endpoint returns public-company default events labeled from SEC filing text. Each row is filtered to is_default = 1 and includes the filing source, evidence text, event type, obligation details, amounts, cure dates, and acceleration state when the filing supports them. Results are returned newest first by event_date.
The endpoint supplies the event cohort, not a survivorship-complete price history. Retaining a company that later delists requires a dated security master and price source that preserve inactive names. Start from every API event in the requested period, then left join market outcomes onto that frozen list. Starting from today's tradable universe discards exactly the failures a default study needs to keep.
Defaults have more than one clock
Field | Meaning | How to use it |
|---|---|---|
event_date | Date of the filing that disclosed the event | Primary public-disclosure anchor |
default_date | Date the filing says the default occurred | Underlying contractual-event anchor when present |
grace_period_end_date | End of a stated cure or grace window | Lifecycle milestone, nullable |
acceleration_declared | Whether the obligation was declared immediately due | Severity field, nullable |
filing_url | SEC filing used as evidence | Primary-source audit link |
The lag between default_date and event_date is economically meaningful. The endpoint serves the filing date but not its EDGAR acceptance time, so a date-only close-to-close study should conservatively begin on the first eligible session after event_date. A separately sourced acceptance timestamp can support a finer before-close rule when its accession, URL, and retrieval provenance stay with the event. Run the public-disclosure and contractual-default clocks as separate specifications.
Pull the full event cohort first
The endpoint accepts exact date or the standard date_gte, date_lte, date_gt, and date_lt range filters. Exact date cannot be combined with a range. The current route does not expose ticker filtering or pagination, so a period pull returns all matching default rows allowed by the account tier.
import os
import requests
import pandas as pd
response = requests.get(
"https://api.alphanume.com/v1/corporate-default-events",
headers={"X-API-Key": os.environ["ALPHANUME_API_KEY"]},
params={"date_gte": "2020-01-01", "date_lte": "2025-12-31"},
timeout=30,
)
response.raise_for_status()
payload = response.json()
events = pd.DataFrame(payload["data"])
assert len(events) == payload["count"]
assert events["event_date"].notna().all()
events.to_parquet("corporate_default_events_raw.parquet", index=False)Save that raw cohort before deduplication or price joins. A company can generate multiple rows as a situation develops from covenant breach to missed payment to acceleration. Those rows may be distinct stages, repeated disclosures, or related obligations. Decide the research unit in writing before collapsing them.
Retain names that disappear
The safest join begins with the event table on the left. Map each event ticker to a time-aware security identifier, fetch prices for active and inactive securities, and preserve an explicit match status. An inner join quietly removes unresolved, halted, bankrupt, acquired, and delisted cases, which makes the remaining sample look healthier.
events["event_date"] = pd.to_datetime(events["event_date"])
trading_sessions = pd.DatetimeIndex(
sorted(pd.to_datetime(prices["date"]).dropna().unique())
)
def first_session_after(value):
position = trading_sessions.searchsorted(value, side="right")
return trading_sessions[position] if position < len(trading_sessions) else pd.NaT
events["anchor_session"] = events["event_date"].map(first_session_after)
event_master = (
events
.merge(security_history, how="left", on=["ticker"], indicator="id_match")
.merge(
prices.rename(columns={"date": "anchor_session"}),
how="left",
on=["security_id", "anchor_session"],
indicator="price_match",
)
)
audit = event_master.groupby(["id_match", "price_match"], dropna=False).size()
assert len(event_master) >= len(events)
assert audit.sum() == len(event_master)
# Keep unmatched events in the exported cohort.
event_master.to_parquet("default_event_master.parquet", index=False)The example uses a simple key only to show join direction. Real identifier history needs effective dates because tickers can be reused or changed. It also needs a defined policy for stale last prices, halted sessions, cash acquisition proceeds, cancellations, and securities that move to an over-the-counter venue. Missing outcome data should remain a reported research result.
Grade severity without inventing facts
Field | What it can support | What null means |
|---|---|---|
event_type | Separate missed payments, covenant issues, acceleration, and other labeled defaults | The filing did not support a normalized type |
principal_outstanding_usd | Size the stated obligation | Amount was not explicitly supported |
missed_payment_amount_usd | Measure the stated missed payment | Unknown, not zero |
amount_accelerated_usd | Measure the stated accelerated balance | Unknown or unstated |
confidence | Review priority for the extraction | It is not bankruptcy probability |
evidence_quote | Audit the label against filing text | Source text was not retained in that field |
A default is a broken debt obligation. A restructuring changes the obligation, a bankruptcy is a court process, and a later cure or waiver can resolve the default. Those events can follow one another, but they are not synonyms. The default feed should not be converted into a zero-equity label, and confidence should not be treated as a return forecast.
Predefine the window and failures
- Set the tradable anchor. Use the first eligible session after
event_datefor a date-only study. Use an intraday rule only after adding a separately sourced acceptance timestamp. - Keep the denominator. Report events with missing identifiers, prices, or delisting outcomes instead of dropping them.
- Control repeated events. Run both first-event-per-issuer and all-event specifications when sequences overlap.
- Separate lifecycle outcomes. Add cures, restructurings, bankruptcies, and resolutions only from dated sources observed later.
- Model implementation costs. Distressed equities can have wide spreads, halts, borrow constraints, and untradeable prints.
Free access covers a trailing 20-trading-session window delayed by one trading session, so a multi-year event study requires historical access. A restricted range is not evidence that no defaults occurred.
Ship the audit before the return chart
Run one calendar year first and export four files: untouched API events, dated identifier matches, outcome-match audit, and final event windows. Check every null amount as unknown, retain every unresolved event, and compare the event_date and default_date specifications before calculating an average return.
Use the Corporate Default Events dataset page to inspect the contract, then follow the Corporate Default Events guide for the broader research context. The cohort is ready for inference only after the missing and delisted cases are visible in the audit.