Assigning economic activity to general ledger accounts using posting rules

A posting module assigns journal entry lines generated by LUSID in reponse to economic activity in portfolios to particular general ledger accounts. See how to create a posting module.

A posting module contains posting rules in a specific order. A posting rule assigns debit or credit amounts for journal entry lines matching a set of criteria to a specific account. Syntax of a posting rule.

For each journal entry line, LUSID applies the first matching posting rule found in the posting module. Unmatched journal entry lines remain unassigned. The goal of a posting module is to create a waterfall of logic that ensures all economic activity is assigned.

Consider the following example, of a posting module with five rules:

{
  "values": [
    {
      "ruleId": "rule_01",
      "account": "3-Capital",
      "ruleFilter": "EconomicBucket startswith 'CA'"
    },
    {
      "ruleId": "rule_02",
      "account": "1-Investments",
      "ruleFilter": "HoldType eq 'P' and EconomicBucket startswith 'NA'"
    },
    {
      "ruleId": "rule_03",
      "account": "4-PnL",
      "ruleFilter": "EconomicBucket startswith 'PL'"
    },
    {
      "ruleId": "rule_04",
      "account": "2-Cash",
      "ruleFilter": "HoldType neq 'P' and EconomicBucket startswith 'NA'"
    },
    {
      "ruleId": "rule_05",
      "account": "Error",
      "ruleFilter": "True"
    }
  ]
}

For each generated journal entry line, LUSID automatically:

  1. Assigns the amount to the 3-Capital account if the economic bucket starts with CA.

  2. If not, assigns the amount to the 1-Investments account if the economic bucket starts with NA and the holding type code is P (position, signifying a non-currency holding).

  3. If not, assigns the amount to the 4-PnL account if the economic bucket starts with PL.

  4. If not, assigns the amount to the 2-Cash account if the economic bucket starts with NA and the holding type code is not equal to P (that is, is a currency holding).

  5. If not, assigns the amount to an Error account for manual remediation.

In this example, rule 5 is a 'catch all' rule that assigns unmatched journal entry lines to a general-purpose account. You could omit rule 5, in which case any journal entry lines failing to match rules 1, 2, 3 and 4 would remain unassigned.

Syntax of a posting rule

A posting rule consists of:

  • A ruleId that uniquely identifies the posting rule in the posting module.

  • An account that is the code of an account in the parent chart of accounts (CoA).

  • A ruleFilter expression that describes criteria for matching journal entry lines to the account.

The syntax of a ruleFilter expression is:

<attribute> <operator> <value>

...where:

<attribute> of a journal entry line

Data type

Example expression

Explanation/origin of <value>

SourceType

String

SourceType eq 'LusidTransaction'

Either LusidTransaction or LusidValuation. Use LusidValuation to map LUSID's automatic mark-to-market valuation activity to an account.

ActivityDate

DateTime

ActivityDate gt 2023-01-01

For a transaction, this is the trade date. For a valuation, this is the date of the latest detected change to market data.

EconomicBucket

String

EconomicBucket startswith 'PL_Real'

LUSID automatically categorises every journal entry line into a broad economic bucket. More information.

SourceId

String

SourceId eq 'Txn01'

For a transaction, this is the unique identifier in the portfolio (txnId).
For a valuation, this is the date and time of the latest change to market data (stored as a string, so only string operators are available).

TaxLotId

String

SourceId eq 'Txn01'

For a transaction, if the underlying instrument is a currency and the trade is settled (holding type of B), the value is 1 (stored as a string). Otherwise, this is the same as the SourceId.

LocalAmount

Decimal

LocalAmount lte 1000

The amount in the transaction currency.

BaseAmount

Decimal

BaseAmount gt 450000

The amount in the portfolio base currency.

DefaultCurrency

String

DefaultCurrency in 'GBP','USD'

The currency of the underlying instrument, if specified.

InstrumentScope

String

InstrumentScope startswith 'Custom'

The instrument scope in which the underlying instrument is mastered.

LusidInstrumentId

String

LusidInstrumentId not startswith 'CCY'

The LUID (globally unique ID) of the underlying instrument.

HoldType

String

HoldType eq 'P'

One of the LUSID holding type codes.

MovementName

String

MovementName eq 'Side1'

For a transaction, this is the name of the movement in the transaction type to which the transaction belongs. If the movement does not have a name (and note its name field is optional), then this is the name of the movement's side.

For a valuation, this is always MarkToMarket.

MovementSign

String

MovementSign eq 'Long'

Either Long or Short.

HoldingSign

String

HoldingSign eq 'Short'

Either Long or Short. You might use this to post amounts of a single transaction that moves from long to short to different accounts. For example, if you have a holding of £100 but a transaction for -£120 then the following journal entry lines are produced:

EconomicBucket

Amount

MovementSign

HoldingSign

NA_COST

-100

Short

Long

NA_COST

-20

Short

Short

A sub-holding key on the portfolio

User-defined

subholdingkeys[Transaction/Ibor/Strategy] eq 'Income'

Under-the-hood, a SHK is defined as a custom property with a 3-stage key in the Transaction domain.

A portfolio field

System-defined

Portfolio.displayName eq 'UK-Equities'

Portfolio.parentPortfolioId.Code eq 'FixedIncome'

This can be any of the stored fields for a portfolio except the href, links and version fields, prefixed by Portfolio. Nested fields can be accessed using dot notation.

A portfolio property

User-defined

properties[Portfolio
/Ibor/Manager] in 'John Smith','Jane Jones'

This can be any property with a 3-stage key in the Portfolio domain.

An instrument field (see also a transaction instrument field, below)

System-defined

Instrument.identifiers['LusidInstrumentId'] eq 'LUID_ZZZZZZZZ'

Instrument.instrumentDefinition.instrumentType in 'Equity','Bond'

This can be any of the stored fields for an instrument except the href, links and version fields, prefixed by Instrument. Nested fields can be accessed using dot notation.

An instrument property (see also a transaction instrument property, below)

User-defined

properties[Instrument/Ibor/AnalystRating] any (~ startswith 'A')

This can be any property with a 3-stage key in the Instrument domain.

A transaction field

System-defined

Transaction.totalConsideration.amount gt 2000

Transaction.instrumentScope eq 'ProtectedScope'

This can be any of the stored fields for a transaction, prefixed by Transaction. Nested fields can be accessed using dot notation.

A transaction property

User-defined

properties[Transaction/Ibor/Broker] eq 'AcmeCorp'

This can be any property with a 3-stage key in the Transaction domain.

An instrument field on a transaction

System-defined

Transaction.Instrument.instrumentDefinition.instrumentType in 'Equity','Bond'

If a journal entry line is generated in response to a currency movement, then the instrument of the line itself is a currency. The instrument of the original transaction, however, may be (for example) an equity. This gives you access to the instrument fields and properties of the equity.

The available fields and properties are the same as those for the instrument of the line itself, above.

An instrument property on a transaction

User-defined

Transaction.Properties[Instrument/Ibor/AnalystRating] any (~ startswith 'A')

An account field

System-defined

Account.type in 'Asset','Liabilities'

This can be any of the stored fields for an account, prefixed by Account. Nested fields can be accessed using dot notation.

An account property

User-defined

properties[Account/Type/AssetClass] eq 'Equity'

This can be any property with a 3-stage key in the Account domain.

An ABOR property

User-defined

properties[Abor/Client/Accountant] not in 'Fred Bloggs','Sarah Smart'

This can be any property with a 3-stage key in the Abor domain.

Note the following:

  • A ruleFilter expression is case-insensitive.

  • A string value must be enclosed in single straight quote marks (the %27 UTF-8 encoding).

  • You can concatenate expressions using the and and or operators, for example:
    MovementName eq 'Side1' and subholdingkeys[Transaction/Ibor/Strategy] eq 'Income'

    If you use both, standard boolean operator precedence applies:
    InstrumentScope startswith 'Custom' or (LocalAmount lte 1000 and HoldType eq 'P')