# What invariants prevent day-count convention drift from splitting interest computations? **Date of Record:** 2026 November 2 **Scope:** Applies to interest-bearing contracts where the day-count convention (ACT/360, ACT/365, 30/360, ACT/ACT) affects the computed interest, and the two parties may compute independently. Does not apply to fixed-amount contracts with no interest computation, or where a single party computes and the other accepts the figure. ## Core Distinction The day-count convention is a fraction `actual_days / basis_days`. The same nominal rate over the same period produces different interest under different conventions, and the difference is not small: ACT/360 and ACT/365 differ by 1.39% on an annualised basis. An instance that treats the convention as documentation cannot detect that the two parties are computing different numbers from the same inputs. **Invariant.** Interest is agreed if and only if both parties use the same tuple `(day_count_numerator_rule, day_count_denominator, compounding_frequency, rounding_at_boundary)`, and the tuples are equal, not merely compatible. **Violation.** The **Day-Count Drift**. The instance computes ACT/365 and the counterparty computes ACT/360; the contract states the rate but only one side's convention is captured in the system of record. At settlement, the two amounts differ by roughly 1.39% of the annualised figure, and neither side's computation is wrong — the computation inputs are. **Asymmetry.** Each party's interest schedule is internally consistent. The discrepancy appears only when the two schedules are compared, and the comparison is made at settlement by a third system that sees a mismatch without knowing which convention either party used. **Recorded elements.** The `day-count-convention-receipt` (the exact convention as four-part tuple, not just its name), the `compounding-frequency-receipt`, and the `rounding-boundary-signature` together make the equality checkable at any point. ## What Was Observed **Signature.** Settlement amount differing from the instance's computed amount by a small fraction, varying by period length and by leap year. Reconciliation flags a discrepancy that is not attributable to a missed payment or an incorrect rate. The discrepancy is systematic, not random, and its magnitude scales with the principal and the elapsed period. **Trace.** Principal $10,000,000, nominal rate 5%, period 365 days. ACT/365 yields exactly $500,000. ACT/360 yields $506,944. The difference is $6,944 — 1.39% of the interest, invisible at the rate level, material at settlement. Two independent systems using the two conventions produce two "correct" numbers, and the reconciliation break is the convention, not the arithmetic. **Why monitoring stays green.** Each party's interest ledger is internally consistent. Rate checks pass. The interest computation runs without error. The mismatch lives in the comparison of two correct ledgers. ## Condition of Validity The instance must ensure at least one holds before it treats interest as agreed: 1. **Must record the convention explicitly.** The convention is stored as a tuple (numerator rule, denominator, compounding frequency, rounding boundary), not as a name that maps to different implementations in different systems. 2. **Must share the tuple.** Both parties use the same stored tuple, verified at contract execution, not inferred from the document. 3. **Must round at a defined boundary.** Rounding is applied at the settlement boundary only, not at intermediate periods; otherwise period-by-period rounding diverges from whole-period rounding. Condition 1 is structural: a convention name is not a convention until it is expanded into its four parameters. ## Decision Boundary - Tuple identical on both sides: agreed. - Names match but tuple components differ (e.g., both say "ACT/365" but one compounds daily and the other annually): not agreed; the name is insufficient. - Tuples differ and the difference is bounded below materiality: treat as agreed only if a tolerance is explicitly accepted in writing. - Tuples differ and no tolerance accepted: not agreed; escalate before settlement. ## Operational Effect The invariant couples a number computed in two places to a convention that is often stored as a name. The instance's correct posture is to expand the convention to its tuple at contract execution, so both sides compute from the same expanded parameters. **Trap.** Store the convention name from the contract text. The name is not the parameters — two systems implementing the same "ACT/365" can differ on leap-year handling, compounding, and rounding, and the name does not reveal which implementation is in force. ```python from dataclasses import dataclass from typing import Tuple class DayCountDriftError(RuntimeError): """Raised when two parties compute interest under different day-count tuples.""" @dataclass(frozen=True) class DayCountTuple: numerator_rule: str # "ACT", "30" denominator: int # 360, 365, 366 compounding: str # "simple", "annual", "daily" rounding: str # "settlement", "period", "none" @dataclass(frozen=True) class DayCountGate: instance_tuple: DayCountTuple counterparty_tuple: DayCountTuple tolerance_accepted: bool = False def verify(self) -> bool: if self.instance_tuple == self.counterparty_tuple: return True if self.tolerance_accepted: return True raise DayCountDriftError( f"tuple mismatch instance={self.instance_tuple} counterparty={self.counterparty_tuple}: " "interest computations will diverge" ) def dispatch_interest_mode(gate: DayCountGate) -> str: try: gate.verify() except DayCountDriftError: return "escalate-tuple-mismatch" return "compute-interest" ```