> ## Documentation Index
> Fetch the complete documentation index at: https://docs.activeviam.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing custom adjustment types

The Market Risk Application supports cube-level, fact-level, and roll-over adjustments for the Sensitivities, PnL, and VaR-ES cubes.

As an example of how to configure a new custom adjustment, this page explains how PNL adjustments have been defined within the `mr-application` module in the
`mr-application/src/main/java/com/activeviam/mr/application/signoff/adjustments/` directory.

<Note>
  PNL adjustments are scalar adjustments.
</Note>

## Configure Adjustment Execution

All adjustment processes are defined within the `AdjustmentExecutionConfig.java` class. Other library classes are mentioned where relevant.

### Fact-level adjustments

Fact-level adjustments are modifications of the underlying data held in the datastore. They operate directly on datastore rows, using `Execution` objects containing functional
components.

These functional components are defined in the `ExecutionFunctionalComponents.java` class.

#### The `Execution` object

<table><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td>appliesOnAdjustments</td><td>Determines whether or not the current adjustment applies for rows created by a previous adjustment.</td></tr><tr><td>inputParser</td><td>An <code>AdjustmentInputParser</code> that takes the request and definition DTOs and matches the required definition inputs to the input provided in the request. The values of the request inputs are parsed using the <code>IParser</code> objects matching the type found in the definition.</td></tr><tr><td>inputRetriever</td><td>An <code>AdjustmentInputRetriever</code> that takes a datastore row, the store format and a list of store fields and returns an object representing the value to be adjusted.</td></tr><tr><td>sourceTagger</td><td>A <code>List</code> of <code>AdjustmentStoreTagger</code> objects that take an execution ID and the initial values of the Source and Input type fields and return a map with the new values of those fields.</td></tr><tr><td>valueField</td><td>The datastore field that holds the values that will be adjusted by the execution.</td></tr><tr><td>expectedInputs</td><td>The <code>List</code> of expected inputs required by the execution.</td></tr><tr><td>inputConverter</td><td>An <code>AdjustmentInputConverter</code> that takes the result of the <code>inputParser</code> and the <code>expectedInputs</code> and generates the request inputs to be used in the adjustment steps.</td></tr><tr><td>steps</td><td>A list of <code>AdjustmentStep</code> objects that use the results of the <code>inputRetriever</code> and <code>inputConverter</code> to create the adjusted values that will be written to the datastore.</td></tr></tbody></table>

As PnL adjustments are scalar (i.e. a fact is a single value, as opposed to a vector), the scalar functions are used:

* `inputParser` is `parseInput()`
* `inputRetriever` is `scalarInputRetriever()` - given the base tuple, the store format and a list of fields, returns a double value held by the first field in the list
* `inputConverter` is `scalarInputConverter()` - given a map of values and the expected input, returns a single `double`-typed value

For the PnL datastore:

* `valueField` parameter is `StoreFieldNames.DAILY`
* `expectedInputs` parameter only contains `StoreFieldNames.DAILY`

#### Add On Execution

* `appliesOnAdjustments` is `false`
* `sourceTagger` contains:
  * `userInputSourceTagging()` - tags the row as direct user input
* `steps` contains:
  * `doubleInputReplacer()` - replaces the initial value with the request input value

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    private final Execution<Double, Double, Double> pnlAddOnExecution = new Execution<>(
            false,
            executionFunctionalComponents.parseInput(),
            executionFunctionalComponents.scalarInputRetriever(),
            List.of(executionFunctionalComponents.userInputSourceTagging()),
            StoreFieldNames.DAILY,
            List.of(StoreFieldNames.DAILY),
            executionFunctionalComponents.scalarInputConverter(),
            List.of(executionFunctionalComponents.doubleInputReplacer())
    );
```

#### Scaling Execution

* `appliesOnAdjustments` is `true`
* `sourceTagger` contains:
  * `inverseTagging()` - tags a row as the inversion of the initial row
  * `scaleTagging()` - tags the row as a scaling of the initial row
* `steps` contains:
  * `doubleInverter()` - the execution replaces the initial value with its inverse (initialValue \* -1.0)
  * `doubleScaler()` - the execution replaces the initial value with the scaled value (initialValue \* scalingFactor)

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    private final Execution<Double, Double, Double> pnlScalingExecution = new Execution<>(
            true,
            executionFunctionalComponents.parseInput(),
            executionFunctionalComponents.scalarInputRetriever(),
            List.of(executionFunctionalComponents.inverseTagging(), executionFunctionalComponents.scaleTagging()),
            StoreFieldNames.DAILY,
            List.of(StoreFieldNames.DAILY),
            executionFunctionalComponents.scalarInputConverter(),
            List.of(executionFunctionalComponents.doubleInverter(), executionFunctionalComponents.doubleScaler())
    );
```

#### Override Execution

* `appliesOnAdjustments` is `true`
* `sourceTagger` contains:
  * `inverseTagging()` - tags a row as the inversion of the initial row
  * `userInputSourceTagging()` - tags the row as direct user input
* `steps` contains:
  * `doubleInverter()` - the execution replaces the initial value with its inverse (initialValue \* -1.0)
  * `doubleInputReplacer()` - replaces the initial value with the request input value

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    private final Execution<Double, Double, Double> pnlOverrideExecution = new Execution<>(
            true,
            executionFunctionalComponents.parseInput(),
            executionFunctionalComponents.scalarInputRetriever(),
            List.of(executionFunctionalComponents.inverseTagging(), executionFunctionalComponents.userInputSourceTagging()),
            StoreFieldNames.DAILY,
            List.of(StoreFieldNames.DAILY),
            executionFunctionalComponents.scalarInputConverter(),
            List.of(executionFunctionalComponents.doubleInverter(), executionFunctionalComponents.doubleInputReplacer())
    );
```

### Cube-level adjustments

Cube-level adjustments rely on the creation of facts added to the base store (and on entries added to stores referenced by the base store if needed)
to define the location at which they are applied, and on entries in an isolated store to define the measures for which they are defined, along with the adjusted values.
Only *add-ons* are supported for cube-level adjustments. The add-ons are aggregated using dynamic aggregation on the adjustment source field of the base store
of the cube for which the adjustment is created.
The notion of location digest has been removed from the logic used for cube-level adjustments.

Cube-level adjustments work by submitting entries into:

1. the `CubeLevelAdjustments` store to specify:

* the ID of the entry, which corresponds to the value of the adjustment source field in the base store
* the measure for which to provide an add-on value
* the value used for the add-on
* the currency in which that add-on value is expressed

2. the base store (and if needed referenced stores)
   A fact is created in the base store of the cube for which a cube-level adjustment is created.
   The value of its adjustment source field corresponds to the id of the associated entry in the `CubeLevelAdjustments` store.
   If the cube-level adjustment is created for a level whose value comes form a store referenced by the base store, an entry is also created in the referenced store.

For instance, if the base store has the following entries:

<table><thead><tr><th>AsOfDate</th><th>TradeId</th><th>RiskFactor</th><th>Sensitivity Name</th><th>Sensitivity Value</th><th>Source</th></tr></thead><tbody><tr><td>06/01/2025</td><td>T001</td><td>RF1</td><td>Equity Delta</td><td>1000.0</td><td>Unadjusted</td></tr><tr><td>06/01/2025</td><td>T001</td><td>RF2</td><td>Equity Delta</td><td>2000.0</td><td>Unadjusted</td></tr></tbody></table>

and the user `John` wants to create an add-on cube level adjustment in cube `Cube1` of 3000.0 euros for the measure `measure1` at the trade id level for T001
for the task `Task1` , the following entry will be created in the base store:

<table><thead><tr><th>AsOfDate</th><th>TradeId</th><th>RiskFactor</th><th>Sensitivity Name</th><th>Sensitivity Value</th><th>Source</th></tr></thead><tbody><tr><td>06/01/2025</td><td>T001</td><td /><td /><td /><td>John\_Task1\_execution\_id\_001</td></tr></tbody></table>

and the following entry is added to the `CubeLevelAdjustments` store:

<table><thead><tr><th>Id</th><th>TaskId</th><th>AsOfDate</th><th>PivotId</th><th>Currency</th><th>Measure</th><th>Value</th></tr></thead><tbody><tr><td>John\_Task1\_execution\_id\_001</td><td>Task1</td><td>06/01/2025</td><td>Cube1</td><td>EUR</td><td>measure1</td><td>3000.0</td></tr></tbody></table>

If the base store references a `TradeAttributes` store with the foreign key defined by (AsOfDate, TradeId), and the base store has this content:

<table><thead><tr><th>AsOfDate</th><th>TradeId</th><th>RiskFactor</th><th>Sensitivity Name</th><th>Sensitivity Value</th><th>Source</th></tr></thead><tbody><tr><td>06/01/2025</td><td>T001</td><td>RF1</td><td>Equity Delta</td><td>1000.0</td><td>Unadjusted</td></tr><tr><td>06/01/2025</td><td>T001</td><td>RF2</td><td>Equity Delta</td><td>2000.0</td><td>Unadjusted</td></tr><tr><td>06/01/2025</td><td>T002</td><td>RF3</td><td>Equity Delta</td><td>5000.0</td><td>Unadjusted</td></tr><tr><td>06/01/2025</td><td>T003</td><td>RF4</td><td>Equity Delta</td><td>7000.0</td><td>Unadjusted</td></tr></tbody></table>

and the `TradeAttributes` store this content:

<table><thead><tr><th>AsOfDate</th><th>TradeId</th><th>Book</th></tr></thead><tbody><tr><td>06/01/2025</td><td>T001</td><td>BookA</td></tr><tr><td>06/01/2025</td><td>T002</td><td>BookA</td></tr><tr><td>06/01/2025</td><td>T003</td><td>BookB</td></tr></tbody></table>

and the user `John` wants to create an add-on cube level adjustment in cube `Cube1` of 8000.0 euros for the measure `measure1` at the book level for ‘BookA’
for the task `Task2` , the following entry will be created in the base store:

<table><thead><tr><th>AsOfDate</th><th>TradeId</th><th>RiskFactor</th><th>Sensitivity Name</th><th>Sensitivity Value</th><th>Source</th></tr></thead><tbody><tr><td>06/01/2025</td><td>John\_Task1\_execution\_id\_002</td><td /><td /><td /><td>John\_Task1\_execution\_id\_002</td></tr></tbody></table>

The following entry will be added to the `TradeAttributes` store:

<table><thead><tr><th>AsOfDate</th><th>TradeId</th><th>Book</th></tr></thead><tbody><tr><td>06/01/2025</td><td>John\_Task1\_execution\_id\_002</td><td>BookA</td></tr></tbody></table>

And the following entry will be added to the `CubeLevelAdjustments` store:

<table><thead><tr><th>Id</th><th>TaskId</th><th>AsOfDate</th><th>PivotId</th><th>Currency</th><th>Measure</th><th>Value</th></tr></thead><tbody><tr><td>John\_Task1\_execution\_id\_002</td><td>Task1</td><td>06/01/2025</td><td>Cube1</td><td>EUR</td><td>measure1</td><td>8000.0</td></tr></tbody></table>

The executor used for cube-level adjustments does not take any argument into account.

### Roll-over adjustments

Roll-over adjustments operate on the datastore, replacing the rows corresponding to the current as-of date with the approved rows from the input as-of date.

The executors use the `rollOver()` method with the following arguments:

<table><thead><tr><th>Argument</th><th>Description</th></tr></thead><tbody><tr><td>inverters</td><td>A <code>Map</code> of inverter <code>Function</code> objects for the datastore fields that should be inverted by the roll-over.</td></tr><tr><td>inverseTagging</td><td>Tagging <code>TriFunction</code> to generate the source and input tags for an inverted datastore row.</td></tr><tr><td>rollOverTagging</td><td>Tagging <code>TriFunction</code> to generate the source and input tags for a rolled-over row.</td></tr></tbody></table>

For the PnL roll-over adjustment, the `inverters` are as follows:

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    private final Map<String, AdjustmentConverterOperator> pnlInverters = Map.of(
            StoreFieldNames.DAILY, executionFunctionalComponents.inPlaceDoubleOrArrayInverter(),
            MONTHLY, executionFunctionalComponents.inPlaceDoubleOrArrayInverter(),
            YEARLY, executionFunctionalComponents.inPlaceDoubleOrArrayInverter(),
            LIFETIME, executionFunctionalComponents.inPlaceDoubleOrArrayInverter()
    );
```

The `inPlaceDoubleOrArrayInverter()` function returns `input * 1.0` for `double` inputs and `((IVector) input).scale(-1.0)` for `IVector` inputs.

### Include Defined Executions in Executors Map

Now that we have defined our adjustment executions we need to add them to our executors `Map` which will be accessed by the services defined in the Sign-off API library.

We want to include our executors in the bean with the qualifier `SP_QUALIFIER__EXECUTORS`
Note that our executors are included in both profiles.

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    @Qualifier(SP_QUALIFIER__EXECUTORS)
    public Map<String, AdjustmentExecutor> executors() {
        executionFunctionalComponentsConfig.getExecutionFunctionalComponents().setStatusService(statusService);
        Map<String, AdjustmentExecutor> executors = new HashMap<>();
		...
        executors.put(PNL_ADD_ON, execution(onBranch, pnlAddOnExecution));
        executors.put(PNL_OVERRIDE, execution(onBranch, pnlOverrideExecution));
        executors.put(PNL_SCALING, execution(onBranch, pnlScalingExecution));
        executors.put(PNL_ROLL_OVER, rollOver(
                pnlInverters,
                executionFunctionalComponents.inverseTagging(),
                executionFunctionalComponents.rollOverTagging()));
        executors.put(PNL_CUBE_LEVEL, cubeLevelAdjustment());
        ...
        return executors;
    }
```

## Define required dimensions for sign-off adjustments

Navigate to `AdjustmentPivotConfig.java`

We add SignOff Source Dimension to the appropriate schema in this case `PnlSchema`.

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    @Qualifier("aPnlDimension")
    @Order(75)
    public HierarchyBuilderConsumer signOffPnlDimensionAdder() {
        return AdjustmentPivotConfig::getSignOffSourceDimension;
    }
```

## Add source tagging fields

Navigate to `SignOffDatastoreCustomisations`

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    public static void loadCustomisations(IDatastoreConfigurator configurator) {
	    ...
        addTagging(configurator, PnLDatastoreDescriptionConfig.SCHEMA, StoreNames.PNL_STORE_NAME);
        addTagging(configurator, PnLDatastoreDescriptionConfig.SCALAR_SCHEMA, StoreNames.PNL_STORE_NAME);
        addTaggingAfterField(configurator, PnLFlatDatastoreDescriptionConfig.SCHEMA, StoreNames.PNL_BASE_STORE, StoreFieldNames.INSTRUMENT_SUB_TYPE);
        addTaggingAfterField(configurator, PnLFlatDatastoreDescriptionConfig.SCALAR_SCHEMA, StoreNames.PNL_BASE_STORE, StoreFieldNames.INSTRUMENT_SUB_TYPE);
        addTaggingAfterField(configurator, PnLAggregatedDatastoreDescriptionConfig.SCHEMA, StoreNames.PNL_BASE_STORE, StoreFieldNames.INSTRUMENT_SUB_TYPE);
        addTaggingAfterField(configurator, PnLAggregatedDatastoreDescriptionConfig.SCALAR_SCHEMA, StoreNames.PNL_BASE_STORE, StoreFieldNames.INSTRUMENT_SUB_TYPE);
        ...
    }
```

## Define supported Adjustment

Navigate to `SupportedAdjustmentsConfig`

Define a `SupportedAdjustmentDTO` bean.

### Add-on

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    public SupportedAdjustmentDTO pnlAddOn() {
        return new SupportedAdjustmentDTO(
                ADD_ON_NAME,
                PNL_ADD_ON,
                true,
                PnLCubeConfig.CUBE_NAME,
                Set.of(StoreNames.PNL_STORE_NAME),
                Set.of(
                        new TypedFieldDTO(AS_OF_LEVEL, StoreFieldNames.AS_OF_DATE, LOCAL_DATE),
                        new TypedFieldDTO(TRADE_LEVEL, StoreFieldNames.TRADE_ID, STRING),
                        new TypedFieldDTO(TYPE_LEVEL, StoreFieldNames.TYPE, STRING),
                        new TypedFieldDTO(RISK_FACTOR_LEVEL, StoreFieldNames.RISK_FACTOR, STRING),
                        new TypedFieldDTO(CURRENCY_LEVEL, StoreFieldNames.VALUE_CCY, STRING)
                ),
                Set.of(DTD_PNL_NATIVE),
                Set.of(new TypedFieldDTO(StoreFieldNames.DAILY, DOUBLE))
        );
    }
```

### Scaling

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    public SupportedAdjustmentDTO pnlScaling() {
        return new SupportedAdjustmentDTO(
                SCALING_NAME,
                PNL_SCALING,
                true,
                PnlCubeConfig.CUBE_NAME,
                Set.of(StoreNames.PNL_STORE_NAME),
                Set.of(
                        new TypedFieldDTO(AS_OF_LEVEL, StoreFieldNames.AS_OF_DATE, LOCAL_DATE),
                        new TypedFieldDTO(TRADE_LEVEL, StoreFieldNames.TRADE_ID, STRING),
                        new TypedFieldDTO(TYPE_LEVEL, StoreFieldNames.TYPE, STRING),
                        new TypedFieldDTO(RISK_FACTOR_LEVEL, StoreFieldNames.RISK_FACTOR, STRING),
                        new TypedFieldDTO(CURRENCY_LEVEL, StoreFieldNames.VALUE_CCY, STRING)
                ),
                Set.of(DTD_PNL_NATIVE),
                Set.of(new TypedFieldDTO(StoreFieldNames.DAILY, DOUBLE))
        );
    }
```

### Override

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    public SupportedAdjustmentDTO pnlOverride() {
        return new SupportedAdjustmentDTO(
                OVERRIDE_NAME,
                PNL_OVERRIDE,
                true,
                PnlCubeConfig.CUBE_NAME,
                Set.of(StoreNames.PNL_STORE_NAME),
                Set.of(
                        new TypedFieldDTO(AS_OF_LEVEL, StoreFieldNames.AS_OF_DATE, LOCAL_DATE),
                        new TypedFieldDTO(TRADE_LEVEL, StoreFieldNames.TRADE_ID, STRING),
                        new TypedFieldDTO(TYPE_LEVEL, StoreFieldNames.TYPE, STRING),
                        new TypedFieldDTO(RISK_FACTOR_LEVEL, StoreFieldNames.RISK_FACTOR, STRING),
                        new TypedFieldDTO(CURRENCY_LEVEL, StoreFieldNames.VALUE_CCY, STRING)
                ),
                Set.of(DTD_PNL_NATIVE),
                Set.of(new TypedFieldDTO(StoreFieldNames.DAILY, DOUBLE))
        );
    }
```

### Roll-over

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    public SupportedAdjustmentDTO pnlRollOver() {
        return new SupportedAdjustmentDTO(
                ROLL_OVER_NAME,
                PNL_ROLL_OVER,
                false,
                PnlCubeConfig.CUBE_NAME,
                Set.of(StoreNames.PNL_STORE_NAME),
                Set.of(
                        new TypedFieldDTO(AS_OF_LEVEL, StoreFieldNames.AS_OF_DATE, LOCAL_DATE),
                        new TypedFieldDTO(DESK_LEVEL, StoreFieldNames.DESK, STRING),
                        new TypedFieldDTO(BOOK_LEVEL, StoreFieldNames.BOOK, STRING, true),
                        new TypedFieldDTO(TRADE_LEVEL, StoreFieldNames.TRADE_ID, STRING, true),
                        new TypedFieldDTO(SIGNOFF_STATUS_LEVEL, ADJUSTMENT_LEVEL_STATUS, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_TASK_LEVEL, ADJUSTMENT_LEVEL_TASK, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_SOURCE_LEVEL, SOURCE_FIELD, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_INPUT_TYPE_LEVEL, INPUT_FIELD, LEVEL_PATH, true)
                ),
                null,
                Set.of(new TypedFieldDTO(StoreFieldNames.AS_OF_DATE, LOCAL_DATE))
        );
    }
```

### Cube-level

```Java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    @Bean
    public SupportedAdjustmentDTO pnlCubeLevelAdjustment() {
        return new SupportedAdjustmentDTO(
                CUBE_LEVEL_NAME,
                PNL_CUBE_LEVEL,
                false,
                PnlCubeConfig.CUBE_NAME,
                null,
                Set.of(
                        new TypedFieldDTO(AS_OF_LEVEL, StoreFieldNames.AS_OF_DATE, LEVEL_PATH),
                        new TypedFieldDTO(DESK_LEVEL, StoreFieldNames.DESK, LEVEL_PATH),
                        new TypedFieldDTO(BOOK_LEVEL, StoreFieldNames.BOOK, LEVEL_PATH, true),
                        new TypedFieldDTO(TRADE_LEVEL, StoreFieldNames.TRADE_ID, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_STATUS_LEVEL, ADJUSTMENT_LEVEL_STATUS, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_TASK_LEVEL, ADJUSTMENT_LEVEL_TASK, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_SOURCE_LEVEL, SOURCE_FIELD, LEVEL_PATH, true),
                        new TypedFieldDTO(SIGNOFF_INPUT_TYPE_LEVEL, INPUT_FIELD, LEVEL_PATH, true)
                ),
                null,
                Set.of( new TypedFieldDTO(CURRENCY, STRING),
                        new TypedFieldDTO(StoreFieldNames.SENSITIVITY_VALUES, DOUBLE))
        );
    }
```
