Regulatory Capital

Compute ISDA SIMM Initial Margin

TeaVM Java builds the scoped CRIF from the editable non-cleared book, adds an actual adjoint and curvature repricing for the first equity trade, applies the demonstration SIMM risk weights and concentration factors, then rolls margins into four product classes.

Non-cleared book
Trade IDCounterpartyProduct classNotionalSide

The first equity-class trade (or first trade if none exists) gets the real option repricing; product class determines the synthetic risk classes.

Simulation (the real repricing)Optional
Java source
IsdaSimmRiskStudio.java

This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.

import com.nablatensor.engine.Nabla;
import com.nablatensor.quant.EquityMarket;
import com.nablatensor.quant.MonteCarlo;
import com.nablatensor.quant.Products;
import com.nablatensor.risk.NestedAggregation;
import com.nablatensor.risk.RiskClassEnum;
import com.nablatensor.risk.RiskFactor;
import com.nablatensor.risk.RiskMeasureEnum;
import com.nablatensor.risk.Sensitivities;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public final class IsdaSimmRiskStudio {
  static final String[][] B = {
    {
      "USD", "EUR"
    }, {
      "1", "4"
    }, {
      "1", "2"
    }, {
      "1", "4"
    }, {
      "1", "5"
    }, {
      "USD", "EUR"
    }
  };
  static final double[] DRW = {
    .011, .008, .28, .22, .19, .075
  }, VRW = {
    .18, .64, .64, .28, .42, .3
  }, RHO = {
    .63, .42, .27, .16, .31, .5
  }, GAMMA = {
    .24, .21, .35, .15, .23, .5
  }, DT = {
    330, 12, 3, 8, 260, 2400
  }, VT = {
    130, 320, 85, 210, 74, 480
  };
  static final RiskClassEnum[] RC = {
    RiskClassEnum.GIRR, RiskClassEnum.CSR_NON_SEC, RiskClassEnum.CSR_SEC, RiskClassEnum.EQUITY,
        RiskClassEnum.COMMODITY, RiskClassEnum.FX
  };
  static final RiskMeasureEnum[] ME = {
    RiskMeasureEnum.DELTA, RiskMeasureEnum.VEGA, RiskMeasureEnum.CURVATURE
  };
  public static void main(String[] z) {
    String[] id = {
      "IRS-USD-10Y", "XCCY-EURUSD-5Y", "CDS-IG-CPTY-5Y", "CDX-HY-INDEX-5Y", "EQ-CALL-SX5E-1Y", "EQ-PUT-SPX-1Y",
          "COMDTY-WTI-SWAP-2Y"
    }, cp = {
      "CPTY-ALPHA", "CPTY-ALPHA", "CPTY-BRAVO", "CPTY-BRAVO", "CPTY-CHARLIE", "CPTY-CHARLIE", "CPTY-DELTA"
    }, pc = {
      "RATES_FX", "RATES_FX", "CREDIT", "CREDIT", "EQUITY", "EQUITY", "COMMODITY"
    };
    double[] notionals = {
      120000000, 80000000, 50000000, 25000000, 15000000, 10000000, 30000000
    };
    int[] side = {
      1, -1, 1, -1, -1, 1, 1
    };
    long paths = 200000L, seed = 42L;
    Set<String> counterparties = new HashSet<>();
    for (String c : cp) counterparties.add(c);
    System.out.println("BOOK|" + id.length + "|" + counterparties.size());
    int heavy = 0;
    for (int t = 0; t < pc.length; t++) if (pc[t].equals("EQUITY")) {
      heavy = t;
      break;
    }
    double scale = side[heavy] * notionals[heavy] / 100.0 / 1.0e6;
    EquityMarket market = EquityMarket.of()
        .spot(100)
        .strike(100)
        .vol(.2)
        .rate(.03)
        .maturity(1)
        .build();
    double hd, hv, hir, hc;
    try (MonteCarlo<EquityMarket> g = MonteCarlo.of(Products.europeanCall())
        .market(market)
        .steps(1)
        .fp64()
        .greeks()
        .on("cpu")
        .build(); MonteCarlo<EquityMarket> p = MonteCarlo.of(Products.europeanCall())
        .market(market)
        .steps(1)
        .fp64()
        .priceOnly()
        .on("cpu")
        .build()) {
      Nabla.TypedValuation<EquityMarket> adj = g.run(paths, seed), base = p.run(paths, seed), up = p.run(EquityMarket.of()
          .spot(130)
          .strike(100)
          .vol(.2)
          .rate(.03)
          .maturity(1)
          .build(), paths, seed), down = p.run(EquityMarket.of()
          .spot(70)
          .strike(100)
          .vol(.2)
          .rate(.03)
          .maturity(1)
          .build(), paths, seed);
      hd = side[heavy] * Math.abs(notionals[heavy]) / 100.0 / 1e6 * adj.greek(EquityMarket::spot);
      hv = side[heavy] * Math.abs(notionals[heavy]) / 100.0 / 1e6 * adj.greek(EquityMarket::vol) * .01;
      hir = side[heavy] * Math.abs(notionals[heavy]) / 100.0 / 1e6 * adj.greek(EquityMarket::rate) * .01;
      double pb = side[heavy] * Math.abs(notionals[heavy]) / 100.0 / 1e6 * base.price(), pu = side[heavy] * Math.abs(notionals[heavy]) / 100.0 / 1e6 * up.price(),
          pd = side[heavy] * Math.abs(notionals[heavy]) / 100.0 / 1e6 * down.price();
      hc = -Math.min(pu - pb - 30 * hd, pd - pb + 30 * hd);
    }
    Sensitivities.Builder builder = Sensitivities.builder();
    for (int t = 0; t < id.length; t++) {
      double s = side[t] * notionals[t] / 1e6;
      int fi = 0;
      for (int c = 0; c < RC.length; c++) if (applies(pc[t], c)) for (String bucket : B[c]) {
        double alt = ((fi++ + t) % 2 == 0) ? 1 : -.6;
        String name = id[t] + "#" + bucket;
        builder.add(factor(RC[c], RiskMeasureEnum.DELTA, bucket, name, 0), s * alt * .02);
        builder.add(factor(RC[c], RiskMeasureEnum.VEGA, bucket, name, 1), Math.abs(s) * .004 * (1
            + .1 * t));
        builder.add(factor(RC[c], RiskMeasureEnum.CURVATURE, bucket, name, 0), Math.abs(s) * alt * .0015);
      }
    }
    builder.add(RiskFactor.equityDelta("4", id[heavy]), hd);
    builder.add(factor(RiskClassEnum.EQUITY, RiskMeasureEnum.VEGA, "4", id[heavy], 1), hv);
    builder.add(RiskFactor.equityDelta("4", id[heavy])
        .asCurvature(), hc);
    builder.add(RiskFactor.girrDelta("USD", 10.0), hir);
    Sensitivities crif = builder.build();
    double[][] result = new double[6][3];
    for (int c = 0; c < 6; c++) for (int m = 0; m < 3; m++) result[c][m] = margin(crif, c, ME[m]);
    for (int c = 0; c < 6; c++) System.out.println("CLASS|" + RC[c] + "|" + result[c][0] + "|" + result[c][1]
        + "|" + result[c][2]);
    String[] products = {
      "RATES_FX", "CREDIT", "EQUITY", "COMMODITY"
    };
    int[][] classes = {
      {
        0, 5
      }, {
        1, 2
      }, {
        3
      }, {
        4
      }
    };
    double total = 0;
    for (int p = 0; p < 4; p++) {
      double[] sums = new double[3];
      for (int m = 0; m < 3; m++) {
        for (int x : classes[p]) {
          sums[m] += result[x][m] * result[x][m];
          for (int y : classes[p]) if (x != y) sums[m] += psi(x, y) * result[x][m] * result[y][m];
        }
        sums[m] = Math.sqrt(Math.max(0, sums[m]));
      }
      double pt = sums[0] + sums[1] + sums[2];
      total += pt;
      System.out.println("PRODUCT|" + products[p] + "|" + sums[0] + "|" + sums[1] + "|" + sums[2]
          + "|" + pt);
    }
    System.out.println("TOTAL|" + total);
  }
  static boolean applies(String p, int c) {
    return p.equals("RATES_FX") ? (c == 0 || c == 5) : p.equals("CREDIT") ? (c == 1 || c == 2) : p.equals("EQUITY") ? c == 3 : c == 4;
  }
  static RiskFactor factor(RiskClassEnum c, RiskMeasureEnum m, String b, String n, double t) {
    return RiskFactor.of()
        .riskClass(c)
        .measure(m)
        .bucket(b)
        .name(n)
        .tenor(t)
        .tenor2(0)
        .build();
  }
  static int bi(int c, String b) {
    for (int i = 0; i < B[c].length; i++) if (B[c][i].equals(b)) return i;
    return 0;
  }
  static double margin(Sensitivities all, int c, RiskMeasureEnum m) {
    Sensitivities x = all.ofClass(RC[c])
        .ofMeasure(m);
    if (x.isEmpty()) return 0;
    if (m == RiskMeasureEnum.CURVATURE) return NestedAggregation.curvature((a, b) -> RHO[c], (a,
        b) -> GAMMA[c])
        .aggregate(x)
        .total();
    double threshold = m == RiskMeasureEnum.DELTA ? DT[c] : VT[c];
    Map<String, Double> sums = new HashMap<>();
    for (RiskFactor f : x.asMap()
        .keySet()) sums.put(f.bucket(), sums.getOrDefault(f.bucket(), 0.0) + x.asMap()
        .get(f));
    NestedAggregation.ConcentrationFactor cr = (f, s) -> Math.max(1, Math.sqrt(Math.abs(sums.getOrDefault(f.bucket(),
        0.0)) / threshold));
    return NestedAggregation.delta(f -> m == RiskMeasureEnum.DELTA ? DRW[c] : VRW[c], (a, b) -> RHO[c],
        (a, b) -> GAMMA[c])
        .withConcentration(cr)
        .aggregate(x)
        .total();
  }
  static double psi(int a, int b) {
    if (a == b) return 1;
    if ((a == 0 && b == 5) || (a == 5 && b == 0)) return .28;
    if ((a == 1 && b == 2) || (a == 2 && b == 1)) return .15;
    return 0;
  }
}
TeaVM compiles and runs the Java source above in this browser.
Implementation guide

Sensitivity-based initial margin

ISDA SIMM transforms agreed risk sensitivities into an initial-margin estimate for non-cleared derivatives.

Core mechanism

Sensitivities are mapped to risk classes, buckets and tenors, concentration-scaled where required, and aggregated with the methodology's risk weights and correlations. The same trade-level risk data can therefore support desk and margin views.

Practical workflow

Apply the licensed, current SIMM methodology and parameter set; align sensitivities, currencies and conventions with counterparties; then reconcile regularly as portfolios and methodology versions change.

Key details

Analysis note — where the heaviest computation in ISDA SIMM / IM model validation sits and whether NablaTensor helps. Not committed scope. Date: 2026-09-02. Verdict: Strong fit — the same sensitivity vectors as FRTB SA, plus a historical-replay backtest; a twice-a-year, every-year obligation for both sell-side and buy-side. Calculators, not sign-off. This note describes where the computation sits and what NablaTensor could compute. Model validation, parameter attestation and regulatory submission stay with the user.

For non-cleared derivatives above the €50 m / $50 m threshold, counterparties exchange initial margin. Most of the market uses ISDA SIMM — a sensitivities-based model: delta, vega and curvature by risk class, with concentration thresholds, aggregated with ISDA-calibrated risk weights and correlations.

1. Sensitivity generation. The full delta / vega / curvature vector for every trade in the non-cleared book — the *same* sensitivities as FRTB SA (different risk weights). Computed daily for margin calls and regenerated in full at each recalibration. By bump-and-revalue this is O(#risk factors) repricings per trade. 2. Annual backtesting. Recompute SIMM IM and the realised portfolio P&L over long historical windows (years of daily data) for every counterparty relationship, then compare against realised moves:

The bottleneck is (1) the daily / per-recalibration full sensitivity set and (2) the historical-window portfolio revaluation for the annual backtest.

Extends the existing com.nablatensor.reg.simm slice (equity delta + vega + concentration) to all risk classes, curvature and the g_bc cross-bucket term. The recalibration cadence and the annual backtest are the recurring commercial hook; a backtest harness over historical market snapshots would reuse com.nablatensor.scenario.*.

Scope and review point

This is an educational aggregation. SIMM is a governed industry methodology with versioning, licensing and bilateral operational requirements.