Skip to main content
Version: Current

ALM app: improving the back end

Here, we shall look into the back-end code and make some useful changes to increase the usability and range of the app.

Multi-directional trade calculations

If you have entered some trades within the default application, you may have noticed that the position calculation keeps a running total, but this does not consider the SIDE for the trade.

Let’s add some pro-code to get the position calculation to include the direction of the trade.

  1. Go to the file ALM\server\ALM-app\src\main\genesis\scripts\ALM-consolidator.kts.

The original consolidator script should look this (but it probably includes additional comments):

### This is  an example to show what Consolidators look like, no need to copy ###

consolidators {
consolidator("POSITION_CALC", FX_TRADE, POSITION) {
select {
POSITION {
sum { notional } into AMOUNT
}
}
groupBy {
Position.ByCurrency(sourceCurrency)
} into {
build {
Position {
currency = groupId.currency
amount = 0.0
settlementDate = DateTime.now()
}
}
}
}
}

This consolidator is consolidating FX_TRADE records into the POSITION table.

To do this, it is listens for all the changes in FX_TRADE records, then updates the POSITION table by summing the NOTIONAL field into the AMOUNT field.

As the groupBy block indicates, this consolidation uses only the source currency from theFX_TRADE table. As a result, we only have a single row inserted or updated in the POSITION table for each FX_TRADE.

If we want to have POSITION update/insert for source and target currency as a result of single trade, we need to have two consolidation blocks.

info

You can handle two consolidation operations with a single consolidator block simply by including two group by statements documented here.

However, we want different calculations for target and source currency.

So, we shall now use two consolidator blocks to implement our change.

  • As you can see below, the final implementation has POSITION_CALC_SOURCE and POSITION_CALC_TARGET. The first calc uses source currency from the trade in the group by block and second uses target currency.

  • There is a small change in the select block as well. Now the SIDE field from the FX_TRADE table is taken into account to decide the sign of amount. When buying, source is negative and vice versa. The RATE field from the FX_TRADE table is used to calculate the correct amount.

  • We can add further consolidators to also take into account LOAN_TRADE and CD_TRADE movements.

  1. Copy the entire code block below and replace the entire contents of the file ALM-consolidator.kts.
import global.genesis.gen.dao.enums.ALM.fx_trade.Side
import global.genesis.gen.dao.enums.ALM.fx_trade.TradeStatus

consolidators {
consolidator("POSITION_CALC_SOURCE", FX_TRADE, POSITION) {
select {
POSITION {
sum { notional / rate * ( if(side==Side.Buy) -1 else 1) } into AMOUNT
}
}
where {
tradeStatus != TradeStatus.Cancelled
}
groupBy {
Position.byCurrencySettlementDate(sourceCurrency, settlementDate)
} into {
build {
Position {
settlementDate = groupId.settlementDate
currency = groupId.currency
amount = 0.0
}
}
}
}

consolidator("POSITION_CALC_TARGET", FX_TRADE, POSITION) {
select {
POSITION {
sum { notional * ( if(side==Side.Sell) -1 else 1) } into AMOUNT
}
}
where {
tradeStatus != TradeStatus.Cancelled
}
groupBy {
Position.byCurrencySettlementDate(targetCurrency, settlementDate)
} into {
build {
Position {
settlementDate = groupId.settlementDate
currency = groupId.currency
amount = 0.0
}
}
}
}

consolidator("POSITION_LOAN", LOAN_TRADE, POSITION) {
select {
POSITION {
sum { paymentAmount } into AMOUNT
}
}
groupBy {
Position.byCurrencySettlementDate(paymentCurrency, paymentDate)
} into {
build {
Position {
settlementDate = groupId.settlementDate
currency = groupId.currency
amount = 0.0
}
}
}
}

consolidator("POSITION_CD", CD_TRADE, POSITION) {
select {
POSITION {
sum { maturityAmount } into AMOUNT
}
}
groupBy {
Position.byCurrencySettlementDate(depositCurrency, maturityDate)
} into {
build {
Position {
settlementDate = groupId.settlementDate
currency = groupId.currency
amount = 0.0
}
}
}
}

// TODO - add new consolidators here
}

Trade versioning

Genesis automatically audits data if you select Generate Audit Trail during Create. This ensures the platform monitors all events that happen on your base table, for example FX_TRADE, keeping a full audit history of your trade and logging the user details and/or events involved in the update.

However, there is often a requirement for a version to appear directly on the trade - this can be done simply with pro-code.

  1. Go to the file ALM/server/ALM-app/src/main/genesis/cfg/ALM-tables-dictionary.kts.

  2. Ensure the TRADE_VERSION field is available on your FX_TRADE table, and add a default value to it. Set the default value to 1 so every new trade entering the system starts as version 1.

field("TRADE_VERSION", LONG).notNull().default(1)

Next, let’s add the logic to the events.

  1. Go to the file ALM/server/ALM-app/src/main/genesis/scripts/ALM-eventhandler.kts.

  2. Go to the FX_TRADE_MODIFY section and add the following code to increment the version on modification:

details.tradeVersion = details.tradeVersion + 1
details.tradeStatus = TradeStatus.Amended
  1. To handle deletion, go to the FX_TRADE_DELETE section and add:
details.tradeVersion = details.tradeVersion + 1
details.tradeStatus = TradeStatus.Cancelled

This should ensure the trade version is incremented and the status updated on every modification or deletion event.

When the trade is deleted, we no longer want it to be removed from the database, just updated.

Go to the next line down in the FX_TRADE_DELETE section and change the entityDb.delete(details) to:

entityDb.modify(details)

Because we have changed the delete to a modify, these changes affect the whole object (not just the ID), so for this event, use the full object (<FxTrade>) in the event definition:

So our two events now look like this:

  eventHandler<FxTrade>("FX_TRADE_MODIFY", transactional = true) {
onCommit { event ->
val details = event.details
//Increment version number and set to amended
details.tradeVersion = details.tradeVersion + 1
details.tradeStatus = TradeStatus.Amended
entityDb.modify(details)
ack()
}
}
//Event object changed to <FxTrade> rather than <FxTrade.ById>
eventHandler<FxTrade>("FX_TRADE_DELETE", transactional = true) {
onCommit { event ->
val details = event.details
//Increment version number and set to cancelled
details.tradeVersion = details.tradeVersion + 1
details.tradeStatus = TradeStatus.Cancelled
//Call modify, rather than delete so it stays in blotter
entityDb.modify(details)
ack()
}
}
Checking your work

You can view a final version of the code for the ALM app, including all the modifications outlined in this guide, in the ALM app repository.