Financial Mathematics¶
Deterministic time-value-of-money, rate conversion, annuity, bond, loan,
corporate-finance, equity-valuation, portfolio-primitive, and VaR/CVaR
building blocks from abaquant.financial_math.
Sections:
Time value of money
Rate conversions and annuities
Bonds and irregular cash flows
Corporate finance (CAPM, WACC, DCF)
Portfolio primitives
Risk (VaR/CVaR) and loan amortization
Beta and alpha estimation
Setup¶
import abaquant
print(f"AbaQuant version: {abaquant.__version__}")
AbaQuant version: 1.0.0rc1
import numpy as np
import pandas as pd
from abaquant.financial_math.annuities import (
arithmetic_gradient_present_value,
effective_annuity_present_value,
geometric_gradient_present_value,
perpetuity_present_value,
)
from abaquant.financial_math.bonds import bond_price, bond_risk, bond_yield
from abaquant.financial_math.cashflows import present_value_of_irregular_cashflows
from abaquant.financial_math.corporate import (
beta_alpha_from_returns,
capm_cost_of_equity,
dcf_sensitivity_matrix,
dcf_valuation,
weighted_average_cost_of_capital,
)
from abaquant.financial_math.equity import gordon_shapiro_valuation, multiples_valuation
from abaquant.financial_math.loans import amortization_schedule
from abaquant.financial_math.portfolio import (
annualized_covariance_from_returns,
annualized_mean_returns_from_returns,
equal_weight,
maximum_sharpe_weights,
portfolio_sharpe,
portfolio_volatility,
simple_returns_from_prices,
)
from abaquant.financial_math.rates import (
continuous_to_effective_rate,
convert_nominal_frequency,
nominal_to_effective_rate,
)
from abaquant.financial_math.risk import monte_carlo_var_cvar, parametric_var
from abaquant.financial_math.tvm import future_value, present_value, rate_of_return
A small deterministic price panel¶
Several sections below (portfolio primitives, beta/alpha) need a return series. We build one synthetic, reproducible price panel for three assets.
def sample_prices() -> pd.DataFrame:
dates = pd.date_range("2025-01-02", periods=36, freq="B")
trend = np.linspace(0.0, 1.0, len(dates))
seasonal = np.sin(np.linspace(0.0, 4.0 * np.pi, len(dates)))
return pd.DataFrame(
{
"ALPHA": 100.0 + 9.0 * trend + 1.8 * seasonal,
"BETA": 82.0 + 6.0 * trend - 1.2 * seasonal,
"GAMMA": 54.0 + 3.0 * trend + 0.7 * np.cos(np.linspace(0.0, 3.0 * np.pi, len(dates))),
},
index=dates,
)
prices = sample_prices()
prices.head()
| ALPHA | BETA | GAMMA | |
|---|---|---|---|
| 2025-01-02 | 100.000000 | 82.000000 | 54.700000 |
| 2025-01-03 | 100.889618 | 81.749779 | 54.760488 |
| 2025-01-06 | 101.698575 | 81.553331 | 54.772343 |
| 2025-01-07 | 102.356501 | 81.457571 | 54.740887 |
| 2025-01-08 | 102.812281 | 81.496575 | 54.674565 |
1. Time value of money¶
Basic accumulation and discounting, plus solving for the periodic rate of return implied by a start value, end value, and horizon.
fv = future_value(1_000.0, 0.06, 5.0)
pv = present_value(1_500.0, 0.06, 5.0)
required_return = rate_of_return(1_000.0, 1_500.0, 5.0)
print(f"Future value: {fv:.4f}")
print(f"Present value: {pv:.4f}")
print(f"Required periodic return: {required_return:.4%}")
Future value: 1338.2256
Present value: 1120.8873
Required periodic return: 8.4472%
2. Rate conversions and annuities¶
Convert between nominal, effective, and continuous rates, and value ordinary annuities, perpetuities, and gradient (growing) payment streams.
rates_and_annuities = {
"effective_from_nominal": nominal_to_effective_rate(0.06, 12),
"effective_from_continuous": continuous_to_effective_rate(0.058),
"semiannual_to_monthly_nominal": convert_nominal_frequency(0.06, 2, 12),
"ordinary_annuity_pv": effective_annuity_present_value(100.0, 0.01, 24),
"perpetuity_pv": perpetuity_present_value(10.0, 0.04),
"geometric_gradient_pv": geometric_gradient_present_value(100.0, 0.05, 0.02, 10),
"arithmetic_gradient_pv": arithmetic_gradient_present_value(100.0, 5.0, 0.05, 10),
}
for key, value in rates_and_annuities.items():
print(f"{key:32s}: {value:.6f}")
effective_from_nominal : 0.061678
effective_from_continuous : 0.059715
semiannual_to_monthly_nominal : 0.059263
ordinary_annuity_pv : 2124.338726
perpetuity_pv : 250.000000
geometric_gradient_pv : 838.810565
arithmetic_gradient_pv : 930.433732
3. Bonds and irregular cash flows¶
Price a coupon bond, back out its yield to maturity, compute duration and convexity, and discount an irregular set of dated cash flows.
price, coupon, coupon_pv, redemption_pv = bond_price(1_000.0, 0.04, 1_000.0, 0.045, 8)
_duration, convexity, modified_duration = bond_risk(1_000.0, 0.04, 1_000.0, 0.045, 8, 2)
ytm = bond_yield(price, 1_000.0, 0.04, 1_000.0, 8)
irregular_pv = present_value_of_irregular_cashflows([100, 150, 1_200], [0.5, 1.0, 2.0], 0.05)
print(f"Bond price: {price:.4f}")
print(f"Periodic coupon: {coupon:.4f}")
print(f"Yield to maturity: {ytm:.6f}")
print(f"Modified duration: {modified_duration:.4f}")
print(f"Convexity: {convexity:.4f}")
print(f"Irregular cash-flow PV: {irregular_pv:.4f}")
Bond price: 967.0206
Periodic coupon: 40.0000
Yield to maturity: 0.045000
Modified duration: 13.7170
Convexity: 3.3407
Irregular cash-flow PV: 1326.0203
4. Corporate finance¶
CAPM cost of equity, WACC, a discounted-cash-flow (DCF) valuation, a DCF sensitivity matrix, and simple equity-valuation multiples.
dcf = dcf_valuation(100.0, 0.06, 0.025, 0.09, 5, 250.0, 50.0)
matrix = dcf_sensitivity_matrix([100, 106, 112], [0.02, 0.025], [0.08, 0.09], 250.0, 50.0)
corporate_finance = {
"capm_cost_of_equity": capm_cost_of_equity(0.04, 1.2, 0.09),
"wacc": weighted_average_cost_of_capital(0.10, 0.7, 0.05, 0.25),
"dcf_equity_value_per_share": float(dcf["price_per_share"]),
"dcf_sensitivity_low_discount": float(matrix[0, 0]),
"gordon_value": gordon_shapiro_valuation(2.0, 0.09, 0.03),
"multiple_value": multiples_valuation(8.0, 20.0),
}
for key, value in corporate_finance.items():
print(f"{key:32s}: {value:.6f}")
capm_cost_of_equity : 0.100000
wacc : 0.081250
dcf_equity_value_per_share : 31.634722
dcf_sensitivity_low_discount : 30.676726
gordon_value : 33.333333
multiple_value : 160.000000
5. Portfolio primitives¶
Compute simple returns, annualized mean/covariance, and the maximum-Sharpe
weight vector directly from a price panel (a lower-level alternative to the
PortfolioAllocator facade covered in notebook 05).
returns = simple_returns_from_prices(prices)
mean_returns = annualized_mean_returns_from_returns(returns)
covariance = annualized_covariance_from_returns(returns)
weights = maximum_sharpe_weights(
mean_returns.to_numpy(), covariance.to_numpy(), risk_free_rate=0.02
)
vol = portfolio_volatility(weights, covariance.to_numpy())
sharpe = portfolio_sharpe(float(np.dot(weights, mean_returns.to_numpy())), vol, risk_free_rate=0.02)
print(f"Equal-weight ALPHA weight: {float(equal_weight(3)[0]):.4f}")
print(f"Max-Sharpe ALPHA weight: {float(weights[0]):.4f}")
print(f"Portfolio volatility: {vol:.4f}")
print(f"Portfolio Sharpe ratio: {sharpe:.4f}")
Equal-weight ALPHA weight: 0.3333
Max-Sharpe ALPHA weight: 0.4500
Portfolio volatility: 0.0009
Portfolio Sharpe ratio: 626.4701
6. Risk (VaR/CVaR) and loan amortization¶
A level-payment loan schedule, parametric Value-at-Risk diagnostics, and a Monte Carlo VaR/CVaR estimate.
schedule = amortization_schedule(10_000.0, 0.01, 12)
var_value, z_score, _, _ = parametric_var(0.08, 0.20, 1_000_000.0, 0.95, 10)
mc_var, mc_cvar = monte_carlo_var_cvar(0.08, 0.20, 1_000_000.0, 0.95, 10, simulations=5_000)
print(f"First loan payment (interest + principal): "
f"{float(schedule.iloc[0]['Interest'] + schedule.iloc[0]['Amortization']):.4f}")
print(f"Parametric VaR (10-day, 95%): {var_value:.2f}")
print(f"Parametric z-score: {z_score:.4f}")
print(f"Monte Carlo VaR: {mc_var:.2f}")
print(f"Monte Carlo CVaR: {mc_cvar:.2f}")
First loan payment (interest + principal): 888.4879
Parametric VaR (10-day, 95%): 62357.93
Parametric z-score: 1.6449
Monte Carlo VaR: 64017.62
Monte Carlo CVaR: 80127.79
schedule.head()
| Period | Opening balance | Interest | Amortization | Outstanding balance | |
|---|---|---|---|---|---|
| 0 | 1 | 10000.000000 | 100.000000 | 788.487887 | 9211.512113 |
| 1 | 2 | 9211.512113 | 92.115121 | 796.372766 | 8415.139348 |
| 2 | 3 | 8415.139348 | 84.151393 | 804.336493 | 7610.802854 |
| 3 | 4 | 7610.802854 | 76.108029 | 812.379858 | 6798.422996 |
| 4 | 5 | 6798.422996 | 67.984230 | 820.503657 | 5977.919339 |
7. Beta and alpha estimation¶
Regress one asset’s returns on another to estimate CAPM beta and alpha.
price_returns = prices.pct_change().dropna()
regression = beta_alpha_from_returns(
price_returns["ALPHA"], price_returns["BETA"], risk_free_rate=0.02
)
print(f"Beta: {float(regression['beta']):.4f}")
print(f"Alpha: {float(regression['alpha']):.6f}")
Beta: -1.2204
Alpha: 1.202752
Takeaway¶
abaquant.financial_math is the pure, deterministic layer underneath
higher-level facades like PortfolioAllocator and MarketTicker. It’s a
good place to reach for compact, explicit calculations inside vectorized or
tabular workflows.