The governed definitions this build depends on. Read them first if a term is unfamiliar.
- PrimaryRights IssueA rights issue offers eligible existing holders rights to subscribe for additional securities under stated terms.
- PrimaryTheoretical Ex-Rights PriceTheoretical ex-rights price estimates the post-rights per-share price from the cum-rights price and subscription terms under simplifying assumptions.
- PrerequisiteCorporate ActionA corporate action is an issuer or security event that changes rights, quantities, cash flows, identity, or trading terms.
- ImportantCorporate Action Effective DateA corporate action effective date is the market or legal date from which the declared event treatment applies.
- ImportantEx-Dividend DateThe ex-dividend date is the effective trading date from which a purchase generally no longer carries entitlement to the declared dividend.
- ImportantPayment DatePayment date is the scheduled or actual date on which a corporate-action entitlement is delivered to eligible holders.
- 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.
- ImportantRecord DateRecord date is the date used under event terms to identify holders of record for a corporate-action entitlement.
- ImportantReturn BaseReturn base is the declared starting value, unit, and economic basis against which a return is measured.
- ImportantTotal ReturnTotal return measures price change together with eligible distributions over a declared holding period.
A rights issue can create an apparent price drop even when an eligible holder receives something valuable: the right to buy new shares at a stated subscription price. A clean history needs to represent that transfer without inventing a market price.
This tutorial gives you an auditable calculation. You will build TERP, value the right in two different units, handle currencies and event revisions, and test the result against a real issuer-documented event.
Start with the distinction that prevents most errors
Theoretical ex-rights price, or TERP, is a reference calculation. It combines an old-share price reference with the cash paid for new shares. It is not a prediction of the opening trade, and it is not evidence that any share changed hands at that number.
Keep these three layers separate:
- Sourced facts: offer ratio, subscription price, currencies, issuer dates, reference-price source, revisions.
- Methodology choices: which price and time to use, whether to remove a dividend disadvantage, which FX observation and out-of-money policy apply.
- Derived values: TERP, implied rights values, adjustment factor, and fractional entitlement.
Observed ex-rights trades form a fourth layer. They need market data and are outside this algorithm.
Read the entitlement basis literally
"7 new shares for every 24 existing shares" means:
- existing shares;
- new shares;
- each existing share carries of an entitlement to one new share.
Do not invert that ratio. Do not call "the value of one right" unless you specify that it is value per old share. The right to acquire one whole new share has a different value.
Put prices on one basis
Let be the selected cum-rights market reference and a cash or dividend disadvantage removed by the chosen methodology:
Let be the subscription price, the number of quote-currency units per subscription-currency unit, and and any explicitly included fee and tax per new share:
If both prices are in pence, keep both in pence. If one says GBP and the other says GBp, convert the scale before calculation. For different currencies, store the FX source and the time it became available. A later FX fixing cannot enter an earlier decision.
Calculate TERP and both rights values
For an in-the-money issue where :
The value theoretically removed from one old share is:
The value of the entitlement to buy one new share is:
These agree after scaling: . The analytical backward-adjustment factor is .
| Symbol | Meaning | Unit |
|---|---|---|
| , | Existing and new shares in the offer basis | shares |
| Sourced cum-rights price reference | quote currency/old share | |
| Declared cash or dividend disadvantage | quote currency/old share | |
| Convention-adjusted reference price | quote currency/old share | |
| Subscription price | subscription currency/new share | |
| Quote currency per subscription currency | FX rate | |
| , | Included fee and tax per new share | quote currency/new share |
| Subscription cash equivalent | quote currency/new share | |
| Theoretical ex-rights price | quote currency/enlarged-basis share | |
| Implied right value per old share | quote currency/old share | |
| Implied value per right to one new share | quote currency/right |
A real event: National Grid's 2024 rights issue
National Grid's 23 May 2024 prospectus states the following facts:
- 7 new shares for every 24 existing shares;
- subscription price of 645 pence per new share;
- entitlement record time of 18:00 London time on 20 May;
- existing shares ex-rights and nil-paid dealings starting at 08:00 on 24 May;
- final acceptance, payment, and registration-of-renunciation deadline at 11:00 on 10 June;
- fully paid new-share dealings starting at 08:00 on 12 June.
The issuer also identified a 1,127.5 GBp closing middle-market price on 22 May and a 39.12 GBp final dividend not payable on the new shares. Under the declared dividend-adjusted reference convention:
Now derive TERP:
That gives:
- GBp per old share;
- GBp per right to acquire one new share;
- ;
- issue-price discount to TERP of 34.7339%.
The last number reconciles to National Grid's rounded 34.7% statement. It still does not tell us what National Grid traded at on 24 May.
Event dates are different clocks
National Grid's record date preceded the announcement. That is not permission to use the terms before publication. A point-in-time system stores both the event's legal/market time and the time the evidence became available to the system.
The package knows the prospectus publication day but does not invent a publication second. Its historical fixture uses 23:59:59Z as a conservative end-of-day availability convention and labels that as an engineering policy.
Revisions matter too. A later corrected ratio or cancellation must create a new revision. The implementation selects the latest availableAt <= asOf; if nothing was available, it stops.
Renunciation, nil-paid rights, and oversubscription
A nil-paid right is an entitlement before the subscription price has been paid. When rights are renounceable, that entitlement can be transferred or sold subject to the offer, venue, broker, and jurisdiction rules. Its traded price can differ from .
Oversubscription is another operation: a holder asks for more than the base entitlement, and an allocation policy decides what they receive. It does not change , , or TERP. The National Grid teaching extract does not establish an oversubscription facility, so the data says "not documented" instead of assuming yes or no.
Fractional entitlements also belong to the legal terms. For 100 old shares, the exact entitlement is:
The calculator reports 29 whole shares plus a 0.166667 fraction. National Grid's prospectus says entitlements were rounded down and fractions generally aggregated for the company's benefit. Another offer can have another policy.
Fees, taxes, and issuer expenses
National Grid estimated issuer expenses but also stated that the company charged investors no commissions, fees, or expenses in connection with the rights issue. Those are different quantities. Do not allocate issuer expenses into a shareholder TERP formula unless the chosen methodology explicitly says to do so.
Taxes depend on holder and jurisdiction. The calculator supports explicit per-new-share amounts only when a documented analytical convention requires them. Zero means "not included in this calculation," not "the investor has no tax cost."
Reject plausible-looking nonsense
If , the algebra produces a zero or negative rights value. Many production methodologies make no price adjustment; others delay treatment. The reference implementation requires the caller to choose:
reject: stop and request the correct methodology;no_adjustment: return and zero rights value, withapplied=false.
It never publishes a negative right value.
The implementation also rejects zero or fractional offer bases, a cash disadvantage that removes the entire reference price, same-currency FX other than one, future FX evidence, timeline inversions, cancellation, and missing source revisions.
Implement it causally
The Python and TypeScript versions follow the same sequence:
- select the knowable revision;
- validate the timeline and terms;
- normalize price, cash, currency, and FX;
- apply the out-of-money policy;
- calculate without intermediate rounding;
- emit revision identifiers and six-decimal diagnostics.
The shared tests cover 16 cases in each language, including cross-currency conversion, future FX, fractional entitlement, cancellation, out-of-money handling, and input immutability.
Explore the reference convention
Open the guided TERP lab. Step through evidence, normalization, calculation, rights values, and the operational timeline. Then compare a cross-currency case and an out-of-money rejection. Reset is deterministic, and the lab's historical values match the shared fixture.
What this result can and cannot prove
Passing the tests proves that the implementation matches its declared convention for covered cases. It does not prove that a specific index provider, broker, tax authority, or data vendor would use the same price, FX, timeline, rounding, or event treatment. It also does not prove an observed ex-rights price or an investment outcome.
Summary
TERP is valuable because it makes an adjustment explainable, not because it predicts a trade. Preserve the old/new basis, source price and timing, currency and FX, issuer timeline, revision history, and policy choices. Calculate both rights-value units. Keep allocation and legal terms beside the arithmetic, and stop when evidence is not knowable.
Continue with Spin-Off Price Adjustment. For earnings comparability, see Rights-Issue Bonus-Factor Adjustment.
Asset map
| Asset | Teaches | Source |
|---|---|---|
| Evidence-to-reference overview | Fact, convention, derivation, market separation | visuals/static/article-hero.svg |
| National Grid value bridge | Exact historical derivation | visuals/static/worked-example.svg |
| Failure guard | Invalid and out-of-money behavior | visuals/static/failure-guard.svg |
| Guided lab | Stepwise scenarios and timeline | visuals/animated/playground.html |
Primary source roles and limitations are in REFERENCES.md. This is educational material, not investment, legal, accounting, or tax advice.
Rendered from the canonical Mermaid sources linked by this article.
TERP evidence and calculation flow
This flow separates sourced evidence from normalization, policy, and derived output.
Takeaway: a reproducible number begins with evidence that was knowable at the decision time; TERP remains a theoretical reference.
Rights-issue operational lifecycle
The National Grid case shows why one generic "event date" is not enough.
Takeaway: record, availability, ex-rights, nil-paid, payment, and fully paid states answer different questions and may use different clocks.
References6 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. No prospectus, exchange feed, index data, or vendor data is redistributed in this package.
R1 - National Grid plc Rights Issue Prospectus
- Organization: National Grid plc; approved by the UK Financial Conduct Authority as described in the document
- Source type: Issuer prospectus
- Publication date: 2024-05-23
- URL: https://www.nationalgrid.com/document/152061/download
- Jurisdiction: United Kingdom
- Supports: 7-for-24 basis; 645 GBp subscription price; record, ex-rights, nil-paid dealing, payment, renunciation, and fully paid dealing dates; fractional treatment; nil-paid and fully paid definitions; investor-fee and tax cautions
- Limitations: The timetable was stated as indicative and channel-specific deadlines differed. FCA approval is not an endorsement of the issuer or securities.
R2 - National Grid 7-for-24 Rights Issue announcement
- Organization: National Grid plc
- Source type: Official issuer announcement
- Publication date: 2024-05-23
- URL: https://www.nationalgrid.com/document/152016/download
- Jurisdiction: United Kingdom
- Supports: Offer size and terms; 1,127.5 GBp closing middle-market reference on 2024-05-22; 39.12 GBp dividend disadvantage; issuer's rounded 34.7% issue-price discount to TERP
- Limitations: The announcement says investment decisions must be based on the prospectus. It does not provide an observed ex-rights trade for this tutorial.
R3 - National Grid Rights Issue and Prospectus hub
- Organization: National Grid plc
- Source type: Official issuer document hub
- URL: https://www.nationalgrid.com/gridforgrowth/rights-issue
- Jurisdiction: United Kingdom
- Supports: Provenance and current official links for the prospectus, announcement, and admission notice
- Limitations: A landing page is not the calculation methodology.
R4 - S&P DJI Equity Indices Policies & Practices
- Organization: S&P Dow Jones Indices
- Source type: Official index methodology
- Version: March 2026
- URL: https://www.spglobal.com/spdji/en/documents/methodologies/methodology-sp-equity-indices-policies-practices.pdf
- Jurisdiction: Global index methodology
- Supports: Previous-close cum-rights convention; rights-value, price-adjustment-factor, and TERP relationships; dividend disadvantage; renounceable/non-renounceable terminology; generic rights lifecycle; unknown-price and open-offer exceptions
- Limitations: S&P DJI policy is not a universal security-master, investor, or tax rule. Terms of use govern index content and data.
R5 - FTSE Russell Corporate Actions and Events Guide
- Organization: FTSE Russell, LSEG
- Source type: Official index methodology
- Version: 6.9, June 2026
- URL: https://research.ftserussell.com/products/downloads/Corporate_Actions_and_Events_Guide.pdf
- Jurisdiction: Global index methodology
- Supports: Standard TERP and price-adjustment-factor example; estimated-price case; nil-paid line treatment; highly dilutive and dividend-disadvantage variants; cancellations and timing exceptions
- Limitations: FTSE Russell policy is provider-specific and may change. Production use must use the methodology effective for the relevant index and date.
R6 - RFC 3339: Date and Time on the Internet
- Organization: IETF
- Source type: Internet standard
- Publication date: 2002-07
- URL: https://www.rfc-editor.org/rfc/rfc3339
- Supports: Offset-bearing timestamp representation used by the package
- Limitations: Updated by RFC 9557; market-calendar and session semantics remain separate.
Evidence decision
The National Grid case is a historical issuer-documented event. Sourced facts are copied only as compact factual inputs. The 1,088.38 GBp reference, 988.261935 GBp TERP, two rights values, factor, and fractional diagnostic are package derivations. No data-provider observation was needed, and no claim is made about the observed 24 May 2024 ex-rights price.
Full dependency-light reference implementations in both supported languages.
/** Auditable TERP calculator. The output is a convention, not a traded-price claim. */
export type RightsInput = { asOf: string; revisions: RightsRevision[] };
export type RightsRevision = {
eventId: string; revisionId: string; revisionSequence: number;
availableAt: string; status: "confirmed" | "cancelled";
recordAt: string; exAt: string; rightsTradingStartAt: string;
paymentDeadlineAt: string; newSharesTradeAt: string;
terms: Record<string, unknown>;
};
const finite = (value: unknown, name: string, minimum?: number): number => {
if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${name} must be a finite number`);
if (minimum !== undefined && value < minimum) throw new RangeError(`${name} must be at least ${minimum}`);
return value;
};
const positive = (value: unknown, name: string): number => {
const result = finite(value, name); if (result <= 0) throw new RangeError(`${name} must be positive`); return result;
};
const positiveInteger = (value: unknown, name: string): number => {
const result = positive(value, name); if (!Number.isInteger(result)) throw new RangeError(`${name} must be a positive integer entitlement basis`); return result;
};
const text = (value: unknown, name: string): string => {
if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a non-empty string`); return value;
};
const instant = (value: unknown, name: string): number => {
const raw = text(value, name);
if (!/(Z|[+-]\d{2}:\d{2})$/.test(raw)) throw new RangeError(`${name} must include a UTC offset`);
const result = Date.parse(raw); if (!Number.isFinite(result)) throw new RangeError(`${name} must be an ISO 8601 instant`); return result;
};
const round6 = (value: number): number => Number((Math.round((value + Number.EPSILON) * 1e6) / 1e6).toFixed(6));
function selectRevision(revisions: RightsRevision[], asOf: number): RightsRevision {
if (!Array.isArray(revisions) || !revisions.length) throw new TypeError("revisions must be a non-empty list");
let eventId: string | undefined; const seen = new Set<string>();
const candidates: { availableAt: number; revision: RightsRevision }[] = [];
for (const revision of revisions) {
if (!revision || typeof revision !== "object" || Array.isArray(revision)) throw new TypeError("each revision must be an object");
const currentEventId = text(revision.eventId, "eventId");
if (eventId === undefined) eventId = currentEventId; else if (currentEventId !== eventId) throw new RangeError("all revisions must describe the same eventId");
if (!Number.isInteger(revision.revisionSequence) || revision.revisionSequence < 1) throw new RangeError("revisionSequence must be a positive integer");
const identity = `${text(revision.revisionId, "revisionId")}\u0000${revision.revisionSequence}`;
if (seen.has(identity)) throw new RangeError("duplicate revision identity"); seen.add(identity);
const availableAt = instant(revision.availableAt, "availableAt");
if (availableAt <= asOf) candidates.push({availableAt, revision});
}
if (!candidates.length) throw new RangeError("no event revision was available at asOf");
candidates.sort((a,b) => a.availableAt - b.availableAt || a.revision.revisionSequence - b.revision.revisionSequence);
return candidates.at(-1)!.revision;
}
function validateTimeline(event: RightsRevision): void {
const values = [instant(event.recordAt,"recordAt"), instant(event.exAt,"exAt"), instant(event.rightsTradingStartAt,"rightsTradingStartAt"), instant(event.paymentDeadlineAt,"paymentDeadlineAt"), instant(event.newSharesTradeAt,"newSharesTradeAt")];
if (!(values[0] < values[1] && values[1] <= values[2] && values[2] <= values[3] && values[3] <= values[4])) throw new RangeError("event timeline must satisfy record < ex <= nil-paid trading start <= payment deadline <= new shares");
}
export function calculate(input: RightsInput): Record<string, unknown> {
if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError("data must be an object");
const data: RightsInput = structuredClone(input); const asOf = instant(data.asOf, "asOf");
const revision = selectRevision(data.revisions, asOf); const status = text(revision.status,"status");
if (status === "cancelled") throw new RangeError("selected event revision is cancelled");
if (status !== "confirmed") throw new RangeError("status must be confirmed or cancelled");
validateTimeline(revision);
const terms = revision.terms; if (!terms || typeof terms !== "object" || Array.isArray(terms)) throw new TypeError("terms must be an object");
const n=positiveInteger(terms.oldShares,"oldShares"), m=positiveInteger(terms.newShares,"newShares"), denominator=n+m;
if (denominator<=0) throw new RangeError("oldShares + newShares must be positive");
const quoteCurrency=text(terms.quoteCurrency,"quoteCurrency"), subscriptionCurrency=text(terms.subscriptionCurrency,"subscriptionCurrency");
const raw=positive(terms.rawCumRightsPrice,"rawCumRightsPrice"), disadvantage=finite(terms.cashDisadvantage ?? 0,"cashDisadvantage",0), reference=raw-disadvantage;
if (reference<=0) throw new RangeError("cashDisadvantage must leave a positive referencePrice");
const subscription=finite(terms.subscriptionPrice,"subscriptionPrice",0), fx=positive(terms.fxQuotePerSubscription,"fxQuotePerSubscription");
if (quoteCurrency===subscriptionCurrency) { if (Math.abs(fx-1)>1e-12) throw new RangeError("same-currency events require fxQuotePerSubscription = 1"); }
else { if (instant(terms.fxAvailableAt,"fxAvailableAt")>asOf) throw new RangeError("FX evidence was not available at asOf"); text(terms.fxSourceId,"fxSourceId"); }
const fees=finite(terms.feesPerNewShareQuote ?? 0,"feesPerNewShareQuote",0), taxes=finite(terms.taxesPerNewShareQuote ?? 0,"taxesPerNewShareQuote",0), cash=subscription*fx+fees+taxes;
if (cash<0) throw new RangeError("subscription cash equivalent cannot be negative");
const inMoney=cash<reference, policy=text(terms.outOfMoneyPolicy ?? "reject","outOfMoneyPolicy");
if (!new Set(["reject","no_adjustment"]).has(policy)) throw new RangeError("outOfMoneyPolicy must be reject or no_adjustment");
if (!inMoney && policy==="reject") throw new RangeError("rights are not in the money under the selected reference-price convention");
const terp=inMoney?(n*reference+m*cash)/denominator:reference, valueOld=inMoney?reference-terp:0, valueRight=inMoney?terp-cash:0;
if (terp<=0 || valueOld<0 || valueRight<0) throw new RangeError("negative theoretical values are invalid under this reference convention");
let entitlement: Record<string,number>|null=null;
if (terms.holdingOldShares!==undefined) { const holding=finite(terms.holdingOldShares,"holdingOldShares",0); if(!Number.isInteger(holding))throw new RangeError("holdingOldShares must be a non-negative integer"); const exact=holding*m/n, whole=Math.floor(exact); entitlement={exactNewShares:round6(exact),wholeNewShares:whole,fractionalNewShare:round6(exact-whole)}; }
return {eventId:revision.eventId,revisionId:revision.revisionId,availableAt:revision.availableAt,quoteCurrency,referencePrice:round6(reference),subscriptionCashQuote:round6(cash),terp:round6(terp),valuePerOldShare:round6(valueOld),valuePerNewShareRight:round6(valueRight),adjustmentFactor:round6(terp/reference),issueDiscountToTerp:inMoney?round6(1-cash/terp):null,inTheMoney:inMoney,applied:inMoney,entitlement,referencePriceIsTradedFact:false};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.