fintech-algorithms
Using a coding agent? Give it the skill: npx skills add IslamBaraka90/Fintech-Algorithms-Library What it does →

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)

Compares a universe you intend to test against the point-in-time roster and reports what is missing. It is a gate rather than a transform: the failure is the product.

Parameters#

NameTypeNotes
input{ universeId: string; definitionId: string; effectiveAt: string; knownAt: string; candidateUniverse: object; universeDefinitions: object[]; membershipRecords: object[]; comparisonRoster: object }candidateUniverse is the roster under test — typically whatever a data pull returned — and comparisonRoster is the point-in-time truth to check it against.

Returns#

{ status, definitionVersion, candidateUniverseAsOf, missingSecurities, extraSecurities, … }

Which securities are missing from the candidate and which should not be there, with the definition version the comparison used.

Errors#

  • When the universe definition is unknown at knownAt — reported as a status rather than thrown

Complexity: time O(members), space O(members).

Worked example#

verified This is the worked example published in the article, replayed by the test suite on every run. The output cannot drift.

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 7 fields: status, definitionVersion, eligibleSecurityIds, ineligibleSecurityIds, unknownSecurityIds, ambiguousSecurityIds, hindsightDiagnostics

{
  "status": "resolved",
  "definitionVersion": "DEF:SYNTH:CORE@1",
  "eligibleSecurityIds": ["SEC:A", "SEC:B", "SEC:C"],
  "ineligibleSecurityIds": ["SEC:D"],
  "unknownSecurityIds": [],
  "ambiguousSecurityIds": [],
  "hindsightDiagnostics": {
    "historicallyEligibleMissingFromCurrent": ["SEC:B"],
    "currentRosterNotHistoricallyEligible": ["SEC:D"],
    "retentionRatio": 0.666667
  }
}

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

The rest of the Point-in-Time Universe family#