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.
- PrimaryStock SplitA stock split increases the number of shares per holder while proportionally changing the per-share basis without intentionally creating value.
- PrerequisiteCorporate ActionA corporate action is an issuer or security event that changes rights, quantities, cash flows, identity, or trading terms.
- ImportantAdjusted PriceAdjusted price is a transformed price expressed on a declared comparison basis after applying specified corporate-action or other adjustment factors.
- ImportantCorporate Action Effective DateA corporate action effective date is the market or legal date from which the declared event treatment applies.
- ImportantPrice RelativePrice relative is the ratio of an ending comparable price to its starting comparable price.
- ImportantPrice ReturnPrice return measures the change in an asset's price while excluding cash distributions such as dividends.
- ImportantReturn BaseReturn base is the declared starting value, unit, and economic basis against which a return is measured.
- ImportantUnadjusted PriceUnadjusted price is the reported or normalized market price before retrospective corporate-action adjustment.
- MentionedRecord DateRecord date is the date used under event terms to identify holders of record for a corporate-action entitlement.
A stock split changes the unit in which one share is measured. If a historical dataset joins prices and share quantities from both sides of that boundary without declaring a common basis, an ordinary unit change can look like a price collapse or a volume surge.
Backward split adjustment solves the comparability problem by expressing earlier observations on a selected later share basis. It does not rewrite the raw trade record, and it does not create or remove economic value. A trustworthy system keeps both series and records exactly which event produced the derived values.
Define the ratio before using it
Split feeds use reciprocal conventions, so the letter is unsafe until its direction is explicit. This article defines:
That gives:
| Event description | Ratio |
|---|---|
| Two-for-one split | 2 |
| Four-for-one split | 4 |
| One-for-five reverse split | 0.2 |
A provider that reports old shares per new share would publish the reciprocal. Never copy a numeric factor without also copying its definition.
The calculation and its boundary
Let be the zero-based index of the first observation already expressed on the post-split basis. Let be raw price and be raw share-count volume. The backward-adjusted values are:
Only observations strictly before the boundary are transformed. The first post-split observation and everything after it remain unchanged.
Before rounding, price times share quantity is invariant:
This is a useful arithmetic check, but it cannot validate the event source or effective date. A wrong factor can still preserve the identity.
Work the synthetic example by hand
Suppose a two-for-one split has already been validated. The input uses , and index 2 is the first post-split observation:
prices = [120, 123, 60, 62]
share volume = [1000, 1200, 2400, 2000]
eventIndex = 2
Transform only indices 0 and 1:
120 / 2 = 60 1000 × 2 = 2000
123 / 2 = 61.5 1200 × 2 = 2400
The values at indices 2 and 3 are already on the target basis, so they remain unchanged.
The exact result shared by the Python and TypeScript tests is:
{
"adjustedPrices": [60, 61.5, 60, 62],
"adjustedVolumes": [2000, 2400, 2400, 2000],
"eventIndex": 2,
"ratioConvention": "post_split_shares_per_pre_split_share"
}
The explicit convention in the result is intentional. It prevents an otherwise plausible JSON array from losing the meaning of its factor.
A historical event that makes the need obvious
Apple announced on July 30, 2020 that its board approved a four-for-one split. The issuer said holders of record on August 24 would receive three additional shares for each share held and that trading would begin on a split-adjusted basis on August 31 (Apple Newsroom).
Apple’s Form 8-K adds a different operational timestamp: each outstanding common share would automatically split into four at 5 p.m. Pacific daylight time on August 28 (SEC filing). Its 2020 Form 10-K later states that share and per-share information was retroactively adjusted to reflect the split (SEC annual filing).
These primary sources establish the event and its ratio without relying on a chart vendor. Under this article’s convention, : an eligible earlier price becomes , while an eligible earlier share quantity becomes .
The case also exposes a common data-modeling mistake. “The split date” is not one timestamp. Announcement, record date, legal effectiveness, first trading session on the new basis, vendor dissemination, and local ingestion can all differ. The transformation boundary must match the purpose of the dataset.
Historical comparability is not point-in-time availability
A backward-adjusted series intentionally applies a later corporate action to earlier observations. That is useful when today’s reader wants one comparable share basis across a chart. It can be wrong in a backtest asking what was knowable at an earlier decision time.
For point-in-time research, retain at least:
- the raw observation and its original basis;
- event announcement or filing time;
- legal-effective time and first new-basis trading session;
- vendor dissemination and local ingestion time;
- the version or
as_oftimestamp of the derived series.
Do not expose a factor to a simulated decision before the factor entered the permitted information set. The reference implementation deliberately avoids pretending to solve that upstream problem: it accepts an already-resolved eventIndex.
Implementation contract
The Python and TypeScript functions accept aligned price and share-volume arrays, eventIndex, and postSplitSharesPerPreSplitShare.
They reject:
- empty or misaligned arrays;
- non-finite or non-positive prices;
- negative share volume;
- an event index that leaves no observations on one side;
- a non-positive factor or a unit factor that does not describe a split.
Both implementations copy rather than mutate the input and round non-negative public outputs half-up to six decimal places. Production code should retain decimal precision internally and apply rounding according to its stated data contract.
Explore forward and reverse cases
The guided playground uses 36 synthetic observations. Start with the two-for-one scenario, step across the first post-split observation, then switch to the one-for-five reverse split. The same ratio definition works in both directions. Finally choose the zero-ratio scenario to see why rejecting an invalid factor is safer than emitting a smooth-looking series.
Failure modes worth testing
- Reciprocal factor: price is multiplied when it should be divided.
- Off-by-one boundary: the first post-split observation is transformed a second time.
- Wrong quantity field: trade count or notional value is treated as share volume.
- Compound events: a dividend, rights issue, merger, or symbol change is collapsed into a simple split.
- Rounding drift: each intermediate event is rounded rather than the declared public output.
- Hindsight leakage: a later factor appears in an earlier point-in-time research view.
- Vendor overgeneralization: one provider’s cumulative field is presented as universal mathematics.
A visually continuous chart is not proof that any of these controls passed.
Summary
Backward split adjustment is small enough to calculate by hand but important enough to deserve an explicit contract. Define as post-split shares per pre-split share, transform only observations before the validated boundary, preserve raw and derived histories, and keep knowledge time separate from effective time.
Continue with Forward Split Adjustment to compare a different target basis. Use Point-in-Time Availability Guard when the research question depends on what was knowable at a historical decision time.
Asset map
| Asset | Placement | Teaching purpose | Source | Status |
|---|---|---|---|---|
| Overview | Opening | Separate raw record, boundary, and audited output | visuals/static/article-hero.svg | XML/layout checks passed; rendered QA pending |
| Calculation | Worked example | Show input, arithmetic, and complete result | visuals/static/worked-example.svg | XML/layout checks passed; rendered QA pending |
| Failure guard | Failure modes | Make rejection visible | visuals/static/failure-guard.svg | XML/layout checks passed; rendered QA pending |
| Guided lab | Exploration | Compare forward, reverse, and invalid ratios | visuals/animated/playground.html | Automated checks passed; browser QA pending |
Primary source roles, access dates, and limitations are recorded in ../REFERENCES.md. All teaching data are synthetic. This article is educational material, not investment, legal, accounting, or tax advice.
Rendered from the canonical Mermaid sources linked by this article.
Backward Split Adjustment calculation flow
This flow shows where validation and the effective boundary protect the calculation.
Takeaway: Ratio direction and target basis are validated before the strict boundary rule is applied.
Backward Split Adjustment evidence lifecycle
The lifecycle separates event validation, calculation eligibility, and point-in-time availability.
Takeaway: an event can be valid yet still unavailable to an earlier point-in-time decision.
References4 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.
Accessed 2026-07-22 unless otherwise stated. Claim-local links in the README and article identify which source supports each statement.
R1 — Apple Reports Third Quarter Results
- Organization or authors: Apple Inc.
- Source type: Issuer press release
- Publication date: 2020-07-30
- URL: https://www.apple.com/newsroom/2020/07/apple-reports-third-quarter-results/
- Jurisdiction: United States issuer disclosure
- Supports: Board approval of the four-for-one split; August 24 record date; three additional shares per share held; August 31 split-adjusted trading basis
- Limitations: Issuer announcement; not a market-data adjustment methodology
R2 — Apple Form 8-K, accession 0001193125-20-213158
- Organization or authors: Apple Inc.; filed with the U.S. Securities and Exchange Commission
- Source type: Issuer regulatory filing
- Filing date: 2020-08-07
- URL: https://www.sec.gov/Archives/edgar/data/320193/000119312520213158/d49399d8k.htm
- Jurisdiction: United States
- Supports: Automatic four-for-one split of each outstanding common share at 5 p.m. Pacific daylight time on 2020-08-28; AAPL registration on Nasdaq
- Limitations: Establishes the legal corporate event, not a vendor’s historical-price field semantics
R3 — Apple 2020 Form 10-K
- Organization or authors: Apple Inc.; filed with the U.S. Securities and Exchange Commission
- Source type: Annual regulatory filing
- Filing date: 2020-10-30
- URL: https://www.sec.gov/Archives/edgar/data/320193/000032019320000096/aapl-20200926.htm
- Jurisdiction: United States
- Supports: Four-for-one split effected for holders of record as of 2020-08-24; share and per-share information retroactively adjusted; AAPL traded on Nasdaq
- Limitations: Financial-statement presentation does not define every market-data provider’s adjustment basis
R4 — CRSP US Stock Data Descriptions Guide
- Organization or authors: Center for Research in Security Prices, surfaced through WRDS
- Source type: Authoritative vendor data dictionary and calculation guide
- URL: https://wrds-www.wharton.upenn.edu/documents/399/Data_Descriptions_Guide.pdf
- Jurisdiction: United States dataset
- Supports: Example of provider-specific cumulative price and share factor conventions
- Limitations: CRSP conventions apply to CRSP fields and licensed data; they are not the universal definition implemented here
Full dependency-light reference implementations in both supported languages.
/** Reference implementation for D02-F01-A01 — Backward Split Adjustment. */
export const RATIO_CONVENTION = "post_split_shares_per_pre_split_share" as const;
export interface BackwardSplitInput {
prices: number[];
volumes: number[];
eventIndex: number;
postSplitSharesPerPreSplitShare: number;
}
export interface BackwardSplitOutput {
adjustedPrices: number[];
adjustedVolumes: number[];
eventIndex: number;
ratioConvention: typeof RATIO_CONVENTION;
}
const roundSix = (value: number): number => {
const scaled = value * 1_000_000;
if (!Number.isFinite(value) || !Number.isFinite(scaled)) {
throw new RangeError("calculation produced a non-finite output");
}
const result = Math.floor(scaled + 0.5) / 1_000_000;
return Number.isInteger(result) ? Math.trunc(result) : result;
};
const numberValue = (value: unknown, name: string): number => {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new TypeError(`${name} must contain only finite numbers`);
}
return value;
};
const positive = (value: unknown, name: string): number => {
const result = numberValue(value, name);
if (result <= 0) throw new RangeError(`${name} must be positive`);
return result;
};
const nonnegative = (value: unknown, name: string): number => {
const result = numberValue(value, name);
if (result < 0) throw new RangeError(`${name} must be non-negative`);
return result;
};
const series = (
data: Record<string, unknown>,
key: string,
validator: (value: unknown, name: string) => number,
): number[] => {
const values = data[key];
if (!Array.isArray(values) || values.length === 0) {
throw new TypeError(`${key} must be a non-empty list`);
}
return values.map((value) => validator(value, key));
};
const eventIndex = (value: unknown, length: number): number => {
if (!Number.isInteger(value) || (value as number) <= 0 || (value as number) >= length) {
throw new RangeError(
"eventIndex must identify the first post-split observation and leave data on both sides",
);
}
return value as number;
};
export function calculate(input: BackwardSplitInput): BackwardSplitOutput {
if (!input || typeof input !== "object" || Array.isArray(input)) {
throw new TypeError("data must be an object");
}
const data = input as unknown as Record<string, unknown>;
const prices = series(data, "prices", positive);
const volumes = series(data, "volumes", nonnegative);
if (prices.length !== volumes.length) throw new RangeError("prices and volumes must align");
const firstPostSplitIndex = eventIndex(data.eventIndex, prices.length);
const ratio = positive(
data.postSplitSharesPerPreSplitShare,
"postSplitSharesPerPreSplitShare",
);
if (ratio === 1) {
throw new RangeError("postSplitSharesPerPreSplitShare must describe a non-unit split");
}
return {
adjustedPrices: prices.map((price, index) =>
roundSix(index < firstPostSplitIndex ? price / ratio : price),
),
adjustedVolumes: volumes.map((volume, index) =>
roundSix(index < firstPostSplitIndex ? volume * ratio : volume),
),
eventIndex: firstPostSplitIndex,
ratioConvention: RATIO_CONVENTION,
};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.