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:

  1. Time value of money

  2. Rate conversions and annuities

  3. Bonds and irregular cash flows

  4. Corporate finance (CAPM, WACC, DCF)

  5. Portfolio primitives

  6. Risk (VaR/CVaR) and loan amortization

  7. 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.