Skip to main content

The Definitive Guide to LX

Using Lumonic's query language to build dashboards, custom calculations and excel reports

Quick start

Every LX expression starts with a pointer to a metric. Add modifiers in square brackets to target a specific period, scenario, or revision.

@reporter.net_income                       # Get a metric @reporter.net_income[scenario=budget]      # Compare to budget @reporter.net_income[period=M::LTM]        # Last twelve months @reporter.net_income[asof=2024-12-31]      # The value as it was known on a date

Finding your configuration

Your global object

The word after the @ is your global object — the level your data lives at. Find it on the deliveab tracker, above the list of companies:

  • "Reporters" → use @reporter

  • "Companies" → use @company

  • "Entities" → use @entity

Your metric names and scenarios

  1. Left sidebar → ReportersMetrics

  2. Open Metric groups

  3. Browse categories (Balance Sheet, etc.) and scenarios (actuals, budget, etc.)

  4. Use these exact names in your queries

Your specific scenarios and metrics are configured during setup.


Core concepts

Basic structure

@reporter.metric_name[modifiers]

Modifiers

Modifiers add specificity using key–value pairs inside square brackets [ ]. They target a specific dimension of your data. You can combine several, comma-separated.

Period modifiers: set the periodicity of a metric and resample from one grain to another with source::target.

# Syntax: period={source_period}::{target_period} @entity.example-1[period=M::LTM]     # Resample monthly data to Last Twelve Months

💡 Resampled targets can be any window period type. Prefer fiscal periods over calendar ones when the intent is ambiguous.

Scenario modifiers — select a version of a metric, such as actuals, budget, or an origination case.

@entity.example-1[period=M, scenario=actual]

Everything else you can put in brackets 🆕 — beyond period and scenario, these modifiers shipped over the last few releases:

Modifier

What it does

Values

asof

Choose which reported revision of a value to use

see below

mapping

Turn entity-mapping resolution on or off

true · false

cadence

View a window (LTM, YTD…) on a chosen grain, e.g. cadence=Q

m · q · fq · y · fy

fill

Fill the synthesized rows created when upsampling

ffill · interpolate · none

carry

Carry a value across every period

forward · backward · both · none

cumulative

Return cumulative (running-total) values

true · false

cashflow_financial_scenario

Select a cashflow financial scenario

per namespace

Smart defaults

When you leave modifiers off, LX fills in sensible defaults:

@reporter.net_income # Same as @reporter.net_income[scenario=actuals, period=M, asof=latest]

As-of revisions 🆕

Every value can be reported more than once. A first estimate, then a restatement. The asof modifier picks which revision you get, so a dashboard can show today's best number or reconstruct what you knew at a point in time.

Pick a revision

@company.revenue[asof=latest]      # Most recently reported (this is the default) @company.revenue[asof=earliest]    # First reported value @company.revenue[asof=previous]    # Second most recent @company.revenue[asof=all]         # All revisions, as a list

Compare revisions

@company.revenue[asof=min]              # Smallest value across all revisions @company.revenue[asof=max]              # Largest value across all revisions @company.revenue[asof=average]          # Average across all revisions @company.revenue[asof=delta]            # Latest − earliest @company.revenue[asof=percent]          # % change, earliest → latest @company.revenue[asof=delta-previous]   # Latest − previous revision @company.revenue[asof=percent-previous] # % change, previous → latest

Reconstruct a point in time — add ::YYYY-MM-DD to bound any strategy to a version cutoff. Only revisions reported on or before that date count. A bare date is shorthand for latest as of that date.

@company.revenue[asof=latest::2024-12-31]  # Latest revision known by year-end 2024 @company.revenue[asof=2024-12-31]          # Shorthand for latest::2024-12-31

When was it reported? — the companion function as_of_date() returns the date a value was reported or recorded.

as_of_date(@company.revenue[asof=latest])

Real-world examples

Go to any Dashboard, Formula, Mapping, or Excel calculation to write LX.

Scenario comparisons

# Compare actual to budget @reporter.net_income - @reporter.net_income[scenario=budget]  # Variance with scaling (shows in thousands) scale(     @reporter.net_income[period=M::FYTD] -     @reporter.net_income[scenario=budget, period=M::FYTD],     1000 )

Growth calculations

# Year-over-year growth (@reporter.net_income[period=M::LTM] /  @reporter.net_income-close) - 1  # Or, more directly, on monthly data: prior_period_percent_change(@reporter.net_income, 12)

Asset coverage

(@reporter.accounts_receivable[scenario=actuals, period=M] * .85 +  coalesce(@reporter.inventory[scenario=actuals, period=M], 0) * .5) /  coalesce(@reporter.total_syndicated_outstanding[],           @reporter.total_debt_outstanding[scenario=actuals, period=M])

A complex, real example

"If LTM revenue is beating our 12-month budgeted average, show the growth rate; otherwise show current revenue, all in thousands."

scale(                                             # Display in 1000s     if(                                            # If / then logic         @reporter.revenue[period=M::LTM] >         # Is LTM revenue greater than         rolling_average(                           # the rolling average             @reporter.revenue[scenario=budget], 12 # of 12 months of budget?         ),         prior_period_percent_change(               # If yes: show % change             @reporter.revenue                      # from prior period         ),         coalesce(@reporter.revenue, 0)             # If no: show revenue or 0     ),     1000                                           # Scale to thousands )

💡 if(), not when(). Older docs used when(...) for conditionals — it's no longer part of LX. Use if() for one branch, or case() for several.


Working with dates 🆕

Dates are now first-class LX values. You can read them, do arithmetic on them, and anchor time-series calculations to a real business date — "since close", "prior fiscal year", "trailing 12 months" — instead of a raw count of rows.

Date math

# Date-returning functions current_date()                     # Today's date at evaluation time (today() is an alias) relative_to_date()                 # The report or widget's anchor date effective_date(@company.cash)      # The date a value was reported  # Pull parts out of a date year(date)   month(date)   quarter(date)   fiscal_year(date)   fiscal_quarter(date)  # Distance between two dates days_between(date1, date2)   months_between(date1, date2)   years_between(date1, date2)

Literal and relative dates

Write a literal date as YYYY-MM-DD. Relative dates shift by calendar or fiscal periods — -1FY (prior fiscal year), -12CM (trailing 12 calendar months), 0CM (current month). Add ! for the start of the period or @ to keep the day-of-period anchor.

exact(@company.revenue, 2024-12-31) date_shift(@company.close-date, -1FY)   # shift a date by a relative date

Duration constructors 🆕

Unlike a fixed reldate like 18CM, these take a computed (even fractional) number and shift a date with + or -.

months(n)   days(n)   years(n)  # Runway: project a cash-out date from computed months of runway effective_date(@company.cash) + months(@company.cash / @company.monthly-burn)  # Maturity date from a loan term @company.close-date + years(@company.loan-term-years)

Date-anchored time series

These shift and roll by a date anchor instead of an integer count — reach for them whenever the question is framed around calendar or fiscal dates.

period_shift(@company.revenue[period=m], -1FY)               # shift to the prior fiscal year period_shift(@company.revenue, @company.close-date)          # shift relative to a date property period_shift_percent_change(@company.revenue[period=m], -1FY) period_rolling_sum(@company.revenue[period=m], -12CM)        # trailing 12 calendar months period_rolling_average(@company.revenue[period=m], -3CM)     # trailing 3-month average cumulative_sum(@company.revenue, @company.close-date)        # accumulate since close

Point-in-time lookups 🆕

Read a single value out of a series, with or without carry-forward semantics.

first(@company.revenue)                              # earliest value last(@company.revenue)                               # latest value previous_known(@company.cash-balance, @company.close-date)  # carry last known value forward next_known(@company.revenue, 2024-01-01)             # first value on or after a date exact(@company.revenue, 2024-12-31)                  # strict match — null if no value on that date

Cross-entity & fund metrics 🆕

Most formulas stay within one entity. When a question genuinely spans related entities — a portfolio total, a share of total, a fund IRR — these functions reach across the hierarchy.

Cross-entity lookups

# Pull from related entities (auto up/down), optionally aggregating and filtering lookup_related(@security.nav, "sum", @security.type != "warrant")  # Aggregate across ALL entities in a namespace lookup_all(@company.loan, "sum")  # Share of total @company.loan / lookup_all(@company.loan, "sum")

The aggregation method is a quoted string: "sum", "avg", "min", "max", or "count".

Cashflows & fund metrics

# Net IRR from combined cashflows plus the latest NAV as terminal value xirr(     combine(@fund.cashflows.contribution, @fund.cashflows.distribution),     latest_nav(@fund.cashflows.nav) )  # Latest NAV, falling back to cost until one exists latest_nav(@fund.cashflows.nav, @fund.cost)  # Roll the latest NAV forward with cashflows since that NAV date cashflow_adjusted_nav(     latest_nav(@fund.cashflows.nav),     combine(@fund.cashflows.contribution, @fund.cashflows.distribution * -1) )

Making numbers look right

Scaling

scale(@reporter.net_income, 1000)   # Display in thousands round(@reporter.net_income, 0)      # Round to whole numbers

Available formats

  • Currency ($)

  • Percent (%)

  • Decimal places (0–2 typical)

  • Thousands separator

Handling missing data

Use coalesce only when you need fallback behavior:

# If adjusted EBITDA is missing, use regular EBITDA coalesce(     @reporter.adjusted_ebitda,     @reporter.ebitda )

Complete syntax reference

Base period types

Code

Meaning

D

Day

M

Month (base period)

Q

Calendar Quarter

Y

Calendar Year

FQ

Fiscal Quarter

FY

Fiscal Year

LTM

Last Twelve Months

NTM

Next Twelve Months

YTD

Year to Date

FYTD

Fiscal Year to Date

QTD

Quarter to Date

FQTD

Fiscal Quarter to Date

Resample with period=M::LTM, period=M::YTD, and so on.

Relative dates (reldates)

Format: [+-]<N><suffix>[!@]. Suffixes: D CW CM CQ CH CY FQ FH FY. Examples: -1FY, -12CM, 0CM, -3CM!. Optional anchors: ! = start of period, @ = keep day-of-period.

💡 Relative dates are usable directly in LX now — in date-aware functions and date arithmetic — not just in the effective-date field.

Operators

+  -  *  /  ^                        # add, subtract, multiply, divide, power >  >=  <  <=  =  !=                  # comparisons

Comments in LX start with #.

Function catalog

This list is generated directly from the LX function specs, so it always matches the shipped grammar. See "Keeping this in sync" at the end.

Conditional

Use when an expression needs branch logic, labels, thresholds, or fallback values.

  • if(condition, truthy_value, falsey_value): Return one value when a condition is true and another when it is false. Example: if(@company.revenue > 0, "Positive", "Negative")

  • case(condition1, result1, condition2, result2, ..., default): Evaluate condition/result pairs in order and return the first matching result. Example: case(@company.revenue > 1000, "High", @company.revenue > 500, "Medium", "Low")

  • switch(value, match1, result1, match2, result2, ..., default): Match a value against explicit choices and return the corresponding result. Example: switch(@company.status, "active", 1, "inactive", 0, null)

Logic

Use when combining or negating boolean checks.

  • any(condition1, condition2, ...): Return true when at least one condition is truthy. Example: any(@company.revenue > 0, @company.ebitda > 0)

  • all(condition1, condition2, ...): Return true only when every condition is truthy. Example: all(@company.revenue > 0, @company.ebitda > 0)

  • not(condition): Invert a boolean expression. Example: not(@company.status == "active")

  • contains(value, "search_text"): Check whether a string or list value contains the provided text. Example: contains(@company.tags, "priority")

Math

Use for numeric transforms, rounding, ratios, powers, logs, and basic arithmetic helpers.

  • max(value1, value2, ...): Return the maximum value across explicit arguments. Example: max(@company.budget_revenue, @company.forecast_revenue)

  • min(value1, value2, ...): Return the minimum value across explicit arguments. Example: min(@company.base_rate, @company.floor_rate)

  • sum(value1, value2, ...): Add explicit expressions together. Example: sum(@company.revenue, @company.other_income)

  • average(value1, value2, ...): Compute the arithmetic mean across explicit arguments. Example: average(@company.gross_margin, @company.net_margin)

  • absolute(value): Return the absolute value of a numeric expression. Example: absolute(@company.variance)

  • scale(value, divisor): Divide a numeric expression by a scale factor for display or comparison. Example: scale(@company.revenue, 1000)

  • round(value, digits?): Round a numeric expression to a number of decimal digits. Example: round(@company.revenue / @company.headcount)

Null Handling

Use when values may be missing and the expression needs defaults or null-aware behavior.

  • coalesce(value1, value2, ..., fallback): Return the first non-null value from an ordered list of expressions. Example: coalesce(@company.adjusted_ebitda, @company.ebitda, 0)

Text

Use for string formatting, matching, extraction, and text cleanup.

  • concat(value1, value2, ...): Concatenate values as text without adding separators automatically. Example: concat("Total: ", @company.revenue)

Time Series

Use for rolling windows, lag/lead comparisons, cumulative values, and period-over-period trends.

  • latest(series): Return the most recent value in a time series. Example: latest(@company.revenue)

  • earliest(series): Return the earliest value in a time series. Example: earliest(@company.revenue)

  • effective_date(series): Return the effective date associated with a non-null series value. Example: effective_date(@company.revenue)

  • as_of_date(series): Return the as-of date associated with a versioned field value. Example: as_of_date(@company.revenue[asof=latest])

  • first(series, date?): Return the earliest value in the series, optionally anchored to a minimum date. Example: first(@company.revenue)

  • last(series, date?): Return the latest value in the series, optionally anchored to a maximum date. Example: last(@company.revenue)

  • previous_known(series, date?): Carry the latest known value forward to the current or explicit date anchor. Example: previous_known(@company.revenue)

  • next_known(series, date?): Return the earliest known value on or after the current or explicit date anchor. Example: next_known(@company.revenue)

  • exact(series, date): Return the value whose effective date exactly matches the provided date. Example: exact(@company.revenue, 2024-12-31)

  • next_period(series, periods_forward): Shift a time series forward by a number of periods. Example: next_period(@company.revenue, 1)

  • prior_period(series, periods_back): Shift a time series backward by a number of periods. Example: prior_period(@company.revenue, 12)

  • prior_period_delta(series, periods_back): Return the difference between the current value and a prior value. Example: prior_period_delta(@company.cash, 1)

  • prior_period_percent_change(series, periods_back): Return the percent change between the current value and a prior value. Example: prior_period_percent_change(@company.revenue, 12)

  • period_shift(series, date_anchor): Shift a series by a date, relative date, or date-valued property instead of a raw period count. Example: period_shift(@company.revenue[period=m], -1FY)

  • cumulative_sum(series, date_anchor): Return a cumulative sum beginning at a date, relative date, or date-valued property. Example: cumulative_sum(@company.revenue, 2021-12-31)

  • period_rolling_sum(series, date_anchor): Return a rolling sum over a date-aware window anchored by a date or relative date. Example: period_rolling_sum(@company.revenue[period=m], -12CM)

  • period_rolling_average(series, date_anchor): Return a rolling average over a date-aware window anchored by a date or relative date. Example: period_rolling_average(@company.revenue[period=m], -3CM)

  • period_shift_percent_change(series, date_anchor): Return the percent change versus a date-aware comparison point. Example: period_shift_percent_change(@company.revenue[period=m], -1FY)

  • quarter_to_date_sum(series, fiscal_month_end?): Return the cumulative sum within each quarter. Example: quarter_to_date_sum(@company.revenue)

  • year_to_date_sum(series, fiscal_month_end?): Return the cumulative sum within each year. Example: year_to_date_sum(@company.revenue)

  • rolling_sum(series, window_size): Return a rolling sum across the most recent N periods. Example: rolling_sum(@company.revenue, 12)

  • rolling_average(series, window_size): Return a rolling average across the most recent N periods. Example: rolling_average(@company.revenue, 3)

  • rolling_min(series, window_size): Return the minimum value seen in the rolling window. Example: rolling_min(@company.revenue, 6)

  • rolling_max(series, window_size): Return the maximum value seen in the rolling window. Example: rolling_max(@company.revenue, 6)

  • rolling_quantile(series, window_size, quantile): Return a rolling quantile across the most recent N periods. Example: rolling_quantile(@company.revenue, 4, 0.5)

Date Math

Use for deriving, shifting, comparing, or formatting dates.

  • current_date(): Return the wall-clock date at evaluation time. Example: current_date()

  • relative_to_date(): Return the evaluation-relative anchor date for the current context. Example: relative_to_date()

  • today(): Alias for current_date(). Example: today()

  • days_between(date1, date2): Return the number of calendar days from the first date to the second date. Example: days_between(@company.close-date, current_date())

  • months_between(date1, date2): Return the number of months from the first date to the second date. Example: months_between(@company.close-date, relative_to_date())

  • years_between(date1, date2): Return the number of years from the first date to the second date. Example: years_between(@company.close-date, relative_to_date())

  • year(date): Extract the calendar year from a date expression. Example: year(@company.close-date)

  • month(date): Extract the calendar month from a date expression. Example: month(@company.close-date)

  • quarter(date): Extract the calendar quarter from a date expression. Example: quarter(@company.close-date)

  • fiscal_year(date): Extract the fiscal year from a date expression using the cluster fiscal-year settings. Example: fiscal_year(@company.close-date)

  • fiscal_quarter(date): Extract the fiscal quarter from a date expression using the cluster fiscal-year settings. Example: fiscal_quarter(effective_date(@company.revenue))

  • months(count): A month duration computed from any numeric expression, for shifting dates. Example: effective_date(@company.cash) + months(@company.cash / @company.monthly-burn)

  • days(count): A day duration computed from any numeric expression, for shifting dates. Example: @company.close-date + days(@company.grace-period-days)

  • years(count): A year duration computed from any numeric expression, for shifting dates. Example: @company.close-date + years(@company.loan-term-years)

Aggregation

Use when collapsing many values into summary statistics.

  • aggregate_sum(series): Collapse a series to a single sum per entity or group. Example: aggregate_sum(@company.cashflows.distribution)

  • aggregate_average(series): Collapse a series to a single average per entity or group. Example: aggregate_average(@company.margin)

  • aggregate_max(series): Collapse a series to its maximum value. Example: aggregate_max(@company.revenue)

  • aggregate_min(series): Collapse a series to its minimum value. Example: aggregate_min(@company.cash_balance)

  • aggregate_mode(series): Collapse a series to its most frequent value. Example: aggregate_mode(@company.status)

  • aggregate_median(series): Collapse a series to its median value. Example: aggregate_median(@company.revenue)

  • aggregate_weighted_average(weight, value): Collapse a series to a weighted average using separate weight and value expressions. Example: aggregate_weighted_average(@company.position_weight, @company.return_pct)

Cashflow

Use for cashflow-specific calculations and schedule-oriented values.

  • combine(series1, series2, ...): Vertically combine multiple cashflow series into one series. Example: combine(@fund.cashflows.contribution, @fund.cashflows.distribution)

  • latest_nav(series, fallback?): Return the latest NAV per entity, ignoring the outer grouping context. Example: latest_nav(@fund.cashflows.nav)

  • xirr(cashflows, terminal_value?): Compute an internal rate of return from cashflow data and an optional terminal value. Example: xirr(combine(@fund.cashflows.contribution, @fund.cashflows.distribution), latest_nav(@fund.cashflows.nav))

  • cashflow_adjusted_nav(base_terminal_nav, adjustment_cashflows): Roll the latest NAV forward with selected cashflows since that NAV date. Example: cashflow_adjusted_nav(latest_nav(@fund.cashflows.nav), combine(@fund.cashflows.contribution, @fund.cashflows.distribution * -1))

Cross-Entity

Use when an expression needs to look across related entities or aggregate across an entity set.

  • lookup_related(expression, "agg_method"?, filter_expression?): Resolve data from related entities across the hierarchy, optionally aggregating it. Example: lookup_related(@security.nav, "sum")

  • lookup_all(expression, "agg_method"?, filter_expression?): Resolve data across all entities in the referenced namespace, optionally aggregating it. Example: lookup_all(@company.loan, "sum")


Common mistakes to avoid

1. when() is gone — use if()

# ✕ Removed when(@reporter.revenue > 0, true, false)  # ✓ Correct if(@reporter.revenue > 0, true, false)

2. Wrong time-period syntax

# ✕ Wrong @reporter.revenue::LTM  # ✓ Right @reporter.revenue[period=M::LTM]

3. Missing base period

# ✕ Wrong @reporter.revenue[period=LTM]  # ✓ Right @reporter.revenue[period=M::LTM]

4. Unnecessary coalesce

# ✕ Wrong (scenarios don't need fallbacks) coalesce(@reporter.revenue[scenario=budget])  # ✓ Right @reporter.revenue[scenario=budget]

⚠️ Heads-up on CW: CW, CH, and FH are relative-date suffixes, not period= values. There is no period=M::CW — the valid period types are listed in the reference above.


Tracing a number back 🆕

Any calculated value on a dashboard or grid can be traced to the source it came from. Click a cell to open the traceback panel, which routes you to the underlying source documents and shows how the number was assembled.

  • See the source document behind each figure, including for cashflows.

  • A "contains carried data" badge appears when a value was carried forward or backward rather than reported for that period.

  • Provenance is date-aware, so restated values and effective dates line up with the right column.


Need help?

Check your configuration

  • Homepage → Recently visited (for your global object)

  • Left sidebar → Metric groups (for metric names)

  • Check your configured scenarios

Build step by step

  • Start with a basic metric

  • Add time periods, then scenarios, then calculations

  • Break a complex formula apart — most functions are inspectable on their own

Contact support — email support@lumonic.com for metric configuration questions, scenario setup, custom calculations, or additional training.


Did this answer your question?