Alphanume

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
  1. Set the tradable anchor. Use the first eligible session after event_date for a date-only study. Use an intraday rule only after adding a separately sourced acceptance timestamp.
  2. Keep the denominator. Report events with missing identifiers, prices, or delisting outcomes instead of dropping them.
  3. Control repeated events. Run both first-event-per-issuer and all-event specifications when sequences overlap.
  4. Separate lifecycle outcomes. Add cures, restructurings, bankruptcies, and resolutions only from dated sources observed later.
  5. 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.