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.
- 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.
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
andPOSITION_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 theFX_TRADE
table is taken into account to decide the sign of amount. When buying, source is negative and vice versa. TheRATE
field from theFX_TRADE
table is used to calculate the correct amount. -
We can add further consolidators to also take into account
LOAN_TRADE
andCD_TRADE
movements.
- 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.
-
Go to the file ALM/server/ALM-app/src/main/genesis/cfg/ALM-tables-dictionary.kts.
-
Ensure the
TRADE_VERSION
field is available on yourFX_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.
-
Go to the file ALM/server/ALM-app/src/main/genesis/scripts/ALM-eventhandler.kts.
-
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
- 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()
}
}
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.