Python

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)

  1. DCF projet : flux [-1000, 300, 400, 500, 600] → VAN à 8% ≈ 458,65, TRI ≈ 24,89%.
  2. Obligation : 1000 €, 4%, 5 ans, YTM 5% → 956,71 €, duration modifiée ≈ 4,40 ans.
  3. Prêt immo : 100 000 €, 5% sur 20 ans mensuel → 659,96 €/mois, tableau d’amortissement via amortization_schedule.
  4. 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/xirr pour 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 mort29 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 mensuelstaux 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)

  1. Sans revente (valeur résiduelle = 0) : que deviennent VAN/TRI/point mort ?
  2. Hausse des taux (+2 pts sur le prêt) : refaites la heatmap → zone de VAN négative s’agrandit ?
  3. Saisonnalité : baisse de 30 % en juillet-août → modélisez 2 mois plus faibles (modifier cashflows).
  4. Affichage : exportez resume_indicateurs.csv et amortissement_pret.xlsx et ajoutez-les dans un e-mail synthétique “banquier”.

6) Ce que vous pouvez réutiliser demain

  • Les fonctions pmt, npv, irr, payback_period pour 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.

Recommandés

CSV en Python — du téléchargement au...
Le CSV paraît simple, jusqu’au...
En savoir plus
Python : pandas.to_csv — Exporter propre, fiable...
DataFrame.to_csv devient un contrat d’échange :...
En savoir plus
Python : pandas.read_csv — Guide pratique enrichi
Pourquoi read_csv reste incontournableParce qu’il...
En savoir plus
Pratique de l’apprentissage automatique avec scikit-learn et...
Cet article vous guide, pas à...
En savoir plus
Python & finance PME — un kit...
Pour une PME, la finance est...
En savoir plus
Python & Pickle : manipuler les fichiers...
Pickle est la bibliothèque standard de...
En savoir plus

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

error: Content is protected !!