The governed definitions this build depends on. Read them first if a term is unfamiliar.
- PrimarySplit FactorA split factor is the declared conversion ratio between new and old per-share units for a stock split.
- PrimaryVolume Adjustment BasisA volume adjustment basis declares how reported volume values are represented and adjusted within a series.
- ImportantAdjusted PriceAdjusted price is a transformed price expressed on a declared comparison basis after applying specified corporate-action or other adjustment factors.
- ImportantCorporate ActionA corporate action is an issuer or security event that changes rights, quantities, cash flows, identity, or trading terms.
- ImportantReturn BaseReturn base is the declared starting value, unit, and economic basis against which a return is measured.
- ImportantRevision LineageAn ordered chain connecting each correction to the exact earlier record it supersedes.
- ImportantStock SplitA stock split increases the number of shares per holder while proportionally changing the per-share basis without intentionally creating value.
- ImportantTrading VolumeTrading volume is the aggregate eligible quantity executed over a stated instrument, source, and interval.
- ImportantUnadjusted PriceUnadjusted price is the reported or normalized market price before retrospective corporate-action adjustment.
CRSP's cumulative share/volume factors can put historical shares outstanding and traded volume on one CRSP-defined share basis. The valuable result is not merely a smoother series. It is a result whose CRSP field, release, basis date, missing state, and limitations remain auditable.
This article is specifically about CRSP US Stock & Indexes Flat File Format 2.0 (CIZ). It does not present CRSP's convention as universal corporate-action truth.
What CRSP defines
The current CRSP CIZ 2.0 database guide defines DlyCumFacShr and MthCumFacShr as cumulative factors for shares and volume arising from split-type events. CRSP sets each series to 1 on the security's last trading day. For a security that delists from tracked exchanges and later returns, CRSP continues the factor across the gap as if no split occurred during that gap.
Those last two sentences are vendor rules. A carried factor does not prove that nothing happened to the issuer while CRSP did not track the security.
Use the exact CRSP share/volume field
For daily records, use DlyCumFacShr. For monthly records, use MthCumFacShr. Do not substitute DlyCumFacPr or MthCumFacPr. CRSP's own documentation distinguishes events where price and share factors differ.
The package calculation is:
Here is a raw CRSP shares-outstanding or volume field, and is the same-date, same-security-lineage, same-release cumulative share factor. Multiplication is the direction. The result is named CRSP-adjusted because its basis comes from CRSP's factor series. The public fixture uses SYNTHETIC-CRSP-CIZ-001, not a fabricated PERMNO; production lineage must retain the real CRSP-assigned PERMNO from the licensed source.
The CRSP release belongs in the result. CRSP's June 2025 release notes changed cumulative-factor display precision from 6.4 to 24.12. This implementation makes a separate package choice: round once at its public output to 12 decimal places using half-away-from-zero, then report a reverse residual. Dividing the rounded result by the factor may not reproduce the raw value exactly.
A synthetic four-for-one example
Suppose a synthetic CRSP-shaped daily series has this state:
| Date | Raw shares (000s) | Raw volume | DlyCumFacShr | CRSP-basis shares (000s) | CRSP-basis volume |
|---|---|---|---|---|---|
| 2024-08-28 | 250 | 100 | 4 | 1,000 | 400 |
| 2024-08-29 | 250 | 125 | 4 | 1,000 | 500 |
| 2024-08-30 | 1,000 | 460 | 1 | 1,000 | 460 |
The first calculation is . The second quantity uses the same CRSP share/volume factor: .
Every date, package key, quantity, and event in this table is synthetic. The package key is not a PERMNO. The table demonstrates direction; it does not claim a historical CRSP observation for a real company.
Daily and monthly are not interchangeable
Daily volume is a daily raw count, so each available DlyVol can be paired with its same-date DlyCumFacShr.
Monthly volume is an aggregate. CRSP CIZ 2.0 defines MthFacShrFlg codes N (none), S (stock split or dividend), O (other), and B (both). If the share factor changes inside the month, one month-end multiplier cannot describe the different bases of all component days. The package calculates the small monthly path only for N; it blocks S, O, B, and explicit null.
The safe package derivation is to return to licensed daily CRSP records:
This daily reconstruction is not implemented in the small function because it requires the full daily licensed data. Month-end shares are a separate endpoint quantity and may still be adjusted with MthCumFacShr.
Missing values, gaps, and partial history
The package uses explicit nulls and statuses:
- missing raw quantity becomes
missing_raw; - missing CRSP factor becomes
missing_crsp_factor; - a factor continued across a CRSP delisting/relisting gap becomes
computed_crsp_gap_assumption; - a monthly aggregate with
MthFacShrFlgS,O,B, or null becomesblocked_mixed_basis_month.
An adapter must still decode the actual CRSP delivery's nullable fields and other flags. Negative legacy sentinels are rejected rather than treated as quantities. The package preserves literal MthFacShrFlg and emits a separate normalized diagnostic.
A partial local extract does not create a new factor basis. Never force the last local row to 1. Keep the CRSP basis date from the factor delivery, even when the local analysis starts late or ends early.
CRSP's May 2026 release notes provide a concrete operational lesson: monthly cumulative-factor rows for incomplete terminal months of delisted securities had been omitted and were added back. That public correction supports preserving release vintage and coverage diagnostics. It does not supply a redistributable issuer-level factor example.
Calculation flow
The important separation is between source evidence and package interpretation. CRSP supplies the field and its vendor basis. The package validates, calculates, and labels. It does not independently validate the issuer event from another data provider.
Explore the scenarios
Open the guided playground. The canonical scenario steps through 24 synthetic daily observations and a share-basis transition. The monthly scenario shows why aggregate volume is blocked. The gap scenario makes CRSP's continuation assumption visible, and the missing-factor scenario stops instead of imputing.
Back, Step, Play, Pause, Reset, and speed all use the same deterministic transition. Keyboard controls work without pointer input, and reduced-motion mode disables autoplay while preserving stepwise learning.
Implementation and tests
The Python implementation and TypeScript implementation consume the same synthetic fixture. They validate CIZ 2.0, factor field, one unmistakable package key, sorted dates, one non-mixed frequency, CRSP basis date, missing states, and monthly aggregate evidence before multiplying. A tie regression pins the shared half-away-from-zero rule. A production adapter remains responsible for preserving the real licensed CRSP PERMNO.
Tests prove definition-level parity for this package contract. They do not prove that a subscriber's CRSP extraction, entitlement, field mapping, or release archive is correct.
Historical-evidence decision
This article does not publish a real company's CRSP factor path. Public CRSP manuals explain the fields, but the needed security observations are licensed and redistribution rights were not established. Synthetic values are more honest than an unattributed real-looking table.
The only real historical item used is CRSP's public 2026 release correction about incomplete delisting-month rows. It explains why release lineage matters; it is not a factual claim about any issuer's split or volume.
Summary
Use CRSP's share/volume factor, not its price factor. Multiply in the documented direction, preserve CRSP's terminal basis and release, distinguish daily observations from monthly aggregates, and keep missing, gap, delisting, and partial-history states visible. A CRSP-adjusted result is valuable precisely because it says which vendor convention produced it.
Continue with Rights-Issue TERP Adjustment to see an event where price and share treatment must remain separate.
Asset map
| Asset | Learning purpose | Source | Status |
|---|---|---|---|
| CRSP terminal basis | Direction and vendor scope | visuals/static/article-hero.svg | Ready |
| Synthetic arithmetic | Verify multiplication | visuals/static/worked-example.svg | Ready |
| Monthly guard | Explain daily reconstruction | visuals/static/failure-guard.svg | Ready |
| Guided lab | Compare canonical, monthly, gap, and missing states | visuals/animated/playground.html | Ready |
Source roles and limitations are recorded in ../REFERENCES.md.
Rendered from the canonical Mermaid sources linked by this article.
CRSP CIZ 2.0 calculation flow
This flow separates a sourced CRSP field from package validation, calculation, and interpretation.
Takeaway: the package calculates a CRSP-scoped result only after verifying the exact CRSP share/volume factor field.
CRSP evidence-state lifecycle
The lifecycle keeps a CRSP result from looking more certain than its CRSP source state.
Takeaway: computed, gap-qualified, missing, rejected, and reconstruction-required are different CRSP-scoped outcomes.
References5 primary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
All sources were accessed 2026-07-22. Only CRSP/Morningstar primary documentation is used for CRSP field claims. No vendor observations are redistributed.
R1 - CRSP US Stock & Indexes Database Guide, Flat File Format 2.0 (CIZ)
- Organization: Center for Research in Security Prices (CRSP), now surfaced by Morningstar Indexes
- Source type: Official vendor schema and field-definition guide
- Version: Flat File Format 2.0 (CIZ), Document last modified April 27, 2026
- URL: https://www.crsp.org/crsp_pdf/crsp-us-stock-indexes-databases-guide-flat-file-format-2-0/
- Jurisdiction/product: CRSP US Stock & Indexes
- Supports:
DlyShrOut,DlyVol,DlyCumFacShr,MthShrOut,MthCumFacShr, literalMthFacShrFlgcodes N/S/O/B, PERMNO, terminal basis of 1, and CRSP's delisting/relisting-gap continuation convention - Limitations: Documents CRSP's licensed product convention. It does not make the convention universal and does not grant redistribution rights for security observations.
R2 - Important Notice: CRSP US Stock & Indexes Flat File Format 2.0 (CIZ)
- Organization: CRSP
- Source type: Official product migration notice
- Publication date: 2025-05-15
- URL: https://www.crsp.org/important-notice-crsp-us-stock-amp-indexes-databases-flat-file-format-2-0-ciz/
- Supports: CIZ 2.0 migration context and retirement of legacy/SIZ deliveries after the December 2024 data release
- Limitations: Operational notice, not a field-calculation specification
R3 - June 2025 Monthly Update: CRSP US Stock & Index Release Notes
- Organization: CRSP
- Source type: Official product release notes
- Publication/effective context: June 2025 release, data ending 2025-06-30
- URL: https://www.crsp.org/wp-content/uploads/mdaz_202506.pdf
- Supports: Cumulative adjustment factor fields
DlyCumFacPr,DlyCumFacShr,MthCumFacPr, andMthCumFacShrmoved from 6.4 to 24.12 display precision - Limitations: Describes representation precision; it does not imply that binary floating-point arithmetic is exact or that older extracts are wrong
R4 - May 2026 Monthly Update: CRSP US Stock & Index Databases Release Notes
- Organization: CRSP
- Source type: Official product release notes
- Publication/effective context: May 2026 release, data ending 2026-04-30
- URL: https://www.crsp.org/wp-content/uploads/mdaz_202604.pdf
- Supports: CRSP corrections to cumulative adjustment factor file formatting and restoration of monthly cumulative-factor records for incomplete months at the end of delisted security histories
- Limitations: The note establishes an operational revision, not a named issuer's adjustment factor or a universal delisting rule
R5 - CRSP US Stock & Indexes Database Data Descriptions Guide
- Organization: CRSP
- Source type: Official legacy CRSPAccess data dictionary and calculation guide
- Version: Current archived guide accessed 2026-07-22
- URL: https://www.crsp.org/wp-content/uploads/guides/CRSP_US_Stock_%26_Indexes_Database_Data_Descriptions_Guide.pdf
- Supports: Historical CRSPAccess
cumfacshr/mcumfacshrterminology, shares-outstanding units, raw volume characteristics, legacy missing-value behavior, and the warning that price and share factors can differ - Limitations: CRSPAccess is retired. The implementation does not accept this legacy schema; adapters must translate source-specific sentinels and fields into the explicit CIZ 2.0 package contract.
Full dependency-light reference implementations in both supported languages.
/** CRSP CIZ 2.0 cumulative share/volume adjustment reference.
*
* This is the documented CRSP vendor convention. A CRSP price factor is not
* accepted as a substitute for DlyCumFacShr or MthCumFacShr.
*/
export const SCHEMA_VERSION = "CRSP_US_STOCK_CIZ_2_0" as const;
export const ROUNDING_DIGITS = 12;
type Frequency = "daily" | "monthly";
type FactorStatus = "observed" | "continued_across_gap" | "missing";
type MonthlyFlagCode = "N" | "S" | "O" | "B";
type MonthlyFlagState = "none" | "within_period" | "unknown";
export interface CrspInputRecord {
packageSecurityKey: string;
date: string;
frequency: Frequency;
crspFactorField: "DlyCumFacShr" | "MthCumFacShr";
crspCumulativeShareFactor: number | null;
crspFactorStatus: FactorStatus;
rawSharesOutstandingThousands: number | null;
rawVolumeShares: number | null;
crspMthFacShrFlg?: MonthlyFlagCode | null;
}
export interface CrspAdjustmentInput {
schemaVersion: typeof SCHEMA_VERSION;
sourceRelease: string;
crspBasisDate: string;
extractCoverage: "full_history" | "partial_extract";
records: CrspInputRecord[];
}
const factorFields: Record<Frequency, "DlyCumFacShr" | "MthCumFacShr"> = {
daily: "DlyCumFacShr",
monthly: "MthCumFacShr",
};
const factorStatuses = new Set<FactorStatus>(["observed", "continued_across_gap", "missing"]);
const monthlyFlagCodes = new Set<MonthlyFlagCode>(["N", "S", "O", "B"]);
function isoDate(value: unknown, name: string): string {
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
throw new TypeError(`${name} must be YYYY-MM-DD`);
}
const parsed = new Date(`${value}T00:00:00Z`);
if (Number.isNaN(parsed.valueOf()) || parsed.toISOString().slice(0, 10) !== value) {
throw new RangeError(`${name} must be YYYY-MM-DD`);
}
return value;
}
function numberOrNull(value: unknown, name: string, positive = false): number | null {
if (value === null) return null;
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new TypeError(`${name} must be finite or null`);
}
if (positive ? value <= 0 : value < 0) {
throw new RangeError(`${name} must be ${positive ? "positive" : "non-negative"}`);
}
return value;
}
function rounded(value: number): number {
if (!Number.isFinite(value)) throw new RangeError("value must be finite before rounding");
const negative = value < 0;
const [mantissa, exponentText = "0"] = Math.abs(value).toString().toLowerCase().split("e");
const [whole, fraction = ""] = mantissa.split(".");
const digits = BigInt(`${whole}${fraction}`);
const decimalExponent = Number(exponentText) - fraction.length;
const scaledExponent = decimalExponent + ROUNDING_DIGITS;
let scaledInteger: bigint;
if (scaledExponent >= 0) {
scaledInteger = digits * (10n ** BigInt(scaledExponent));
} else {
const divisor = 10n ** BigInt(-scaledExponent);
const quotient = digits / divisor;
const remainder = digits % divisor;
scaledInteger = quotient + (remainder * 2n >= divisor ? 1n : 0n);
}
if (scaledInteger === 0n) return 0;
const padded = scaledInteger.toString().padStart(ROUNDING_DIGITS + 1, "0");
const integerPart = padded.slice(0, -ROUNDING_DIGITS);
const fractionalPart = padded.slice(-ROUNDING_DIGITS).replace(/0+$/, "");
const decimalText = fractionalPart ? `${integerPart}.${fractionalPart}` : integerPart;
return Number(`${negative ? "-" : ""}${decimalText}`);
}
function quantityResult(
raw: number | null,
factor: number | null,
factorStatus: FactorStatus,
monthlyVolumeFlag?: MonthlyFlagState,
): [string, number | null, number | null] {
if (raw === null) return ["missing_raw", null, null];
if (factorStatus === "missing") return ["missing_crsp_factor", null, null];
if (monthlyVolumeFlag === "within_period" || monthlyVolumeFlag === "unknown") {
return ["blocked_mixed_basis_month", null, null];
}
if (factor === null) throw new Error("internal factor validation failure");
const adjusted = rounded(raw * factor);
const residual = rounded(adjusted / factor - raw);
const status = factorStatus === "continued_across_gap"
? "computed_crsp_gap_assumption"
: "computed_crsp";
return [status, adjusted, residual];
}
export function calculate(data: CrspAdjustmentInput) {
if (!data || typeof data !== "object" || Array.isArray(data)) {
throw new TypeError("data must be an object");
}
if (data.schemaVersion !== SCHEMA_VERSION) {
throw new RangeError(`schemaVersion must equal ${SCHEMA_VERSION}`);
}
if (typeof data.sourceRelease !== "string" || !data.sourceRelease.trim()) {
throw new TypeError("sourceRelease must be a non-empty string");
}
const basisDate = isoDate(data.crspBasisDate, "crspBasisDate");
if (data.extractCoverage !== "full_history" && data.extractCoverage !== "partial_extract") {
throw new RangeError("extractCoverage must be full_history or partial_extract");
}
if (!Array.isArray(data.records) || data.records.length === 0) {
throw new TypeError("records must be a non-empty list");
}
let expectedSecurityKey: string | null = null;
let expectedFrequency: Frequency | null = null;
let previousDate: string | null = null;
let lastFactor: number | null = null;
let lastFactorStatus: FactorStatus | null = null;
const rows = data.records.map((record, index) => {
if (!record || typeof record !== "object" || Array.isArray(record)) {
throw new TypeError(`records[${index}] must be an object`);
}
if (typeof record.packageSecurityKey !== "string" || !record.packageSecurityKey.trim()) {
throw new TypeError(`records[${index}].packageSecurityKey must be a non-empty string`);
}
const securityKey = record.packageSecurityKey.trim();
if (!securityKey.startsWith("SYNTHETIC-")) {
throw new RangeError("packageSecurityKey must begin SYNTHETIC-; real CRSP PERMNO belongs in licensed lineage");
}
expectedSecurityKey ??= securityKey;
if (securityKey !== expectedSecurityKey) {
throw new RangeError("one calculation may contain only one packageSecurityKey");
}
const recordDate = isoDate(record.date, `records[${index}].date`);
if (recordDate > basisDate) throw new RangeError("record date cannot be after the CRSP basis date");
if (previousDate !== null && recordDate <= previousDate) {
throw new RangeError("record dates must be strictly increasing");
}
previousDate = recordDate;
if (record.frequency !== "daily" && record.frequency !== "monthly") {
throw new RangeError("frequency must be daily or monthly");
}
expectedFrequency ??= record.frequency;
if (record.frequency !== expectedFrequency) {
throw new RangeError("one calculation cannot mix daily and monthly CRSP frequencies");
}
const expectedField = factorFields[record.frequency];
if (record.crspFactorField !== expectedField) {
throw new RangeError(`${record.frequency} records require CRSP field ${expectedField}`);
}
if (!factorStatuses.has(record.crspFactorStatus)) throw new RangeError("invalid crspFactorStatus");
const factor = numberOrNull(record.crspCumulativeShareFactor, "crspCumulativeShareFactor", true);
if (record.crspFactorStatus === "missing" && factor !== null) {
throw new RangeError("a missing CRSP factor must have a null value");
}
if (record.crspFactorStatus !== "missing" && factor === null) {
throw new RangeError("an observed CRSP factor must have a value");
}
lastFactor = factor;
lastFactorStatus = record.crspFactorStatus;
const shares = numberOrNull(record.rawSharesOutstandingThousands, "rawSharesOutstandingThousands");
const volume = numberOrNull(record.rawVolumeShares, "rawVolumeShares");
if (shares === null && volume === null) {
throw new RangeError("each row needs a raw shares or raw volume value");
}
const monthlyCode = record.crspMthFacShrFlg;
let monthlyState: MonthlyFlagState | undefined;
if (record.frequency === "monthly") {
if (monthlyCode === undefined || (monthlyCode !== null && !monthlyFlagCodes.has(monthlyCode))) {
throw new RangeError("monthly rows require CRSP MthFacShrFlg code N, S, O, B, or null");
}
monthlyState = monthlyCode === null ? "unknown" : monthlyCode === "N" ? "none" : "within_period";
} else if (monthlyCode !== undefined) {
throw new RangeError("daily rows must not set crspMthFacShrFlg");
}
const [sharesStatus, adjustedShares, sharesResidual] = quantityResult(
shares, factor, record.crspFactorStatus,
);
const [volumeStatus, adjustedVolume, volumeResidual] = quantityResult(
volume,
factor,
record.crspFactorStatus,
monthlyState,
);
return {
packageSecurityKey: securityKey,
date: recordDate,
frequency: record.frequency,
crspFactorField: expectedField,
crspCumulativeShareFactor: factor,
crspFactorStatus: record.crspFactorStatus,
crspMthFacShrFlg: record.frequency === "monthly" ? monthlyCode : null,
crspMonthlyShareFactorState: monthlyState ?? null,
crspSharesStatus: sharesStatus,
crspAdjustedSharesOutstandingThousands: adjustedShares,
crspSharesRoundTripResidual: sharesResidual,
crspVolumeStatus: volumeStatus,
crspAdjustedVolumeShares: adjustedVolume,
crspVolumeRoundTripResidual: volumeResidual,
};
});
if (data.extractCoverage === "full_history" && (
previousDate !== basisDate || lastFactorStatus !== "observed" || lastFactor !== 1
)) {
throw new RangeError("full_history must end on the CRSP basis date with observed factor 1");
}
return {
schemaVersion: SCHEMA_VERSION,
sourceRelease: data.sourceRelease.trim(),
packageSecurityKey: expectedSecurityKey,
crspBasisDate: basisDate,
extractCoverage: data.extractCoverage,
roundingDigits: ROUNDING_DIGITS,
rows,
};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.