447 lines
10 KiB
TypeScript
447 lines
10 KiB
TypeScript
/**
|
|
* Basic Financial Calculations
|
|
* Core mathematical functions for financial analysis
|
|
*/
|
|
|
|
/**
|
|
* Calculate percentage change between two values
|
|
*/
|
|
export function percentageChange(oldValue: number, newValue: number): number {
|
|
if (oldValue === 0) {
|
|
return 0;
|
|
}
|
|
return ((newValue - oldValue) / oldValue) * 100;
|
|
}
|
|
|
|
/**
|
|
* Calculate simple return
|
|
*/
|
|
export function simpleReturn(initialPrice: number, finalPrice: number): number {
|
|
if (initialPrice === 0) {
|
|
return 0;
|
|
}
|
|
return (finalPrice - initialPrice) / initialPrice;
|
|
}
|
|
|
|
/**
|
|
* Calculate logarithmic return
|
|
*/
|
|
export function logReturn(initialPrice: number, finalPrice: number): number {
|
|
if (initialPrice <= 0 || finalPrice <= 0) {
|
|
return 0;
|
|
}
|
|
return Math.log(finalPrice / initialPrice);
|
|
}
|
|
|
|
/**
|
|
* Calculate compound annual growth rate (CAGR)
|
|
*/
|
|
export function cagr(startValue: number, endValue: number, years: number): number {
|
|
if (years <= 0 || startValue <= 0 || endValue <= 0) {
|
|
return 0;
|
|
}
|
|
return Math.pow(endValue / startValue, 1 / years) - 1;
|
|
}
|
|
|
|
/**
|
|
* Calculate annualized return from periodic returns
|
|
*/
|
|
export function annualizeReturn(periodicReturn: number, periodsPerYear: number): number {
|
|
return Math.pow(1 + periodicReturn, periodsPerYear) - 1;
|
|
}
|
|
|
|
/**
|
|
* Calculate annualized volatility from periodic returns
|
|
*/
|
|
export function annualizeVolatility(periodicVolatility: number, periodsPerYear: number): number {
|
|
return periodicVolatility * Math.sqrt(periodsPerYear);
|
|
}
|
|
|
|
/**
|
|
* Calculate present value
|
|
*/
|
|
export function presentValue(futureValue: number, rate: number, periods: number): number {
|
|
return futureValue / Math.pow(1 + rate, periods);
|
|
}
|
|
|
|
/**
|
|
* Calculate future value
|
|
*/
|
|
export function futureValue(presentValue: number, rate: number, periods: number): number {
|
|
return presentValue * Math.pow(1 + rate, periods);
|
|
}
|
|
|
|
/**
|
|
* Calculate net present value of cash flows
|
|
*/
|
|
export function netPresentValue(cashFlows: number[], discountRate: number): number {
|
|
return cashFlows.reduce((npv, cashFlow, index) => {
|
|
return npv + cashFlow / Math.pow(1 + discountRate, index);
|
|
}, 0);
|
|
}
|
|
|
|
/**
|
|
* Calculate internal rate of return (IRR) using Newton-Raphson method
|
|
*/
|
|
export function internalRateOfReturn(
|
|
cashFlows: number[],
|
|
guess: number = 0.1,
|
|
maxIterations: number = 100
|
|
): number {
|
|
let rate = guess;
|
|
|
|
for (let i = 0; i < maxIterations; i++) {
|
|
let npv = 0;
|
|
let dnpv = 0;
|
|
|
|
for (let j = 0; j < cashFlows.length; j++) {
|
|
npv += cashFlows[j] / Math.pow(1 + rate, j);
|
|
dnpv += (-j * cashFlows[j]) / Math.pow(1 + rate, j + 1);
|
|
}
|
|
|
|
if (Math.abs(npv) < 1e-10) {
|
|
break;
|
|
}
|
|
if (Math.abs(dnpv) < 1e-10) {
|
|
break;
|
|
}
|
|
|
|
rate = rate - npv / dnpv;
|
|
}
|
|
|
|
return rate;
|
|
}
|
|
|
|
/**
|
|
* Calculate payback period
|
|
*/
|
|
export function paybackPeriod(initialInvestment: number, cashFlows: number[]): number {
|
|
let cumulativeCashFlow = 0;
|
|
|
|
for (let i = 0; i < cashFlows.length; i++) {
|
|
cumulativeCashFlow += cashFlows[i];
|
|
if (cumulativeCashFlow >= initialInvestment) {
|
|
return i + 1 - (cumulativeCashFlow - initialInvestment) / cashFlows[i];
|
|
}
|
|
}
|
|
|
|
return -1; // Never pays back
|
|
}
|
|
|
|
/**
|
|
* Calculate compound interest
|
|
*/
|
|
export function compoundInterest(
|
|
principal: number,
|
|
rate: number,
|
|
periods: number,
|
|
compoundingFrequency: number = 1
|
|
): number {
|
|
return principal * Math.pow(1 + rate / compoundingFrequency, compoundingFrequency * periods);
|
|
}
|
|
|
|
/**
|
|
* Calculate effective annual rate
|
|
*/
|
|
export function effectiveAnnualRate(nominalRate: number, compoundingFrequency: number): number {
|
|
return Math.pow(1 + nominalRate / compoundingFrequency, compoundingFrequency) - 1;
|
|
}
|
|
|
|
/**
|
|
* Calculate bond price given yield
|
|
*/
|
|
export function bondPrice(
|
|
faceValue: number,
|
|
couponRate: number,
|
|
yieldToMaturity: number,
|
|
periodsToMaturity: number,
|
|
paymentsPerYear: number = 2
|
|
): number {
|
|
const couponPayment = (faceValue * couponRate) / paymentsPerYear;
|
|
const discountRate = yieldToMaturity / paymentsPerYear;
|
|
|
|
let price = 0;
|
|
|
|
// Present value of coupon payments
|
|
for (let i = 1; i <= periodsToMaturity; i++) {
|
|
price += couponPayment / Math.pow(1 + discountRate, i);
|
|
}
|
|
|
|
// Present value of face value
|
|
price += faceValue / Math.pow(1 + discountRate, periodsToMaturity);
|
|
|
|
return price;
|
|
}
|
|
|
|
/**
|
|
* Calculate bond yield given price (Newton-Raphson approximation)
|
|
*/
|
|
export function bondYield(
|
|
price: number,
|
|
faceValue: number,
|
|
couponRate: number,
|
|
periodsToMaturity: number,
|
|
paymentsPerYear: number = 2,
|
|
guess: number = 0.05
|
|
): number {
|
|
let yield_ = guess;
|
|
const maxIterations = 100;
|
|
const tolerance = 1e-8;
|
|
|
|
for (let i = 0; i < maxIterations; i++) {
|
|
const calculatedPrice = bondPrice(
|
|
faceValue,
|
|
couponRate,
|
|
yield_,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
const diff = calculatedPrice - price;
|
|
|
|
if (Math.abs(diff) < tolerance) {
|
|
break;
|
|
}
|
|
|
|
// Numerical derivative
|
|
const delta = 0.0001;
|
|
const priceUp = bondPrice(
|
|
faceValue,
|
|
couponRate,
|
|
yield_ + delta,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
const derivative = (priceUp - calculatedPrice) / delta;
|
|
|
|
if (Math.abs(derivative) < tolerance) {
|
|
break;
|
|
}
|
|
|
|
yield_ = yield_ - diff / derivative;
|
|
}
|
|
|
|
return yield_;
|
|
}
|
|
|
|
/**
|
|
* Calculate duration (Macaulay duration)
|
|
*/
|
|
export function macaulayDuration(
|
|
faceValue: number,
|
|
couponRate: number,
|
|
yieldToMaturity: number,
|
|
periodsToMaturity: number,
|
|
paymentsPerYear: number = 2
|
|
): number {
|
|
const couponPayment = (faceValue * couponRate) / paymentsPerYear;
|
|
const discountRate = yieldToMaturity / paymentsPerYear;
|
|
const bondPriceValue = bondPrice(
|
|
faceValue,
|
|
couponRate,
|
|
yieldToMaturity,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
|
|
let weightedTime = 0;
|
|
|
|
// Weighted time of coupon payments
|
|
for (let i = 1; i <= periodsToMaturity; i++) {
|
|
const presentValue = couponPayment / Math.pow(1 + discountRate, i);
|
|
weightedTime += (i * presentValue) / bondPriceValue;
|
|
}
|
|
|
|
// Weighted time of face value
|
|
const faceValuePV = faceValue / Math.pow(1 + discountRate, periodsToMaturity);
|
|
weightedTime += (periodsToMaturity * faceValuePV) / bondPriceValue;
|
|
|
|
return weightedTime / paymentsPerYear; // Convert to years
|
|
}
|
|
|
|
/**
|
|
* Calculate modified duration
|
|
*/
|
|
export function modifiedDuration(
|
|
faceValue: number,
|
|
couponRate: number,
|
|
yieldToMaturity: number,
|
|
periodsToMaturity: number,
|
|
paymentsPerYear: number = 2
|
|
): number {
|
|
const macDuration = macaulayDuration(
|
|
faceValue,
|
|
couponRate,
|
|
yieldToMaturity,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
return macDuration / (1 + yieldToMaturity / paymentsPerYear);
|
|
}
|
|
|
|
/**
|
|
* Calculate bond convexity
|
|
*/
|
|
export function bondConvexity(
|
|
faceValue: number,
|
|
couponRate: number,
|
|
yieldToMaturity: number,
|
|
periodsToMaturity: number,
|
|
paymentsPerYear: number = 2
|
|
): number {
|
|
const couponPayment = (faceValue * couponRate) / paymentsPerYear;
|
|
const discountRate = yieldToMaturity / paymentsPerYear;
|
|
|
|
let convexity = 0;
|
|
const bondPriceValue = bondPrice(
|
|
faceValue,
|
|
couponRate,
|
|
yieldToMaturity,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
|
|
for (let i = 1; i <= periodsToMaturity; i++) {
|
|
const presentValue = couponPayment / Math.pow(1 + discountRate, i);
|
|
convexity += (i * (i + 1) * presentValue) / Math.pow(1 + discountRate, 2);
|
|
}
|
|
|
|
const faceValuePV = faceValue / Math.pow(1 + discountRate, periodsToMaturity);
|
|
convexity +=
|
|
(periodsToMaturity * (periodsToMaturity + 1) * faceValuePV) / Math.pow(1 + discountRate, 2);
|
|
|
|
return convexity / (bondPriceValue * paymentsPerYear * paymentsPerYear);
|
|
}
|
|
|
|
/**
|
|
* Calculate dollar duration
|
|
*/
|
|
export function dollarDuration(
|
|
faceValue: number,
|
|
couponRate: number,
|
|
yieldToMaturity: number,
|
|
periodsToMaturity: number,
|
|
paymentsPerYear: number = 2,
|
|
basisPointChange: number = 0.01 // 1 basis point = 0.01%
|
|
): number {
|
|
const modifiedDur = modifiedDuration(
|
|
faceValue,
|
|
couponRate,
|
|
yieldToMaturity,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
const bondPriceValue = bondPrice(
|
|
faceValue,
|
|
couponRate,
|
|
yieldToMaturity,
|
|
periodsToMaturity,
|
|
paymentsPerYear
|
|
);
|
|
return modifiedDur * bondPriceValue * basisPointChange;
|
|
}
|
|
|
|
/**
|
|
* Calculate accrued interest
|
|
*/
|
|
export function accruedInterest(
|
|
faceValue: number,
|
|
couponRate: number,
|
|
daysSinceLastCoupon: number,
|
|
daysInCouponPeriod: number
|
|
): number {
|
|
return faceValue * couponRate * (daysSinceLastCoupon / daysInCouponPeriod);
|
|
}
|
|
|
|
/**
|
|
* Calculate clean price
|
|
*/
|
|
export function cleanPrice(dirtyPrice: number, accruedInterestValue: number): number {
|
|
return dirtyPrice - accruedInterestValue;
|
|
}
|
|
|
|
/**
|
|
* Calculate dirty price
|
|
*/
|
|
export function dirtyPrice(cleanPriceValue: number, accruedInterestValue: number): number {
|
|
return cleanPriceValue + accruedInterestValue;
|
|
}
|
|
|
|
/**
|
|
* Calculate dividend discount model (DDM)
|
|
*/
|
|
export function dividendDiscountModel(
|
|
currentDividend: number,
|
|
growthRate: number,
|
|
discountRate: number
|
|
): number {
|
|
if (discountRate <= growthRate) {
|
|
return NaN;
|
|
} // Indeterminate
|
|
return (currentDividend * (1 + growthRate)) / (discountRate - growthRate);
|
|
}
|
|
|
|
/**
|
|
* Calculate weighted average cost of capital (WACC)
|
|
*/
|
|
export function weightedAverageCostOfCapital(
|
|
costOfEquity: number,
|
|
costOfDebt: number,
|
|
equityWeight: number,
|
|
debtWeight: number,
|
|
taxRate: number
|
|
): number {
|
|
return equityWeight * costOfEquity + debtWeight * costOfDebt * (1 - taxRate);
|
|
}
|
|
|
|
/**
|
|
* Calculate capital asset pricing model (CAPM)
|
|
*/
|
|
export function capitalAssetPricingModel(
|
|
riskFreeRate: number,
|
|
beta: number,
|
|
marketRiskPremium: number
|
|
): number {
|
|
return riskFreeRate + beta * marketRiskPremium;
|
|
}
|
|
|
|
/**
|
|
* Calculate hurdle rate
|
|
*/
|
|
export function hurdleRate(costOfCapital: number, riskPremium: number): number {
|
|
return costOfCapital + riskPremium;
|
|
}
|
|
|
|
/**
|
|
* Calculate degree of operating leverage (DOL)
|
|
*/
|
|
export function degreeOfOperatingLeverage(
|
|
contributionMargin: number,
|
|
operatingIncome: number
|
|
): number {
|
|
return contributionMargin / operatingIncome;
|
|
}
|
|
|
|
/**
|
|
* Calculate degree of financial leverage (DFL)
|
|
*/
|
|
export function degreeOfFinancialLeverage(ebit: number, earningsBeforeTax: number): number {
|
|
return ebit / earningsBeforeTax;
|
|
}
|
|
|
|
/**
|
|
* Calculate degree of total leverage (DTL)
|
|
*/
|
|
export function degreeOfTotalLeverage(dol: number, dfl: number): number {
|
|
return dol * dfl;
|
|
}
|
|
|
|
/**
|
|
* Calculate economic value added (EVA)
|
|
*/
|
|
export function economicValueAdded(
|
|
netOperatingProfitAfterTax: number,
|
|
capitalInvested: number,
|
|
wacc: number
|
|
): number {
|
|
return netOperatingProfitAfterTax - capitalInvested * wacc;
|
|
}
|