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;
import java.util.Objects;
import java.util.List;
import java.util.EnumMap;
public final class IsdaSimmRiskStudio {
enum ProductClass {
RATES_FX(RiskClassEnum.GIRR, RiskClassEnum.FX), CREDIT(RiskClassEnum.CSR_NON_SEC, RiskClassEnum.CSR_SEC),
EQUITY(RiskClassEnum.EQUITY), COMMODITY(RiskClassEnum.COMMODITY);
final List<RiskClassEnum> riskClasses;
ProductClass(RiskClassEnum...riskClasses) {
this.riskClasses = List.of(riskClasses);
}
}
enum Side {
LONG(1), SHORT(-1);
final int sign;
Side(int sign) {
this.sign = sign;
}
}
record Trade(String id, String counterparty, ProductClass productClass, double notional, Side side) {
Trade {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(counterparty, "counterparty");
Objects.requireNonNull(productClass, "productClass");
Objects.requireNonNull(side, "side");
if (!Double.isFinite(notional)) throw new IllegalArgumentException("notional must be finite");
}
static Builder builder() {
return new Builder();
}
static final class Builder {
private String id;
private String counterparty;
private ProductClass productClass;
private double notional = Double.NaN;
private Side side;
Builder id(String id) {
this.id = id;
return this;
}
Builder counterparty(String counterparty) {
this.counterparty = counterparty;
return this;
}
Builder productClass(ProductClass productClass) {
this.productClass = productClass;
return this;
}
Builder notional(double notional) {
this.notional = notional;
return this;
}
Builder side(Side side) {
this.side = side;
return this;
}
Trade build() {
return new Trade(id, counterparty, productClass, notional, side);
}
}
}
record RiskClassParameters(RiskClassEnum riskClass, List<String> buckets, double deltaRiskWeight,
double vegaRiskWeight, double withinBucketCorrelation, double betweenBucketCorrelation, double deltaConcentrationThreshold,
double vegaConcentrationThreshold) {
RiskClassParameters {
Objects.requireNonNull(riskClass, "riskClass");
buckets = List.copyOf(buckets);
if (buckets.isEmpty()) throw new IllegalArgumentException("buckets must not be empty");
for (double value : new double[] {
deltaRiskWeight, vegaRiskWeight, withinBucketCorrelation, betweenBucketCorrelation, deltaConcentrationThreshold,
vegaConcentrationThreshold
}) {
if (!Double.isFinite(value)) throw new IllegalArgumentException("all parameters must be finite");
}
if (deltaConcentrationThreshold <= 0 || vegaConcentrationThreshold <= 0) throw new IllegalArgumentException("concentration thresholds must be positive");
}
static Builder builder() {
return new Builder();
}
static final class Builder {
private RiskClassEnum riskClass;
private List<String> buckets;
private double deltaRiskWeight = Double.NaN;
private double vegaRiskWeight = Double.NaN;
private double withinBucketCorrelation = Double.NaN;
private double betweenBucketCorrelation = Double.NaN;
private double deltaConcentrationThreshold = Double.NaN;
private double vegaConcentrationThreshold = Double.NaN;
Builder riskClass(RiskClassEnum riskClass) {
this.riskClass = riskClass;
return this;
}
Builder buckets(String...buckets) {
this.buckets = List.of(buckets);
return this;
}
Builder deltaRiskWeight(double deltaRiskWeight) {
this.deltaRiskWeight = deltaRiskWeight;
return this;
}
Builder vegaRiskWeight(double vegaRiskWeight) {
this.vegaRiskWeight = vegaRiskWeight;
return this;
}
Builder withinBucketCorrelation(double withinBucketCorrelation) {
this.withinBucketCorrelation = withinBucketCorrelation;
return this;
}
Builder betweenBucketCorrelation(double betweenBucketCorrelation) {
this.betweenBucketCorrelation = betweenBucketCorrelation;
return this;
}
Builder deltaConcentrationThreshold(double deltaConcentrationThreshold) {
this.deltaConcentrationThreshold = deltaConcentrationThreshold;
return this;
}
Builder vegaConcentrationThreshold(double vegaConcentrationThreshold) {
this.vegaConcentrationThreshold = vegaConcentrationThreshold;
return this;
}
RiskClassParameters build() {
return new RiskClassParameters(riskClass, buckets, deltaRiskWeight, vegaRiskWeight, withinBucketCorrelation,
betweenBucketCorrelation, deltaConcentrationThreshold, vegaConcentrationThreshold);
}
}
}
static final RiskClassParameters[] DEMO_PARAMETERS = {
RiskClassParameters.builder().riskClass(RiskClassEnum.GIRR)
.buckets("USD", "EUR")
.deltaRiskWeight(0.011).vegaRiskWeight(0.18)
.withinBucketCorrelation(0.63).betweenBucketCorrelation(0.24)
.deltaConcentrationThreshold(330).vegaConcentrationThreshold(130).build(),
RiskClassParameters.builder().riskClass(RiskClassEnum.CSR_NON_SEC)
.buckets("1", "4")
.deltaRiskWeight(0.008).vegaRiskWeight(0.64)
.withinBucketCorrelation(0.42).betweenBucketCorrelation(0.21)
.deltaConcentrationThreshold(12).vegaConcentrationThreshold(320).build(),
RiskClassParameters.builder().riskClass(RiskClassEnum.CSR_SEC)
.buckets("1", "2")
.deltaRiskWeight(0.28).vegaRiskWeight(0.64)
.withinBucketCorrelation(0.27).betweenBucketCorrelation(0.35)
.deltaConcentrationThreshold(3).vegaConcentrationThreshold(85).build(),
RiskClassParameters.builder().riskClass(RiskClassEnum.EQUITY)
.buckets("1", "4")
.deltaRiskWeight(0.22).vegaRiskWeight(0.28)
.withinBucketCorrelation(0.16).betweenBucketCorrelation(0.15)
.deltaConcentrationThreshold(8).vegaConcentrationThreshold(210).build(),
RiskClassParameters.builder().riskClass(RiskClassEnum.COMMODITY)
.buckets("1", "5")
.deltaRiskWeight(0.19).vegaRiskWeight(0.42)
.withinBucketCorrelation(0.31).betweenBucketCorrelation(0.23)
.deltaConcentrationThreshold(260).vegaConcentrationThreshold(74).build(),
RiskClassParameters.builder().riskClass(RiskClassEnum.FX)
.buckets("USD", "EUR")
.deltaRiskWeight(0.075).vegaRiskWeight(0.3)
.withinBucketCorrelation(0.5).betweenBucketCorrelation(0.5)
.deltaConcentrationThreshold(2400).vegaConcentrationThreshold(480).build()
};
static final RiskMeasureEnum[] MEASURES = {
RiskMeasureEnum.DELTA, RiskMeasureEnum.VEGA, RiskMeasureEnum.CURVATURE
};
public static void main(String[] z) {
Trade[] trades = {
Trade.builder().id("IRS-USD-10Y")
.counterparty("CPTY-ALPHA").productClass(ProductClass.RATES_FX)
.notional(120000000).side(Side.LONG).build(),
Trade.builder().id("XCCY-EURUSD-5Y")
.counterparty("CPTY-ALPHA").productClass(ProductClass.RATES_FX)
.notional(80000000).side(Side.SHORT).build(),
Trade.builder().id("CDS-IG-CPTY-5Y")
.counterparty("CPTY-BRAVO").productClass(ProductClass.CREDIT)
.notional(50000000).side(Side.LONG).build(),
Trade.builder().id("CDX-HY-INDEX-5Y")
.counterparty("CPTY-BRAVO").productClass(ProductClass.CREDIT)
.notional(25000000).side(Side.SHORT).build(),
Trade.builder().id("EQ-CALL-SX5E-1Y")
.counterparty("CPTY-CHARLIE").productClass(ProductClass.EQUITY)
.notional(15000000).side(Side.SHORT).build(),
Trade.builder().id("EQ-PUT-SPX-1Y")
.counterparty("CPTY-CHARLIE").productClass(ProductClass.EQUITY)
.notional(10000000).side(Side.LONG).build(),
Trade.builder().id("COMDTY-WTI-SWAP-2Y")
.counterparty("CPTY-DELTA").productClass(ProductClass.COMMODITY)
.notional(30000000).side(Side.LONG).build()
};
long paths = 200000L, seed = 42L;
Set<String> counterparties = new HashSet<>();
for (Trade trade : trades) counterparties.add(trade.counterparty());
System.out.println("BOOK|" + trades.length + "|" + counterparties.size());
Trade heavy = trades[0];
for (Trade trade : trades) if (trade.productClass() == ProductClass.EQUITY) {
heavy = trade;
break;
}
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 = heavy.side()
.sign * Math.abs(heavy.notional()) / 100.0 / 1e6 * adj.greek(EquityMarket::spot);
hv = heavy.side()
.sign * Math.abs(heavy.notional()) / 100.0 / 1e6 * adj.greek(EquityMarket::vol) * .01;
hir = heavy.side()
.sign * Math.abs(heavy.notional()) / 100.0 / 1e6 * adj.greek(EquityMarket::rate) * .01;
double pb = heavy.side()
.sign * Math.abs(heavy.notional()) / 100.0 / 1e6 * base.price(), pu = heavy.side()
.sign * Math.abs(heavy.notional()) / 100.0 / 1e6 * up.price(), pd = heavy.side()
.sign * Math.abs(heavy.notional()) / 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 < trades.length; t++) {
Trade trade = trades[t];
double s = trade.side()
.sign * trade.notional() / 1e6;
int fi = 0;
for (RiskClassParameters parameters : DEMO_PARAMETERS) if (trade.productClass()
.riskClasses.contains(parameters.riskClass())) for (String bucket : parameters.buckets()) {
double alt = ((fi++ + t) % 2 == 0) ? 1 : -.6;
String name = trade.id() + "#" + bucket;
builder.add(factor(parameters.riskClass(), RiskMeasureEnum.DELTA, bucket, name, 0), s * alt * .02);
builder.add(factor(parameters.riskClass(), RiskMeasureEnum.VEGA, bucket, name, 1), Math.abs(s) * .004 * (1
+ .1 * t));
builder.add(factor(parameters.riskClass(), RiskMeasureEnum.CURVATURE, bucket, name, 0),
Math.abs(s) * alt * .0015);
}
}
builder.add(RiskFactor.equityDelta("4", heavy.id()), hd);
builder.add(factor(RiskClassEnum.EQUITY, RiskMeasureEnum.VEGA, "4", heavy.id(), 1), hv);
builder.add(RiskFactor.equityDelta("4", heavy.id())
.asCurvature(), hc);
builder.add(RiskFactor.girrDelta("USD", 10.0), hir);
Sensitivities crif = builder.build();
Map<RiskClassEnum, double[]> result = new EnumMap<>(RiskClassEnum.class);
for (RiskClassParameters parameters : DEMO_PARAMETERS) {
double[] margins = new double[MEASURES.length];
for (int m = 0; m < MEASURES.length; m++) margins[m] = margin(crif, parameters, MEASURES[m]);
result.put(parameters.riskClass(), margins);
System.out.println("CLASS|" + parameters.riskClass() + "|" + margins[0] + "|" + margins[1]
+ "|" + margins[2]);
}
double total = 0;
for (ProductClass product : ProductClass.values()) {
double[] sums = new double[MEASURES.length];
for (int m = 0; m < MEASURES.length; m++) {
for (RiskClassEnum x : product.riskClasses) {
sums[m] += result.get(x)[m] * result.get(x)[m];
for (RiskClassEnum y : product.riskClasses) if (x != y) sums[m] += productClassCorrelation(x,
y) * result.get(x)[m] * result.get(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|" + product + "|" + sums[0] + "|" + sums[1] + "|" + sums[2] + "|"
+ pt);
}
System.out.println("TOTAL|" + total);
}
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 double margin(Sensitivities all, RiskClassParameters parameters, RiskMeasureEnum m) {
Sensitivities x = all.ofClass(parameters.riskClass())
.ofMeasure(m);
if (x.isEmpty()) return 0;
if (m == RiskMeasureEnum.CURVATURE) return NestedAggregation.curvature((a, b) -> parameters.withinBucketCorrelation(),
(a, b) -> parameters.betweenBucketCorrelation())
.aggregate(x)
.total();
double threshold = m == RiskMeasureEnum.DELTA ? parameters.deltaConcentrationThreshold() : parameters.vegaConcentrationThreshold();
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 ? parameters.deltaRiskWeight() : parameters.vegaRiskWeight(),
(a, b) -> parameters.withinBucketCorrelation(), (a, b) -> parameters.betweenBucketCorrelation())
.withConcentration(cr)
.aggregate(x)
.total();
}
static double productClassCorrelation(RiskClassEnum a, RiskClassEnum b) {
if (a == b) return 1;
if ((a == RiskClassEnum.GIRR && b == RiskClassEnum.FX) || (a == RiskClassEnum.FX && b == RiskClassEnum.GIRR)) return .28;
if ((a == RiskClassEnum.CSR_NON_SEC && b == RiskClassEnum.CSR_SEC) || (a == RiskClassEnum.CSR_SEC && b == RiskClassEnum.CSR_NON_SEC)) return .15;
return 0;
}
}