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…).Derrière chaque tableau électrique, chaque moteur ou chaque circuit d’alimentation se cache un élément souvent…
Une fiche de traçabilité HACCP n'attire généralement l'attention que lorsqu'une information devient difficile à retrouver.…
Deux dates figurent régulièrement sur les emballages alimentaires : la DLC et la DDM. Souvent…
Ordinateurs, imprimantes, véhicules, outils industriels, équipements informatiques, appareils de mesure ou matériels de chantier :…
Chaque produit alimentaire laisse derrière lui une multitude d'informations souvent invisibles pour le consommateur :…
La scène est familière. Un lundi matin, quelques minutes avant le début du service, un…
This website uses cookies.