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…).Le modèle - Rapport moral du président d’association en PDF - qui se lit facilement,…
Exemple clair, prêt à télécharger, pour présenter votre activité avec un rendu premium Une présentation…
On croit souvent qu’un frigo “fait le job” tant qu’il est froid au moment où…
Visualiser les forces réelles d’une équipe ne relève plus de l’intuition lorsqu’une matrice des compétences…
Comprendre, structurer et rédiger un document fondateur clair, crédible et inspirant Rédiger les statuts d’une…
Gérez les pannes, bugs, coupures réseau et alertes sécurité avec une procédure claire, structurée et…
This website uses cookies.