CFDLAcademy
All chapters

Part 5 · Reference · Chapter 25 of 27

Appendix A: language quick reference

Everything the course taught, compressed for the working desk. Shapes use angle brackets for what you supply and square brackets for what is optional; the normative grammar lives in the specification at cfdl.dev.

Model structure

version 0.1
model "<name>"
use pack "<id>" version "<ver>"        -- optional; root file only
import "<file>.cfdl"                   -- root and imported files; no cycles,
                                       -- never outside the model directory
time calendar <daily|monthly|quarterly|annual> from <date> for <N>
phase <name> from <date> to <date>

One time grid per model; header statements live in the root file only. Everything else — entities, assumptions, curves, streams, contracts, events, options, waterfalls — may live in any file, in any order. Order never carries meaning.

Entities and fields

entity <family> <name> : <Type>                  -- family: asset | party |
entity <family> <name> : <Type> {                --         contract | reference
  <field> = <literal>                            -- a fact: literal only
  <field> init <expr>                            -- a rule: the recurrence
          next <expr>                            --   `prev` = own prior value
  part of <family>.<name>                        -- hierarchy; rollups follow
}

Reads, from any expression: asset.x.field (this period, at close), prev.asset.x.field (prior close), entity.field (the attached entity's own field). Rules see only the completed prior column — same-period cycles are unwritable.

Streams

stream <a>.<b>[.<c>] on entity <family>.<name> <inflow|outflow> currency <CCY> {
  schedule <see below>
  [active when <boolean expr>]
  amount = <expr>
}

Names are dotted, at least two segments, unique. Direction is declared; amounts are written positive.

Schedules

schedule on <date> [mid] [convention <conv>] [calendar "<cal>"]
schedule every <day|week|month|quarter|year> [due|mid]
         [on day <n> | on eom]
         [net <n> [days|months]]
         from <date|phase_start("p")> to <date|phase_end("p")>
         [except [<dates>]] [also [<dates>]]
schedule on phase_enter("<phase>")

Placement: default = period end (arrears); due = start (advance; stride schedules only — one payment in advance is every month due from X to X); mid = midpoint (mid-period convention). Conventions: none, following, preceding, modified_following, modified_preceding. Calendars: "weekend", "us", "uk", "target". net counts days, or steps calendar months. Occurrences pushed off the grid are compile errors.

Assumptions and curves

assume <name> = <expr>
assume <name> ~ Normal(mean=, stdev=, clip=[lo, hi])
             ~ LogNormal(mu=, sigma=)     ~ Uniform(min=, max=)
             ~ Triangular(min=, mode=, max=)
curve <name> <step|linear> { <date>: <value>  ... }

Read as inputs.<name> and curve_value("<name>", <date>). Deterministic runs use central values: Normal → mean, LogNormal → exp(mu + sigma²/2), Uniform → midpoint, Triangular → (min + mode + max) / 3 — not the mode. Curves clamp outside their stated range.

Contracts, events, options, waterfalls

contract <pack>.<type>[.<suffix>] [on entity <ref>] {
  term <date>..<date>
  terms { <key> = <literal | inputs.name>  ... }
}

event <a>.<b> when <boolean expr> {          -- fires ONCE, first true period
  set entity <family>.<name>.<field> = <value>
  activate stream <name> | deactivate stream <name>
  exercise option <name>
}

option <a>.<b> type <Type> {
  exercise when <boolean expr>
  payoff <expr>
}

waterfall <a>.<b> on entity <ref> {
  schedule <as streams>
  from <expr>                                -- the pot; fields and series work
  pay <step> to <family>.<name> = <expr>     -- receives min(max(0,owed),remaining)
}

Waterfall step expressions may read remaining, owed.<step>, paid.<step> (earlier steps only). The five step shapes: stated amount, capped (min), pay-to-target (balance − target), shortfall (owed.x − paid.x), sweep (remaining).

Expressions

Scopes: time.t (period index from 0), time.date, time.phase · inputs.* · cfg.*, obs.* (run-supplied) · field reads as above. Arithmetic is decimal; % is remainder, not percent.

FamilyFunctions
Bounds and shapemin max abs clamp(x, lo, hi)
Growthpow exp ln
Roundinground(x, digits) round_up round_down · round_to(x, step) — nearest multiple
Conditionalif(cond, then, else)
Datesdate edate eomonth days_between months_between year_frac is_business_day add_business_days roll
TVMpv fv pmt(rate, nper, pv) ipmt ppmt nper rate
Seriesseries_sum("name", from_t, to_t) series_avg — second-pass; readers cannot read readers
Domaincpr_to_smm macrs_rate(year, life) curve_value

Full signatures: the expressions reference at cfdl.dev.

Run configuration

{
  "deterministic": { "annual_discount_rate": 0.08, "as_of": "2026-01-01",
                     "parameters": { "cfg.x": 1.0, "obs.y": 0.05, "inputs.z": 42 } },
  "scenarios": { "<name>": { "annual_discount_rate": 0.08, "parameters": {} } },
  "monte_carlo": { "trial_count": 1000, "seed": 42 }
}

Annual rates convert to the grid geometrically: (1 + r)^(1/n) − 1. Parameters override cfg/obs/inputs per scenario without touching the model. A run monte_carlo trials N seed N statement in the model is overridden by the configuration when both exist.

The disciplines, one line each

Model at the grain cash moves; roll up, never slice down. Opinions on the assumption page; structure literal. Dates in phases. Expressions that read aloud. One hand-checked anchor per file. Match a source's method, then its answer. Recompute ratios from rolled-up flows. Fix the first error only. Predict before you run.