--- title: "Compliance rule template syntax" slug: "compliance-rule-syntax" updated: 2026-08-17T12:42:56Z published: 2026-08-17T12:42:56Z canonical: "support.lusid.com/compliance-rule-syntax" --- > ## Documentation Index > Fetch the complete documentation index at: https://support.lusid.com/llms.txt > Use this file to discover all available pages before exploring further. # Compliance rule template syntax This article describes how parameters are bound to steps in a custom compliance rule template, and the expression language you can use for filtering, grouping, and calculating values within a rule. If you’re using a standard system compliance template as-is, you won’t need this reference. For an introduction to compliance rules in general, see [What is compliance in LUSID?](/v1/docs/what-is-compliance-in-lusid) ## Mapping rule parameters to template steps A compliance template is made up of steps, and each step has a `label`. When you create a compliance rule from that template, you supply values for that step using a parameter key made up of the step’s label and the name of the setting you’re providing, joined with `.`: ```plaintext "{StepLabel}.{SettingName}" ``` For example, if your template has a step labelled `Compare`, you can target its settings with keys like `Compare.Numerator` and `Compare.UpperBound`. The settings available for a step depend on its type. The following sections cover the step types you can use, and the parameter keys and value types each one accepts. ### FilterStep Filters individual holdings or orders. Drops anything that does not meet the condition before running subsequent steps. | Parameter key | Value type | What it does | | --- | --- | --- | | `{StepLabel}.Predicate` | `FilterPredicateComplianceParameter` | A boolean condition using [LUSID filtering syntax](/v1/docs/filtering-information-retrieved-from-lusid) that is checked against each holding or order. Keeps `true` results, drops `false` results. | ### GroupByStep Splits holdings or orders into groups based on your chosen value (for example portfolio or asset class). Subsequent steps run separately for each group. | Parameter key | Value type | What it does | | --- | --- | --- | | `{StepLabel}.GroupingKey` | `GroupBySelectorComplianceParameter` | A value used to determine which group each holding or order belongs to | ### BranchStep Works like GroupByStep, but each resulting branch is evaluated independently rather than nested as a sub-group. | Parameter key | Value type | What it does | | --- | --- | --- | | `{StepLabel}.BranchingKey` | `GroupBySelectorComplianceParameter` | A value used to determine which branch each holding or order belongs to | ### GroupFilterStep Filters at the group level, dropping an entire group if it fails the condition (rather than individual holdings or orders). | Parameter key | Value type | What it does | | --- | --- | --- | | `{StepLabel}.Predicate` | `GroupFilterPredicateComplianceParameter` | A boolean condition using [LUSID filtering syntax](/v1/docs/filtering-information-retrieved-from-lusid) that is checked against each group. Keeps `true` results, drops `false` results. | ### CheckStep A general pass/fail check, applied to each group. | Parameter key | Value type | What it does | | --- | --- | --- | | `{StepLabel}.HardPredicate` | `GroupFilterPredicateComplianceParameter` | A boolean condition using [LUSID filtering syntax](/v1/docs/filtering-information-retrieved-from-lusid) that is checked against each group. `false` results set the rule result for that group to **Failed**. | | `{StepLabel}.SoftPredicate` | `GroupFilterPredicateComplianceParameter` | A boolean condition using [LUSID filtering syntax](/v1/docs/filtering-information-retrieved-from-lusid) that is checked against each group. If this result is `false` but the `HardPredicate` is `true`, the rule result for the group is **Warning**. | > [!NOTE] > Note > > Both parameter keys are required. A group must fail the `HardPredicate` to be a breach; failing `SoftPredicate` but passing `HardPredicate` produces a warning, not a failure. > > To disable warnings, set `SoftPredicate` to the same condition as `HardPredicate`. ### PercentCheckStep A dedicated step for percentage-based pass/fail checks. It calculates $\left(\frac{\text{Numerator}}{\text{Denominator}}\right) \times 100$ for each group and checks the result against your bounds. | Parameter key | Value type | What it does | | --- | --- | --- | | `{StepLabel}.Numerator` | `GroupCalculationComplianceParameter` | A [valuation metric](/v1/docs/which-metrics-can-i-include-in-my-valuation-report) used as the numerator for this group | | `{StepLabel}.Denominator` | `GroupCalculationComplianceParameter` | A [valuation metric](/v1/docs/which-metrics-can-i-include-in-my-valuation-report) used as the denominator for this group; typically a total from an earlier group | | `{StepLabel}.UpperBound` | `DecimalComplianceParameter` | The hard upper limit %; exceeding this produces a **Failed** result | | `{StepLabel}.LowerBound` | `DecimalComplianceParameter` | The hard lower limit %; falling below this produces a **Failed** result | | `{StepLabel}.UpperWarning` | `DecimalComplianceParameter` | The soft upper limit %; exceeding this, but not `UpperBound`, produces a **Warning** | | `{StepLabel}.LowerWarning` | `DecimalComplianceParameter` | The soft lower limit %; falling below this, but not `LowerBound`, produces a **Warning** | > [!NOTE] > Note > > If you want a simple "no more than X% of the portfolio" check, set the denominator to reference the portfolio-level group created by an earlier `GroupByStep`, rather than the group the `PercentCheckStep` itself is working with. ### RecombineStep Merges grouped holdings or orders back together. This step doesn’t require any parameters. ## Expression language The following parameter types all use [LUSID filtering syntax](/v1/docs/filtering-information-retrieved-from-lusid) for their expressions: - Filter (`FilterPredicateComplianceParameter`) - Group by (`GroupBySelectorComplianceParameter`) - Group filter (`GroupFilterPredicateComplianceParameter`) - Group calculation (`GroupCalculationComplianceParameter`) ### Fields you can group by For single holdings or orders, you can group by any of the following: | Field | Description | Accessors | | --- | --- | --- | | `PortfolioId` | The [portfolio](/v1/docs/portfolios) scope and code | `.Code`, `.Scope` | | `OrderId` | For pre-trade checks, specify the [order scope and code](/v1/docs/what-is-an-order-in-lusid) | `.Code`, `.Scope` | | `LusidInstrumentId` | The instrument’s [LUID](/v1/docs/what-is-a-lusid-instrument-id-or-luid) | N/A | | `HoldingType` | The [type of holding](/v1/docs/what-are-the-holding-types), for example `Position` or `Cash` | | `HoldingId` | The [holding’s unique identifier](/v1/docs/what-is-a-holding-id) | | `InstrumentType` | The LUSID [instrument type](/v1/docs/supported-instruments-in-lusid), for example `Equity` or `Bond` | | `Properties[...]` | A [property](/v1/docs/properties) value referenced by key, for example `Properties[Instrument/default/AssetClass]` | ### Fields you can filter on You can filter on any of the following fields: | Field | Filters | Description | Accessors | | --- | --- | --- | --- | | `PortfolioId` | Single holdings/orders | The [portfolio](/v1/docs/portfolios) scope and code | `.Code`, `.Scope` | | `OrderId` | Single holdings/orders | For pre-trade checks, specify the [order scope and code](/v1/docs/what-is-an-order-in-lusid) | `.Code`, `.Scope` | | `LusidInstrumentId` | Single holdings/orders | The instrument’s [LUID](/v1/docs/what-is-a-lusid-instrument-id-or-luid) | N/A | | `HoldingType` | Single holdings/orders | The [type of holding](/v1/docs/what-are-the-holding-types), for example `Position` or `Cash` | | `HoldingId` | Single holdings/orders | The [holding’s unique identifier](/v1/docs/what-is-a-holding-id) | | `InstrumentType` | Single holdings/orders | The LUSID [instrument type](/v1/docs/supported-instruments-in-lusid), for example `Equity` or `Bond` | | `SecurityCount` | Groups | The number of securities in the group | | `GroupKey` | Groups | The value that was used to create this group | | `Results[…]` | Both groups and single holdings/orders | A calculated value referenced by address key, for example `Results[Valuation/PV]` (single). At group level, this returns every value in the group, so aggregate the values, for example `Sum(...)`. | | `Properties[...]` | Both groups and single holdings/orders | A [property](/v1/docs/properties) value referenced by key, for example `Properties[Instrument/default/AssetClass]` | At group level, append `.Singular` to get the single shared value (if every holding or order in the group has the same one). | | `ReferenceLists[…]` | Both groups and single holdings/orders | Filter on membership in a [reference list](/v1/docs/using-reference-lists-in-lusid), for example `HoldingType in ReferenceLists[Scope/RestrictedTypes]` (single) or `GroupKey in ReferenceLists[Scope/ExcludedIssuers]` (group) | N/A | ### Referencing an earlier step’s result You can refer to a group created by an earlier step in the same rule using its label: ```plaintext PreviousStep[StepLabel] ``` This is most often used in a [PercentCheckStep](/v1/docs/compliance-rule-syntax#percentcheckstep), where the `denominator` needs to be a pre-filtering total, for example a portfolio's total value, calculated before a step filtered down to just international equities. All functions you can apply to groups can also be applied through `PreviousStep`, for example: ```plaintext Sum(PreviousStep[PortfolioIdGroup].Results[Valuation/PvInPortfolioCcy]) PreviousStep[PortfolioIdGroup].GroupKey ``` ### Aggregate functions | Function | Example | Result | | --- | --- | --- | | `Sum` | `Sum(Results[Valuation/PvInPortfolioCcy])` | The total of all values in the group | | `Average` | `Average(Results[Holding/Units])` | The average of all values in the group | | `Abs` | `Abs(Sum(Results[Valuation/PvInPortfolioCcy]))` | The absolute value of the result | ### Available operators See [Appendix A: Supported operators](/v1/docs/filtering-information-retrieved-from-lusid#appendix-a-supported-operators) for more information on each operator: - `eq` - `neq` - `gt` - `gte` - `lt` - `lte` - `in` - `not in` - `and` - `or` > [!NOTE] > Note > > String comparisons are case-insensitive. ## Example: Limiting exposure to international equities In this example, a compliance rule checks that international equities do not exceed 10% of a portfolio’s value. To do so, the rule uses a custom template with four steps: 1. `ExcludeCash` 2. `PortfolioIdGroup` 3. `InternationalEquityFilter` 4. `Compare` The example uses the LUSID Python SDK: ```python parameters = {    # Remove cash before working out the portfolio total    "ExcludeCash.Predicate": lm.FilterPredicateComplianceParameter(        value="Properties[Instrument/default/AssetClass] != 'Cash'",        compliance_parameter_type="FilterPredicateComplianceParameter"    ),    # Group by portfolio — this group's total becomes the denominator    "PortfolioIdGroup.GroupingKey": lm.GroupBySelectorComplianceParameter(        value="PortfolioId",        compliance_parameter_type="GroupBySelectorComplianceParameter"    ),    # Narrow down to international equities for the numerator    "InternationalEquityFilter.Predicate": lm.FilterPredicateComplianceParameter(        value="Properties[Instrument/default/AssetClass] == 'Equity'"              "and Properties[Instrument/default/DomicileCountry] != 'GB'",        compliance_parameter_type="FilterPredicateComplianceParameter"    ),    # Numerator: value of international equities remaining after the filter    "Compare.Numerator": lm.GroupCalculationComplianceParameter(        value="Sum(Results[Valuation/PvInPortfolioCcy])",        compliance_parameter_type="GroupCalculationComplianceParameter"    ),    # Denominator: total portfolio value, from before the filter was applied    "Compare.Denominator": lm.GroupCalculationComplianceParameter(        value="Sum(PreviousStep[PortfolioIdGroup].Results[Valuation/PvInPortfolioCcy])",        compliance_parameter_type="GroupCalculationComplianceParameter"    ),    "Compare.UpperBound": lm.DecimalComplianceParameter(        value=10.0, compliance_parameter_type="DecimalComplianceParameter"    ),    "Compare.LowerBound": lm.DecimalComplianceParameter(        value=-1.0, compliance_parameter_type="DecimalComplianceParameter"    ),    "Compare.UpperWarning": lm.DecimalComplianceParameter(        value=8.0, compliance_parameter_type="DecimalComplianceParameter"    ),    "Compare.LowerWarning": lm.DecimalComplianceParameter(        value=-1.0, compliance_parameter_type="DecimalComplianceParameter"    ), } ``` For an end-to-end tutorial, see [Performing daily post-trade compliance runs](/v1/docs/performing-daily-post-trade-compliance-runs). For more examples using the LUSID Python SDK, see the [GitHub repo](https://github.com/finbourne/finbourne-notebooks/blob/main/V3/product/compliance/Configuring%20and%20Running%20Pre%20and%20Post%20Trade%20Compliance.ipynb).