Python est devenu l’outil “couteau suisse” des analystes et contrôleurs financiers. Ce guide rassemble les fonctions de base dont vous aurez besoin au quotidien : valeur temps de l’argent, VAN/TRI (et leurs versions avec dates), prêts, obligations, rendements/volatilité/Sharpe, et un mini-kit pour démarrer proprement vos calculs.
Notation : on adopte la convention Excel/financière : entrées d’argent positives, sorties négatives. Les taux périodiques doivent être cohérents avec le nombre de périodes (mensuel, annuel, etc.).
python -m pip install --upgrade pip
pip install numpy pandas
import numpy as np
import pandas as pd
from datetime import datetime, date
PV, FV, PMTFonctions de base (compatibles avec la logique Excel, y compris le paramètre when pour paiements en début ou fin de période) :
def pv(rate, nper, pmt=0.0, fv=0.0, when='end'):
"""Valeur actuelle d'une série de flux (convention Excel)."""
when = 1 if when == 'begin' else 0
if rate == 0:
return - (pmt * nper + fv)
return - (pmt * (1 + rate * when) * (1 - (1 + rate)**-nper) / rate + fv * (1 + rate)**-nper)
def fv(rate, nper, pmt=0.0, pv_=0.0, when='end'):
"""Valeur future d'une série de flux (convention Excel)."""
when = 1 if when == 'begin' else 0
if rate == 0:
return - (pv_ + pmt * nper)
return - (pv_ * (1 + rate)**nper + pmt * (1 + rate * when) * ((1 + rate)**nper - 1) / rate)
def pmt(rate, nper, pv_, fv=0.0, when='end'):
"""Annuité constante (mensualité de prêt, etc.)."""
when = 1 if when == 'begin' else 0
if rate == 0:
return - (pv_ + fv) / nper
return - (rate * (pv_ * (1 + rate)**nper + fv)) / ((1 + rate * when) * ((1 + rate)**nper - 1))
Exemple (prêt) : 100 000 €, 5% annuel, 20 ans, mensualités (taux mensuel = 0,05/12 ; nper = 20×12).
La mensualité vaut ≈ 659,96 € (sortie de trésorerie).
mensualite = pmt(0.05/12, 20*12, 100_000)
def npv(rate, cashflows):
"""VAN classique (incluant le flux t=0)."""
return sum(cf / (1 + rate)**t for t, cf in enumerate(cashflows))
def irr(cashflows, guess=0.1, tol=1e-8, maxiter=200):
"""TRI via méthode de Newton (flux périodiques)."""
def f(r):
return sum(cf / (1 + r)**t for t, cf in enumerate(cashflows))
def fprime(r):
return sum(-t * cf / (1 + r)**(t + 1) for t, cf in enumerate(cashflows) if t > 0)
r = guess
for _ in range(maxiter):
y, yp = f(r), fprime(r)
if yp == 0:
break
r_new = r - y / yp
if abs(r_new - r) < tol:
return r_new
r = r_new
return r
Exemple : flux [-1000, 300, 400, 500, 600] au taux 8% → VAN ≈ 458,65 ; TRI ≈ 24,89%.
Quand les dates ne sont pas espacées régulièrement (cas réel : investissements, coupons décalés), utilisez la version “avec dates” :
def xnpv(rate, cashflows, dates):
"""VAN à dates irrégulières (ACT/365 simple)."""
d0 = dates[0]
return sum(cf / (1 + rate)**(((d - d0).days) / 365.0) for cf, d in zip(cashflows, dates))
def xirr(cashflows, dates, guess=0.1, tol=1e-8, maxiter=100):
"""TRI à dates irrégulières (Newton)."""
r = guess
for _ in range(maxiter):
d0 = dates[0]
f = sum(cf / (1 + r)**(((d - d0).days) / 365.0) for cf, d in zip(cashflows, dates))
df = sum(-( (d - d0).days / 365.0 ) * cf / (1 + r)**((((d - d0).days) / 365.0) + 1)
for cf, d in zip(cashflows, dates))
if df == 0:
break
r_new = r - f / df
if abs(r_new - r) < tol:
return r_new
r = r_new
return r
💡 Jour-comptage : ici ACT/365 (simple). Selon vos conventions (ACT/360, 30/360, ACT/365F…), adaptez le dénominateur.
def amortization_schedule(principal, annual_rate, years, freq=12, when='end'):
r = annual_rate / freq
n = years * freq
pay = pmt(r, n, principal, when=when)
rows = []
balance = principal
for k in range(1, n + 1):
# Intérêt sur solde début (ou fin si when='begin')
interest = (balance * r) if when == 'end' else ((balance - pay) * r)
principal_paid = -pay - interest
balance -= principal_paid
rows.append({
"periode": k,
"paiement": round(-pay, 2),
"interets": round(interest, 2),
"principal": round(principal_paid, 2),
"solde": round(balance, 2)
})
return pd.DataFrame(rows)
Usage : amortization_schedule(100_000, 0.05, 20, 12) renvoie un DataFrame prêt à exporter en Excel.
def bond_price(face, coupon_rate, ytm, maturity_years, freq=2):
c = coupon_rate * face / freq
n = int(round(maturity_years * freq))
y = ytm / freq
price = sum(c / (1 + y)**t for t in range(1, n + 1)) + face / (1 + y)**n
return price
Exemple : nominal 1000 €, coupon 4%, YTM 5%, maturité 5 ans, annuel → prix ≈ 956,71 €.
def bond_duration_convexity(face, coupon_rate, ytm, maturity_years, freq=2):
c = coupon_rate * face / freq
n = int(round(maturity_years * freq))
y = ytm / freq
times = np.arange(1, n + 1)
cashflows = np.full(n, c, dtype=float); cashflows[-1] += face
disc = 1 / (1 + y)**times
price = np.sum(cashflows * disc)
macaulay = np.sum(times * cashflows * disc) / price
modified = macaulay / (1 + y)
convexity = np.sum(times * (times + 1) * cashflows * disc) / (price * (1 + y)**2)
# Conversion en années
return {
"prix": float(price),
"duration_macaulay_ans": float(macaulay / freq),
"duration_modifiee_ans": float(modified / freq),
"convexite_annee2": float(convexity / (freq**2))
}
Interprétation rapide : la duration modifiée ≈ variation % du prix pour une petite variation du taux (ΔP/P ≈ −Dur_mod × Δy). La convexité corrige cette approximation pour de plus grands mouvements.
Sans SciPy, une dichotomie est robuste :
def ytm_from_price(face, coupon_rate, price, maturity_years, freq=2, tol=1e-8, maxiter=200):
lo, hi = 0.0, 1.0 # 0% à 100% de rendement initialement
for _ in range(maxiter):
mid = (lo + hi) / 2
p = bond_price(face, coupon_rate, mid, maturity_years, freq)
if abs(p - price) < tol:
return mid
if p > price:
lo = mid
else:
hi = mid
return (lo + hi) / 2
YEARFRAC (simple)Pour XNPV/XIRR, nous avons utilisé ACT/365. Voici un utilitaire explicite :
def yearfrac_act365(d1, d2):
return (d2 - d1).days / 365.0
En contexte obligataire, choisissez la bonne convention (30/360 US/EU, ACT/360, ACT/365F…). Les résultats (prix/duration) varient selon ce choix.
def log_returns(prices: np.ndarray):
"""Prix -> rendements log (taux continus)."""
prices = np.asarray(prices, dtype=float)
return np.log(prices[1:] / prices[:-1])
def annualize_ret_vol(mu, sigma, periods=252):
"""Annualisation pour des séries quotidiennes (252 j ouvrés)."""
return mu * periods, sigma * np.sqrt(periods)
def sharpe_ratio(returns, rf=0.0, periods=252):
"""Sharpe annualisé (rf = taux sans risque annualisé)."""
r = np.asarray(returns, dtype=float)
mu, sigma = r.mean(), r.std(ddof=1)
mu_a, sigma_a = annualize_ret_vol(mu, sigma, periods=periods)
return (mu_a - rf) / sigma_a if sigma_a != 0 else np.nan
Astuce : pour des rendements mensuels, remplacez
periods=12. Pour des rendements hebdo,periods=52.
def portfolio_metrics(weights, mean_returns, cov_matrix, rf=0.0, periods=252):
w = np.asarray(weights, dtype=float)
mu = np.dot(w, mean_returns) # rendement espéré (périodique)
var = float(np.dot(w, np.dot(cov_matrix, w))) # variance (périodique)
mu_a, vol_a = mu * periods, np.sqrt(var) * np.sqrt(periods)
sharpe = (mu_a - rf) / vol_a if vol_a != 0 else np.nan
return {"ret_ann": mu_a, "vol_ann": vol_a, "sharpe": sharpe}
def capm_beta(asset_returns, market_returns):
"""β = Cov(Ra, Rm)/Var(Rm)."""
a = np.asarray(asset_returns); m = np.asarray(market_returns)
cov = np.cov(a, m, ddof=1)[0,1]
var_m = np.var(m, ddof=1)
return cov / var_m if var_m != 0 else np.nan
[-1000, 300, 400, 500, 600] → VAN à 8% ≈ 458,65, TRI ≈ 24,89%.amortization_schedule.sharpe_ratio(returns, rf=…) ; ajustez la fréquence.pv, fv, pmtnpv, irr, xnpv/xirr pour dates réellesamortization_schedule (DataFrame)bond_price, ytm_from_price, bond_duration_convexitylog_returns, sharpe_ratio, portfolio_metrics, capm_betayearfrac_act365 (adaptez selon conventions)un notebook structuré en sections, 2) des visualisations lisibles (cash-flow vs cumul, point mort), 3) des sensibilités à 2 facteurs (marge et taux du prêt) + un pack d’exports (CSV/Excel/PNG).
Tout est prêt à copier-coller dans votre notebook.
Cellule 1 — Imports & fonctions utilitaires
import numpy as np, pandas as pd, matplotlib.pyplot as plt
from dataclasses import dataclass
# --- Fonctions financières de base ---
def pmt(rate, nper, pv_, fv=0.0, when='end'):
when_v = 1 if when == 'begin' else 0
if rate == 0: return - (pv_ + fv) / nper
return - (rate * (pv_ * (1 + rate)**nper + fv)) / ((1 + rate * when_v) * ((1 + rate)**nper - 1))
def npv(rate, cashflows):
return sum(cf / (1 + rate)**t for t, cf in enumerate(cashflows))
def irr(cashflows, guess=0.1, tol=1e-8, maxiter=200):
def f(r): return sum(cf / (1 + r)**t for t, cf in enumerate(cashflows))
def fp(r): return sum(-t * cf / (1 + r)**(t+1) for t, cf in enumerate(cashflows) if t>0)
r=guess
for _ in range(maxiter):
y, yp = f(r), fp(r)
if yp == 0: break
r_new = r - y/yp
if abs(r_new-r) < tol: return r_new
r = r_new
return r
def payback_period(cashflows):
cum = 0.0
for t, cf in enumerate(cashflows):
cum += cf
if cum >= 0:
return t
return None # jamais amorti dans l’horizon étudié
Cellule 2 — Hypothèses de base (cas Lina)
# Hypothèses (mensuelles sauf mention)
prix_machine = 5_000.0
taux_pret_ann = 0.06
n_mois = 36
taux_actual_ann = 0.10
valeur_residuelle = 500.0
# Résultats opérationnels
marge_suppl = 350.0 # marge brute mensuelle attendue
maintenance = 20.0 # coût mensuel de maintenance
net_avant_dette = marge_suppl - maintenance # 330 €/mois
# Taux convertis
r_pret_m = taux_pret_ann/12
r_act_m = taux_actual_ann/12
Cellule 3 — Calculs & tableau d’amortissement
# Mensualité
mensualite = -pmt(r_pret_m, n_mois, prix_machine) # valeur positive à payer
flux_net = net_avant_dette - mensualite
# Flux (t=0 achat ; t=1..35 flux_net ; t=36 flux_net + revente)
cashflows = [-prix_machine] + [flux_net]*35 + [flux_net + valeur_residuelle]
# Indicateurs
van = npv(r_act_m, cashflows)
tri_ann = (1 + irr(cashflows, guess=0.02))**12 - 1
pm = payback_period(cashflows)
# Tableau d’amortissement (intérêt/principal/solde)
def amortization_schedule(principal, annual_rate, months):
r = annual_rate/12
pay = mensualite
rows=[]; balance=principal
for k in range(1, months+1):
interest = balance * r
principal_paid = pay - interest
balance -= principal_paid
rows.append({"mois":k, "paiement":round(pay,2), "intérêts":round(interest,2),
"principal":round(principal_paid,2), "solde":round(balance,2)})
return pd.DataFrame(rows)
amort = amortization_schedule(prix_machine, taux_pret_ann, n_mois)
# Résumé lisible
resume = pd.DataFrame({
"Métrique":[
"Mensualité du prêt (€)",
"Flux net mensuel après dette (€)",
"VAN (10% ann.) (€)",
"TRI annualisé (%)",
"Point mort (mois)"
],
"Valeur":[round(mensualite,2), round(flux_net,2), round(van,2), round(100*tri_ann,2), pm]
})
resume
Cellule 4 — Visualisations (cash-flow vs cumul + point mort)
# Série cash-flow
series = pd.DataFrame({
"mois": np.arange(0, n_mois+1),
"cashflow": cashflows
})
series["cumul"] = series["cashflow"].cumsum()
# Graphe 1: Cash-flow mensuel
plt.figure()
plt.bar(series["mois"], series["cashflow"])
plt.axhline(0, linestyle="--")
plt.title("Cash-flow mensuel (machine à café)")
plt.xlabel("Mois"); plt.ylabel("€")
plt.tight_layout(); plt.show()
# Graphe 2: Cumul & point mort
plt.figure()
plt.plot(series["mois"], series["cumul"], marker="o")
plt.axhline(0, linestyle="--")
if pm is not None:
plt.axvline(pm, linestyle=":")
plt.text(pm, series.loc[series["mois"]==pm, "cumul"].values[0], f" Point mort: M{pm}")
plt.title("Cumul des flux — point mort")
plt.xlabel("Mois"); plt.ylabel("€ cumulés")
plt.tight_layout(); plt.show()
Cellule 5 — Sensibilités (2 facteurs) + exports
# Grille: Net avant dette ∈ [260..400], Taux prêt ann. ∈ [3%..9%]
nets = np.arange(260, 401, 20) # €/mois
tauxs = np.arange(0.03, 0.091, 0.01) # annuel
def eval_case(net, taux_ann):
r_p = taux_ann/12
pay = -pmt(r_p, n_mois, prix_machine)
cf = net - pay
cfs = [-prix_machine] + [cf]*35 + [cf + valeur_residuelle]
return npv(r_act_m, cfs)
mat = np.zeros((len(nets), len(tauxs)))
for i, net in enumerate(nets):
for j, t in enumerate(tauxs):
mat[i, j] = eval_case(net, t)
# Heatmap VAN
plt.figure()
plt.imshow(mat, origin="lower", aspect="auto",
extent=[tauxs[0]*100, tauxs[-1]*100, nets[0], nets[-1]])
plt.colorbar(label="VAN (€)")
plt.xlabel("Taux du prêt (ann. %)"); plt.ylabel("Net avant dette (€/mois)")
plt.title("Sensibilité 2D — VAN (€)")
plt.tight_layout(); plt.show()
# Exports
series.to_csv("cashflows_series.csv", index=False)
resume.to_csv("resume_indicateurs.csv", index=False)
amort.to_excel("amortissement_pret.xlsx", index=False)
Règle de pouce : si votre marge nette avant dette baisse de 50 € (ex. passages plus faibles), la VAN peut basculer négative. Surveillez 3 choses les 2 premiers mois : volume matin, taux de casse/maintenance, panier moyen.
taux/12).net_avant_dette ≤ mensualité, le flux net doit être ≤ 0.assert mensualite > 0, "La mensualité doit être positive (sortie de trésorerie)"
assert len(cashflows) == n_mois + 1, "Horizon incohérent"
cashflows).resume_indicateurs.csv et amortissement_pret.xlsx et ajoutez-les dans un e-mail synthétique “banquier”.pmt, npv, irr, payback_period pour tout mini-investissement (vitrine réfrigérée, vélo-cargo, four mixte…).Deux outils concrets pour piloter la qualité sans alourdir vos équipes Un système qualité n’avance…
Un chantier se gagne souvent avant même l’arrivée des équipes. Quand tout est clair dès…
Le mariage a du sens quand il repose sur une décision libre, mûrie et partagée.…
Une étude de cas réussie commence par une structure sûre. Ce modèle Word vous guide…
Les soft skills se repèrent vite sur une fiche, mais elles ne pèsent vraiment que…
Outil de comparaison et repérage des offres étudiantes Choisir des verres progressifs ressemble rarement à…
This website uses cookies.