LexiFi's contract description language.

Nicolas Ojeda Bär

In this note we discuss how structured products are represented inside LexiFi’s technological stack. This representation is one of the cornerstones of LexiFi’s approach to structured products.

Structured products have great variability and scope, which makes it difficult to develop generic tools around them. Indeed, many approaches to structured products consist in developing each relevant functionality for specific products or families of products. This has the downside that these functionalities need to be rebuilt or extended in ad-hoc ways whenever a new structured product or variation is introduced into the system.

Instead, LexiFi has approached this problem by designing a domain-specific language to represent structured products. This language is able to provide a self-contained definition of arbitrary financial contracts (not limited to a fixed family of products), and in turn, serves as the fundamental building block on which the rest of our technology is built.

The design of such a language is subject to two opposite forces. On the one hand, the set of basic building blocks should be chosen so as to maximize the perimeter of contracts that can be encoded with them. On the other hand, the properties of these building blocks should be constrained as much as possible so as to remain amenable to efficient algorithmic analysis. Finding the right balance is a real challenge, and there have been many attempts to do so. LexiFi’s design, developed and refined for over 20 years, has been battle-tested in industrial settings and represents an industry-leading answer to this question.

A domain-specific language

LexiFi’s contract description language is a domain-specific language (DSL). A DSL is a “small” computer language specialized for a specific problem domain. Well-known examples are SQL, used to describe queries for relational databases, HTML, used to specify the content of web pages, the Excel formula language, etc.

Domain-specific languages have many advantages over general-purpose languages. The main one is that notions exposed in the language can be tailored to the specialization domain. Thus a given concept can often be expressed more concisely in a DSL than in a general-purpose language. Second, their limited expressive power means they are more amenable to algorithmic analysis, which in turn means they can serve as the basis of more powerful tools. Third, they are often highly composable. This means that their constituent pieces can be combined arbitrarily, in a Lego-like fashion, to build complex structures by putting together simpler ones.

We will see in what follows how each of these advantages plays out in LexiFi’s contract description language.

Disclaimer. The language discussed in this note is a simplified version of the real thing. LexiFi’s actual implementation has greater scope, and is backed up by an industrial-strength implementation able to handle large and complex contracts both individually as well as in portfolios. However, the central ideas remain the same.

The scope

What is the scope of LexiFi’s contract DSL? In other words, which contract characteristics are captured by it? The DSL will describe bilateral financial contracts, from the point of view of one of the parties (which we will call the long party). The counterparty will be called the short party. DSL terms will describe the rights and obligations of the two parties, in terms of cash flows, other kinds of deliveries, and options available to both parties.

Note that the parties just mentioned are abstract notions. A real contract would entail the identification of actual parties in the real world, but the mapping between abstract and real parties is outside the scope of the DSL.

Similarly, underlyings are represented by abstract names. The mapping of abstract underlyings to actual underlyings is outside the scope of the DSL and is the responsibility of the DSL user.

Finally, the behaviour captured by the DSL correspond to the “nominal” behaviour of the contract. Extraordinary events, such as either party failing to meet their obligations, unplanned corporate events, termination of payment currencies, etc. are not captured by the DSL.

Some of the reasons for choosing this particular scope for the DSL are discussed at length in a previous post Challenges for automating product lifecycle event management, which we encourage reading if you want to learn more about this topic.

Expressions and contracts

The terms of the contract description language can be organized in two categories: expressions and contracts. Expressions are terms that represent values such as numbers or boolean conditions. As mentioned above, contracts represent bilateral financial contracts, from the point of view of the “long party”.

The expressions of the language are as follows.

Constants. These consist of literals of different types: numbers, dates, booleans.

Examples. 42, 123.0, 42.7, 2022-01-15, true, false.

Market observations. Given an underlying S and an expression T, the observation of S at T is written S(T), and it stands for the value of S on the date T. The expression T should be an expression representing a date.

Examples. SP_500(2021-03-01), EURO_STOXX_50(2022-01-03), etc.

Operators. Expressions can be combined using the usual arithmetic and comparison operators. More precisely, given two expressions e1 and e2 and an operator op (eg +, -, *, /, <=, etc), we can form e1 op e2.

Examples. The condition that the performance of the underlying SP_500 be positive in the period (2021-01-01, 2022-01-01) can be written as SP_500(2022-01-01) / SP_500(2021-01-01) > 1.

Next we have the terms that correspond to contracts.

Cash flows. Given an expression representing a date T (the payment date), a currency C, and an expression representing a number e (the payment amount), the term cash_flow(T, C, e) represents the contract where the short party must pay the long party a single cash flow on date T in the currency C and amount e.

Examples. cash_flow(2021-12-12, EUR, 47) is the contract the short party pays a single cash flow of EUR 47 on 2021-12-12 to the long party. We interpret a cash flow with a positive payment amount to be a payment to the long party. To express payment to the short party, we use a negative amount: cash_flow(2021-12-12, USD, -15) represents a payment of USD 15 to the short party.

The payment amount is an expression and so can depend on market observations, as in the following example where the payment amount depends on the performance of an underlying.

cash_flow(2021-11-10, EUR, 1000 * SP_500(2021-11-10) / SP_500(2020-11-10))

Bundling. If c1, …, cN are contracts, then we can “bundle” them together in a single contract combining all its cash flows: c1, c2, ..., cN (we will write such sequences vertically in what follows for ease of reading). Note: the order of the contracts in the sequence is immaterial since every reordering represents the same set of cash flows.

Example. A simple bond consisting of two 10% coupons on a principal of EUR 1000 could be described by the following contract:

cash_flow(2020-11-10, EUR, 100)
cash_flow(2021-11-10, EUR, 100)
cash_flow(2021-11-10, EUR, 1000)

The last construction of the language is in some respect the most important one as it is the one that gives the language most of its expressive power.

Conditionals. Given an expression e as before and two contracts c1 and c2 we can form the conditional if e then c1 else c2. This is a contract that coincides with c1 or c2 depending on whether the condition e holds (as e is an expression, whether e holds or not may depend on market observations).

Examples. A conditional cash flow can be written as follows.

if SP_500(2021-11-10) <= SP_500(2020-11-10) then
  cash_flow(2021-11-10, EUR, 1000)

Summary. The contract description language consists of the following elements:

  • constants, market observations, and operators;
  • cash flows, sequencing, and conditionals.

Note that the real version of the language also supports barriers, options, credit events, etc. necessary to represent most derivative contracts present in the market.

Well-formedness of contracts. Using the above language one can write terms which are nonsensical, for example 1 + 2022-01-34, or cash_flow(true, EUR, 2022-10-12). In general we will always assume that every contract term under consideration is well-formed, that is, that all operations are applied to arguments for which they make sense.

Let us see now how it is possible to combine these few elements to encode several common structured products.

Bond

This contract pays two coupons, one every six months during the life of the product, and returns the EUR 1000 principal at expiry. The coupon rate is the Euribor 3-month rate.

cash_flow(2021-05-10, EUR, 1000 * EURIBOR_3M(2020-11-10))
cash_flow(2021-11-10, EUR, 1000 * EURIBOR_3M(2021-05-10))
cash_flow(2021-11-10, EUR, 1000)

Reverse Convertible

This contract pays an unconditional 10% coupon on a principal of EUR 1000, and returns the principal at the end of the observation period, exposing the investor to any downside performance of the underlying.

cash_flow(2021-11-10, EUR, 100)
if SP_500(2020-11-10) <= SP_500(2021-11-10) then
  cash_flow(2021-11-10, EUR, 1000)
else
  cash_flow(2021-11-10, EUR, 1000 * SP_500(2021-11-10) / SP_500(2020-11-10))

Capital Protected

This contract offers capital protection in addition to participation in the performance of the underlying.

At the end of the holding period, the capital protection (95% of the nominal) is paid out together with a cash payment proportional to the difference between the performance of the underlying and the strike (5%).

if SP_500(2020-11-10) <= SP_500(2021-11-10) then
  cash_flow(2021-11-10, EUR, 1000 * (SP_500(2021-11-10) / SP_500(2020-11-10) - 0.05))
else
  cash_flow(2021-11-10, EUR, 1000 * 0.95)

Bonus

This contract allows the investor to participate in the performance of the underlying, while at the same time benefitting from a downside protection as long as a barrier condition is not crossed.

The barrier is triggered when the underlying loses more than 30% of its value over the observation period. If that happens, the investor receives a cash amount that reflects the performance of the underlying. Otherwise, the investor participates in the positive performance of the underlying, receiving a minimum redemption (the bonus) of 20%.

if SP_500(2021-11-10) <= 0.7 * SP_500(2020-11-10) then
  cash_flow(2021-11-10, EUR, 1000 * SP_500(2021-11-10) / SP_500(2020-11-10))
else if SP_500(2021-11-10) <= 1.2 * SP_500(2020-11-10) then
  cash_flow(2021-11-10, EUR, 1000 * 1.2)
else
  cash_flow(2021-11-10, EUR, 1000 * SP_500(2021-11-10) / SP_500(2020-11-10))

Autocall Athena

This contract pays a periodic coupon with the possibility of early redemption if an autocall condition is triggered. Moreover, the investor is exposed to the downside performance of the underlying in some conditions.

This contract pays three 10% coupons on a principal of EUR 1000. The contract is redeemed early if the performance of the underlying SP_500 enters positive territory (autocall condition), and this condition is fixed once yearly on 10/11. Moreover, the contract has a memory effect (the coupon payments are delayed until expiration). Lastly, the contract exposes the investor to the downside performance of the underlying if the contract is not redeemed early and the underlying loses more than 40% of its value during the life of the contract.

if SP_500(2020-11-10) <= SP_500(2021-11-10) then
  cash_flow(2021-11-10, EUR, 100)
  cash_flow(2021-11-10, EUR, 1000)
else if SP_500(2020-11-10) <= SP_500(2022-11-10) then
  cash_flow(2022-11-10, EUR, 200)
  cash_flow(2022-11-10, EUR, 1000)
else if SP_500(2020-11-10) <= SP_500(2023-11-10) then
  cash_flow(2023-11-10, EUR, 300)
  cash_flow(2023-11-10, EUR, 1000)
else if SP_500(2020-11-10) <= SP_500(2024-11-11) then
  cash_flow(2024-11-11, EUR, 400)
  cash_flow(2024-11-11, EUR, 1000)
else if SP_500(2024-11-11) <= 0.6 * SP_500(2020-11-10) then
  cash_flow(2024-11-11, EUR, 1000 * SP_500(2024-11-11) / SP_500(2020-11-10))
else
  cash_flow(2024-11-11, EUR, 1000)

What is all this good for?

We have seen that it is possible to encode common structured products using LexiFi’s contract description language. As nice as that may be, it begs the question: what do we gain, concretely, from using this language to describe structured products?

The answer is this: this description allows building simple, yet powerful solutions to many important challenges that arise in the management of structured products.

A communication medium

Even though the contract description language may look similar to a programming language, it is not one (and contract terms are not executable entities). Rather, it is a data description language and as such, its first purpose is to be analyzed, transformed, stored and communicated (not unlike an XML or JSON document). The language is self-describing: its meaning does not depend on external entities (modulo the interpretation of abstract notions such as underlyings, as discussed above). It fully encapsulates the semantics of the contract payoff. This makes it an ideal communication medium between financial actors, particularly well-suited for treatment by computers.

flowchart LR
  client1[CLIENT] <--> dsl1((DSL CONTRACT)) <--> client2[CLIENT]

Life-cycle management

Life-cycle management is the most basic operation that can be performed on a contract. The goal is to provide an accurate picture of how the contract will evolve in response to outside events. Common examples of such events are:

  • applying fixings;
  • extracting cash flows;
  • applying option decisions, credit events, etc.

In our language-based approach, life-cycle management takes a conceptually simple form. Life-cycle operations (such as providing a fixing) are applied to a contract description and produce a sequence of cash flows, together with a virtual “residual” contract representing the remaining rights and obligations.

flowchart LR
  payoff[DSL CONTRACT] --> engine((MANAGEMENT ENGINE)) --> residual[VIRTUAL RESIDUAL DSL CONTRACT] & flows[CASH FLOWS]
  residual -.-> engine

Let us see how this plays out in an example. Consider the contract that pays back a principal of EUR 1000 except if the underlying SP_500 has lost more than 40% of its value in the observation period, in which case, the amount paid back reflects the performance of the underlying.

if SP_500(2021-11-10) <= 0.6 * SP_500(2020-11-10) then
  cash_flow(2021-11-10, EUR, 1000 * SP_500(2021-11-10) / SP_500(2020-11-10))
else
  cash_flow(2021-11-10, EUR, 1000)

The payoff depends on two fixings: SP_500(2020-11-10) and SP_500(2021-11-10).

First, the contract is managed by providing the initial fixing, say SP_500(2020-11-10) = 150. The management engine returns a residual term obtained by simplifying the original term taking into account the “new” knowledge provided by the initial fixing.

if SP_500(2021-11-10) <= 90 then
  cash_flow(2021-11-10, EUR, 6.67 * SP_500(2021-11-10))
else
  cash_flow(2021-11-10, EUR, 1000)

No cash flows are returned because there isn’t enough information to decide which one of the two cash flows will be selected, as this depends on the other fixing SP_500(2021-11-10).

Next, the second fixing is managed by providing its value, say SP_500(2021-11-10) = 80, which lands us in the first branch of the “if”. The management engine simplifies the term and returns as residual payoff the empty term, together with a single cash flow cash_flow(2021-11-10, EUR, 533.6). The residual payoff is empty as the contract has been fully managed and is now expired.

This example illustrates a couple of important aspects of our approach to life-cycle management:

  • Life-cycle management is generic, defined purely in terms of the semantics of the contract description.
  • Symbolic analysis of the contract term yields valuable insight into its nature.

Symbolic analysis of the contract term plays an important role in LexiFi’s software. It is used in particular to build the schedule of the contract. This is a “picture” of its possible future evolution. It contains, in particular, all future cash flows and fixings, and classifies them as being definitive (will take place for sure), potential (may take place depending on specific market observations), optional (depending on options), etc.

Pricing

Being able to describe payoffs, and having tools to manage their life cycle is well and good, but one would equally like to be able to calculate the fair prices of such contracts (for given pricing models) or other quantitative analytics.

Here again our language-based approach turns out to be remarkably powerful and effective. In this approach, pricing is a two-step process. First, the payoff description is fed into a compiler that transforms it into an executable program. This program is a representation of the payoff optimized for fast execution. During compilation, semantic-preserving transformations are applied to the contract description in order to produce highly-optimized code.

In order to run the resulting pricing code, the system must provide the market data on which the payoff depends. Providing this information is the role of the chosen pricing model and market data selection, which act as parameters of the pricing code. Once these are specified, the pricing code can be executed, producing a price.

Schematically, then, the general architecture can be described by the following diagram:

flowchart LR
  payoff[DSL CONTRACT] --> compil((COMPILATION)) --- code(EXECUTABLE PRICING CODE) --- exec((EXECUTION)) --> price{PRICE}
  model(PRICING MODEL AND MARKET DATA) -.-> compil & exec

This approach has several advantages.

  • The payoff description is independent of the pricing infrastructure; only the payoff semantics are concerned. The pricing engine, on the other hand, is concerned with implementing the given semantics in relation to a specific pricing model and market data selection.
  • The pricing engine is generic. It works on an arbitrary payoff description; it is not defined in terms of specific families of products.
  • The pricing model can intervene in the compilation process itself in order to produce more adapted pricing code on a model-specific basis. For example, certain models allow the use of “closed-form” solutions for pricing, producing more efficient pricing code.

Booking products (or: how to read a term sheet)

We have seen that encoding a contract in LexiFi’s contract description language enables powerful tools and techniques to work on it. Given this, it is desirable to have an easy way to “book” products into LexiFi’s system. At LexiFi we have developed powerful helper tools to simplify this task.

Today, contracts are communicated in multiple forms. Some of the most common ones are PDF term sheets. Such terms sheets can be produced manually or programmatically, and contain a description of the payoff meant to be read by human beings.

Other common alternatives are the various XML/JSON/etc. formats sponsored by banks and other financial institutions. We emphasize that these formats are not, contrary to LexiFi’s contract description language, self-contained description of the semantics of the payoff. Rather these formats are a computer-readable translation of the term sheet. Given that the term sheet themselves often require interpretation and may not be completely self-contained, these formats often have most of the same shortcomings as PDF term sheets.

In any case, at LexiFi we have developed software tools to aid the importing of contracts described in these existing formats into LexiFi’s language. A PDF importer tool is able to automatically book contracts by parsing the PDF term sheet from various common emitters (and coverage is being improved continuously). Automatic parsers of common XML and JSON formats are also available.

flowchart TB
  pdf(PDF TERM SHEET) & manual(MANUAL INPUT) & xml(XML FORMAT) & json(JSON FORMAT) --> lexifi((LEXIFI)) --> payoff[DSL CONTRACT]

For the case of manual input, LexiFi has developed user-friendly “entry screens” allowing for easy and quick entry of contract parameters (similarly to how they are stated in PDF term sheets). The system then builds the contract term representing the payoff specified by these parameters. The component implementing the mapping between parameters and contract terms is called an instrument. LexiFi’s applications already include many instruments covering a large perimeter of products, and new ones or extensions of existing ones can be developed quickly, as needed.

Conclusion

We have presented LexiFi’s contract description language. This language consists of a small collection of basic building blocks for structured contracts. We showed that it is possible to encode several common structured products in it. The building blocks of the language, as presented in this post, are:

  • constants, market observations and operators;
  • cash flows, bundling of contracts, and conditionals.

However, the real version of the language as it exists inside LexiFi also supports other important features necessary to represent most financial derivatives currently in the market:

  • barriers (including continuous ones),
  • options (european, american),
  • credit events,
  • etc

A key aspect of the language is its composability: the building blocks can be combined with each other without restriction, building arbitrarily complex contracts out of simple pieces.

Thinking of structured products as being built out of a small collection of basic elements is a powerful idea. This idea, refined over 20 years of industrial experience, enables an approach where functionalities such as pricing, life cycle management, reporting, etc, are developed generically for all products, instead of on a product-by-product basis.

Stay tuned, and thank you for reading!