fintech-algorithms

Survivorship-Bias Guard

Keep Historical Failures in the Test

Install and import

bash
npm install fintech-algorithms
ts
import { guardSurvivorship } from "fintech-algorithms/corporate-actions-and-security-master-data/point-in-time-universe/survivorship-bias-guard";

Signature

guardSurvivorship(input)

Worked example

executed Captured by running this function on the input its own test provides. Real output of real code — but not asserted against a published figure.

Input

input
{
  "universeId": "UNIVERSE:SYNTH:CORE",
  "definitionId": "DEF:SYNTH:CORE",
  "effectiveAt": "2023-12-31T23:59:59Z",
  "knownAt": "2024-01-02T12:00:00Z",
  "candidateUniverse": {
    "construction": "historical_membership_ledger",
    "asOf": "2023-12-31T23:59:59Z",
    "availableAt": "2024-01-02T10:00:00Z",
    "securityIds": ["SEC:A", "SEC:B", "SEC:C", "SEC:D"],
    "sourceId": "SYNTH-CANDIDATE-LEDGER"
  },
  "universeDefinitions": [
    {
      "definitionId": "DEF:SYNTH:CORE",
      "universeId": "UNIVERSE:SYNTH:CORE",
      "revision": 1,
      "effectiveFrom": "2020-01-01T00:00:00Z",
      "effectiveTo": null,
      "announcedAt": "2019-12-15T12:00:00Z",
      "availableAt": "2019-12-15T12:05:00Z",
      "status": "active",
      "baseType": "named_research_universe",
      "baseId": "BASE:SYNTH:001",
      "ruleSummary": "Synthetic members documented by effective-dated membership evidence.",
      "sourceId": "SYNTH-DEFINITION-R1"
    },
    {
      "definitionId": "DEF:SYNTH:CORE",
      "universeId": "UNIVERSE:SYNTH:CORE",
      "revision": 2,
      "effectiveFrom": "2020-01-01T00:00:00Z",
      "effectiveTo": null,
      "announcedAt": "2024-02-01T09:00:00Z",
      "availableAt": "2024-02-01T09:05:00Z",
      "status": "active",
      "baseType": "named_research_universe",
      "baseId": "BASE:SYNTH:001",
      "ruleSummary": "Later documentation revision; unavailable to the canonical query.",
      "sourceId": "SYNTH-DEFINITION-R2"
    }
  ],
  "membershipRecords": [
    {
      "membershipId": "MEM-A",
      "revision": 1,
      "universeId": "UNIVERSE:SYNTH:CORE",
      "securityId": "SEC:A",
      "validFrom": "2020-01-01T00:00:00Z",
      "validTo": null,
      "announcedAt": "2019-12-15T12:00:00Z",
      "availableAt": "2019-12-15T12:05:00Z",
      "status": "active",
      "changeType": "entry",
      "eventId": "EVENT-A-ENTRY",
      "sourceId": "SYNTH-MEM-A"
    },
    {
      "membershipId": "MEM-B",
      "revision": 1,
      "universeId": "UNIVERSE:SYNTH:CORE",
      "securityId": "SEC:B",
      "validFrom": "2020-01-01T00:00:00Z",
      "validTo": "2024-06-01T00:00:00Z",
      "announcedAt": "2023-12-20T10:00:00Z",
      "availableAt": "2023-12-20T10:05:00Z",
      "status": "active",
      "changeType": "exit",
      "eventId": "EVENT-B-EXIT",
      "sourceId": "SYNTH-MEM-B-R1"
    },
    {
      "membershipId": "MEM-B",
      "revision": 2,
      "universeId": "UNIVERSE:SYNTH:CORE",
      "securityId": "SEC:B",
      "validFrom": "2020-01-01T00:00:00Z",
      "validTo": "2023-12-15T00:00:00Z",
      "announcedAt": "2024-01-10T08:00:00Z",
      "availableAt": "2024-01-10T08:05:00Z",
      "status": "active",
      "changeType": "revision",
      "eventId": "EVENT-B-CORRECTION",
      "sourceId": "SYNTH-MEM-B-R2"
    }
  ],
  "comparisonRoster": {
    "observedAt": "2025-01-02T00:00:00Z",
    "availableAt": "2025-01-02T12:00:00Z",
    "securityIds": ["SEC:A", "SEC:C", "SEC:D"],
    "sourceId": "SYNTH-CURRENT-ROSTER"
  }
}

Call

guardSurvivorship(input)

Returns

object with 16 fields: status, universeId, definitionId, definitionVersion, effectiveAt, knownAt, candidateUniverseSourceId, candidateUniverseAsOf, …

{
  "status": "resolved",
  "universeId": "UNIVERSE:SYNTH:CORE",
  "definitionId": "DEF:SYNTH:CORE",
  "definitionVersion": "DEF:SYNTH:CORE@1",
  "effectiveAt": "2023-12-31T23:59:59Z",
  "knownAt": "2024-01-02T12:00:00Z",
  "candidateUniverseSourceId": "SYNTH-CANDIDATE-LEDGER",
  "candidateUniverseAsOf": "2023-12-31T23:59:59Z",
  "classifications": [
    {
      "securityId": "SEC:A",
      "state": "eligible",
      "evidenceVersions": ["MEM-A@1"],
      "diagnostic": "one known active membership interval covers effectiveAt"
    },
    {
      "securityId": "SEC:B",
      "state": "eligible",
      "evidenceVersions": ["MEM-B@1"],
      "diagnostic": "one known active membership interval covers effectiveAt"
    },
    {
      "securityId": "SEC:C",
      "state": "eligible",
      "evidenceVersions": ["MEM-C@1"],
      "diagnostic": "one known active membership interval covers effectiveAt"
    }
  ],
  "eligibleSecurityIds": ["SEC:A", "SEC:B", "SEC:C"],
  "ineligibleSecurityIds": ["SEC:D"],
  "unknownSecurityIds": [],
  "ambiguousSecurityIds": [],
  "membershipEventVersionsUsed": ["MEM-A@1", "MEM-B@1", "MEM-C@1", "MEM-D@1"]
}

Showing 14 of 16 fields.

Other exports

This module also exports compareEqualWeightedReturns. Every module additionally exports run as an alias of its primary function, and a meta object carrying its catalog id, domain, family, shape and article URL.

Diagrams

Survivorship-Bias Guard — article hero
Survivorship-Bias Guard — failure guard
Survivorship-Bias Guard — worked example

Calculation flow

Causal membership and hindsight diagnostics
flowchart LR
    Q["effectiveAt and knownAt"] --> A["Rows available by knownAt"]
    A --> V["Validate revision prefixes"]
    V --> D["Resolve supported definition"]
    D --> C{"Historical candidate ledger complete?"}
    C -->|No| I["Incomplete"]
    C -->|Yes| M["Classify membership intervals"]
    M --> R["Causal result"]
    R -.-> H["Non-causal later-roster diagnostic"]
    R -.-> P["Synthetic-only return illustration"]
Membership classification states
stateDiagram-v2
    [*] --> SelectKnownVersions
    SelectKnownVersions --> Eligible: exactly one interval covers effectiveAt
    SelectKnownVersions --> Ineligible: active evidence excludes effectiveAt
    SelectKnownVersions --> Unknown: no active evidence available
    SelectKnownVersions --> Ambiguous: multiple intervals cover effectiveAt
    Eligible --> Resolved
    Ineligible --> Resolved
    Unknown --> NeedsEvidence
    Ambiguous --> NeedsReconciliation

How it works

This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.

Read the article →

References

  • R1 - S&P Dow Jones Indices Equity Indices Policies & Practices — S&P Dow Jones Indices
  • R2 - Lehman Brothers Form 8-K: Chapter 11 filing — Lehman Brothers Holdings Inc., filed with the U.S. Securities and Exchange Commission
  • R3 - Lehman Brothers Form 8-K: NYSE suspension — Lehman Brothers Holdings Inc., filed with the U.S. Securities and Exchange Commission
  • R4 - S&P 150 comparison — S&P Dow Jones Indices
  • R5 - CRSP Research Data Products — Center for Research in Security Prices
  • R6 - CRSP PERMNO and PERMCO — Center for Research in Security Prices
  • R7 - CRSP10 US Stock Database Guide — Center for Research in Security Prices
  • R8 - SEC EDGAR access guidance — U.S. Securities and Exchange Commission