Financial Calculations in Java

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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper