Skip to content

Commit a6ed1a4

Browse files
springbootdemo-tomcat11: add Java financial benchmark service
Java reference implementation of the Axis2/C financial benchmark service (axis-axis2-c-core/samples/user_guide/financial-benchmark-service). Provides identical API and financial calculations to the C service, enabling side-by-side performance comparison: - C on a $20 2GB Android phone: portfolioVariance 500 assets in ~5ms / 30MB - Java: requires 16-32GB JVM minimum to start Three operations (7 files): portfolioVariance — O(n²) covariance matrix multiplication σ²_p = Σ_i Σ_j w_i · w_j · σ_ij Accepts 2D double[][] or flat double[] covariance matrix. normalizeWeights: rescales weights to sum 1.0 (for unnormalized exposures). nPeriodsPerYear: controls annualized volatility (default 252). Reports weight_sum and weights_normalized for audit. monteCarlo — Geometric Brownian Motion VaR simulation S(t+dt) = S(t) × exp((μ − σ²/2)·dt + σ·√dt·Z) Uses Random.nextGaussian() (polar method). Seeded (randomSeed != 0) for reproducibility, unseeded for production. percentiles: caller-specified VaR levels, default [0.01, 0.05]. Returns fixed var95/var99/cvar95 fields plus percentileVars list. nPeriodsPerYear: controls GBM time step dt = 1/nPeriodsPerYear. scenarioAnalysis — expected return + HashMap vs ArrayList benchmark E[r_i] = Σ_j p_j × (price_j / currentPrice − 1) probTolerance: configurable probability sum validation (default 1e-4). Mirrors DPT v2 Array→Map optimization for 500+ asset portfolios. Reports linear_us, hash_lookup_us, speedup, hash_build_us. API parity with C: all request/response field names match the C structs in financial_benchmark_service.h (camelCase vs snake_case only). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2c0577f commit a6ed1a4

7 files changed

Lines changed: 1314 additions & 0 deletions

File tree

modules/samples/userguide/src/userguide/springbootdemo-tomcat11/src/main/java/userguide/springboot/webservices/FinancialBenchmarkService.java

Lines changed: 526 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package userguide.springboot.webservices;
20+
21+
/**
22+
* Request for Monte Carlo Value-at-Risk simulation.
23+
*
24+
* <p>Simulates portfolio value paths using Geometric Brownian Motion:
25+
* <pre>S(t+dt) = S(t) × exp((μ − σ²/2)·dt + σ·√dt·Z)</pre>
26+
* where dt = 1/nPeriodsPerYear and Z ~ N(0,1).
27+
*
28+
* <p>All fields have defaults so a minimal {@code {}} request body is valid.
29+
*
30+
* <h3>Example</h3>
31+
* <pre>{@code
32+
* {
33+
* "nSimulations": 50000,
34+
* "nPeriods": 252,
35+
* "initialValue": 1000000.0,
36+
* "expectedReturn": 0.08,
37+
* "volatility": 0.20,
38+
* "randomSeed": 42,
39+
* "percentiles": [0.01, 0.05, 0.10]
40+
* }
41+
* }</pre>
42+
*/
43+
public class MonteCarloRequest {
44+
45+
/** Number of simulation paths. Default: 10,000. Max: 1,000,000. */
46+
private int nSimulations = 10_000;
47+
48+
/** Number of time steps per path (e.g., 252 trading days). Default: 252. */
49+
private int nPeriods = 252;
50+
51+
/** Initial portfolio value in currency units. Default: $1,000,000. */
52+
private double initialValue = 1_000_000.0;
53+
54+
/** Expected annualized return (e.g., 0.08 for 8%). Default: 0.08. */
55+
private double expectedReturn = 0.08;
56+
57+
/** Annualized volatility (e.g., 0.20 for 20%). Default: 0.20. */
58+
private double volatility = 0.20;
59+
60+
/**
61+
* Random seed for reproducibility. 0 (default) → non-deterministic.
62+
* Seeded runs produce identical results across calls, enabling diff testing.
63+
*/
64+
private long randomSeed = 0;
65+
66+
/**
67+
* Trading periods per year. Controls GBM time step: dt = 1/nPeriodsPerYear.
68+
* Must match the frequency basis of {@code expectedReturn} and {@code volatility}
69+
* (both must be annualized). Default: 252. Common alternatives: 260, 365, 12.
70+
*/
71+
private int nPeriodsPerYear = 252;
72+
73+
/**
74+
* Percentile tail levels for VaR reporting. Values in (0, 1).
75+
* Each entry p produces: VaR_p = initialValue − sorted_final_values[p × nSimulations].
76+
* Default: [0.01, 0.05] (99% and 95% VaR). Up to 8 entries; extras are ignored.
77+
*/
78+
private double[] percentiles = {0.01, 0.05};
79+
80+
/** Optional identifier echoed in the response for request tracing. */
81+
private String requestId;
82+
83+
// ── getters ──────────────────────────────────────────────────────────────
84+
85+
public int getNSimulations() { return nSimulations; }
86+
public int getNPeriods() { return nPeriods; }
87+
public double getInitialValue() { return initialValue; }
88+
public double getExpectedReturn() { return expectedReturn; }
89+
public double getVolatility() { return volatility; }
90+
public long getRandomSeed() { return randomSeed; }
91+
public int getNPeriodsPerYear() { return nPeriodsPerYear > 0 ? nPeriodsPerYear : 252; }
92+
public double[] getPercentiles() { return percentiles; }
93+
public String getRequestId() { return requestId; }
94+
95+
// ── setters ──────────────────────────────────────────────────────────────
96+
97+
public void setNSimulations(int nSimulations) { this.nSimulations = nSimulations; }
98+
public void setNPeriods(int nPeriods) { this.nPeriods = nPeriods; }
99+
public void setInitialValue(double initialValue) { this.initialValue = initialValue; }
100+
public void setExpectedReturn(double expectedReturn) { this.expectedReturn = expectedReturn; }
101+
public void setVolatility(double volatility) { this.volatility = volatility; }
102+
public void setRandomSeed(long randomSeed) { this.randomSeed = randomSeed; }
103+
public void setNPeriodsPerYear(int nPeriodsPerYear) { this.nPeriodsPerYear = nPeriodsPerYear; }
104+
public void setPercentiles(double[] percentiles) { this.percentiles = percentiles; }
105+
public void setRequestId(String requestId) { this.requestId = requestId; }
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package userguide.springboot.webservices;
20+
21+
import java.util.List;
22+
23+
/**
24+
* Response for Monte Carlo Value-at-Risk simulation.
25+
*
26+
* <p>Fixed fields ({@code var95}, {@code var99}, {@code cvar95}) are always
27+
* populated on success for backward compatibility. The {@code percentileVars}
28+
* list reflects the caller-specified {@code percentiles} array.
29+
*/
30+
public class MonteCarloResponse {
31+
32+
private String status;
33+
private String errorMessage;
34+
35+
// ── Distribution statistics ───────────────────────────────────────────────
36+
37+
/** Mean of final portfolio values across all paths */
38+
private double meanFinalValue;
39+
40+
/** Median (50th percentile) of final portfolio values */
41+
private double medianFinalValue;
42+
43+
/** Standard deviation of final portfolio values */
44+
private double stdDevFinalValue;
45+
46+
// ── Fixed VaR fields (always populated) ──────────────────────────────────
47+
48+
/** Value at Risk at 95% confidence: initialValue − 5th-percentile final value */
49+
private double var95;
50+
51+
/** Value at Risk at 99% confidence: initialValue − 1st-percentile final value */
52+
private double var99;
53+
54+
/** Conditional VaR (Expected Shortfall) at 95%: initialValue − mean(worst 5%) */
55+
private double cvar95;
56+
57+
// ── Additional risk metrics ───────────────────────────────────────────────
58+
59+
/** Maximum drawdown observed across all simulation paths (0–1 fraction) */
60+
private double maxDrawdown;
61+
62+
/** Fraction of paths where final value > initial value */
63+
private double probProfit;
64+
65+
// ── Caller-specified percentile VaR ──────────────────────────────────────
66+
67+
/**
68+
* VaR values for the percentile levels requested in {@code MonteCarloRequest.percentiles}.
69+
* Each entry: {@code {"percentile": 0.01, "var": 185432.10}}.
70+
*/
71+
private List<PercentileVar> percentileVars;
72+
73+
// ── Performance metrics ───────────────────────────────────────────────────
74+
75+
/** Wall-clock time for the simulation in microseconds */
76+
private long calcTimeUs;
77+
78+
/** Simulation throughput: nSimulations / (calcTimeUs / 1e6) */
79+
private double simulationsPerSecond;
80+
81+
/** JVM heap used at response time in MB */
82+
private long memoryUsedMb;
83+
84+
/** Echoed from request */
85+
private String requestId;
86+
87+
// ── Inner types ──────────────────────────────────────────────────────────
88+
89+
/**
90+
* A single percentile VaR entry in {@code percentileVars}.
91+
*/
92+
public static class PercentileVar {
93+
private double percentile;
94+
private double var;
95+
96+
public PercentileVar() {}
97+
98+
public PercentileVar(double percentile, double var) {
99+
this.percentile = percentile;
100+
this.var = var;
101+
}
102+
103+
public double getPercentile() { return percentile; }
104+
public void setPercentile(double percentile) { this.percentile = percentile; }
105+
public double getVar() { return var; }
106+
public void setVar(double var) { this.var = var; }
107+
}
108+
109+
// ── Factory ──────────────────────────────────────────────────────────────
110+
111+
public static MonteCarloResponse failed(String errorMessage) {
112+
MonteCarloResponse r = new MonteCarloResponse();
113+
r.status = "FAILED";
114+
r.errorMessage = errorMessage;
115+
return r;
116+
}
117+
118+
// ── Getters / setters ────────────────────────────────────────────────────
119+
120+
public String getStatus() { return status; }
121+
public void setStatus(String status) { this.status = status; }
122+
123+
public String getErrorMessage() { return errorMessage; }
124+
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
125+
126+
public double getMeanFinalValue() { return meanFinalValue; }
127+
public void setMeanFinalValue(double meanFinalValue) { this.meanFinalValue = meanFinalValue; }
128+
129+
public double getMedianFinalValue() { return medianFinalValue; }
130+
public void setMedianFinalValue(double medianFinalValue) { this.medianFinalValue = medianFinalValue; }
131+
132+
public double getStdDevFinalValue() { return stdDevFinalValue; }
133+
public void setStdDevFinalValue(double stdDevFinalValue) { this.stdDevFinalValue = stdDevFinalValue; }
134+
135+
public double getVar95() { return var95; }
136+
public void setVar95(double var95) { this.var95 = var95; }
137+
138+
public double getVar99() { return var99; }
139+
public void setVar99(double var99) { this.var99 = var99; }
140+
141+
public double getCvar95() { return cvar95; }
142+
public void setCvar95(double cvar95) { this.cvar95 = cvar95; }
143+
144+
public double getMaxDrawdown() { return maxDrawdown; }
145+
public void setMaxDrawdown(double maxDrawdown) { this.maxDrawdown = maxDrawdown; }
146+
147+
public double getProbProfit() { return probProfit; }
148+
public void setProbProfit(double probProfit) { this.probProfit = probProfit; }
149+
150+
public List<PercentileVar> getPercentileVars() { return percentileVars; }
151+
public void setPercentileVars(List<PercentileVar> percentileVars) { this.percentileVars = percentileVars; }
152+
153+
public long getCalcTimeUs() { return calcTimeUs; }
154+
public void setCalcTimeUs(long calcTimeUs) { this.calcTimeUs = calcTimeUs; }
155+
156+
public double getSimulationsPerSecond() { return simulationsPerSecond; }
157+
public void setSimulationsPerSecond(double simulationsPerSecond) { this.simulationsPerSecond = simulationsPerSecond; }
158+
159+
public long getMemoryUsedMb() { return memoryUsedMb; }
160+
public void setMemoryUsedMb(long memoryUsedMb) { this.memoryUsedMb = memoryUsedMb; }
161+
162+
public String getRequestId() { return requestId; }
163+
public void setRequestId(String requestId) { this.requestId = requestId; }
164+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package userguide.springboot.webservices;
20+
21+
/**
22+
* Request for portfolio variance calculation.
23+
*
24+
* <p>Computes σ²_p = Σ_i Σ_j w_i · w_j · σ_ij — an O(n²) operation
25+
* that mirrors correlation/risk calculations in DPT v2 and similar systems.
26+
*
27+
* <h3>Covariance matrix formats</h3>
28+
* <ul>
29+
* <li><b>2D array</b> (preferred): {@code covarianceMatrix[i][j]} — natural JSON nested array</li>
30+
* <li><b>Flat array</b> (alternative): {@code covarianceMatrixFlat} of length n² in row-major order</li>
31+
* </ul>
32+
* If both are supplied, {@code covarianceMatrix} takes precedence.
33+
*
34+
* <h3>Example</h3>
35+
* <pre>{@code
36+
* {
37+
* "weights": [0.4, 0.6],
38+
* "covarianceMatrix": [[0.04, 0.006], [0.006, 0.09]],
39+
* "normalizeWeights": false,
40+
* "nPeriodsPerYear": 252
41+
* }
42+
* }</pre>
43+
*/
44+
public class PortfolioVarianceRequest {
45+
46+
/** Portfolio weights. Length determines n_assets when nAssets is not set. */
47+
private double[] weights;
48+
49+
/**
50+
* Covariance matrix in 2D format: {@code covarianceMatrix[i][j]}.
51+
* Takes precedence over {@code covarianceMatrixFlat} if both are provided.
52+
*/
53+
private double[][] covarianceMatrix;
54+
55+
/**
56+
* Covariance matrix in flat row-major format: element (i,j) is at index
57+
* {@code i * nAssets + j}. Length must be nAssets². Used when the caller
58+
* cannot produce a nested JSON array (e.g., numpy {@code .flatten()}).
59+
*/
60+
private double[] covarianceMatrixFlat;
61+
62+
/**
63+
* When {@code true}, weights are rescaled to sum to 1.0 before computing
64+
* variance. Allows callers to pass unnormalized exposures (e.g., notional
65+
* position values) without a client-side preprocessing step.
66+
* When {@code false} (default), weights that deviate from 1.0 by more than
67+
* 1e-4 return an error.
68+
*/
69+
private boolean normalizeWeights = false;
70+
71+
/**
72+
* Trading periods per year used to annualize volatility.
73+
* {@code annualizedVolatility = portfolioVolatility × sqrt(nPeriodsPerYear)}.
74+
* Common values: 252 (equity, default), 260 (some fixed-income conventions),
75+
* 365 (crypto), 12 (monthly factor models).
76+
*/
77+
private int nPeriodsPerYear = 252;
78+
79+
/** Optional identifier echoed in the response for request tracing. */
80+
private String requestId;
81+
82+
// ── getters ──────────────────────────────────────────────────────────────
83+
84+
public double[] getWeights() { return weights; }
85+
public double[][] getCovarianceMatrix() { return covarianceMatrix; }
86+
public double[] getCovarianceMatrixFlat() { return covarianceMatrixFlat; }
87+
public boolean isNormalizeWeights() { return normalizeWeights; }
88+
public int getNPeriodsPerYear() { return nPeriodsPerYear > 0 ? nPeriodsPerYear : 252; }
89+
public String getRequestId() { return requestId; }
90+
91+
// ── setters ──────────────────────────────────────────────────────────────
92+
93+
public void setWeights(double[] weights) { this.weights = weights; }
94+
public void setCovarianceMatrix(double[][] covarianceMatrix) { this.covarianceMatrix = covarianceMatrix; }
95+
public void setCovarianceMatrixFlat(double[] covarianceMatrixFlat) { this.covarianceMatrixFlat = covarianceMatrixFlat; }
96+
public void setNormalizeWeights(boolean normalizeWeights) { this.normalizeWeights = normalizeWeights; }
97+
public void setNPeriodsPerYear(int nPeriodsPerYear) { this.nPeriodsPerYear = nPeriodsPerYear; }
98+
public void setRequestId(String requestId) { this.requestId = requestId; }
99+
100+
/** Derived: number of assets inferred from weights array length. */
101+
public int getNAssets() {
102+
return weights != null ? weights.length : 0;
103+
}
104+
}

0 commit comments

Comments
 (0)