Python pour la finance — les fonctions de base
Recommandés
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.).
0) Préparer l’environnement
python -m pip install --upgrade pip
pip install numpy pandas
import numpy as np
import pandas as pd
from datetime import datetime, date
1) Valeur temps de l’argent : PV, FV, PMT
Fonctions 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)
2) VAN & TRI : flux réguliers et irréguliers
2.1 VAN/TRI périodiques (flux à t=0,1,2,…)
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%.
2.2 XNPV/XIRR (flux datés, irréguliers)
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.
3) Prêts : tableau d’amortissement
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.
4) Obligations : prix, rendement, duration, convexité
4.1 Prix (coupon fixe)
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 €.
4.2 Duration (Macaulay/modifiée) & convexité
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.
4.3 Rendement à l’échéance (YTM) à partir du prix
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
5) Dates & conventions : 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.
6) Marchés : rendements, volatilité, Sharpe
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.
7) Portefeuilles : moyenne, variance, bêta (CAPM)
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
8) Mini-cas fil rouge (express)
- DCF projet : flux
[-1000, 300, 400, 500, 600]→ VAN à 8% ≈ 458,65, TRI ≈ 24,89%. - Obligation : 1000 €, 4%, 5 ans, YTM 5% → 956,71 €, duration modifiée ≈ 4,40 ans.
- Prêt immo : 100 000 €, 5% sur 20 ans mensuel → 659,96 €/mois, tableau d’amortissement via
amortization_schedule. - Portefeuille : calculez le Sharpe avec
sharpe_ratio(returns, rf=…); ajustez la fréquence.
9) Bonnes pratiques (vraiment utiles)
- Cohérence temporelle : un taux mensuel s’applique à des flux mensuels, etc.
- Signes : décidez d’une convention et tenez-vous-y (entrées +, sorties −).
- Vectorisez (NumPy) : plus rapide et plus lisible.
- Conventions de dates : documentez le day-count utilisé (audit).
- Robustesse numérique : préférez dichotomie ou Brent aux Newton “à l’aveugle” quand la dérivée peut s’annuler.
- Tests : figez des cas de test (prix d’obligation connu, VAN/TRI de référence) pour éviter les régressions.
10) À emporter : votre “boîte à outils”
- Valeur temps :
pv,fv,pmt - VAN/TRI :
npv,irr,xnpv/xirrpour dates réelles - Prêts :
amortization_schedule(DataFrame) - Obligations :
bond_price,ytm_from_price,bond_duration_convexity - Marchés/portefeuilles :
log_returns,sharpe_ratio,portfolio_metrics,capm_beta - Dates :
yearfrac_act365(adaptez selon conventions)

Cas débutant « machine à café » : passer du calcul à l’aide à la décision
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.
1) Plan propre du notebook (sections et conventions)
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)
2) Lecture métier des résultats (mode “expliquez-moi simplement”)
- Mensualité ≈ 152,11 € → c’est votre “loyer” de la machine.
- Flux net après dette ≈ +177,89 €/mois → ce que la machine rapporte après avoir payé la mensualité.
- Point mort ≈ 29 mois → à partir de là, vous avez “récupéré” l’investissement (sur 36 mois, avec revente).
- VAN (10 %) ≈ +884 € → à votre coût du capital, le projet crée de la valeur.
- TRI annualisé ≈ 23 % → bien au-dessus de 10 % requis, intéressant.
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.
3) Trame “banquier/partenaire” (3 bullets qui rassurent)
- Stabilité des flux : historique de ventes matin + retours clients (photos/avis) → hypothèses crédibles.
- Coussin de sécurité : VAN testée en scénario prudent (net 280 €/mois) + réserve de trésorerie = 3 mensualités.
- Sortie : valeur résiduelle réaliste 500 € (devis revendeur ou cote), horizon 36 mois.
4) Contrôles rapides (éviter les erreurs classiques)
- Cohérence des unités : flux mensuels ⇔ taux mensuels (
taux/12). - Signes : achat négatif au mois 0, encaissements positifs.
- Horizon : la revente est bien au mois 36 (pas au 35).
- Sanity check : si
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"
5) Exercices-guides (pour débutants)
- Sans revente (valeur résiduelle = 0) : que deviennent VAN/TRI/point mort ?
- Hausse des taux (+2 pts sur le prêt) : refaites la heatmap → zone de VAN négative s’agrandit ?
- Saisonnalité : baisse de 30 % en juillet-août → modélisez 2 mois plus faibles (modifier
cashflows). - Affichage : exportez
resume_indicateurs.csvetamortissement_pret.xlsxet ajoutez-les dans un e-mail synthétique “banquier”.
6) Ce que vous pouvez réutiliser demain
- Les fonctions
pmt,npv,irr,payback_periodpour tout mini-investissement (vitrine réfrigérée, vélo-cargo, four mixte…). - La heatmap 2D pour visualiser votre zone de sécurité (marge vs taux).
- Le tableau d’amortissement pour discuter assurance/anticiper un rachat par anticipation.

