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.