Skip to main content
CFDL

Curves

A series is a shape of assume — rate curves, price decks, escalation paths — read at the reader's period as inputs.<name>, and at another date with curve_value(inputs.<name>, date).

assume sofr = curve linear {
  2026-01: 0.050
  2027-01: 0.038
}

stream loan.interest on entity asset.loan inflow currency USD {
  schedule every month from 2026-01 to 2026-12
  amount = 1000000 * (inputs.sofr + 0.0275) / 12
}

Interpolation

  • step — flat-forward: each value holds until the next point.
  • linear — calendar-day interpolation between points.
  • Before the first point a series reads the first value. Past the last point it holds the last value flat-forward, and a stream or field that reads it there is warned once (W5024).
  • A series may state the dates it is good for on its header, curve from 2026-01 to 2029-05 { … }. A read outside them has no value, and the run is refused (E5040), naming the series, the date and the reader.

Duplicate point dates or malformed points fail compilation with E5008_INVALID_CURVE; two assumptions of one name are E1005. The old string spelling, curve_value("sofr", …), is refused with the new one.

Where a series shines

The credit pack's floating-rate loan reads its coupon index from a series the index term names, with margin, floor, and cap. An interest-only loan states amortization = "interest_only":

contract credit.loan.bridge on entity asset.buyer {
  term 2026-01..2029-05
  terms {
    amortization = "interest_only"
    principal = 15000000
    index = inputs.sofr
    margin = 0.0275
    rate_floor = 0.07
    rate_cap = 0.09
    amortization_months = 36
  }
}

A series supplied by the run

A series the model states is its fallback. The run's inputs file can replace it by full name, with its own points, interpolation, effective dates and source, or fill a series the model declares with a type and no body:

assume sofr : rate

The run then must supply sofr, or it is refused before any period exists (E5045). See Scenarios and run configurations.

A series as the discount rate

A run that names a series as annual_discount_curve, in place of annual_discount_rate, discounts each period at that period's annual rate. The results record run.annual_discount_curve in place of the scalar rate.

Quantiles: a spread instead of a path

A series is indexed by date. A quantile is indexed by cumulative share, for a quantity whose spread within a period drives the answer, such as the hours of a month's power prices:

assume prices = quantile linear {
  0.00:  11.0
  0.50:  28.0
  0.98: 340.0
  1.00: 512.0
}

It is read with quantile_at(inputs.prices, share), quantile_mean(inputs.prices, from, to) and quantile_of(inputs.prices, value).

Uncertainty and a series

A series is deterministic, and it is not sampled. Uncertainty about its level is an ordinary distributed assumption scaling the read, inputs.price_factor * inputs.power_price. A rate path that moves stochastically is computed, not stated: a field on a reference entity whose rule reads a distributed assumption, or sample(inputs.<name>) for a draw per entity and period.