Overview
Financial calculations require precision, accuracy, and proper handling of rounding rules. Java provides several classes and libraries specifically designed for financial computations.
Key Libraries and Classes
- BigDecimal: For precise decimal arithmetic
- MathContext: For rounding and precision control
- Java Money API (JSR 354): For currency handling
- Apache Commons Math: For advanced financial mathematics
- JQuantLib: For quantitative finance
Basic Setup and Dependencies
1. Maven Dependencies
<dependencies> <!-- Java Money API --> <dependency> <groupId>org.javamoney</groupId> <artifactId>moneta</artifactId> <version>1.4.2</version> </dependency> <!-- Apache Commons Math --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-math3</artifactId> <version>3.6.1</version> </dependency> <!-- For date calculations --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.12.5</version> </dependency> </dependencies>
2. Basic BigDecimal Configuration
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Currency;
public class FinancialConfig {
// Standard precision for financial calculations
public static final MathContext FINANCIAL_CONTEXT = new MathContext(34, RoundingMode.HALF_EVEN);
public static final int FINANCIAL_SCALE = 10;
// Common currencies
public static final Currency USD = Currency.getInstance("USD");
public static final Currency EUR = Currency.getInstance("EUR");
public static final Currency GBP = Currency.getInstance("GBP");
public static final Currency JPY = Currency.getInstance("JPY");
// Financial constants
public static final BigDecimal DAYS_IN_YEAR = new BigDecimal("365.25");
public static final BigDecimal MONTHS_IN_YEAR = new BigDecimal("12");
public static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
}
Time Value of Money (TVM) Calculations
1. Present Value and Future Value
import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class TimeValueOfMoney {
private static final MathContext MC = FinancialConfig.FINANCIAL_CONTEXT;
// Future Value of a single sum
public static BigDecimal futureValue(BigDecimal presentValue, BigDecimal rate,
int periods) {
// FV = PV * (1 + r)^n
BigDecimal onePlusRate = BigDecimal.ONE.add(rate, MC);
BigDecimal compoundingFactor = onePlusRate.pow(periods, MC);
return presentValue.multiply(compoundingFactor, MC);
}
// Present Value of a single sum
public static BigDecimal presentValue(BigDecimal futureValue, BigDecimal rate,
int periods) {
// PV = FV / (1 + r)^n
BigDecimal onePlusRate = BigDecimal.ONE.add(rate, MC);
BigDecimal discountFactor = onePlusRate.pow(periods, MC);
return futureValue.divide(discountFactor, MC);
}
// Future Value of an annuity (ordinary annuity)
public static BigDecimal futureValueAnnuity(BigDecimal payment, BigDecimal rate,
int periods) {
// FV = P * [((1 + r)^n - 1) / r]
if (rate.compareTo(BigDecimal.ZERO) == 0) {
return payment.multiply(new BigDecimal(periods), MC);
}
BigDecimal onePlusRate = BigDecimal.ONE.add(rate, MC);
BigDecimal numerator = onePlusRate.pow(periods, MC).subtract(BigDecimal.ONE, MC);
return payment.multiply(numerator, MC).divide(rate, MC);
}
// Present Value of an annuity (ordinary annuity)
public static BigDecimal presentValueAnnuity(BigDecimal payment, BigDecimal rate,
int periods) {
// PV = P * [1 - (1 + r)^-n] / r
if (rate.compareTo(BigDecimal.ZERO) == 0) {
return payment.multiply(new BigDecimal(periods), MC);
}
BigDecimal onePlusRate = BigDecimal.ONE.add(rate, MC);
BigDecimal discountFactor = BigDecimal.ONE.divide(
onePlusRate.pow(periods, MC), MC);
BigDecimal numerator = BigDecimal.ONE.subtract(discountFactor, MC);
return payment.multiply(numerator, MC).divide(rate, MC);
}
// Net Present Value (NPV)
public static BigDecimal netPresentValue(BigDecimal[] cashFlows, BigDecimal discountRate) {
BigDecimal npv = BigDecimal.ZERO;
for (int i = 0; i < cashFlows.length; i++) {
BigDecimal pv = presentValue(cashFlows[i], discountRate, i);
npv = npv.add(pv, MC);
}
return npv;
}
// Internal Rate of Return (IRR) - simplified implementation
public static BigDecimal internalRateOfReturn(BigDecimal[] cashFlows,
BigDecimal guess,
int maxIterations) {
BigDecimal irr = guess;
BigDecimal precision = new BigDecimal("0.00001");
for (int i = 0; i < maxIterations; i++) {
BigDecimal npv = netPresentValue(cashFlows, irr);
BigDecimal derivative = calculateNpvDerivative(cashFlows, irr);
if (derivative.abs().compareTo(precision) < 0) {
break; // Avoid division by very small numbers
}
BigDecimal adjustment = npv.divide(derivative, MC);
irr = irr.subtract(adjustment, MC);
if (adjustment.abs().compareTo(precision) < 0) {
break; // Converged
}
}
return irr;
}
private static BigDecimal calculateNpvDerivative(BigDecimal[] cashFlows, BigDecimal rate) {
BigDecimal derivative = BigDecimal.ZERO;
for (int i = 1; i < cashFlows.length; i++) {
BigDecimal term = new BigDecimal(-i).multiply(cashFlows[i], MC)
.divide(BigDecimal.ONE.add(rate, MC).pow(i + 1, MC), MC);
derivative = derivative.add(term, MC);
}
return derivative;
}
}
2. Loan and Mortgage Calculations
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class LoanCalculations {
private static final MathContext MC = FinancialConfig.FINANCIAL_CONTEXT;
public static class LoanPayment {
private final int paymentNumber;
private final LocalDate paymentDate;
private final BigDecimal paymentAmount;
private final BigDecimal principal;
private final BigDecimal interest;
private final BigDecimal remainingBalance;
public LoanPayment(int paymentNumber, LocalDate paymentDate,
BigDecimal paymentAmount, BigDecimal principal,
BigDecimal interest, BigDecimal remainingBalance) {
this.paymentNumber = paymentNumber;
this.paymentDate = paymentDate;
this.paymentAmount = paymentAmount;
this.principal = principal;
this.interest = interest;
this.remainingBalance = remainingBalance;
}
// Getters
public int getPaymentNumber() { return paymentNumber; }
public LocalDate getPaymentDate() { return paymentDate; }
public BigDecimal getPaymentAmount() { return paymentAmount; }
public BigDecimal getPrincipal() { return principal; }
public BigDecimal getInterest() { return interest; }
public BigDecimal getRemainingBalance() { return remainingBalance; }
}
public static class AmortizationSchedule {
private final List<LoanPayment> payments;
private final BigDecimal totalInterest;
private final BigDecimal totalPayments;
public AmortizationSchedule(List<LoanPayment> payments) {
this.payments = payments;
this.totalInterest = payments.stream()
.map(LoanPayment::getInterest)
.reduce(BigDecimal.ZERO, BigDecimal::add);
this.totalPayments = payments.stream()
.map(LoanPayment::getPaymentAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// Getters
public List<LoanPayment> getPayments() { return payments; }
public BigDecimal getTotalInterest() { return totalInterest; }
public BigDecimal getTotalPayments() { return totalPayments; }
}
// Calculate monthly payment for a fixed-rate loan
public static BigDecimal calculateMonthlyPayment(BigDecimal loanAmount,
BigDecimal annualRate,
int termMonths) {
// PMT = P * [r(1+r)^n] / [(1+r)^n - 1]
BigDecimal monthlyRate = annualRate.divide(new BigDecimal("12"), MC);
BigDecimal onePlusRate = BigDecimal.ONE.add(monthlyRate, MC);
BigDecimal numerator = monthlyRate.multiply(onePlusRate.pow(termMonths, MC), MC);
BigDecimal denominator = onePlusRate.pow(termMonths, MC).subtract(BigDecimal.ONE, MC);
return loanAmount.multiply(numerator, MC).divide(denominator, MC);
}
// Generate amortization schedule
public static AmortizationSchedule generateAmortizationSchedule(
BigDecimal loanAmount, BigDecimal annualRate, int termMonths,
LocalDate startDate) {
BigDecimal monthlyRate = annualRate.divide(new BigDecimal("12"), MC);
BigDecimal monthlyPayment = calculateMonthlyPayment(loanAmount, annualRate, termMonths);
List<LoanPayment> payments = new ArrayList<>();
BigDecimal remainingBalance = loanAmount;
for (int i = 1; i <= termMonths; i++) {
BigDecimal interest = remainingBalance.multiply(monthlyRate, MC);
BigDecimal principal = monthlyPayment.subtract(interest, MC);
// Adjust last payment to account for rounding
if (i == termMonths) {
principal = remainingBalance;
monthlyPayment = principal.add(interest, MC);
}
remainingBalance = remainingBalance.subtract(principal, MC);
LocalDate paymentDate = startDate.plusMonths(i - 1);
LoanPayment payment = new LoanPayment(
i, paymentDate, monthlyPayment, principal, interest, remainingBalance);
payments.add(payment);
}
return new AmortizationSchedule(payments);
}
// Calculate loan payoff date with extra payments
public static LocalDate calculatePayoffDate(BigDecimal loanAmount,
BigDecimal annualRate,
int originalTermMonths,
BigDecimal regularPayment,
BigDecimal extraPayment,
LocalDate startDate) {
BigDecimal monthlyRate = annualRate.divide(new BigDecimal("12"), MC);
BigDecimal balance = loanAmount;
LocalDate currentDate = startDate;
int months = 0;
while (balance.compareTo(BigDecimal.ZERO) > 0 && months < originalTermMonths * 2) {
BigDecimal interest = balance.multiply(monthlyRate, MC);
BigDecimal totalPayment = regularPayment.add(extraPayment, MC);
BigDecimal principal = totalPayment.subtract(interest, MC);
if (principal.compareTo(balance) > 0) {
principal = balance;
}
balance = balance.subtract(principal, MC);
currentDate = currentDate.plusMonths(1);
months++;
}
return currentDate;
}
}
Bond Calculations
1. Bond Pricing and Yield
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class BondCalculations {
private static final MathContext MC = FinancialConfig.FINANCIAL_CONTEXT;
public static class Bond {
private final BigDecimal faceValue;
private final BigDecimal couponRate;
private final int couponFrequency; // times per year
private final LocalDate issueDate;
private final LocalDate maturityDate;
public Bond(BigDecimal faceValue, BigDecimal couponRate, int couponFrequency,
LocalDate issueDate, LocalDate maturityDate) {
this.faceValue = faceValue;
this.couponRate = couponRate;
this.couponFrequency = couponFrequency;
this.issueDate = issueDate;
this.maturityDate = maturityDate;
}
// Getters
public BigDecimal getFaceValue() { return faceValue; }
public BigDecimal getCouponRate() { return couponRate; }
public int getCouponFrequency() { return couponFrequency; }
public LocalDate getIssueDate() { return issueDate; }
public LocalDate getMaturityDate() { return maturityDate; }
}
// Calculate bond price given yield to maturity
public static BigDecimal calculateBondPrice(Bond bond, BigDecimal yieldToMaturity,
LocalDate settlementDate) {
BigDecimal couponPayment = bond.getFaceValue()
.multiply(bond.getCouponRate(), MC)
.divide(new BigDecimal(bond.getCouponFrequency()), MC);
long periodsToMaturity = calculatePeriodsToMaturity(bond, settlementDate);
BigDecimal periodYield = yieldToMaturity.divide(
new BigDecimal(bond.getCouponFrequency()), MC);
BigDecimal price = BigDecimal.ZERO;
// Present value of coupon payments
for (long i = 1; i <= periodsToMaturity; i++) {
BigDecimal pvCoupon = couponPayment.divide(
BigDecimal.ONE.add(periodYield, MC).pow((int)i, MC), MC);
price = price.add(pvCoupon, MC);
}
// Present value of face value
BigDecimal pvFace = bond.getFaceValue().divide(
BigDecimal.ONE.add(periodYield, MC).pow((int)periodsToMaturity, MC), MC);
price = price.add(pvFace, MC);
return price;
}
// Calculate yield to maturity given bond price
public static BigDecimal calculateYieldToMaturity(Bond bond, BigDecimal bondPrice,
LocalDate settlementDate) {
// Use Newton-Raphson method to solve for YTM
BigDecimal guess = bond.getCouponRate(); // Initial guess
BigDecimal precision = new BigDecimal("0.000001");
int maxIterations = 100;
for (int i = 0; i < maxIterations; i++) {
BigDecimal priceAtGuess = calculateBondPrice(bond, guess, settlementDate);
BigDecimal error = priceAtGuess.subtract(bondPrice, MC);
if (error.abs().compareTo(precision) < 0) {
break;
}
// Calculate derivative (price sensitivity to yield)
BigDecimal derivative = calculateBondPriceDerivative(bond, guess, settlementDate);
if (derivative.abs().compareTo(precision) < 0) {
break; // Avoid division by very small numbers
}
BigDecimal adjustment = error.divide(derivative, MC);
guess = guess.subtract(adjustment, MC);
}
return guess;
}
// Calculate Macaulay Duration
public static BigDecimal calculateMacaulayDuration(Bond bond, BigDecimal yieldToMaturity,
LocalDate settlementDate) {
BigDecimal bondPrice = calculateBondPrice(bond, yieldToMaturity, settlementDate);
BigDecimal periodYield = yieldToMaturity.divide(
new BigDecimal(bond.getCouponFrequency()), MC);
long periodsToMaturity = calculatePeriodsToMaturity(bond, settlementDate);
BigDecimal couponPayment = bond.getFaceValue()
.multiply(bond.getCouponRate(), MC)
.divide(new BigDecimal(bond.getCouponFrequency()), MC);
BigDecimal weightedTimeSum = BigDecimal.ZERO;
for (long t = 1; t <= periodsToMaturity; t++) {
BigDecimal cashFlow = (t == periodsToMaturity) ?
couponPayment.add(bond.getFaceValue(), MC) : couponPayment;
BigDecimal presentValue = cashFlow.divide(
BigDecimal.ONE.add(periodYield, MC).pow((int)t, MC), MC);
weightedTimeSum = weightedTimeSum.add(
presentValue.multiply(new BigDecimal(t), MC), MC);
}
return weightedTimeSum.divide(bondPrice, MC)
.divide(new BigDecimal(bond.getCouponFrequency()), MC);
}
// Calculate Modified Duration
public static BigDecimal calculateModifiedDuration(Bond bond, BigDecimal yieldToMaturity,
LocalDate settlementDate) {
BigDecimal macaulayDuration = calculateMacaulayDuration(bond, yieldToMaturity, settlementDate);
BigDecimal periodYield = yieldToMaturity.divide(
new BigDecimal(bond.getCouponFrequency()), MC);
return macaulayDuration.divide(BigDecimal.ONE.add(periodYield, MC), MC);
}
private static long calculatePeriodsToMaturity(Bond bond, LocalDate settlementDate) {
long yearsToMaturity = ChronoUnit.YEARS.between(settlementDate, bond.getMaturityDate());
return yearsToMaturity * bond.getCouponFrequency();
}
private static BigDecimal calculateBondPriceDerivative(Bond bond, BigDecimal yield,
LocalDate settlementDate) {
// Numerical approximation of derivative
BigDecimal h = new BigDecimal("0.0001");
BigDecimal price1 = calculateBondPrice(bond, yield.add(h, MC), settlementDate);
BigDecimal price2 = calculateBondPrice(bond, yield.subtract(h, MC), settlementDate);
return price1.subtract(price2, MC).divide(h.multiply(new BigDecimal("2"), MC), MC);
}
}
Portfolio Calculations
1. Portfolio Risk and Return
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class PortfolioCalculations {
private static final MathContext MC = FinancialConfig.FINANCIAL_CONTEXT;
public static class Portfolio {
private final Map<String, BigDecimal> holdings; // Asset -> Weight
private final Map<String, BigDecimal> returns; // Asset -> Expected Return
private final Map<String, Map<String, BigDecimal>> covariances; // Asset1 -> (Asset2 -> Covariance)
public Portfolio() {
this.holdings = new HashMap<>();
this.returns = new HashMap<>();
this.covariances = new HashMap<>();
}
public void addAsset(String asset, BigDecimal weight, BigDecimal expectedReturn) {
holdings.put(asset, weight);
returns.put(asset, expectedReturn);
}
public void setCovariance(String asset1, String asset2, BigDecimal covariance) {
covariances.computeIfAbsent(asset1, k -> new HashMap<>()).put(asset2, covariance);
covariances.computeIfAbsent(asset2, k -> new HashMap<>()).put(asset1, covariance);
}
// Getters
public Map<String, BigDecimal> getHoldings() { return holdings; }
public Map<String, BigDecimal> getReturns() { return returns; }
public Map<String, Map<String, BigDecimal>> getCovariances() { return covariances; }
}
// Calculate portfolio expected return
public static BigDecimal calculateExpectedReturn(Portfolio portfolio) {
BigDecimal expectedReturn = BigDecimal.ZERO;
for (Map.Entry<String, BigDecimal> entry : portfolio.getHoldings().entrySet()) {
String asset = entry.getKey();
BigDecimal weight = entry.getValue();
BigDecimal assetReturn = portfolio.getReturns().get(asset);
expectedReturn = expectedReturn.add(weight.multiply(assetReturn, MC), MC);
}
return expectedReturn;
}
// Calculate portfolio variance
public static BigDecimal calculatePortfolioVariance(Portfolio portfolio) {
BigDecimal variance = BigDecimal.ZERO;
String[] assets = portfolio.getHoldings().keySet().toArray(new String[0]);
for (int i = 0; i < assets.length; i++) {
for (int j = 0; j < assets.length; j++) {
String asset1 = assets[i];
String asset2 = assets[j];
BigDecimal weight1 = portfolio.getHoldings().get(asset1);
BigDecimal weight2 = portfolio.getHoldings().get(asset2);
BigDecimal covariance = getCovariance(portfolio, asset1, asset2);
variance = variance.add(
weight1.multiply(weight2, MC).multiply(covariance, MC), MC);
}
}
return variance;
}
// Calculate portfolio standard deviation (volatility)
public static BigDecimal calculatePortfolioVolatility(Portfolio portfolio) {
BigDecimal variance = calculatePortfolioVariance(portfolio);
return sqrt(variance, 10); // 10 iterations for precision
}
// Calculate Sharpe Ratio
public static BigDecimal calculateSharpeRatio(Portfolio portfolio,
BigDecimal riskFreeRate) {
BigDecimal expectedReturn = calculateExpectedReturn(portfolio);
BigDecimal volatility = calculatePortfolioVolatility(portfolio);
BigDecimal excessReturn = expectedReturn.subtract(riskFreeRate, MC);
return excessReturn.divide(volatility, MC);
}
// Calculate portfolio beta (simplified)
public static BigDecimal calculatePortfolioBeta(Portfolio portfolio,
Map<String, BigDecimal> assetBetas) {
BigDecimal portfolioBeta = BigDecimal.ZERO;
for (Map.Entry<String, BigDecimal> entry : portfolio.getHoldings().entrySet()) {
String asset = entry.getKey();
BigDecimal weight = entry.getValue();
BigDecimal assetBeta = assetBetas.get(asset);
if (assetBeta != null) {
portfolioBeta = portfolioBeta.add(weight.multiply(assetBeta, MC), MC);
}
}
return portfolioBeta;
}
// Efficient Frontier calculation (simplified)
public static Map<BigDecimal, BigDecimal> calculateEfficientFrontier(
Portfolio portfolio, int points) {
Map<BigDecimal, BigDecimal> frontier = new HashMap<>();
BigDecimal minReturn = findMinimumReturn(portfolio);
BigDecimal maxReturn = findMaximumReturn(portfolio);
BigDecimal step = maxReturn.subtract(minReturn, MC)
.divide(new BigDecimal(points), MC);
for (int i = 0; i <= points; i++) {
BigDecimal targetReturn = minReturn.add(step.multiply(new BigDecimal(i), MC), MC);
// In practice, you would solve optimization problem here
// This is a simplified version
BigDecimal minVariance = estimateMinimumVariance(portfolio, targetReturn);
frontier.put(targetReturn, sqrt(minVariance, 10));
}
return frontier;
}
private static BigDecimal getCovariance(Portfolio portfolio, String asset1, String asset2) {
if (asset1.equals(asset2)) {
// Variance of the asset
BigDecimal return1 = portfolio.getReturns().get(asset1);
// Simplified - in practice, use historical data
return return1.multiply(return1, MC).multiply(new BigDecimal("0.1"), MC);
}
Map<String, BigDecimal> covMap = portfolio.getCovariances().get(asset1);
return covMap != null ? covMap.getOrDefault(asset2, BigDecimal.ZERO) : BigDecimal.ZERO;
}
// Square root implementation for BigDecimal
private static BigDecimal sqrt(BigDecimal value, int scale) {
BigDecimal x0 = new BigDecimal("0");
BigDecimal x1 = BigDecimal.valueOf(Math.sqrt(value.doubleValue()));
while (!x0.equals(x1)) {
x0 = x1;
x1 = value.divide(x0, scale, RoundingMode.HALF_UP);
x1 = x1.add(x0);
x1 = x1.divide(BigDecimal.valueOf(2), scale, RoundingMode.HALF_UP);
}
return x1;
}
private static BigDecimal findMinimumReturn(Portfolio portfolio) {
return portfolio.getReturns().values().stream()
.min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
}
private static BigDecimal findMaximumReturn(Portfolio portfolio) {
return portfolio.getReturns().values().stream()
.max(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
}
private static BigDecimal estimateMinimumVariance(Portfolio portfolio, BigDecimal targetReturn) {
// Simplified estimation - real implementation would use quadratic programming
BigDecimal baseVariance = calculatePortfolioVariance(portfolio);
return baseVariance.multiply(new BigDecimal("1.1"), MC); // Placeholder
}
}
Option Pricing Models
1. Black-Scholes Model
import java.math.BigDecimal;
import java.math.MathContext;
public class OptionPricing {
private static final MathContext MC = FinancialConfig.FINANCIAL_CONTEXT;
private static final BigDecimal TWO = new BigDecimal("2");
private static final BigDecimal SQRT_2PI = new BigDecimal("2.506628274631000502415765284811");
public static class Option {
private final BigDecimal spotPrice;
private final BigDecimal strikePrice;
private final BigDecimal timeToExpiry; // in years
private final BigDecimal riskFreeRate;
private final BigDecimal volatility;
private final OptionType type;
public enum OptionType { CALL, PUT }
public Option(BigDecimal spotPrice, BigDecimal strikePrice, BigDecimal timeToExpiry,
BigDecimal riskFreeRate, BigDecimal volatility, OptionType type) {
this.spotPrice = spotPrice;
this.strikePrice = strikePrice;
this.timeToExpiry = timeToExpiry;
this.riskFreeRate = riskFreeRate;
this.volatility = volatility;
this.type = type;
}
// Getters
public BigDecimal getSpotPrice() { return spotPrice; }
public BigDecimal getStrikePrice() { return strikePrice; }
public BigDecimal getTimeToExpiry() { return timeToExpiry; }
public BigDecimal getRiskFreeRate() { return riskFreeRate; }
public BigDecimal getVolatility() { return volatility; }
public OptionType getType() { return type; }
}
// Black-Scholes option pricing
public static BigDecimal calculateOptionPrice(Option option) {
BigDecimal d1 = calculateD1(option);
BigDecimal d2 = calculateD2(option, d1);
BigDecimal nd1 = cumulativeDistribution(d1);
BigDecimal nd2 = cumulativeDistribution(d2);
if (option.getType() == Option.OptionType.CALL) {
// Call price = S * N(d1) - K * e^(-rT) * N(d2)
BigDecimal firstTerm = option.getSpotPrice().multiply(nd1, MC);
BigDecimal discountFactor = exp(option.getRiskFreeRate().negate(MC)
.multiply(option.getTimeToExpiry(), MC));
BigDecimal secondTerm = option.getStrikePrice()
.multiply(discountFactor, MC)
.multiply(nd2, MC);
return firstTerm.subtract(secondTerm, MC);
} else {
// Put price = K * e^(-rT) * N(-d2) - S * N(-d1)
BigDecimal discountFactor = exp(option.getRiskFreeRate().negate(MC)
.multiply(option.getTimeToExpiry(), MC));
BigDecimal firstTerm = option.getStrikePrice()
.multiply(discountFactor, MC)
.multiply(cumulativeDistribution(d2.negate()), MC);
BigDecimal secondTerm = option.getSpotPrice()
.multiply(cumulativeDistribution(d1.negate()), MC);
return firstTerm.subtract(secondTerm, MC);
}
}
// Calculate option Greeks
public static class OptionGreeks {
public final BigDecimal delta;
public final BigDecimal gamma;
public final BigDecimal theta;
public final BigDecimal vega;
public final BigDecimal rho;
public OptionGreeks(BigDecimal delta, BigDecimal gamma, BigDecimal theta,
BigDecimal vega, BigDecimal rho) {
this.delta = delta;
this.gamma = gamma;
this.theta = theta;
this.vega = vega;
this.rho = rho;
}
}
public static OptionGreeks calculateOptionGreeks(Option option) {
BigDecimal d1 = calculateD1(option);
BigDecimal d2 = calculateD2(option, d1);
BigDecimal nd1 = cumulativeDistribution(d1);
BigDecimal pdfD1 = probabilityDensity(d1);
BigDecimal sqrtT = sqrt(option.getTimeToExpiry(), 10);
BigDecimal discountFactor = exp(option.getRiskFreeRate().negate(MC)
.multiply(option.getTimeToExpiry(), MC));
// Delta
BigDecimal delta = (option.getType() == Option.OptionType.CALL) ?
nd1 : nd1.subtract(BigDecimal.ONE, MC);
// Gamma
BigDecimal gamma = pdfD1.divide(
option.getSpotPrice().multiply(option.getVolatility(), MC).multiply(sqrtT, MC), MC);
// Theta (simplified)
BigDecimal theta = calculateTheta(option, d1, d2, pdfD1, discountFactor);
// Vega
BigDecimal vega = option.getSpotPrice().multiply(sqrtT, MC).multiply(pdfD1, MC)
.divide(new BigDecimal("100"), MC); // per 1% change in volatility
// Rho (simplified)
BigDecimal rho = calculateRho(option, d2, discountFactor);
return new OptionGreeks(delta, gamma, theta, vega, rho);
}
private static BigDecimal calculateD1(Option option) {
BigDecimal numerator = ln(option.getSpotPrice().divide(option.getStrikePrice(), MC))
.add(option.getRiskFreeRate().add(
option.getVolatility().pow(2, MC).divide(TWO, MC), MC)
.multiply(option.getTimeToExpiry(), MC), MC);
BigDecimal denominator = option.getVolatility().multiply(
sqrt(option.getTimeToExpiry(), 10), MC);
return numerator.divide(denominator, MC);
}
private static BigDecimal calculateD2(Option option, BigDecimal d1) {
return d1.subtract(option.getVolatility().multiply(
sqrt(option.getTimeToExpiry(), 10), MC), MC);
}
// Cumulative distribution function for standard normal distribution
private static BigDecimal cumulativeDistribution(BigDecimal x) {
// Approximation using error function
BigDecimal t = BigDecimal.ONE.divide(BigDecimal.ONE.add(
new BigDecimal("0.2316419").multiply(x.abs(), MC), MC), MC);
BigDecimal d = new BigDecimal("0.319381530");
d = d.multiply(t, MC).subtract(new BigDecimal("0.356563782").multiply(t.pow(2, MC), MC), MC);
d = d.add(new BigDecimal("1.781477937").multiply(t.pow(3, MC), MC), MC);
d = d.subtract(new BigDecimal("1.821255978").multiply(t.pow(4, MC), MC), MC);
d = d.add(new BigDecimal("1.330274429").multiply(t.pow(5, MC), MC), MC);
BigDecimal pdf = probabilityDensity(x);
BigDecimal result = BigDecimal.ONE.subtract(pdf.multiply(d, MC), MC);
return x.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ONE.subtract(result, MC) : result;
}
// Probability density function for standard normal distribution
private static BigDecimal probabilityDensity(BigDecimal x) {
BigDecimal exponent = x.pow(2, MC).divide(TWO, MC).negate(MC);
return exp(exponent).divide(SQRT_2PI, MC);
}
// Natural logarithm for BigDecimal
private static BigDecimal ln(BigDecimal x) {
return new BigDecimal(Math.log(x.doubleValue()));
}
// Exponential function for BigDecimal
private static BigDecimal exp(BigDecimal x) {
return new BigDecimal(Math.exp(x.doubleValue()));
}
private static BigDecimal calculateTheta(Option option, BigDecimal d1, BigDecimal d2,
BigDecimal pdfD1, BigDecimal discountFactor) {
// Simplified theta calculation
BigDecimal term1 = option.getSpotPrice().multiply(pdfD1, MC)
.multiply(option.getVolatility(), MC)
.divide(TWO.multiply(sqrt(option.getTimeToExpiry(), 10), MC), MC);
BigDecimal term2 = option.getRiskFreeRate().multiply(option.getStrikePrice(), MC)
.multiply(discountFactor, MC)
.multiply(cumulativeDistribution(d2), MC);
return term1.add(term2, MC).negate(MC).divide(new BigDecimal("365"), MC); // per day
}
private static BigDecimal calculateRho(Option option, BigDecimal d2,
BigDecimal discountFactor) {
BigDecimal rho = option.getStrikePrice().multiply(option.getTimeToExpiry(), MC)
.multiply(discountFactor, MC)
.multiply(cumulativeDistribution(d2), MC);
return rho.divide(new BigDecimal("100"), MC); // per 1% change in interest rate
}
}
Using Java Money API
1. Currency Handling
import org.javamoney.moneta.Money;
import org.javamoney.moneta.format.CurrencyStyle;
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import javax.money.MonetaryAmount;
import javax.money.convert.CurrencyConversion;
import javax.money.convert.MonetaryConversions;
import java.math.BigDecimal;
import java.util.Locale;
public class CurrencyCalculations {
public static void demonstrateMoneyAPI() {
// Create monetary amounts
CurrencyUnit usd = Monetary.getCurrency("USD");
CurrencyUnit eur = Monetary.getCurrency("EUR");
Money price1 = Money.of(100, usd);
Money price2 = Money.of(75, eur);
// Basic arithmetic
Money total = price1.add(price2); // Requires same currency
Money discount = price1.multiply(new BigDecimal("0.1"));
System.out.println("Price 1: " + price1);
System.out.println("Price 2: " + price2);
System.out.println("Discount: " + discount);
// Currency conversion (requires conversion provider)
try {
CurrencyConversion usdToEur = MonetaryConversions.getConversion("EUR");
Money converted = price1.with(usdToEur);
System.out.println("Converted: " + converted);
} catch (Exception e) {
System.out.println("Conversion not available: " + e.getMessage());
}
// Formatting
String formatted = price1.toString();
System.out.println("Formatted: " + formatted);
}
public static class FinancialInstrument {
private final String symbol;
private final MonetaryAmount price;
private final int quantity;
public FinancialInstrument(String symbol, MonetaryAmount price, int quantity) {
this.symbol = symbol;
this.price = price;
this.quantity = quantity;
}
public MonetaryAmount calculateValue() {
return price.multiply(quantity);
}
public MonetaryAmount calculateValueInCurrency(CurrencyUnit targetCurrency) {
try {
CurrencyConversion conversion = MonetaryConversions.getConversion(targetCurrency);
return calculateValue().with(conversion);
} catch (Exception e) {
throw new RuntimeException("Conversion failed", e);
}
}
}
public static void demonstratePortfolioValue() {
CurrencyUnit usd = Monetary.getCurrency("USD");
CurrencyUnit eur = Monetary.getCurrency("EUR");
FinancialInstrument stock1 = new FinancialInstrument("AAPL", Money.of(150, usd), 100);
FinancialInstrument stock2 = new FinancialInstrument("SAP", Money.of(120, eur), 50);
MonetaryAmount value1 = stock1.calculateValue();
MonetaryAmount value2 = stock2.calculateValueInCurrency(usd);
System.out.println("Stock 1 value: " + value1);
System.out.println("Stock 2 value (USD): " + value2);
MonetaryAmount totalValue = value1.add(value2);
System.out.println("Total portfolio value: " + totalValue);
}
}
Best Practices for Financial Calculations
1. Precision and Rounding
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
public class FinancialBestPractices {
public static class FinancialRounding {
private final int scale;
private final RoundingMode roundingMode;
public FinancialRounding(int scale, RoundingMode roundingMode) {
this.scale = scale;
this.roundingMode = roundingMode;
}
public BigDecimal round(BigDecimal value) {
return value.setScale(scale, roundingMode);
}
public Money round(Money money) {
return Money.of(round(money.getNumber().numberValue(BigDecimal.class)),
money.getCurrency());
}
// Common rounding standards
public static FinancialRounding CURRENCY_ROUNDING =
new FinancialRounding(2, RoundingMode.HALF_EVEN);
public static FinancialRounding INTEREST_RATE_ROUNDING =
new FinancialRounding(6, RoundingMode.HALF_UP);
public static FinancialRounding PERCENTAGE_ROUNDING =
new FinancialRounding(4, RoundingMode.HALF_EVEN);
}
public static class FinancialValidator {
public static void validatePositive(BigDecimal value, String fieldName) {
if (value == null || value.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException(fieldName + " must be positive");
}
}
public static void validatePercentage(BigDecimal value, String fieldName) {
if (value == null || value.compareTo(BigDecimal.ZERO) < 0 ||
value.compareTo(new BigDecimal("100")) > 0) {
throw new IllegalArgumentException(
fieldName + " must be between 0 and 100");
}
}
public static void validateWeights(List<BigDecimal> weights) {
BigDecimal sum = weights.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (sum.compareTo(BigDecimal.ONE) != 0) {
throw new IllegalArgumentException("Weights must sum to 1.0");
}
}
}
public static class FinancialFormatter {
public static String formatCurrency(BigDecimal amount, String currencyCode) {
return String.format("%,.2f %s", amount, currencyCode);
}
public static String formatPercentage(BigDecimal percentage) {
return String.format("%,.4f%%", percentage.multiply(new BigDecimal("100")));
}
public static String formatBasisPoints(BigDecimal value) {
return String.format("%,.2f bps", value.multiply(new BigDecimal("10000")));
}
}
public static void demonstrateBestPractices() {
// Rounding examples
BigDecimal rawValue = new BigDecimal("123.456789");
BigDecimal roundedCurrency = FinancialRounding.CURRENCY_ROUNDING.round(rawValue);
BigDecimal roundedRate = FinancialRounding.INTEREST_RATE_ROUNDING.round(rawValue);
System.out.println("Raw: " + rawValue);
System.out.println("Currency rounded: " + roundedCurrency);
System.out.println("Rate rounded: " + roundedRate);
// Validation examples
try {
FinancialValidator.validatePositive(new BigDecimal("-100"), "Amount");
} catch (IllegalArgumentException e) {
System.out.println("Validation caught: " + e.getMessage());
}
// Formatting examples
String formattedCurrency = FinancialFormatter.formatCurrency(
new BigDecimal("1234567.89"), "USD");
String formattedPercentage = FinancialFormatter.formatPercentage(
new BigDecimal("0.123456"));
System.out.println("Formatted currency: " + formattedCurrency);
System.out.println("Formatted percentage: " + formattedPercentage);
}
}
These financial calculation examples provide a solid foundation for building financial applications in Java. Remember to always test thoroughly with known values and consider regulatory requirements for financial calculations in production systems.