<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Timeseries | nodlin.com</title><link>https://nodlin.com/tag/timeseries/</link><atom:link href="https://nodlin.com/tag/timeseries/index.xml" rel="self" type="application/rss+xml"/><description>Timeseries</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 12 Sep 2026 00:00:00 +0000</lastBuildDate><image><url>https://nodlin.com/media/logo.svg</url><title>Timeseries</title><link>https://nodlin.com/tag/timeseries/</link></image><item><title>Timeseries in Nodlin</title><link>https://nodlin.com/platform/timeseries/</link><pubDate>Sat, 12 Sep 2026 00:00:00 +0000</pubDate><guid>https://nodlin.com/platform/timeseries/</guid><description>&lt;h2 id="what-is-the-timeseries-library">What is the timeseries library?&lt;/h2>
&lt;p>Nodlin’s &lt;strong>timeseries&lt;/strong> module models regular economic quantities — revenue, debt, rates, margins — as first-class values on the graph. It is available in:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>
&lt;/strong> — single-line formulas (examples on this page use that style)&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — multi-line pipelines with the same &lt;code>timeseries&lt;/code> name&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — package &lt;code>github.com/johnha/nodlinGo/v2/timeseries&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>This page is the &lt;strong>shared concepts and API guide&lt;/strong>. Surface-specific notes (single-line constraints, multi-line packages, Go imports) live in the linked docs.&lt;/p>
&lt;p>&lt;strong>Audience:&lt;/strong> you understand stocks, flows, rates, and income statements. You do not need to know Go to use expressions or scripts.&lt;/p>
&lt;h3 id="when-to-use-it">When to use it&lt;/h3>
&lt;p>Use timeseries when:&lt;/p>
&lt;ul>
&lt;li>Quantities sit on a &lt;strong>shared calendar&lt;/strong> (monthly, quarterly, business-day, …)&lt;/li>
&lt;li>Economics matter: &lt;strong>stock vs flow vs rate&lt;/strong>, currency units, day-count accrual&lt;/li>
&lt;li>Irregular dated hits (issuances, announcements) must &lt;strong>project&lt;/strong> onto a regular model grid&lt;/li>
&lt;li>Changing one driver should recompute dependents with kind, unit, and timeline preserved&lt;/li>
&lt;/ul>
&lt;p>Prefer plain numbers, lists, or maps when you only need a scalar total or a one-off table column — see
.&lt;/p>
&lt;hr>
&lt;h2 id="mental-model">Mental model&lt;/h2>
&lt;p>Regular economic quantities are a &lt;strong>Series&lt;/strong>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimension&lt;/th>
&lt;th>Meaning&lt;/th>
&lt;th>Examples&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Timeline&lt;/strong>&lt;/td>
&lt;td>&lt;em>When&lt;/em> regular periods exist&lt;/td>
&lt;td>Quarterly from 2025-01-01 for 12 periods&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Values&lt;/strong>&lt;/td>
&lt;td>&lt;em>How much&lt;/em> each period&lt;/td>
&lt;td>&lt;code>[12.4, 13.1, …]&lt;/code> $bn&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Valid&lt;/strong>&lt;/td>
&lt;td>&lt;em>Whether&lt;/em> each period is observed (optional)&lt;/td>
&lt;td>missing mid-history, not yet published&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Unit&lt;/strong>&lt;/td>
&lt;td>&lt;em>In what units&lt;/em>&lt;/td>
&lt;td>&lt;code>USD&lt;/code>, dimensionless&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Kind&lt;/strong>&lt;/td>
&lt;td>&lt;em>What the numbers mean over time&lt;/em>&lt;/td>
&lt;td>Stock, Flow, Rate, Ratio, …&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Irregular quantities use an &lt;strong>EventSeries&lt;/strong> (dated spikes). They become a Series only after &lt;code>project&lt;/code> / &lt;code>asof&lt;/code> onto a timeline. Timelines stay regular and half-open &lt;code>[start, end)&lt;/code>.&lt;/p>
&lt;p>Nodlin’s graph holds the &lt;strong>causal structure&lt;/strong> (which node depends on which). Each formula is a &lt;strong>series calculation&lt;/strong>. Change an assumption series; dependents recompute — still with full kind, unit, and timeline semantics between nodes.&lt;/p>
&lt;h3 id="kind-semantics">Kind (semantics)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Kind&lt;/th>
&lt;th>Meaning&lt;/th>
&lt;th>Typical use&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>stock&lt;/code>&lt;/td>
&lt;td>Level at a point in time&lt;/td>
&lt;td>Debt outstanding, cash balance&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>flow&lt;/code>&lt;/td>
&lt;td>Quantity over a period&lt;/td>
&lt;td>Revenue, COGS, interest expense&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>rate&lt;/code>&lt;/td>
&lt;td>Rate applying over time (usually annual)&lt;/td>
&lt;td>Coupon / financing rate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ratio&lt;/code>&lt;/td>
&lt;td>Dimensionless relationship (does &lt;strong>not&lt;/strong> accrue)&lt;/td>
&lt;td>Gross margin, tax rate, LTV&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>index&lt;/code>&lt;/td>
&lt;td>Level with arbitrary base&lt;/td>
&lt;td>Cumulative growth index&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>change&lt;/code>&lt;/td>
&lt;td>Period-to-period change&lt;/td>
&lt;td>Growth after &lt;code>pct_change&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Critical rules:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>stock + flow&lt;/code> is &lt;strong>rejected&lt;/strong> — apply a flow to a stock with &lt;code>timeseries.accumulate&lt;/code>&lt;/li>
&lt;li>&lt;code>stock * rate&lt;/code> is &lt;strong>rejected&lt;/strong> — use &lt;code>debt.accrue(rate)&lt;/code> so day-count and period length apply correctly&lt;/li>
&lt;/ul>
&lt;h3 id="units-and-currency">Units and currency&lt;/h3>
&lt;ul>
&lt;li>Currency codes (&lt;code>USD&lt;/code>, &lt;code>GBP&lt;/code>, …) are first-class &lt;strong>series units&lt;/strong> set on construction: &lt;code>unit=&amp;quot;USD&amp;quot;&lt;/code> or &lt;code>unit=timeseries.usd&lt;/code>. They live on the series value (&lt;code>value.unit&lt;/code>), &lt;strong>not&lt;/strong> on the expression form’s Format → Currency (&lt;code>ccy&lt;/code>) control.&lt;/li>
&lt;li>Form &lt;strong>&lt;code>ccy&lt;/code> / Scale&lt;/strong> only affect &lt;strong>number&lt;/strong> (and list-of-number) &lt;em>display&lt;/em>. They do not change a timeseries unit and do not perform FX.&lt;/li>
&lt;li>&lt;code>USD + GBP&lt;/code> fails until you convert: &lt;code>usd_series.convert(&amp;quot;GBP&amp;quot;, fx)&lt;/code> where &lt;code>fx&lt;/code> is &lt;strong>target per source&lt;/strong> (GBP per 1 USD).&lt;/li>
&lt;li>Omitting &lt;code>unit=&lt;/code> yields dimensionless (&lt;code>unit: &amp;quot;1&amp;quot;&lt;/code>). Then &lt;code>convert&lt;/code> fails (&lt;em>source series is not a pure currency unit&lt;/em>).&lt;/li>
&lt;li>Prefer &lt;strong>&lt;code>convert&lt;/code>&lt;/strong> over &lt;code>revenue * fx&lt;/code>: multiply keeps the &lt;strong>left-hand unit&lt;/strong> (still &lt;code>USD&lt;/code>) even when values scale.&lt;/li>
&lt;li>Rates and ratios are &lt;strong>decimals&lt;/strong> in storage: &lt;code>0.05&lt;/code> = 5%, &lt;code>0.65&lt;/code> = 65% margin — not &lt;code>5&lt;/code> or &lt;code>65&lt;/code>.&lt;/li>
&lt;li>Dates are &lt;strong>UTC only&lt;/strong> (&lt;code>YYYY-MM-DD&lt;/code>). No wall-clock time zones.&lt;/li>
&lt;li>Missing values use an explicit validity mask (not silent zeros). Arithmetic propagates NA: &lt;code>10 + NA = NA&lt;/code> until you &lt;code>fill_na&lt;/code> / &lt;code>ffill&lt;/code> / &lt;code>interpolate&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="first-class-series-and-timeline-values">First-class series and timeline values&lt;/h3>
&lt;p>A series or timeline is stored as a &lt;strong>tagged map&lt;/strong>, so the UI and evaluator treat it as a domain type (not a free-form dictionary).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Concept&lt;/th>
&lt;th>How it works&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Return a series&lt;/strong>&lt;/td>
&lt;td>Expression ends with a series object (e.g. &lt;code>data_center + gaming&lt;/code>). Do &lt;strong>not&lt;/strong> peel off &lt;code>.values&lt;/code> if dependents should keep series semantics.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Stored shape&lt;/strong>&lt;/td>
&lt;td>Discriminator &lt;code>type: &amp;quot;timeseries.series&amp;quot;&lt;/code> or &lt;code>type: &amp;quot;timeseries.timeline&amp;quot;&lt;/code> (also event series, regression, calendar).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Round-trip&lt;/strong>&lt;/td>
&lt;td>Aliases and &lt;code>related()&lt;/code> &lt;strong>rehydrate&lt;/strong> the tag into a live object: &lt;code>+ - * /&lt;/code>, &lt;code>.accrue&lt;/code>, &lt;code>.lag&lt;/code>, … work without wrapping again.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Leaf lists&lt;/strong>&lt;/td>
&lt;td>Plain numeric lists need one explicit &lt;code>timeseries.series(values=…, start=…, …)&lt;/code> at the boundary. After that, pass the series by alias.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Graph card&lt;/strong>&lt;/td>
&lt;td>Series, timeline, event-series, and regression results get bespoke SVG cards.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Form Result&lt;/strong>&lt;/td>
&lt;td>Ordered text: &lt;code>start → end · frequency · unit · N periods · M values&lt;/code>.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Display title&lt;/strong>&lt;/td>
&lt;td>Fallback chain: &lt;strong>series name → node alias → comment snippet → kind&lt;/strong> (e.g. FLOW).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Domain errors&lt;/strong>&lt;/td>
&lt;td>Kind/unit/timeline mismatches mark the node with a warning and keep the last good value when possible; the cascade is &lt;strong>not&lt;/strong> cancelled.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Example chain once intermediates return series:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># leaf (once)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.series(values=dc_units_list, start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;, name=&amp;#34;data_center&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># dependents — aliases are already series
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">data_center + gaming + auto + other
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">debt.accrue(int_rate, daycount=&amp;#34;actual365&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="module-constants">Module constants&lt;/h2>
&lt;p>String helpers you can pass into constructors (plain strings such as &lt;code>&amp;quot;stock&amp;quot;&lt;/code> or &lt;code>&amp;quot;quarterly&amp;quot;&lt;/code> also work):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Constant&lt;/th>
&lt;th>Value&lt;/th>
&lt;th>Use&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>timeseries.stock&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;stock&amp;quot;&lt;/code>&lt;/td>
&lt;td>kind&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.flow&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;flow&amp;quot;&lt;/code>&lt;/td>
&lt;td>kind&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.rate&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;rate&amp;quot;&lt;/code>&lt;/td>
&lt;td>kind&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.index&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;index&amp;quot;&lt;/code>&lt;/td>
&lt;td>kind&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.ratio&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;ratio&amp;quot;&lt;/code>&lt;/td>
&lt;td>kind&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.change&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;change&amp;quot;&lt;/code>&lt;/td>
&lt;td>kind&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.daily&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;daily&amp;quot;&lt;/code>&lt;/td>
&lt;td>frequency&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.business_day&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;business_day&amp;quot;&lt;/code>&lt;/td>
&lt;td>frequency (needs calendar)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.monthly&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;monthly&amp;quot;&lt;/code>&lt;/td>
&lt;td>frequency&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.quarterly&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;quarterly&amp;quot;&lt;/code>&lt;/td>
&lt;td>frequency&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.yearly&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;yearly&amp;quot;&lt;/code>&lt;/td>
&lt;td>frequency&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.gbp&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;GBP&amp;quot;&lt;/code>&lt;/td>
&lt;td>unit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.usd&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;USD&amp;quot;&lt;/code>&lt;/td>
&lt;td>unit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.actual365&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;actual365&amp;quot;&lt;/code>&lt;/td>
&lt;td>day-count for &lt;code>accrue&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.calendar_day&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;calendar_day&amp;quot;&lt;/code>&lt;/td>
&lt;td>every day is a business day&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>timeseries.calendar_weekend&lt;/code>&lt;/td>
&lt;td>&lt;code>&amp;quot;weekend&amp;quot;&lt;/code>&lt;/td>
&lt;td>Mon–Fri business calendar&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Custom holiday sets use &lt;strong>&lt;code>timeseries.calendar(...)&lt;/code>&lt;/strong> (not a string constant).&lt;/p>
&lt;hr>
&lt;h2 id="constructors">Constructors&lt;/h2>
&lt;h3 id="timeseriescalendarholidays-weekends">&lt;code>timeseries.calendar(holidays?, weekends?)&lt;/code>&lt;/h3>
&lt;p>Builds a &lt;strong>business calendar&lt;/strong> from explicit non-business UTC dates (company shutdowns, exchange holidays).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Argument&lt;/th>
&lt;th>Required&lt;/th>
&lt;th>Default&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>holidays&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>[]&lt;/code>&lt;/td>
&lt;td>List of &lt;code>&amp;quot;YYYY-MM-DD&amp;quot;&lt;/code> UTC dates treated as non-business&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>weekends&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>True&lt;/code>&lt;/td>
&lt;td>If true, Saturday and Sunday are also non-business&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Mon–Fri only (same as calendar=&amp;#34;weekend&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.calendar()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Weekends + custom holidays
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.calendar(holidays=[&amp;#34;2025-12-25&amp;#34;, &amp;#34;2026-01-01&amp;#34;])
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Holiday dates only (Saturdays still count as business unless listed)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.calendar(holidays=[&amp;#34;2025-12-25&amp;#34;], weekends=False)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Extend a base calendar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.calendar().with_holidays([&amp;#34;2025-12-25&amp;#34;, &amp;#34;2026-01-01&amp;#34;])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Attributes / methods:&lt;/strong> &lt;code>name&lt;/code>, &lt;code>holidays&lt;/code>, &lt;code>weekends&lt;/code>, &lt;code>is_business_day(&amp;quot;YYYY-MM-DD&amp;quot;)&lt;/code>, &lt;code>with_holidays([...])&lt;/code>.&lt;/p>
&lt;p>Pass the calendar into &lt;code>timeline(..., calendar=cal)&lt;/code>. Named string calendars still work: &lt;code>&amp;quot;weekend&amp;quot;&lt;/code>, &lt;code>&amp;quot;calendar_day&amp;quot;&lt;/code>.&lt;/p>
&lt;h3 id="timeseriestimelinestart-periods-frequency-calendar">&lt;code>timeseries.timeline(start, periods, frequency?, calendar?)&lt;/code>&lt;/h3>
&lt;p>Immutable timeline of calendar periods (half-open &lt;code>[start, end)&lt;/code> per period).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Argument&lt;/th>
&lt;th>Required&lt;/th>
&lt;th>Default&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>start&lt;/code>&lt;/td>
&lt;td>yes&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&lt;code>&amp;quot;YYYY-MM-DD&amp;quot;&lt;/code> UTC&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>periods&lt;/code>&lt;/td>
&lt;td>yes&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Positive integer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>frequency&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;monthly&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;code>daily&lt;/code> / &lt;code>business_day&lt;/code> / &lt;code>weekly&lt;/code> / &lt;code>monthly&lt;/code> / &lt;code>quarterly&lt;/code> / &lt;code>yearly&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>calendar&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>calendar day&lt;/td>
&lt;td>String name (&lt;code>&amp;quot;weekend&amp;quot;&lt;/code>) &lt;strong>or&lt;/strong> a calendar object. Required for &lt;code>business_day&lt;/code>.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Attributes:&lt;/strong> &lt;code>start&lt;/code>, &lt;code>end&lt;/code>, &lt;code>periods&lt;/code>, &lt;code>frequency&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 8, frequency=&amp;#34;quarterly&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 8, frequency=timeseries.quarterly)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 10, frequency=&amp;#34;business_day&amp;#34;, calendar=&amp;#34;weekend&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Returning a timeline stores a tagged object and shows a timeline card. Dependents can use it for &lt;code>series.resample(shared_timeline)&lt;/code> or &lt;code>events.project(shared_timeline)&lt;/code> without rebuilding dates by hand.&lt;/p>
&lt;h3 id="timeseriesseries">&lt;code>timeseries.series(...)&lt;/code>&lt;/h3>
&lt;p>Creates a regular series on a timeline. Values are dense by default (all valid).&lt;/p>
&lt;p>&lt;strong>Form A — from a timeline&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Argument&lt;/th>
&lt;th>Required&lt;/th>
&lt;th>Default&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>timeline&lt;/code>&lt;/td>
&lt;td>yes*&lt;/td>
&lt;td>—&lt;/td>
&lt;td>From &lt;code>timeseries.timeline&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>values&lt;/code>&lt;/td>
&lt;td>yes&lt;/td>
&lt;td>—&lt;/td>
&lt;td>List of numbers; length = timeline periods&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>kind&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;stock&amp;quot;&lt;/code>&lt;/td>
&lt;td>See kinds above&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>unit&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>dimensionless&lt;/td>
&lt;td>e.g. &lt;code>&amp;quot;USD&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>name&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/td>
&lt;td>Label for errors/display&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># In a *script* you can assign; in an *expression* nest instead:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.series(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 4, frequency=&amp;#34;quarterly&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [12.0, 13.0, 14.0, 15.0],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;data_center&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Form B — one-shot (preferred in expression nodes)&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Argument&lt;/th>
&lt;th>Required&lt;/th>
&lt;th>Default&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>values&lt;/code>&lt;/td>
&lt;td>yes&lt;/td>
&lt;td>—&lt;/td>
&lt;td>List of numbers&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>start&lt;/code>&lt;/td>
&lt;td>yes*&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&lt;code>&amp;quot;YYYY-MM-DD&amp;quot;&lt;/code> if no timeline&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>periods&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>len(values)&lt;/code>&lt;/td>
&lt;td>Override period count&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>frequency&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;monthly&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>kind&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;stock&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>unit&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>dimensionless&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>name&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.series(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values=[12.0, 13.0, 14.0, 15.0],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> start=&amp;#34;2025-01-01&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> frequency=&amp;#34;quarterly&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=timeseries.usd,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;data_center&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Attributes:&lt;/strong> &lt;code>values&lt;/code>, &lt;code>valid&lt;/code>, &lt;code>has_na&lt;/code>, &lt;code>kind&lt;/code>, &lt;code>unit&lt;/code>, &lt;code>name&lt;/code>, &lt;code>len&lt;/code>.&lt;/p>
&lt;p>Stored series shape (conceptual):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> type: &amp;#34;timeseries.series&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name, kind, unit,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values, # numbers; null where NA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> valid?, # only when any observation is missing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> periods, start, end, frequency
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="timeseriesaccumulatestock-flow">&lt;code>timeseries.accumulate(stock=, flow=)&lt;/code>&lt;/h3>
&lt;p>Applies a &lt;strong>flow&lt;/strong> onto a &lt;strong>stock&lt;/strong> after alignment:&lt;/p>
&lt;p>&lt;code>result[t] = stock[t] + cumsum(flow)[t]&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.accumulate(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> stock=timeseries.series(values=[100.0, 100.0, 100.0], start=&amp;#34;2025-01-01&amp;#34;, kind=&amp;#34;stock&amp;#34;, unit=&amp;#34;USD&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> flow=timeseries.series(values=[10.0, 5.0, 1.0], start=&amp;#34;2025-01-01&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># values → [110, 115, 116]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Use for opening debt + net new borrowing, cash build-up, and similar. Do &lt;strong>not&lt;/strong> write &lt;code>debt + borrowing&lt;/code>.&lt;/p>
&lt;h3 id="timeseriesonestimeline-kind-unit">&lt;code>timeseries.ones(timeline, kind?, unit?)&lt;/code>&lt;/h3>
&lt;p>Series of &lt;code>1.0&lt;/code> on a timeline (default kind &lt;code>ratio&lt;/code>, dimensionless). Useful for keep-rates:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.ones(timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 8, frequency=&amp;#34;quarterly&amp;#34;)) - tax_rate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># or simply, for dimensionless ratio series:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 - tax_rate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Also: &lt;code>series.ones_like()&lt;/code> returns ones on the same timeline.&lt;/p>
&lt;h3 id="timeseriesevents--irregular-observations">&lt;code>timeseries.events(...)&lt;/code> — irregular observations&lt;/h3>
&lt;p>An &lt;strong>event series&lt;/strong> is a bag of dated values that do &lt;strong>not&lt;/strong> live on a regular grid:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Real-world example&lt;/th>
&lt;th>Why it is an event&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Debt issuance of $2bn on 2025-03-14&lt;/td>
&lt;td>Once, on an arbitrary day&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Special dividend / one-off tax refund&lt;/td>
&lt;td>Point adjustment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rate announcement effective 2025-06-01&lt;/td>
&lt;td>Level that holds &lt;em>from then on&lt;/em> (as-of)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Capex invoices on irregular dates&lt;/td>
&lt;td>Sum into the quarter that contains them&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [&amp;#34;2025-03-14&amp;#34;, 2.0],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [&amp;#34;2025-09-30&amp;#34;, 1.5],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [&amp;#34;2026-01-10&amp;#34;, -0.5],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;debt_issuance&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Argument&lt;/th>
&lt;th>Required&lt;/th>
&lt;th>Default&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>events&lt;/code>&lt;/td>
&lt;td>yes&lt;/td>
&lt;td>—&lt;/td>
&lt;td>List of &lt;code>[&amp;quot;YYYY-MM-DD&amp;quot;, number]&lt;/code> pairs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>kind&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;flow&amp;quot;&lt;/code>&lt;/td>
&lt;td>Semantic kind after projection&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>unit&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>dimensionless&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>name&lt;/code>&lt;/td>
&lt;td>no&lt;/td>
&lt;td>&lt;code>&amp;quot;&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Methods:&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Result&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>events.project(timeline, method?)&lt;/code>&lt;/td>
&lt;td>Regular &lt;strong>Series&lt;/strong> on that timeline&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>events.asof(timeline)&lt;/code>&lt;/td>
&lt;td>Same as &lt;code>project(..., method=&amp;quot;asof&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="project-methods">Project methods&lt;/h4>
&lt;p>Periods are half-open &lt;code>[start, end)&lt;/code>. An event at &lt;code>2025-04-01&lt;/code> falls in the period that &lt;strong>contains&lt;/strong> that instant.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;code>method&lt;/code>&lt;/th>
&lt;th>Behaviour&lt;/th>
&lt;th>Typical use&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>&amp;quot;sum&amp;quot;&lt;/code> (default)&lt;/td>
&lt;td>Sum all events whose date falls in the period&lt;/td>
&lt;td>Issuances, invoices&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;quot;last&amp;quot;&lt;/code>&lt;/td>
&lt;td>Last event in the period&lt;/td>
&lt;td>End-of-period reading&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;quot;first&amp;quot;&lt;/code>&lt;/td>
&lt;td>First event in the period&lt;/td>
&lt;td>Opening spike&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;quot;impulse&amp;quot;&lt;/code>&lt;/td>
&lt;td>Point mass in containing period&lt;/td>
&lt;td>Single hit placement&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>&amp;quot;asof&amp;quot;&lt;/code>&lt;/td>
&lt;td>Latest event with &lt;code>date &amp;lt; period_end&lt;/code>, carried forward&lt;/td>
&lt;td>Rate/announcement levels&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Periods with no contributing events are &lt;strong>NA&lt;/strong> (not zero), except &lt;code>asof&lt;/code> which forward-fills once a first event has been seen.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">EventSeries ──project(timeline, method)──► Series ──+ / accrue / accumulate──► model
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> (sparse) │ (regular)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── asof ───────────► step series of “latest known”
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Debt issuance → quarterly borrowing flow:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[[&amp;#34;2025-03-14&amp;#34;, 2.0], [&amp;#34;2025-09-30&amp;#34;, 1.5]],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;issuances&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).project(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 8, frequency=&amp;#34;quarterly&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> method=&amp;#34;sum&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>model_tl&lt;/code> and &lt;code>issuances&lt;/code> are aliased nodes:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">issuances.project(model_tl, method=&amp;#34;sum&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Policy rate announcements → as-of rate series:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [&amp;#34;2024-12-15&amp;#34;, 0.045],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [&amp;#34;2025-06-12&amp;#34;, 0.050],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [&amp;#34;2025-11-01&amp;#34;, 0.0475],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;rate&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;fed_path&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).asof(timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 8, frequency=&amp;#34;quarterly&amp;#34;))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>One-off P&amp;amp;L item&lt;/strong> (empty quarters must become zero before subtraction):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">op_income - timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[[&amp;#34;2025-08-20&amp;#34;, 0.4]],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;litigation&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).project(model_tl, method=&amp;#34;sum&amp;#34;).fill_na(0.0)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Shared timeline node (recommended graph shape):&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">model_tl (timeline expression)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─► issuances.project(model_tl, method=&amp;#34;sum&amp;#34;) → issuance_flow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─► coupons.project(model_tl, method=&amp;#34;sum&amp;#34;) → coupon_flow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─► rate_events.asof(model_tl) → int_rate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="timeseriesframetimeline">&lt;code>timeseries.frame(timeline)&lt;/code>&lt;/h3>
&lt;p>Named collection of series that &lt;strong>share one timeline&lt;/strong> (matrix helper). Methods: &lt;code>.set(name, series)&lt;/code>, &lt;code>.get(name)&lt;/code>, &lt;code>.matrix(names=[...])&lt;/code>. Prefer separate expression nodes in graphs; frame is mainly for scripts or dense multi-column hand-offs.&lt;/p>
&lt;h3 id="timeseriesregression--timeseriescalibrate">&lt;code>timeseries.regression&lt;/code> / &lt;code>timeseries.calibrate&lt;/code>&lt;/h3>
&lt;p>Fit a linear model after aligning predictors and dropping incomplete rows. Prefer &lt;strong>one expression node per series&lt;/strong>, then a &lt;strong>model&lt;/strong> node, then a &lt;strong>forecast&lt;/strong> node.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.regression(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> gdp,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> interest_rate.lag(2),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inflation.lag(1),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unemployment,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">gdp_model.predict([
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> projected_rate.lag(2),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> projected_inflation.lag(1),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> projected_unemployment,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.calibrate(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> target=gdp,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> basis=[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> interest_rate.lag(2),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inflation.lag(1),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unemployment,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> loss=&amp;#34;rmse&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Inspect: &lt;code>gdp_model.intercept&lt;/code>, &lt;code>gdp_model.coefficients&lt;/code>, &lt;code>gdp_model.r_squared&lt;/code>, &lt;code>gdp_model.rmse&lt;/code>, &lt;code>gdp_model.observations&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Tip:&lt;/strong> drivers that move in lockstep produce a singular design matrix. Use independent variation, and enough complete rows after lags.&lt;/p>
&lt;hr>
&lt;h2 id="series-methods">Series methods&lt;/h2>
&lt;p>Unless noted, methods return a &lt;strong>new&lt;/strong> series (immutable style).&lt;/p>
&lt;h3 id="arithmetic">Arithmetic&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Op&lt;/th>
&lt;th>Rule (simplified)&lt;/th>
&lt;th>Result kind (typical)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>a + b&lt;/code> / &lt;code>a - b&lt;/code>&lt;/td>
&lt;td>Same kind; compatible units; NA propagates&lt;/td>
&lt;td>Same as operands&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>a * b&lt;/code>&lt;/td>
&lt;td>Units multiply; kinds must be sensible&lt;/td>
&lt;td>e.g. flow × ratio → flow&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>a / b&lt;/code>&lt;/td>
&lt;td>Units divide&lt;/td>
&lt;td>stock/stock → ratio; flow/stock → rate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>a * k&lt;/code> / &lt;code>a / k&lt;/code>&lt;/td>
&lt;td>Scalar &lt;code>k&lt;/code>&lt;/td>
&lt;td>Same series&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>1 - a&lt;/code> / &lt;code>k - a&lt;/code>&lt;/td>
&lt;td>Dimensionless series (e.g. tax rate)&lt;/td>
&lt;td>Same kind&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Rejected by design:&lt;/strong> &lt;code>stock + flow&lt;/code>, &lt;code>stock * rate&lt;/code>, &lt;code>USD + GBP&lt;/code> (convert first).&lt;/p>
&lt;p>When two series have different timelines, the engine &lt;strong>aligns&lt;/strong> them (default: finest frequency, intersection of ranges) and resamples by kind. Override with &lt;code>a.align(b, frequency=..., range=...)&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Gross profit = revenue × margin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.series(values=[10.0, 12.0], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;) \
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * timeseries.series(values=[0.70, 0.72], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;ratio&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1 - tax_rate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pretax * (1 - tax_rate)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="scale-and-fx">Scale and FX&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>scale(k)&lt;/code>&lt;/td>
&lt;td>Scenario multiplier (same unit/kind) — e.g. &lt;code>rev.scale(1.1)&lt;/code> = +10%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>convert(target, fx)&lt;/code>&lt;/td>
&lt;td>FX: multiply by &lt;code>fx&lt;/code> and &lt;strong>retag unit&lt;/strong> to &lt;code>target&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>with_name(label)&lt;/code>&lt;/td>
&lt;td>Copy with a new display name (charts / cards)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>&lt;code>fx&lt;/code> convention:&lt;/strong> units of &lt;strong>target&lt;/strong> currency per 1 unit of &lt;strong>source&lt;/strong>. Example: &lt;code>usd_gbp = 0.79&lt;/code> means £0.79 per $1.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">rev.scale(1.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">revenue.convert(&amp;#34;GBP&amp;#34;, usd_gbp).with_name(&amp;#34;revenue_gbp&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Common mistakes:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>What you did&lt;/th>
&lt;th>What happens&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Built revenue without &lt;code>unit=&amp;quot;USD&amp;quot;&lt;/code>&lt;/td>
&lt;td>Unit is &lt;code>&amp;quot;1&amp;quot;&lt;/code>; &lt;code>convert&lt;/code> errors&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Set Format tab Currency only&lt;/td>
&lt;td>Display formatting only — series card still reads &lt;code>value.unit&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Wrote &lt;code>revenue * usd_gbp&lt;/code>&lt;/td>
&lt;td>Values scale; &lt;strong>unit stays USD&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Charted bare convert next to source&lt;/td>
&lt;td>Both keep the same &lt;code>name&lt;/code> — chain &lt;code>.with_name(...)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="lag-lead-diff-growth">Lag, lead, diff, growth&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">bank_rate.lag(1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">closing_debt.lag(1).fill_na(opening_seed_value)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">debt.diff()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">revenue.pct_change() # QoQ growth as decimal, e.g. 0.08 = +8%
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Leading/trailing gaps from lag/lead are &lt;strong>NA&lt;/strong> (not zero).&lt;/p>
&lt;h3 id="cumulative">Cumulative&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">fcf.cumsum() # flow → stock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">growth.compound() # relative change / rate → index
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="missing-values">Missing values&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Effect&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>fill_na(value)&lt;/code>&lt;/td>
&lt;td>Replace NA with constant&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ffill()&lt;/code>&lt;/td>
&lt;td>Forward fill (leading NA remain)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>bfill()&lt;/code>&lt;/td>
&lt;td>Backward fill (trailing NA remain)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>interpolate()&lt;/code>&lt;/td>
&lt;td>Linear between valid neighbors&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>coalesce(other)&lt;/code>&lt;/td>
&lt;td>Prefer self; take other where NA&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>drop_na()&lt;/code>&lt;/td>
&lt;td>Shorten to contiguous valid block&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>is_na()&lt;/code>&lt;/td>
&lt;td>Ratio series of 0/1 flags&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">issuance_flow.fill_na(0.0)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">rate_path.ffill()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sparse.coalesce(fallback)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="windows-and-ewm">Windows and EWM&lt;/h3>
&lt;p>Trailing windows: integer observation count &lt;strong>or&lt;/strong> duration string (&lt;code>&amp;quot;12m&amp;quot;&lt;/code>, &lt;code>&amp;quot;30d&amp;quot;&lt;/code>, &lt;code>&amp;quot;4q&amp;quot;&lt;/code>).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">revenue.rolling_mean(4)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">revenue.rolling_mean(window=&amp;#34;4q&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">revenue.rolling_sum(&amp;#34;12m&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">revenue.rolling_std(4)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">inflation.ewm(alpha=0.2)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">inflation.ewm(span=6)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="align-and-resample">Align and resample&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">annual_cpi.asof(model_tl)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">a.align(b, frequency=&amp;#34;finest&amp;#34;, range=&amp;#34;intersection&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Kind defaults apply when method is omitted (stock → last/ffill, flow → sum/distribute, …)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">q_rev.resample(timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 12, frequency=&amp;#34;monthly&amp;#34;))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">q_rev.resample(timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 12, frequency=&amp;#34;monthly&amp;#34;), method=&amp;#34;linear&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="stats">Stats&lt;/h3>
&lt;p>Valid observations only; NA is skipped. Pairwise stats align timelines first.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mean&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">variance&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">quantile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.9&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">corr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">revenue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">covariance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="accrue-financing-primitive">Accrue (financing primitive)&lt;/h3>
&lt;p>Balance must be a &lt;strong>stock&lt;/strong>; &lt;code>rate&lt;/code> a &lt;strong>rate&lt;/strong>. Result is a &lt;strong>flow&lt;/strong> in the balance’s unit:&lt;/p>
&lt;p>&lt;code>interest[t] ≈ balance[t] × period_factor(rate[t], period[t], day-count)&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">debt.accrue(interest_rate)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">debt.accrue(interest_rate, daycount=&amp;#34;actual365&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">debt.accrue(interest_rate, daycount=timeseries.actual365)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For quarterly models, a 5% annual rate does &lt;strong>not&lt;/strong> become 5% per quarter. Accrual uses each quarter’s actual year fraction (e.g. ~90/365).&lt;/p>
&lt;hr>
&lt;h2 id="modelling-pattern-on-the-graph">Modelling pattern on the graph&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Layer&lt;/th>
&lt;th>What lives there&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Assumption nodes&lt;/strong>&lt;/td>
&lt;td>Drivers: demand, price, margin, rates, tax rate (edit these)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Driver / intermediate nodes&lt;/strong>&lt;/td>
&lt;td>Unit demand × price → segment revenue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>P&amp;amp;L nodes&lt;/strong>&lt;/td>
&lt;td>Totals, gross profit, opex, EBIT, tax, NI&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Balance / financing nodes&lt;/strong>&lt;/td>
&lt;td>Debt stock, borrowing/repayment flows, accrued interest&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Optional cash nodes&lt;/strong>&lt;/td>
&lt;td>FCF, cash stock, repayment policy&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Edges mean &lt;strong>dependency&lt;/strong>: the expression node &lt;strong>listens&lt;/strong> to the nodes it reads. Topology is the engine.&lt;/p>
&lt;p>&lt;strong>Recommended habits&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Alias every input; name expression nodes after the output metric.&lt;/li>
&lt;li>Prefer &lt;strong>many small nodes&lt;/strong> over one giant formula — the graph is the audit trail.&lt;/li>
&lt;li>Share one &lt;strong>timeline&lt;/strong> node and &lt;code>resample&lt;/code> / &lt;code>events.project&lt;/code> onto it.&lt;/li>
&lt;li>Return the &lt;strong>series object&lt;/strong> from intermediates (not only &lt;code>.values&lt;/code>).&lt;/li>
&lt;li>Use &lt;strong>&lt;code>accrue&lt;/code> / &lt;code>accumulate&lt;/code>&lt;/strong> for time and stock–flow economics.&lt;/li>
&lt;/ol>
&lt;p>How to write formulas in the expression editor:
. Multi-line packages:
.&lt;/p>
&lt;hr>
&lt;h2 id="worked-example--nvidia-driver-network">Worked example — NVIDIA driver network&lt;/h2>
&lt;h3 id="purpose">Purpose&lt;/h3>
&lt;p>Model NVIDIA as a &lt;strong>forward-looking driver network&lt;/strong>, not a full three-statement accounting replica:&lt;/p>
&lt;ul>
&lt;li>Segment revenue driven by demand × price (causality, not a single “revenue” plug)&lt;/li>
&lt;li>P&amp;amp;L waterfall to net income&lt;/li>
&lt;li>Debt stock → &lt;strong>accrued&lt;/strong> interest → earnings&lt;/li>
&lt;li>Optional feedback: cash / repayment → debt → future interest&lt;/li>
&lt;/ul>
&lt;p>Horizon: &lt;strong>8 quarters from 2025-01-01&lt;/strong> (illustrative numbers, not a forecast). Scale: &lt;strong>USD billions&lt;/strong> as plain numbers with &lt;code>unit=&amp;quot;USD&amp;quot;&lt;/code>.&lt;/p>
&lt;h3 id="network-sketch">Network sketch&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> NVIDIA financial drivers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────────────────┼─────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Revenue Expenses Financing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌────┼────┐ ┌────┼────┐ ┌─────┴─────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │ │ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> DC Gaming Auto COGS R&amp;amp;D SG&amp;amp;A Debt Int. rate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │ │ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──┬─┴─┬──┘ │ │ │ └─────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ │ │ │ │ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Total Revenue │ │ │ Interest expense
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── × gross_margin ┘ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Gross profit │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── − R&amp;amp;D ──────────────┘ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── − SG&amp;amp;A ──────────────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Operating income │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── − interest ───────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Pre-tax income
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── × (1 − tax_rate)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Net income
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Causal revenue (optional richer demo):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">AI compute demand ──► DC unit demand ──┬──► Data Center revenue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> GPU price ────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Gaming demand ──┬──► Gaming revenue
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Gaming price ───┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="node-catalogue">Node catalogue&lt;/h3>
&lt;p>Use &lt;strong>one Nodlin node per series&lt;/strong>. Suggested aliases match expression variables.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Alias&lt;/th>
&lt;th>Kind&lt;/th>
&lt;th>Unit&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>dc_units&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>dimensionless&lt;/td>
&lt;td>Data center GPU units (m)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>dc_price&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>ASP — keep scale consistent with units&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>data_center&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>DC revenue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gaming_units&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Gaming units&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gaming_price&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Gaming ASP&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gaming&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Gaming revenue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>auto&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Automotive revenue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>other&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Other revenue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>total_revenue&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Sum of segments&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gross_margin&lt;/code>&lt;/td>
&lt;td>ratio&lt;/td>
&lt;td>—&lt;/td>
&lt;td>e.g. 0.75&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gross_profit&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>revenue × margin&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>cogs&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>revenue × (1 − margin)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>rd&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>R&amp;amp;D expense&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sga&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>SG&amp;amp;A&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op_income&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>EBIT-like&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>debt&lt;/code>&lt;/td>
&lt;td>stock&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Debt outstanding&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>borrow&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>New borrowing&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>repay&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Repayments&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>closing_debt&lt;/code>&lt;/td>
&lt;td>stock&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>After borrow/repay&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>int_rate&lt;/code>&lt;/td>
&lt;td>rate&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Annual financing rate (decimal)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>interest&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>Accrued interest&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>pretax&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>EBT&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>tax_rate&lt;/code>&lt;/td>
&lt;td>ratio&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Effective tax rate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>net_income&lt;/code>&lt;/td>
&lt;td>flow&lt;/td>
&lt;td>USD&lt;/td>
&lt;td>NI&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="input-series-illustrative">Input series (illustrative)&lt;/h3>
&lt;p>Eight quarters, quarterly frequency, start &lt;code>2025-01-01&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.series(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values=[2.0, 2.2, 2.5, 2.8, 3.0, 3.2, 3.4, 3.6],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> start=&amp;#34;2025-01-01&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> frequency=&amp;#34;quarterly&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;dc_units&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Alias&lt;/th>
&lt;th>Example values (8Q)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>dc_units&lt;/code>&lt;/td>
&lt;td>&lt;code>[2.0, 2.2, 2.5, 2.8, 3.0, 3.2, 3.4, 3.6]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>dc_price&lt;/code>&lt;/td>
&lt;td>&lt;code>[4.0, 4.0, 4.1, 4.1, 4.2, 4.2, 4.3, 4.3]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gaming_units&lt;/code>&lt;/td>
&lt;td>&lt;code>[1.5, 1.5, 1.4, 1.4, 1.3, 1.3, 1.2, 1.2]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gaming_price&lt;/code>&lt;/td>
&lt;td>&lt;code>[0.8, 0.8, 0.8, 0.75, 0.75, 0.75, 0.7, 0.7]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>auto&lt;/code>&lt;/td>
&lt;td>&lt;code>[0.4, 0.4, 0.45, 0.45, 0.5, 0.5, 0.55, 0.55]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>other&lt;/code>&lt;/td>
&lt;td>&lt;code>[0.3]*8&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gross_margin&lt;/code>&lt;/td>
&lt;td>&lt;code>[0.75]*8&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>rd&lt;/code>&lt;/td>
&lt;td>&lt;code>[2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sga&lt;/code>&lt;/td>
&lt;td>&lt;code>[1.0]*8&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>debt&lt;/code>&lt;/td>
&lt;td>&lt;code>[10.0]*8&lt;/code> opening path, or evolve via accumulate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>int_rate&lt;/code>&lt;/td>
&lt;td>&lt;code>[0.045]*8&lt;/code> (4.5% annual)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>tax_rate&lt;/code>&lt;/td>
&lt;td>&lt;code>[0.15]*8&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="expression-nodes-copy-ready">Expression nodes (copy-ready)&lt;/h3>
&lt;p>Assumes aliases already resolve to &lt;strong>series&lt;/strong>. Only &lt;strong>leaf&lt;/strong> plain lists need an explicit wrap:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.series(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> values=dc_units, # plain list from a non-series node
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> start=&amp;#34;2025-01-01&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> frequency=&amp;#34;quarterly&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;dc_units&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After that, dependents use the alias directly.&lt;/p>
&lt;p>&lt;strong>Data Center revenue&lt;/strong> (units × price):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">dc_units * dc_price
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Gaming revenue:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">gaming_units * gaming_price
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Total revenue:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">data_center + gaming + auto + other
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Gross profit and COGS:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">total_revenue * gross_margin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">total_revenue * (1 - gross_margin)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>1 - gross_margin&lt;/code> is supported for dimensionless &lt;strong>ratio&lt;/strong> series. Equivalent COGS:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">total_revenue - gross_profit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Operating income:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">gross_profit - rd - sga
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Interest expense&lt;/strong> — do not multiply:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># Wrong (rejected or economically sloppy):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># debt * int_rate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Correct:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">debt.accrue(int_rate, daycount=&amp;#34;actual365&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Pre-tax and net income:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">op_income - interest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">pretax * (1 - tax_rate)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or as its own node &lt;code>keep_ratio&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1 - tax_rate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">pretax * keep_ratio
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="debt-evolution">Debt evolution&lt;/h3>
&lt;p>&lt;strong>Static debt&lt;/strong> is a single stock assumption. &lt;strong>Dynamic debt&lt;/strong> uses flows:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">opening_debt (stock)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">borrow (flow) repay (flow)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> \ /
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> accumulate → closing_debt (stock)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> closing_debt.accrue(int_rate) → interest (flow)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.accumulate(stock=opening_debt, flow=(borrow - repay))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If &lt;code>borrow - repay&lt;/code> is not yet a single flow node, create &lt;strong>net_borrow&lt;/strong> = &lt;code>borrow - repay&lt;/code> first, then:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.accumulate(stock=opening_debt, flow=net_borrow)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>lag(1)&lt;/code> leaves t0 as &lt;strong>NA&lt;/strong> — seed it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">closing_debt.lag(1).fill_na(10.0) # or coalesce with an opening_debt series
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">closing_debt.accrue(int_rate, daycount=&amp;#34;actual365&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="optional-irregular-financing-as-events">Optional: irregular financing as events&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># node: model_tl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 8, frequency=&amp;#34;quarterly&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># node: issuances
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[[&amp;#34;2025-03-14&amp;#34;, 2.0], [&amp;#34;2025-09-01&amp;#34;, 1.0]],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;issuances&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># node: issuance_flow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">issuances.project(model_tl, method=&amp;#34;sum&amp;#34;).fill_na(0.0)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># node: closing_debt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">timeseries.accumulate(stock=opening_debt, flow=issuance_flow - repay)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Policy rate path from announcement dates:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[[&amp;#34;2024-12-18&amp;#34;, 0.045], [&amp;#34;2025-06-19&amp;#34;, 0.05]],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;rate&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> name=&amp;#34;policy_rate_events&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).asof(model_tl)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">closing_debt.accrue(policy_rate, daycount=&amp;#34;actual365&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="optional-cash-feedback">Optional cash feedback&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">net_income + da - capex - ΔNWC → fcf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fcf → repay policy / cash stock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">repay → lower debt → lower interest → higher NI
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">net_income + da - capex - nwc_change
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.accumulate(stock=cash, flow=fcf)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="standalone-smoke-tests-no-graph">Standalone smoke tests (no graph)&lt;/h3>
&lt;p>&lt;strong>Accrue one quarter of interest on $10bn at 4.5% annual:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.series(values=[10.0, 10.0], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;stock&amp;#34;, unit=&amp;#34;USD&amp;#34;, name=&amp;#34;debt&amp;#34;).accrue(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.series(values=[0.045, 0.045], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;rate&amp;#34;, name=&amp;#34;int_rate&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> daycount=&amp;#34;actual365&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).values[0]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Total revenue from four segments:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.series(values=[8.0, 9.0], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ timeseries.series(values=[1.2, 1.1], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ timeseries.series(values=[0.4, 0.5], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ timeseries.series(values=[0.3, 0.3], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).values
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Gross profit:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.series(values=[10.0, 12.0], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* timeseries.series(values=[0.75, 0.76], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;ratio&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).values
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Debt after borrowing:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.accumulate(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> stock=timeseries.series(values=[10.0, 10.0, 10.0], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;stock&amp;#34;, unit=&amp;#34;USD&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> flow=timeseries.series(values=[1.0, 0.5, -0.5], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).values
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Keep after tax:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.series(values=[10.0, 12.0], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* (1 - timeseries.series(values=[0.15, 0.15], start=&amp;#34;2025-01-01&amp;#34;, frequency=&amp;#34;quarterly&amp;#34;, kind=&amp;#34;ratio&amp;#34;))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).values
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Events → quarterly flow:&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.events(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> events=[[&amp;#34;2025-02-10&amp;#34;, 1.0], [&amp;#34;2025-02-20&amp;#34;, 0.5]],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> kind=&amp;#34;flow&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> unit=&amp;#34;USD&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).project(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> timeseries.timeline(&amp;#34;2025-01-01&amp;#34;, 4, frequency=&amp;#34;monthly&amp;#34;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> method=&amp;#34;sum&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">).fill_na(0.0).values
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Illegal ops (should error):&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.series(values=[10.0], start=&amp;#34;2025-01-01&amp;#34;, kind=&amp;#34;stock&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">+ timeseries.series(values=[1.0], start=&amp;#34;2025-01-01&amp;#34;, kind=&amp;#34;flow&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">timeseries.series(values=[10.0], start=&amp;#34;2025-01-01&amp;#34;, kind=&amp;#34;stock&amp;#34;, unit=&amp;#34;USD&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">* timeseries.series(values=[0.05], start=&amp;#34;2025-01-01&amp;#34;, kind=&amp;#34;rate&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="suggested-build-order">Suggested build order&lt;/h3>
&lt;ol>
&lt;li>Timeline convention: quarterly, &lt;code>2025-01-01&lt;/code>, N quarters.&lt;/li>
&lt;li>Leaf assumptions: prices, units, margins, opex, tax, rate.&lt;/li>
&lt;li>Segment revenues (&lt;code>units * price&lt;/code>).&lt;/li>
&lt;li>&lt;code>total_revenue&lt;/code>, &lt;code>gross_profit&lt;/code>, &lt;code>cogs&lt;/code>, &lt;code>op_income&lt;/code>.&lt;/li>
&lt;li>&lt;code>debt&lt;/code> + &lt;code>int_rate&lt;/code> → &lt;code>interest&lt;/code> via &lt;strong>&lt;code>accrue&lt;/code>&lt;/strong>.&lt;/li>
&lt;li>&lt;code>pretax&lt;/code>, &lt;code>net_income&lt;/code>.&lt;/li>
&lt;li>Add &lt;code>borrow&lt;/code> / &lt;code>repay&lt;/code> / &lt;code>accumulate&lt;/code> for closing debt.&lt;/li>
&lt;li>Optional: FCF and repayment feedback.&lt;/li>
&lt;/ol>
&lt;p>That sequence shows why Nodlin is a &lt;strong>reactive knowledge graph&lt;/strong>: financing cost is not a spreadsheet cell multiply — it is a typed temporal operation on related stocks and rates, recalculated when either side changes.&lt;/p>
&lt;hr>
&lt;h2 id="operator-cheat-sheet">Operator cheat-sheet&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Intent&lt;/th>
&lt;th>Expression&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Sum segment revenues&lt;/td>
&lt;td>&lt;code>a + b + c&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Apply margin&lt;/td>
&lt;td>&lt;code>revenue * margin&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>COGS from GP&lt;/td>
&lt;td>&lt;code>revenue - gross_profit&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Keep after tax / margin&lt;/td>
&lt;td>&lt;code>1 - tax_rate&lt;/code>, &lt;code>pretax * (1 - tax_rate)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Subtract opex&lt;/td>
&lt;td>&lt;code>gp - rd - sga&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Interest&lt;/td>
&lt;td>&lt;code>debt.accrue(rate, daycount=&amp;quot;actual365&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stock ← stock + flow&lt;/td>
&lt;td>&lt;code>timeseries.accumulate(stock=s, flow=f)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>FX&lt;/td>
&lt;td>&lt;code>revenue.convert(&amp;quot;GBP&amp;quot;, usd_gbp).with_name(&amp;quot;revenue_gbp&amp;quot;)&lt;/code> with &lt;code>unit=&amp;quot;USD&amp;quot;&lt;/code> on revenue&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rename series&lt;/td>
&lt;td>&lt;code>series.with_name(&amp;quot;label&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Growth&lt;/td>
&lt;td>&lt;code>level.pct_change()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Smooth&lt;/td>
&lt;td>&lt;code>flow.rolling_mean(4)&lt;/code> or &lt;code>flow.rolling_mean(&amp;quot;12m&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>EWM&lt;/td>
&lt;td>&lt;code>series.ewm(alpha=0.2)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Delay&lt;/td>
&lt;td>&lt;code>series.lag(1)&lt;/code> or &lt;code>series.lag(&amp;quot;6m&amp;quot;)&lt;/code> (leading NA — often &lt;code>.fill_na(...)&lt;/code>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stats&lt;/td>
&lt;td>&lt;code>mean()&lt;/code>, &lt;code>std()&lt;/code>, &lt;code>quantile(p)&lt;/code>, &lt;code>corr(other)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Regression&lt;/td>
&lt;td>&lt;code>timeseries.regression(y, [x1, x2])&lt;/code> → &lt;code>.predict([...])&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Calibrate&lt;/td>
&lt;td>&lt;code>timeseries.calibrate(target=…, basis=[…], loss=&amp;quot;rmse&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Change frequency&lt;/td>
&lt;td>&lt;code>series.resample(timeline, method?)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Scenario&lt;/td>
&lt;td>&lt;code>series.scale(1.1)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sparse dated hits&lt;/td>
&lt;td>&lt;code>events.project(tl, method=&amp;quot;sum&amp;quot;).fill_na(0)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Announcement path&lt;/td>
&lt;td>&lt;code>events.asof(tl)&lt;/code> then &lt;code>debt.accrue(rate)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Fill holes&lt;/td>
&lt;td>&lt;code>series.ffill()&lt;/code>, &lt;code>fill_na(0)&lt;/code>, &lt;code>interpolate()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Business calendar&lt;/td>
&lt;td>&lt;code>timeseries.calendar(holidays=[&amp;quot;2025-12-25&amp;quot;])&lt;/code> then &lt;code>timeline(..., frequency=&amp;quot;business_day&amp;quot;, calendar=cal)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="practical-tips">Practical tips&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Pick one scale&lt;/strong> ($m or $bn) and stick to it across the graph.&lt;/li>
&lt;li>&lt;strong>Rates and ratios are decimals&lt;/strong> (&lt;code>0.045&lt;/code>, not &lt;code>4.5&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Alias every input&lt;/strong> the expression reads; name nodes after the output metric. Alias doubles as the series &lt;strong>card title&lt;/strong> when &lt;code>name&lt;/code> is empty.&lt;/li>
&lt;li>Prefer &lt;strong>many small nodes&lt;/strong> over one giant formula.&lt;/li>
&lt;li>Use &lt;strong>&lt;code>accrue&lt;/code> / &lt;code>accumulate&lt;/code>&lt;/strong> whenever the economics involve time or stock–flow.&lt;/li>
&lt;li>Expression nodes = one expression. Multi-step &lt;strong>packages&lt;/strong> use the same &lt;code>timeseries&lt;/code> module — see
.&lt;/li>
&lt;li>When a result should feed another node as a series, return the &lt;strong>series object&lt;/strong> (not only &lt;code>.values&lt;/code>).&lt;/li>
&lt;li>Share one &lt;strong>timeline&lt;/strong> node and project/resample onto it when frequencies or event dates differ.&lt;/li>
&lt;li>&lt;strong>Events vs series:&lt;/strong> continuous paths stay series; dated one-offs and announcements are events, then project.&lt;/li>
&lt;li>After &lt;code>project(method=&amp;quot;sum&amp;quot;)&lt;/code>, empty periods are NA — usually &lt;code>.fill_na(0.0)&lt;/code> before adding into a P&amp;amp;L. After &lt;code>asof&lt;/code>, leading periods stay NA until the first event.&lt;/li>
&lt;li>Domain eval errors soft-fail on expression nodes: fix the formula on the warning node; siblings and prior good values keep the cascade moving.&lt;/li>
&lt;li>The expression form &lt;strong>Chart&lt;/strong> action charts a &lt;strong>list&lt;/strong> or &lt;strong>&lt;code>timeseries.series&lt;/code>&lt;/strong>. Project event series first, then chart the resulting series.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="related-documentation">Related documentation&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>
&lt;/strong> — single-line formulas, aliases, &lt;code>related()&lt;/code>, timeseries in the expression editor&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — multi-line Starlark packages using &lt;code>timeseries&lt;/code>&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — external agents and the Go timeseries package&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — expression editor control&lt;/li>
&lt;/ul></description></item></channel></rss>