// pip install quantfinance

QuantFinance

Package Python professionnel pour la finance quantitative — pricing, risque, optimisation de portefeuille et backtesting.

Python 3.8+ MIT PyPI CI Coverage

Présentation

QuantFinance est une bibliothèque Python complète pour la finance quantitative. Elle couvre le pricing d'instruments financiers, la gestion des risques, l'optimisation de portefeuille et le backtesting de stratégies.

Portefeuille

  • Markowitz
  • Risk Parity
  • Black-Litterman
  • HRP
  • Max Diversification

Pricing

  • Black-Scholes
  • Binomial Tree
  • Monte Carlo
  • Options exotiques
  • Obligations

Risque

  • VaR (4 méthodes)
  • CVaR / ES
  • Sharpe, Sortino, Calmar
  • Max Drawdown
  • Stress Testing

Utilitaires

  • Yahoo Finance
  • Données synthétiques
  • RSI, MACD, Bollinger
  • Rééquilibrage

Installation

pip
Source
Extras
bash
pip install quantfinance
bash
git clone https://github.com/Mafoya1er/quantfinance.git
cd quantfinance
pip install -e .
bash
pip install quantfinance[data] # Analyse de données
pip install quantfinance[dev] # Développement
pip install quantfinance[all] # Tout installer

Démarrage rapide

python
from quantfinance.pricing.options import BlackScholes
from quantfinance.portfolio.optimization import PortfolioOptimizer
from quantfinance.risk.var import VaRCalculator
from quantfinance.utils.data import DataLoader

# Pricing d'une option call
bs = BlackScholes(S=100, K=105, T=1, r=0.05, sigma=0.25, option_type='call')
print(f"Prix: {bs.price():.2f}, Delta: {bs.delta():.4f}")

# Optimisation de portefeuille
prices = DataLoader.generate_synthetic_prices(n_assets=5, n_days=756)
returns = prices.pct_change().dropna()
optimizer = PortfolioOptimizer(returns, risk_free_rate=0.02)
result = optimizer.maximize_sharpe()
print(f"Sharpe: {result['sharpe_ratio']:.3f}")

# Value at Risk
var_95 = VaRCalculator.historical_var(returns.iloc[:, 0], 0.95)
print(f"VaR 95%: {var_95:.2%}")

quantfinance.portfolio

Module d'optimisation de portefeuille, d'allocation d'actifs et de backtesting.

PortfolioOptimizer
Classe principale pour l'optimisation de portefeuille.
__init__(returns, risk_free_rate=0.02)

Initialise l'optimiseur avec les rendements historiques et le taux sans risque.

python
from quantfinance.portfolio.optimization import PortfolioOptimizer

optimizer = PortfolioOptimizer(returns_df, risk_free_rate=0.03)
equal_weight()
→ Dict

Crée un portefeuille équipondéré (poids égaux pour chaque actif).

python
result = optimizer.equal_weight()
print("Poids:", result['weights'])
print("Rendement:", result['return'])
minimize_volatility(constraints=None)
→ Dict

Minimise la volatilité du portefeuille — portefeuille à variance minimale.

python
result = optimizer.minimize_volatility()
print(f"Volatilité minimale: {result['volatility']:.2%}")
print(f"Rendement associé: {result['return']:.2%}")
print(result['weights'])
maximize_sharpe(constraints=None)
→ Dict

Maximise le ratio de Sharpe — point tangent de la frontière efficiente.

python
result = optimizer.maximize_sharpe()
print(f"Sharpe max: {result['sharpe_ratio']:.3f}")
print(f"Rendement: {result['return']:.2%}")
print(f"Volatilité: {result['volatility']:.2%}")
maximize_return(target_volatility)
→ Dict

Maximise le rendement sous contrainte de volatilité cible. Retourne un objet résultat scipy avec les poids optimaux dans result.x.

python
result = optimizer.maximize_return(target_volatility=0.18)
weights = pd.Series(result.x, index=optimizer.assets)
ret, vol, sharpe = optimizer.portfolio_performance(weights)
print(f"Rendement max (vol≤18%): {ret:.2%}")
risk_parity()
→ Dict

Portefeuille de parité de risque — chaque actif contribue également au risque total.

python
result = optimizer.risk_parity()
print("Contribution au risque:", result['risk_contribution'])
print("Poids:", result['weights'])
portfolio_performance(weights)
→ Tuple[float, float, float]

Calcule les métriques annualisées d'un portefeuille : rendement, volatilité et ratio de Sharpe.

python
import numpy as np
weights = np.array([0.25, 0.25, 0.25, 0.25])
ret, vol, sharpe = optimizer.portfolio_performance(weights)
print(f"Rendement: {ret:.2%} | Volatilité: {vol:.2%} | Sharpe: {sharpe:.3f}")
expected_return(weights) / portfolio_volatility(weights)
→ float

Utilitaires bas niveau — calcule séparément le rendement espéré ou la volatilité d'un portefeuille donné.

python
weights = np.array([0.4, 0.3, 0.2, 0.1])
print(f"Rendement: {optimizer.expected_return(weights):.2%}")
print(f"Volatilité: {optimizer.portfolio_volatility(weights):.2%}")
EfficientFrontier
Calcul et visualisation de la frontière efficiente de Markowitz.
__init__(optimizer)
python
from quantfinance.portfolio.optimization import EfficientFrontier

frontier = EfficientFrontier(optimizer)
calculate_frontier(n_points=50, min_return=None, max_return=None)
→ pd.DataFrame

Calcule les portefeuilles optimaux sur la frontière efficiente.

python
frontier_data = frontier.calculate_frontier(n_points=50)
print(frontier_data.head())
# columns: return, volatility, sharpe_ratio, weights...
plot(show_assets=True, show_optimal=True, figsize=(12,8))
→ Figure

Trace la frontière efficiente avec les actifs individuels et le portefeuille optimal.

python
import matplotlib.pyplot as plt

fig = frontier.plot(show_assets=True, show_optimal=True)
plt.show()
AssetAllocator
Stratégies d'allocation avancées — HRP, min corrélation, max diversification.
__init__(returns)
python
from quantfinance.portfolio.optimization import AssetAllocator

# Internalement : cov_matrix et std_devs sont annualisés (×252)
allocator = AssetAllocator(returns_df) # returns = rendements quotidiens
hierarchical_risk_parity()
→ pd.Series

Allocation HRP — utilise la hiérarchie des corrélations pour diversifier sans inversion de matrice.

python
weights = allocator.hierarchical_risk_parity()
print("Poids HRP:", weights)
minimum_correlation()
→ pd.Series

Minimise la corrélation moyenne entre les actifs du portefeuille.

python
weights = allocator.minimum_correlation()
print("Poids min corr:", weights)
maximum_diversification()
→ pd.Series

Maximise le ratio de diversification — rapport volatilité pondérée / volatilité portefeuille.

python
weights = allocator.maximum_diversification()
print("Poids max div:", weights)
Rebalancer
Gestion du rééquilibrage de portefeuille.
__init__(target_weights, prices, initial_capital=100000.0, transaction_cost=0.001)
python
from quantfinance.portfolio.rebalancing import Rebalancer

rebalancer = Rebalancer(target_weights, prices_df,
 initial_capital=100000, transaction_cost=0.001)
periodic_rebalancing(frequency='monthly')
→ pd.DataFrame

Rééquilibre à intervalles fixes. Fréquences : 'daily', 'weekly' (lundi), 'monthly' (fin de mois), 'quarterly', 'yearly'. Retourne un DataFrame avec colonnes : portfolio_value, cash, holdings_value, is_rebalance, weight_<asset>.

python
results = rebalancer.periodic_rebalancing(frequency='quarterly')
print(results.head())
threshold_rebalancing(threshold=0.05)
→ pd.DataFrame

Rééquilibre uniquement quand un poids dévie de plus de threshold par rapport à la cible. Retourne un DataFrame avec les mêmes colonnes que periodic_rebalancing, plus rebalance_count (compteur cumulatif de rééquilibrages).

python
# Rééquilibre si déviation > 3%
results = rebalancer.threshold_rebalancing(threshold=0.03)
print(results.head())
Strategy (ABC)
Classe abstraite de base pour toutes les stratégies — à sous-classer pour créer une stratégie personnalisée.
generate_signals(data)
→ pd.Series · Méthode abstraite

Génère les signaux de trading. Doit retourner une Series avec les valeurs : 1 (long), 0 (neutre), -1 (short).

python
from quantfinance.portfolio.backtesting import Strategy

class MyStrategy(Strategy):
 def generate_signals(self, data):
 # Exemple : signal basé sur RSI personnalisé
 close = data['Close']
 signal = pd.Series(0, index=data.index)
 signal[close > close.rolling(20).mean()] = 1
 signal[close < close.rolling(20).mean()] = -1
 return signal

strategy = MyStrategy()
bt = Backtester(data, strategy, initial_capital=10000)
results = bt.run()
Backtester
Framework de backtesting de stratégies de trading.
__init__(data, strategy, initial_capital=10000.0, commission=0.001)
python
from quantfinance.portfolio.backtesting import Backtester, MovingAverageCrossover

strategy = MovingAverageCrossover(short_window=20, long_window=50)
backtester = Backtester(prices_df, strategy,
 initial_capital=10000, commission=0.0005)
run()
→ Dict

Exécute le backtest et retourne les métriques de performance.

python
results = backtester.run()
print(f"Capital final: {results['Final Value']:.2f}")
print(f"Rendement total: {results['Total Return']:.2%}")
print(f"Ratio de Sharpe: {results['Sharpe Ratio']:.3f}")
print(f"Max Drawdown: {results['Max Drawdown']:.2%}")
print(f"Nb transactions: {results['Number of Trades']}")
print(f"Win Rate: {results['Win Rate']:.1%}")
plot_results(figsize=(14,10))
→ Figure

Trace la courbe de capital, les signaux de trading et les drawdowns. Lève ValueError si run() n'a pas été appelé avant. Génère 3 sous-graphiques : prix + signaux, valeur du portefeuille, drawdown.

python
import matplotlib.pyplot as plt
fig = backtester.plot_results()
plt.show()
Stratégies de trading
Stratégies prêtes à l'emploi pour le backtesting.
BuyAndHoldStrategy
Stratégie passive

Achète au début et conserve jusqu'à la fin. Sert de benchmark de référence.

python
from quantfinance.portfolio.backtesting import BuyAndHoldStrategy

strategy = BuyAndHoldStrategy()
signals = strategy.generate_signals(prices_df)
print(signals.head()) # Toujours 1 (long)
MovingAverageCrossover(short_window=20, long_window=50)
Stratégie de tendance

Signal d'achat quand la MA courte croise la MA longue à la hausse, et de vente à la baisse.

python
from quantfinance.portfolio.backtesting import MovingAverageCrossover

strategy = MovingAverageCrossover(short_window=10, long_window=30)
signals = strategy.generate_signals(prices_df)
print(signals.head()) # 1 = long, -1 = short, 0 = neutre

# Backtest complet
bt = Backtester(prices_df, strategy, initial_capital=50000)
results = bt.run()
print(f"Rendement total: {results['Total Return']:.2%}")

quantfinance.pricing

Module de pricing d'options (vanille et exotiques) et d'obligations.

BlackScholes
Modèle de Black-Scholes pour options européennes — prix et grecques complètes.
__init__(S, K, T, r, sigma, option_type='call', q=0.0)
Initialise le modèle Black-Scholes.
ParamètreTypeDescription
SfloatPrix spot du sous-jacent
KfloatPrix d'exercice (strike)
TfloatMaturité en années
rfloatTaux sans risque annualisé
sigmafloatVolatilité annualisée
option_typestr'call' ou 'put'
qfloatTaux de dividende continu (défaut : 0.0)
python
from quantfinance.pricing.options import BlackScholes

# Option call européenne
bs_call = BlackScholes(S=100, K=105, T=1, r=0.05, sigma=0.25, option_type='call')

# Option put européenne
bs_put = BlackScholes(S=100, K=95, T=0.5, r=0.03, sigma=0.20, option_type='put')
price()
→ float

Calcule le prix théorique de l'option selon Black-Scholes.

python
prix = bs_call.price()
print(f"Prix de l'option: {prix:.2f}") # ex: 10.45
delta()
→ float · ∂V/∂S

Sensibilité du prix au prix du sous-jacent. Entre 0 et 1 pour un call, entre -1 et 0 pour un put.

python
print(f"Delta: {bs_call.delta():.4f}") # ex: 0.4523
print(f"Delta put: {bs_put.delta():.4f}") # ex: -0.3871
gamma()
→ float · ∂²V/∂S²

Convexité — taux de variation du delta par rapport au prix du sous-jacent. Toujours positif.

python
print(f"Gamma: {bs_call.gamma():.6f}") # ex: 0.018432
vega()
→ float · ∂V/∂σ

Sensibilité du prix à la volatilité. Exprimé pour une variation de 1% de sigma.

python
print(f"Vega: {bs_call.vega():.4f}") # ex: 0.3841
theta()
→ float · ∂V/∂t

Décroissance temporelle — perte de valeur par jour écoulé. Généralement négatif.

python
print(f"Theta: {bs_call.theta():.4f}") # ex: -0.0152 (perte de 1.52 cts/jour)
rho()
→ float · ∂V/∂r

Sensibilité au taux sans risque.

python
print(f"Rho: {bs_call.rho():.4f}") # ex: 0.2910
implied_volatility(market_price)
→ float

Calcule la volatilité implicite par la méthode de Newton-Raphson à partir du prix de marché observé.

ParamètreTypeDescription
market_pricefloatPrix de marché observé de l'option
python
market_price = 8.50
implied_vol = bs_call.implied_volatility(market_price)
print(f"Volatilité implicite: {implied_vol:.2%}") # ex: 21.34%
dv01(ytm)
→ float

Dollar Value of a Basis Point — variation du prix pour un mouvement de 1bp (0.01%) du rendement.

python
dv01 = bond.dv01(ytm=0.045)
print(f"DV01: {dv01:.4f}€") # variation de prix pour +1bp
accrued_interest(days_since_last_coupon)
→ float

Calcule les intérêts courus depuis le dernier paiement de coupon.

ParamètreTypeDescription
days_since_last_couponintNombre de jours depuis le dernier coupon
python
accrued = bond.accrued_interest(days_since_last_coupon=45)
print(f"Intérêts courus: {accrued:.4f}€")
dirty_price(ytm, days_since_last_coupon)
→ float

Prix sale = prix propre + intérêts courus. Représente le montant réellement payé à l'achat.

python
dirty = bond.dirty_price(ytm=0.045, days_since_last_coupon=45)
clean = bond.price(ytm=0.045)
print(f"Prix propre: {clean:.2f}€ | Prix sale: {dirty:.2f}€")
cash_flows()
→ pd.DataFrame

Génère le calendrier complet des flux de trésorerie de l'obligation.

python
cf = bond.cash_flows()
print(cf)
# Period  Time (years)  Cash Flow
#      1           0.5       25.0
#      2           1.0       25.0
#    ...           ...        ...
#     20          10.0     1025.0
price_change_approximation(ytm, yield_change)
→ Tuple[float, float]

Approxime le changement de prix avec duration seule, puis avec duration + convexité.

python
# Impact d'une hausse de 50bp
dur_approx, full_approx = bond.price_change_approximation(ytm=0.045, yield_change=0.005)
print(f"Approx. duration : {dur_approx:.4f}€")
print(f"Approx. dur+conv : {full_approx:.4f}€")
d1() / d2()
→ float

Calcule d1 et d2 de la formule de Black-Scholes. d1 = [ln(S/K) + (r − q + σ²/2)·T] / (σ√T), d2 = d1 − σ√T.

python
bs = BlackScholes(S=100, K=105, T=1, r=0.05, sigma=0.25)
print(f"d1={bs.d1():.4f}, d2={bs.d2():.4f}")
greeks()
→ Dict[str, float]

Retourne toutes les grecques en une seule fois : {'delta', 'gamma', 'vega', 'theta', 'rho'}.

python
g = bs.greeks()
print(f"Delta: {g['delta']:.4f} | Gamma: {g['gamma']:.4f}")
print(f"Vega: {g['vega']:.4f} | Theta: {g['theta']:.4f} | Rho: {g['rho']:.4f}")
BinomialTree
Arbre binomial — options américaines et européennes.
__init__(S, K, T, r, sigma, N=100, option_type='call', exercise_type='european', q=0.0)
ParamètreTypeDescription
NintNombre de pas dans l'arbre (défaut : 100)
exercise_typestr'european' ou 'american' (défaut : 'european')
qfloatTaux de dividende continu (défaut : 0.0)
python
from quantfinance.pricing.options import BinomialTree

# Option américaine put (exercice anticipé possible)
bt = BinomialTree(S=100, K=105, T=1, r=0.05, sigma=0.25,
 N=200, option_type='put', exercise_type='american')
print(f"Prix option américaine: {bt.price():.2f}")

# Comparaison européenne vs américaine
bt_eu = BinomialTree(S=100, K=105, T=1, r=0.05, sigma=0.25, exercise_type='european')
print(f"Prime d'exercice anticipé: {bt.price() - bt_eu.price():.2f}")
MonteCarlo
Simulation Monte Carlo — options vanille et exotiques.
price()
→ float

Prix standard par simulation de trajectoires du sous-jacent.

python
from quantfinance.pricing.options import MonteCarlo

mc = MonteCarlo(S=100, K=105, T=1, r=0.05, sigma=0.25, n_simulations=50000)
print(f"Prix MC: {mc.price():.2f}")
price_asian_option(option_type='arithmetic')
→ float

Prix d'une option asiatique basé sur la moyenne du sous-jacent sur la durée de vie.

python
# Moyenne arithmétique (standard)
prix_asiatique = mc.price_asian_option(option_type='arithmetic')
print(f"Option asiatique: {prix_asiatique:.2f}")

# Moyenne géométrique
prix_geo = mc.price_asian_option(option_type='geometric')
print(f"Option asiatique géo: {prix_geo:.2f}")
price_barrier_option(barrier, barrier_type='up-and-out')
→ float

Prix d'une option à barrière — s'active ou se désactive si le sous-jacent touche la barrière.

barrier_typeTypeDescription
'up-and-out'strL'option disparaît si S dépasse la barrière
'up-and-in'strL'option s'active si S dépasse la barrière
'down-and-out'strL'option disparaît si S tombe sous la barrière
'down-and-in'strL'option s'active si S tombe sous la barrière
python
prix_barriere = mc.price_barrier_option(barrier=120, barrier_type='up-and-out')
print(f"Option à barrière up-out: {prix_barriere:.2f}")
price_with_confidence_interval(confidence_level=0.95)
→ Tuple[float, float, float]

Calcule le prix avec intervalle de confiance. Retourne (prix, borne_inf, borne_sup).

python
price, low, high = mc.price_with_confidence_interval(confidence_level=0.95)
print(f"Prix: {price:.2f} | IC 95%: [{low:.2f}, {high:.2f}]")
Bond
Pricing d'obligations — YTM, Duration, Convexité.
__init__(face_value, coupon_rate, maturity, frequency=2)
python
from quantfinance.pricing.bonds import Bond

# Obligation 1000€, coupon 5%, maturité 10 ans, semi-annuel
bond = Bond(face_value=1000, coupon_rate=0.05, maturity=10, frequency=2)
price(ytm)
→ float

Prix théorique de l'obligation pour un taux de rendement donné.

python
prix = bond.price(ytm=0.04)
print(f"Prix: {prix:.2f}€") # > 1000 car YTM < coupon
ytm(market_price)
→ float

Yield to Maturity — rendement actuariel jusqu'à maturité.

python
ytm = bond.ytm(market_price=980)
print(f"YTM: {ytm:.2%}") # ex: 5.24%
duration(ytm) / modified_duration(ytm) / convexity(ytm)
→ float

Mesures de sensibilité aux taux d'intérêt.

python
ytm = 0.045
print(f"Duration Macaulay: {bond.duration(ytm):.2f} ans")
print(f"Duration modifiée: {bond.modified_duration(ytm):.2f}")
print(f"Convexité: {bond.convexity(ytm):.2f}")
ZeroCouponBond
Obligation zéro-coupon — ne verse que la valeur nominale à maturité.
__init__(face_value, maturity)
python
from quantfinance.pricing.bonds import ZeroCouponBond

# Obligation zéro-coupon 1000€, maturité 5 ans
zcb = ZeroCouponBond(face_value=1000, maturity=5)
print(f"Prix (YTM 4%): {zcb.price(ytm=0.04):.2f}€")
print(f"YTM (prix 820): {zcb.ytm(price=820):.2%}")
print(f"Duration: {zcb.duration():.1f} ans") # = maturité pour un zéro-coupon
YieldCurve
Courbe de taux avec interpolation linéaire, quadratique ou cubique (spline).
__init__(maturities, rates)
python
from quantfinance.pricing.bonds import YieldCurve

maturities = [0.25, 0.5, 1, 2, 5, 10, 20, 30]
rates = [0.04, 0.041, 0.043, 0.045, 0.047, 0.049, 0.050, 0.051]
curve = YieldCurve(maturities, rates)
interpolate(maturity, method='linear')
→ float | np.ndarray

Interpole le taux pour une ou plusieurs maturités. Méthodes disponibles : 'linear', 'cubic', 'quadratic'.

python
r_3y = curve.interpolate(3, method='cubic')
print(f"Taux 3 ans: {r_3y:.2%}")
forward_rate(t1, t2, method='linear')
→ float

Taux forward entre deux dates : f(t1, t2) = (r₂·t₂ − r₁·t₁) / (t₂ − t₁).

python
# Taux forward entre 1 an et 3 ans
fwd = curve.forward_rate(t1=1, t2=3)
print(f"Taux forward 1x3: {fwd:.2%}")
discount_factor(maturity, method='linear')
→ float

Facteur d'actualisation DF(T) = e^(−r·T).

python
df_5y = curve.discount_factor(5)
print(f"Facteur d'actualisation 5 ans: {df_5y:.4f}")
par_rate(maturity, frequency=2, method='linear')
→ float

Taux par — taux du coupon qui donne un prix de 100.

python
par = curve.par_rate(maturity=5, frequency=2)
print(f"Taux par 5 ans: {par:.2%}")
plot(n_points=100, method='cubic')
→ Figure

Trace la courbe de taux interpolée avec les points de marché.

python
import matplotlib.pyplot as plt
fig = curve.plot(method='cubic')
plt.show()
instantaneous_forward_rate(maturity, method='cubic', h=0.001)
→ float

Taux forward instantané f(t) = r(t) + t · dr/dt, calculé par différence finie.

python
ifr = curve.instantaneous_forward_rate(maturity=5, method='cubic')
print(f"Taux forward instantané (5 ans): {ifr:.2%}")
BondPortfolio
Portefeuille d'obligations — agrégation des métriques de risque.
__init__(bonds, weights)
python
from quantfinance.pricing.bonds import Bond, BondPortfolio

b1 = Bond(face_value=1000, coupon_rate=0.05, maturity=5)
b2 = Bond(face_value=1000, coupon_rate=0.03, maturity=10)
b3 = Bond(face_value=1000, coupon_rate=0.04, maturity=2)

portfolio = BondPortfolio(bonds=[b1, b2, b3], weights=[0.5, 0.3, 0.2])
price(ytm) / duration(ytm) / modified_duration(ytm) / convexity(ytm)
→ float

Métriques agrégées du portefeuille. ytm peut être un scalaire (même taux pour toutes) ou un array (un taux par obligation).

python
ytm = 0.045
print(f"Prix: {portfolio.price(ytm):.2f}")
print(f"Duration Macaulay: {portfolio.duration(ytm):.2f} ans")
print(f"Duration modifiée: {portfolio.modified_duration(ytm):.2f}")
print(f"Convexité: {portfolio.convexity(ytm):.2f}")
summary(ytm)
→ pd.DataFrame

Tableau récapitulatif de toutes les métriques par obligation et pour le portefeuille total.

python
df = portfolio.summary(ytm=0.045)
print(df)
# Bond     Weight   Price  Duration  Modified Duration  Convexity  YTM
# Bond 1     0.50  ...     ...       ...                ...        0.045
# Bond 2     0.30  ...     ...       ...                ...        0.045
# PORTFOLIO  1.00  ...     ...       ...                ...        0.045
ImpliedVolatility
Utilitaires pour le calcul de volatilité implicite et surface de vol.
calculate(market_price, S, K, T, r, option_type, method='newton')
→ float · Méthode statique

Calcule la volatilité implicite d'une option à partir de son prix de marché.

python
from quantfinance.pricing.options import ImpliedVolatility

iv = ImpliedVolatility.calculate(
 market_price=8.50, S=100, K=105, T=1, r=0.05, option_type='call'
)
print(f"Volatilité implicite: {iv:.2%}")
volatility_surface(market_prices, S, strikes, maturities, r, option_type)
→ np.ndarray · Méthode statique

Calcule une surface de volatilité implicite complète à partir d'une matrice de prix de marché (n_strikes × n_maturities).

ParamètreTypeDescription
market_pricesnp.ndarrayMatrice (n_strikes × n_maturities) des prix observés
strikesnp.ndarrayTableau des prix d'exercice
maturitiesnp.ndarrayTableau des maturités
python
import numpy as np

strikes = np.array([90, 95, 100, 105, 110])
maturities = np.array([0.25, 0.5, 1.0, 2.0])
prices = np.random.uniform(1, 15, (5, 4)) # exemple

surface = ImpliedVolatility.volatility_surface(
 market_prices=prices, S=100, strikes=strikes,
 maturities=maturities, r=0.05, option_type='call'
)
print(surface) # Matrice 5x4 de vols implicites

quantfinance.risk

Module de mesure et d'analyse des risques de marché.

VaRCalculator
Value at Risk et Expected Shortfall — 4 méthodes.
historical_var(returns, confidence)
→ float · Méthode statique

VaR historique non paramétrique — quantile empirique des pertes observées.

python
from quantfinance.risk.var import VaRCalculator

var_95 = VaRCalculator.historical_var(returns, confidence=0.95)
var_99 = VaRCalculator.historical_var(returns, confidence=0.99)
print(f"VaR 95%: {var_95:.2%}")
print(f"VaR 99%: {var_99:.2%}")
parametric_var(returns, confidence)
→ float · Méthode statique

VaR paramétrique sous hypothèse de normalité des rendements.

python
var_param = VaRCalculator.parametric_var(returns, confidence=0.95)
print(f"VaR paramétrique 95%: {var_param:.2%}")
ewma_var(returns, confidence, lambda_=0.94)
→ float · Méthode statique

VaR avec pondération exponentielle — donne plus de poids aux observations récentes (modèle RiskMetrics).

python
# lambda_ = 0.94 est le standard RiskMetrics pour données journalières
var_ewma = VaRCalculator.ewma_var(returns, confidence=0.95, lambda_=0.94)
print(f"VaR EWMA 95%: {var_ewma:.2%}")
monte_carlo_var(returns, confidence, n_sim=10000)
→ float · Méthode statique

VaR par simulation Monte Carlo de trajectoires futures.

python
var_mc = VaRCalculator.monte_carlo_var(returns, confidence=0.95, n_sim=100000)
print(f"VaR Monte Carlo 95%: {var_mc:.2%}")
expected_shortfall(returns, confidence)
→ float · Méthode statique

CVaR / Expected Shortfall — perte moyenne dans les scénarios pires que la VaR. Mesure cohérente du risque.

python
es_95 = VaRCalculator.expected_shortfall(returns, confidence=0.95)
print(f"CVaR 95%: {es_95:.2%}") # toujours >= VaR

# Comparaison des 4 méthodes
for method, val in [
 ("Historique", VaRCalculator.historical_var(returns, 0.95)),
 ("Paramétrique", VaRCalculator.parametric_var(returns, 0.95)),
 ("EWMA", VaRCalculator.ewma_var(returns, 0.95)),
 ("Monte Carlo", VaRCalculator.monte_carlo_var(returns, 0.95)),
]:
 print(f"{method:14s}: {val:.2%}")
RiskMetrics
Ratios de performance et métriques de risque.
sharpe_ratio(returns, risk_free_rate=0.0, periods_per_year=252)
→ float · Méthode statique

Ratio rendement/risque annualisé. Mesure le rendement excédentaire par unité de volatilité totale.

python
from quantfinance.risk.metrics import RiskMetrics

sharpe = RiskMetrics.sharpe_ratio(returns, risk_free_rate=0.02)
print(f"Sharpe: {sharpe:.3f}") # > 1 = bon, > 2 = excellent
sortino_ratio(returns, risk_free_rate=0.0, target_return=None, periods_per_year=252)
→ float · Méthode statique

Comme Sharpe mais ne pénalise que la volatilité négative (downside deviation).

python
sortino = RiskMetrics.sortino_ratio(returns, risk_free_rate=0.02)
print(f"Sortino: {sortino:.3f}")
calmar_ratio(returns, periods_per_year=252)
→ float · Méthode statique

Rendement annualisé divisé par le drawdown maximum. Mesure la récupération après perte.

python
calmar = RiskMetrics.calmar_ratio(returns)
print(f"Calmar: {calmar:.3f}")
max_drawdown(returns)
→ float · Méthode statique

Perte maximale depuis un pic précédent, exprimée en pourcentage.

python
mdd = RiskMetrics.max_drawdown(returns)
print(f"Max Drawdown: {mdd:.2%}") # ex: -23.45%
information_ratio(returns, benchmark_returns, periods_per_year=252)
→ float · Méthode statique

Alpha annualisé divisé par le tracking error — mesure la valeur ajoutée par rapport à un benchmark.

python
ir = RiskMetrics.information_ratio(portfolio_returns, benchmark_returns, periods_per_year=252)
print(f"Information Ratio: {ir:.3f}")
omega_ratio(returns, threshold=0.0)
→ float · Méthode statique

Ratio Omega — rapport entre les gains au-dessus du seuil et les pertes en-dessous. Contrairement au Sharpe, ne suppose pas la normalité.

python
omega = RiskMetrics.omega_ratio(returns, threshold=0.0)
print(f"Ratio Omega: {omega:.3f}") # > 1 = bonne performance
beta(returns, market_returns)
→ float · Méthode statique

Sensibilité au marché. β = Cov(R, R_market) / Var(R_market). β > 1 = plus volatile que le marché.

python
b = RiskMetrics.beta(portfolio_returns, market_returns)
print(f"Beta: {b:.3f}")
alpha(returns, market_returns, risk_free_rate=0.0, periods_per_year=252)
→ float · Méthode statique

Alpha de Jensen annualisé — surperformance par rapport au CAPM : α = E[R] − (Rf + β·(E[Rm] − Rf)).

python
a = RiskMetrics.alpha(portfolio_returns, market_returns, risk_free_rate=0.02)
print(f"Alpha annualisé: {a:.2%}")
tracking_error(returns, benchmark_returns, periods_per_year=252)
→ float · Méthode statique

Tracking error annualisée — écart-type des rendements actifs (R − R_benchmark).

python
te = RiskMetrics.tracking_error(portfolio_returns, benchmark_returns)
print(f"Tracking Error: {te:.2%}")
downside_deviation(returns, target_return=0.0, periods_per_year=252)
→ float · Méthode statique

Déviation à la baisse annualisée — racine de la moyenne des carrés des rendements inférieurs au seuil cible.

python
dd_dev = RiskMetrics.downside_deviation(returns, target_return=0.0)
print(f"Downside deviation: {dd_dev:.2%}")
drawdown_series(returns)
→ array | pd.Series · Méthode statique

Série complète de drawdowns à chaque point dans le temps (valeurs entre -1 et 0).

python
dd_series = RiskMetrics.drawdown_series(returns)
print(dd_series.describe())
max_drawdown_duration(returns)
→ int · Méthode statique

Durée maximale du drawdown en nombre de périodes (du pic au creux).

python
duration = RiskMetrics.max_drawdown_duration(returns)
print(f"Durée max drawdown: {duration} jours")
skewness(returns) / kurtosis(returns, excess=True)
→ float · Méthodes statiques

Statistiques de distribution. Skewness > 0 = queue droite. Kurtosis en excès > 0 = queues plus épaisses que la normale (leptokurtique).

python
print(f"Skewness: {RiskMetrics.skewness(returns):.3f}")
print(f"Kurtosis (excès): {RiskMetrics.kurtosis(returns):.3f}") # excès = True par défaut
print(f"Kurtosis (brut): {RiskMetrics.kurtosis(returns, excess=False):.3f}")
var_ratio(returns, q=2)
→ float · Méthode statique

Variance ratio — test de l'hypothèse de marche aléatoire. VR = 1 si marche aléatoire, VR > 1 suggère une tendance, VR < 1 une mean-reversion.

python
vr = RiskMetrics.var_ratio(returns, q=5)
print(f"Variance Ratio (q=5): {vr:.3f}") # 1.0 = random walk
PerformanceAnalyzer
Analyse complète des performances d'un portefeuille.
__init__(returns, risk_free_rate=0.02)
python
from quantfinance.risk.metrics import PerformanceAnalyzer

analyzer = PerformanceAnalyzer(returns, risk_free_rate=0.02)
summary_statistics()
→ pd.DataFrame

Tableau complet de toutes les métriques de performance et de risque.

python
summary = analyzer.summary_statistics()
print(summary)
# Rendement annualisé : 12.34%
# Volatilité annualisée : 8.92%
# Ratio de Sharpe : 1.382
# Max Drawdown : -15.23%
# VaR 95% : -1.45%
# CVaR 95% : -2.10%
rolling_metrics(window=252)
→ pd.DataFrame

Métriques calculées sur une fenêtre glissante — utile pour suivre l'évolution dans le temps.

python
# Fenêtre de 252 jours (1 an)
rolling = analyzer.rolling_metrics(window=252)
print(rolling[['sharpe', 'volatility']].tail())
plot_performance()
→ Figure

Graphique de la performance cumulée et des drawdowns.

python
import matplotlib.pyplot as plt
fig = analyzer.plot_performance()
plt.show()
portfolio_var(returns_matrix, weights, confidence_level=0.95, method='parametric')
→ float · Méthode statique

VaR d'un portefeuille pondéré. Méthodes disponibles : 'historical', 'parametric', 'monte_carlo'.

ParamètreTypeDescription
returns_matrixpd.DataFrameMatrice des rendements (colonnes = actifs)
weightsnp.ndarrayPoids du portefeuille (normalisés automatiquement)
python
import numpy as np

weights = np.array([0.4, 0.3, 0.2, 0.1])
pvar = VaRCalculator.portfolio_var(returns_matrix, weights, confidence_level=0.95)
print(f"VaR Portefeuille (95%): {pvar:.2%}")
component_var(returns_matrix, weights, confidence_level=0.95)
→ pd.Series · Méthode statique

VaR componentielle — contribution de chaque actif à la VaR totale du portefeuille. La somme des composantes est égale à la VaR totale.

python
cvar_comp = VaRCalculator.component_var(returns_matrix, weights)
print(cvar_comp) # Contribution de chaque actif à la VaR totale
print(f"Somme: {cvar_comp.sum():.4f}") # ≈ portfolio_var
marginal_var(returns_matrix, weights, confidence_level=0.95)
→ pd.Series · Méthode statique

VaR marginale — variation de VaR pour une petite augmentation de poids dans chaque actif. Utile pour décider où allouer ou retirer du capital.

python
mvar = VaRCalculator.marginal_var(returns_matrix, weights)
print(mvar) # Actif avec mvar le + bas = meilleure diversification
monthly_returns_table()
→ pd.DataFrame

Génère une table pivot des rendements mensuels — lignes = années, colonnes = mois (Jan–Déc) + colonne moyenne annuelle. Requiert un index DatetimeIndex.

python
table = analyzer.monthly_returns_table()
print(table.applymap(lambda x: f"{x:.1%}" if pd.notna(x) else ""))
StressTesting
Tests de résistance sous scénarios extrêmes.
historical_scenarios(returns)
→ pd.DataFrame · Méthode statique

Applique des scénarios de crises historiques (2008, COVID-19, etc.) au portefeuille.

python
from quantfinance.risk.stress import StressTesting

results = StressTesting.historical_scenarios(returns)
print(results)
# Crise 2008 : -38.2%
# COVID 2020 : -12.4%
# Dot-com 2000 : -25.1%
custom_scenario(returns, shocks)
→ float · Méthode statique

Applique des chocs personnalisés sur les facteurs de risque.

python
# Scénario : choc de -30% sur les actions, +200bps sur les taux
shocks = {'equity': -0.30, 'rates': 0.02}
pnl = StressTesting.custom_scenario(returns, shocks)
print(f"P&L sous stress: {pnl:.2%}")

quantfinance.utils

Utilitaires pour le chargement de données et le calcul d'indicateurs techniques.

DataLoader
Chargement et génération de données financières.
load_csv(path)
→ pd.DataFrame · Méthode statique

Charge un fichier CSV local de prix ou de rendements.

python
from quantfinance.utils.data import DataLoader

prices = DataLoader.load_csv('data/prices.csv')
print(prices.head())
load_yahoo(tickers, start, end)
→ pd.DataFrame · Méthode statique

Télécharge les données historiques depuis Yahoo Finance.

python
prices = DataLoader.load_yahoo(
 tickers=['AAPL', 'MSFT', 'GOOGL', 'AMZN'],
 start='2020-01-01',
 end='2024-01-01'
)
returns = prices.pct_change().dropna()
print(returns.describe())
generate_synthetic_prices(n_assets, n_days)
→ pd.DataFrame · Méthode statique

Génère des prix synthétiques par mouvement brownien géométrique (GBM).

python
# 5 actifs sur 3 ans de données journalières
prices = DataLoader.generate_synthetic_prices(n_assets=5, n_days=252*3)
returns = prices.pct_change().dropna()
generate_ohlcv_data(n_days)
→ pd.DataFrame · Méthode statique

Génère des données OHLCV (Open, High, Low, Close, Volume) pour le backtesting.

python
data = DataLoader.generate_ohlcv_data(n_days=500)
print(data.columns.tolist())
# ['Open', 'High', 'Low', 'Close', 'Volume']
clean_data(df)
→ pd.DataFrame · Méthode statique

Nettoie les données financières : valeurs manquantes, outliers, données incohérentes.

python
raw_data = DataLoader.load_csv('data/raw_prices.csv')
clean = DataLoader.clean_data(raw_data)
print(f"Lignes supprimées: {len(raw_data) - len(clean)}")
TechnicalIndicators
Indicateurs techniques classiques sur séries de prix.
sma(prices, window) / ema(prices, window)
→ pd.Series · Méthodes statiques

Moyennes mobiles simple (SMA) et exponentielle (EMA).

python
from quantfinance.utils.indicators import TechnicalIndicators

close = prices['Close']
sma_20 = TechnicalIndicators.sma(close, window=20)
ema_50 = TechnicalIndicators.ema(close, window=50)

# Signal de croisement
signal = (sma_20 > ema_50).astype(int)
rsi(prices, window=14)
→ pd.Series · Méthode statique

Relative Strength Index — oscillateur entre 0 et 100. Suracheté > 70, survendu < 30.

python
rsi = TechnicalIndicators.rsi(close, window=14)
print(f"RSI actuel: {rsi.iloc[-1]:.1f}")
survendu = rsi[rsi <30]
surachete = rsi[rsi >70]
macd(prices)
→ Dict · Méthode statique

MACD (12,26,9) — retourne la ligne MACD, la ligne signal et l'histogramme.

python
macd = TechnicalIndicators.macd(close)
print(macd['macd'].tail())
print(macd['signal'].tail())
print(macd['histogram'].tail())
bollinger_bands(prices, window=20)
→ Dict · Méthode statique

Bandes de Bollinger (±2 écarts-types autour de la SMA).

python
bands = TechnicalIndicators.bollinger_bands(close, window=20)
print(f"Bande haute: {bands['upper'].iloc[-1]:.2f}")
print(f"Bande centrale: {bands['middle'].iloc[-1]:.2f}")
print(f"Bande basse: {bands['lower'].iloc[-1]:.2f}")
generate_ohlcv_data(n_days=252, initial_price=100, volatility=0.02, volume_mean=1000000, random_seed=None)
→ pd.DataFrame · Méthode statique

Génère des données OHLCV (Open, High, Low, Close, Volume) synthétiques via mouvement brownien géométrique.

python
ohlcv = DataLoader.generate_ohlcv_data(n_days=252, volatility=0.02, random_seed=42)
print(ohlcv.head())
# Retourne DataFrame avec colonnes: Open, High, Low, Close, Volume
download_pandas_datareader(tickers, start_date, end_date=None, source='yahoo')
→ pd.DataFrame · Méthode statique

Télécharge des données via pandas-datareader. Nécessite pip install pandas-datareader. Sources disponibles : 'yahoo', 'fred', 'iex', etc.

python
# Données FRED (taux d'intérêt, indicateurs macro)
gdp = DataLoader.download_pandas_datareader(
 tickers='GDP', start_date='2010-01-01', source='fred'
)
scenario_analysis(returns_matrix, weights, scenarios)
→ pd.DataFrame · Méthode statique

Analyse de scénarios de stress définis manuellement. scenarios est un dict {nom: array_chocs} où chaque array contient un choc par actif.

ParamètreTypeDescription
scenariosDict[str, np.ndarray]Ex: {'Crise 2008': np.array([-0.4, -0.3, -0.1])}
python
from quantfinance.risk.var import StressTesting
import numpy as np

scenarios = {
 'Crise 2008': np.array([-0.40, -0.35, -0.10]),
 'Covid 2020': np.array([-0.30, -0.25, -0.05]),
 'Hausse taux': np.array([-0.05, -0.15, 0.02]),
}
result = StressTesting.scenario_analysis(returns_matrix, weights, scenarios)
print(result) # Colonnes: Scenario, Portfolio Return, P&L, New Value
historical_stress_test(returns_matrix, weights, stress_periods)
→ pd.DataFrame · Méthode statique

Stress test basé sur des périodes historiques réelles. stress_periods est un dict {nom: (date_debut, date_fin)}. Retourne rendement cumulé, max drawdown, pire/meilleur jour et volatilité pour chaque période.

python
stress_periods = {
 'Crise financière': ('2008-09-01', '2009-03-31'),
 'Covid crash': ('2020-02-19', '2020-03-23'),
}
result = StressTesting.historical_stress_test(returns_matrix, weights, stress_periods)
print(result)
monte_carlo_stress_test(returns_matrix, weights, n_scenarios=1000, confidence_levels=[0.95, 0.99], horizon=1, random_seed=None)
→ Dict · Méthode statique

Stress test Monte Carlo — simule n_scenarios scénarios et retourne VaR, ES et distribution des pertes. Clés du dict retourné : scenarios, mean, std, min, max, VaR_95, ES_95, VaR_99, ES_99.

python
result = StressTesting.monte_carlo_stress_test(
 returns_matrix, weights, n_scenarios=5000, random_seed=42
)
print(f"VaR 95%: {result['VaR_95']:.2%} | ES 99%: {result['ES_99']:.2%}")
DataCleaner
Nettoyage et préparation de données financières.
handle_missing_values(data, method='ffill', limit=None)
→ pd.DataFrame · Méthode statique

Gère les valeurs manquantes. Méthodes : 'ffill', 'bfill', 'interpolate', 'drop', 'mean', 'median'.

python
from quantfinance.utils.data import DataCleaner

clean = DataCleaner.handle_missing_values(prices, method='ffill', limit=5)
clean = DataCleaner.handle_missing_values(prices, method='interpolate')
remove_outliers(data, n_std=3.0, method='clip')
→ pd.DataFrame · Méthode statique

Retire ou corrige les valeurs aberrantes. Méthodes : 'clip' (ajuste aux bornes ±n_std), 'remove' (supprime les lignes), 'winsorize' (clip aux percentiles 5%/95%).

python
clean = DataCleaner.remove_outliers(returns, n_std=3.0, method='clip')
winsorize(data, lower_percentile=0.05, upper_percentile=0.95)
→ pd.DataFrame · Méthode statique

Plafonne les valeurs extrêmes aux percentiles spécifiés.

python
winsorized = DataCleaner.winsorize(returns, lower_percentile=0.01, upper_percentile=0.99)
calculate_returns(prices, method='simple', periods=1)
→ pd.DataFrame · Méthode statique

Calcule les rendements à partir des prix. Méthodes : 'simple' (arithmétique, pct_change) ou 'log' (logarithmique, ln(P_t/P_{t-1})). Supprime automatiquement les NaN.

python
returns = DataCleaner.calculate_returns(prices, method='log')
returns_5d = DataCleaner.calculate_returns(prices, method='simple', periods=5)
align_data(*dataframes, join='inner') / resample_data(data, frequency, method='last')
→ tuple | pd.DataFrame · Méthodes statiques

align_data aligne plusieurs DataFrames sur les mêmes dates. resample_data rééchantillonne à une fréquence différente — méthodes : 'last', 'first', 'mean', 'sum'.

python
df1, df2 = DataCleaner.align_data(prices1, prices2, join='inner')
monthly = DataCleaner.resample_data(daily_prices, frequency='M', method='last')
DataTransformer
Transformations de données financières : normalisation, indicateurs techniques, features ML.
normalize(data, method='zscore')
→ pd.DataFrame · Méthode statique

Normalise les données. Méthodes : 'zscore' (standardisation), 'minmax' (0–1), 'robust' (médiane/IQR).

python
from quantfinance.utils.data import DataTransformer

normalized = DataTransformer.normalize(returns, method='robust')
add_technical_indicators(data, indicators=None)
→ pd.DataFrame · Méthode statique

Ajoute des indicateurs techniques à un DataFrame OHLCV. Indicateurs disponibles : 'SMA' (20/50/200), 'EMA' (12/26), 'RSI' (14), 'MACD' (12/26/9), 'BB' (Bollinger 20), 'ATR' (14). Par défaut : ['SMA', 'EMA'].

IndicateurColonnes ajoutéesDescription
SMASMA_20, SMA_50, SMA_200Moyennes mobiles simples
EMAEMA_12, EMA_26Moyennes mobiles exponentielles
RSIRSIRelative Strength Index
MACDMACD, MACD_Signal, MACD_HistMACD + signal + histogramme
BBBB_Upper, BB_Middle, BB_LowerBandes de Bollinger
ATRATRAverage True Range (nécessite OHLC)
python
data_with_indicators = DataTransformer.add_technical_indicators(
 ohlcv, indicators=['SMA', 'EMA', 'RSI', 'MACD', 'BB', 'ATR']
)
print(data_with_indicators.columns.tolist())
create_lagged_features(data, lags=[1, 2, 3, 5])
→ pd.DataFrame · Méthode statique

Crée des features retardées pour les modèles de machine learning. Les NaN initiaux sont supprimés.

python
features = DataTransformer.create_lagged_features(returns, lags=[1, 2, 5, 10])
# Ajoute colonnes: col_lag_1, col_lag_2, col_lag_5, col_lag_10
rolling_statistics(data, windows=[20, 60, 120])
→ pd.DataFrame · Méthode statique

Calcule des statistiques roulantes (moyenne, écart-type, min, max) pour chaque colonne et chaque fenêtre. Les NaN initiaux sont supprimés.

python
stats = DataTransformer.rolling_statistics(returns, windows=[20, 60])
# Ajoute: col_mean_20, col_std_20, col_min_20, col_max_20, etc.
ƒ
Fonctions utilitaires
Fonctions standalone dans quantfinance.utils.helpers pour l'annualisation et la conversion de rendements.
annualize_return(returns, periods_per_year=252)
→ float | array | Series

Annualise un rendement par multiplication simple : returns × periods_per_year. Pour les rendements quotidiens → annuels, utilise 252.

python
from quantfinance.utils.helpers import annualize_return, annualize_volatility

daily_return = 0.0004  # 0.04%/jour
annual = annualize_return(daily_return, periods_per_year=252)
print(f"Rendement annualisé: {annual:.2%}") # ≈ 10.08%
annualize_volatility(volatility, periods_per_year=252)
→ float | array | Series

Annualise une volatilité par la règle en racine carrée du temps : σ × √periods_per_year.

python
daily_vol = 0.012  # 1.2%/jour
annual_vol = annualize_volatility(daily_vol)
print(f"Volatilité annualisée: {annual_vol:.2%}") # ≈ 19.05%
compound_returns(returns)
→ float

Calcule le rendement composé total : ∏(1 + r_t) − 1.

python
from quantfinance.utils.helpers import compound_returns

total = compound_returns(daily_returns)
print(f"Rendement total: {total:.2%}")
deannualize_return(annual_return, periods_per_year=252) / deannualize_volatility(annual_volatility, periods_per_year=252)
→ float

Inverses des fonctions d'annualisation. deannualize_return : r / n. deannualize_volatility : σ / √n.

python
from quantfinance.utils.helpers import deannualize_return, deannualize_volatility

daily_r = deannualize_return(0.10)  # 10% annuel → quotidien
daily_v = deannualize_volatility(0.20)  # 20% annuel → quotidien
date_range(start, end, freq='D')
→ pd.DatetimeIndex

Crée une plage de dates. Fréquences : 'D' (quotidien), 'B' (jours ouvrés), 'W', 'M', 'Q', 'Y'.

python
from quantfinance.utils.helpers import date_range

trading_days = date_range('2023-01-01', '2023-12-31', freq='B')
print(f"{len(trading_days)} jours de trading en 2023")
Plotter
Visualisations financières de base — prix, rendements, corrélations, drawdowns.
__init__(style='seaborn-v0_8-darkgrid', figsize=(12, 6))
python
from quantfinance.utils.plotting import Plotter
plotter = Plotter(style='seaborn-v0_8-darkgrid', figsize=(14, 7))
plot_prices(prices, title='Prix des Actifs', figsize=(12,6), normalize=False)
→ Figure · Méthode statique

Trace l'évolution des prix. Avec normalize=True, réindexe à 100 en début de période pour comparer des actifs à des échelles différentes.

python
fig = Plotter.plot_prices(prices, normalize=True, title="Performance relative")
fig.savefig('prices.png', dpi=150)
plot_returns(returns, title='Distribution des Rendements', figsize=(14,8), bins=50)
→ Figure · Méthode statique

Histogramme de distribution des rendements avec courbe normale de référence pour chaque actif.

python
fig = Plotter.plot_returns(returns)
plot_correlation_matrix(returns, title='Matrice de Corrélation', annot=True, method='pearson')
→ Figure · Méthode statique

Heatmap de corrélation avec triangle inférieur seulement. Méthodes : 'pearson', 'spearman', 'kendall'.

python
fig = Plotter.plot_correlation_matrix(returns, method='spearman')
plot_drawdown(returns, title='Analyse de Drawdown', figsize=(12,8))
→ Figure · Méthode statique

2 sous-graphiques : valeur cumulée + série de drawdowns avec annotation automatique du max drawdown.

python
fig = Plotter.plot_drawdown(returns)
plot_portfolio_weights(weights, kind='both')
→ Figure · Méthode statique

Graphique des poids du portefeuille. kind : 'bar', 'pie', ou 'both' (barre + camembert côte à côte).

python
result = optimizer.maximize_sharpe()
fig = Plotter.plot_portfolio_weights(result['weights'], kind='both')
plot_efficient_frontier(returns, n_portfolios=10000, show_cml=True, risk_free_rate=0.02)
→ Figure · Méthode statique

Frontière efficiente Monte Carlo avec actifs individuels, max Sharpe (★ rouge), min volatilité (★ vert) et Capital Market Line (CML) optionnelle.

python
fig = Plotter.plot_efficient_frontier(returns, n_portfolios=5000, risk_free_rate=0.03)
FinancialPlotter
Graphiques financiers avancés — chandeliers, scatter risque/rendement, métriques roulantes.
plot_candlestick(data, title='Graphique en Chandelier', figsize=(14,8), volume=True)
→ Figure · Méthode statique

Graphique en chandelier. Utilise mplfinance si installé, sinon fallback manuel. Requiert un DataFrame OHLCV avec colonnes Open, High, Low, Close et index DatetimeIndex.

python
from quantfinance.utils.plotting import FinancialPlotter

ohlcv = DataLoader.generate_ohlcv_data(n_days=60)
fig = FinancialPlotter.plot_candlestick(ohlcv, volume=True)
# pip install mplfinance pour graphique complet
plot_risk_return_scatter(returns, figsize=(12,8), periods_per_year=252)
→ Figure · Méthode statique

Nuage de points risque (volatilité annualisée) vs rendement annualisé pour chaque actif.

python
fig = FinancialPlotter.plot_risk_return_scatter(returns)
plot_rolling_metrics(returns, window=60, figsize=(14,10), periods_per_year=252)
→ Figure · Méthode statique

3 sous-graphiques roulants : rendement annualisé, volatilité annualisée, ratio de Sharpe.

python
fig = FinancialPlotter.plot_rolling_metrics(
 returns=portfolio_returns, window=60
)
ƒ
bootstrap_zero_curve()
Fonction standalone — bootstrap d'une courbe zéro-coupon à partir de prix d'obligations.
bootstrap_zero_curve(bond_prices, coupon_rates, maturities, face_value=100, frequency=2)
→ YieldCurve

Bootstrap une courbe de taux zéro-coupon à partir d'une liste de prix d'obligations. Retourne un objet YieldCurve.

python
from quantfinance.pricing.bonds import bootstrap_zero_curve

prices = [99.5, 98.0, 96.5, 94.0]
coupons = [0.02, 0.03, 0.04, 0.05]
maturities = [1, 2, 3, 5]

zero_curve = bootstrap_zero_curve(prices, coupons, maturities)
print(f"Taux zéro 3 ans: {zero_curve.interpolate(3):.2%}")
download_yahoo_finance(tickers, start_date, end_date=None, column='Adj Close', interval='1d')
→ pd.DataFrame · Méthode statique

Télécharge des données depuis Yahoo Finance. Nécessite pip install yfinance. Colonnes disponibles : 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume'. Intervalles : '1d', '1wk', '1mo'.

python
prices = DataLoader.download_yahoo_finance(
 tickers=['AAPL', 'MSFT', 'GOOGL'],
 start_date='2020-01-01',
 end_date='2024-01-01',
 column='Adj Close'
)
print(prices.tail())

Formules mathématiques

Les équations fondamentales implémentées dans QuantFinance.

Black-Scholes

Prix d'un call européen
C = S · N(d₁) − K · erT · N(d₂)
d₁ = [ln(S/K) + (r + σ²/2)T] / (σT)
d₂ = d₁σT
S = Prix spot  ·  K = Strike  ·  T = Maturité  ·  r = Taux sans risque  ·  σ = Volatilité  ·  N(·) = Loi normale cumulée

Les Grecques

Sensibilités du prix de l'option
Δ (Delta) = N(d₁)
Γ (Gamma) = N′(d₁) / (S · σ · √T)
ν (Vega) = S · N′(d₁) · √T
Θ (Theta) = −[S·N′(d₁σ / (2√T)] − r·K·erT·N(d₂)
ρ (Rho) = K·T·erT·N(d₂)
N′(·) = densité de la loi normale standard

Value at Risk

VaR paramétrique (hypothèse de normalité)
VaRα = μzα · σ
CVaRα = μσ · φ(zα) / (1 − α)
zα = quantile de la loi normale pour α  ·  φ = densité normale  ·  α = niveau de confiance (ex: 0.95)

EWMA — RiskMetrics

Variance à pondération exponentielle décroissante
σ²t = λ · σ²t−1 + (1 − λ) · t−1
λ = facteur de décroissance (0.94 pour données journalières, 0.97 pour hebdomadaires)

Ratios de performance

Sharpe · Sortino · Calmar
Sharpe = (RpRf) / σp
Sortino = (RpRf) / σdownside
Calmar = Rannualisé / |MDD|
Rp = rendement portefeuille  ·  Rf = taux sans risque  ·  MDD = Maximum Drawdown

Optimisation de Markowitz

Problème d'optimisation quadratique
min wΣw    s.c.    w1 = 1,   w ≥ 0
max S(w) = (wμRf) / √(wΣw)
w = vecteur de poids  ·  Σ = matrice de covariance  ·  μ = rendements espérés

Duration & Convexité (Obligations)

Sensibilité aux taux d'intérêt
DMacaulay = Σ [ t · Ct · eyt ] / P
Dmodifiée = DMacaulay / (1 + y/m)
ΔP/P ≈ −Dmod · Δy + ½ · Convexité · (Δy
y = YTM  ·  m = fréquence de coupon  ·  P = prix de l'obligation

Comparaison des méthodes

Pricing d'options

MéthodeOptions américainesOptions exotiquesVitessePrécisionRecommandé pour
BlackScholes Européennes seul. ExacteOptions européennes vanille, calcul des grecques
BinomialTree Exercice anticipé ConvergeOptions américaines, options avec dividendes discrets
MonteCarlo Avec LSM Path-dependent StochastiqueOptions asiatiques, barrières, structures complexes

Calcul de VaR

MéthodeHypothèseQueues épaissesDonnées requisesVitesseRecommandé quand
historical_varAucune Capturées500+ joursDistribution non-normale, données historiques abondantes
parametric_varNormalité des rendements Sous-estimées30+ joursCalcul rapide, portefeuilles bien diversifiés
ewma_varNormalité + vol. variable Sous-estimées100+ joursVolatilité qui change dans le temps (λ = 0.94)
monte_carlo_varGBM ou modèle custom ConfigurablesParamètresPrécision maximale, portefeuilles non-linéaires

Optimisation de portefeuille

MéthodeInversion matriceRobustesseGrands universComplexitéRecommandé quand
equal_weightNulleBenchmark simple, manque de données
minimize_volatilityFaibleMinimiser le risque absolu, profil défensif
maximize_sharpeMoyenneRendement/risque optimal, rendements prévisibles
risk_parityMoyenneÉgaliser la contribution au risque de chaque actif
hierarchical_risk_parityÉlevéeGrands univers, instabilité numérique, fonds alternatifs

Stratégies de backtesting

StratégieTypeParamètresMarchés favorablesRisque sur-optimisation
BuyAndHoldPassiveAucunTendances longuesNul
MovingAverageCrossoverTendanceshort_window, long_windowMarchés directionnelsÉlevé (2 params)

Exemples complets

Des workflows de bout en bout combinant plusieurs modules.

Analyse complète d'une option
Pricing, grecques, volatilité implicite et comparaison des 3 modèles
python
from quantfinance.pricing.options import BlackScholes, BinomialTree, MonteCarlo

# Paramètres communs
params = dict(S=100, K=105, T=1, r=0.05, sigma=0.25, option_type='call')

# Comparaison des 3 modèles
bs = BlackScholes(**params)
bt = BinomialTree(**params, N=500)
mc = MonteCarlo(**params, n_simulations=100000)

print(" Comparaison des prix ")
print(f"Black-Scholes : {bs.price():.4f}")
print(f"Binomial Tree : {bt.price():.4f}")
print(f"Monte Carlo : {mc.price():.4f}")

# Toutes les grecques d'un coup
print("
 Grecques ")
for name, val in {'Delta': bs.delta(), 'Gamma': bs.gamma(),
 'Vega': bs.vega(), 'Theta': bs.theta(), 'Rho': bs.rho()}.items():
 print(f" {name:6s}: {val:.6f}")

# Volatilité implicite depuis un prix de marché
impl_vol = bs.implied_volatility(market_price=9.50)
print(f"
Vol. implicite: {impl_vol:.2%}")

# Smile de volatilité
print("
 Smile de volatilité ")
for K in [85, 90, 95, 100, 105, 110, 115]:
 b = BlackScholes(S=100, K=K, T=1, r=0.05, sigma=0.25)
 print(f" K={K:3d}: prix={b.price():.2f} delta={b.delta():.3f}")
Pipeline données → optimisation → risque
Chargement, 4 stratégies d'optimisation, analyse de risque complète
python
from quantfinance.utils.data import DataLoader
from quantfinance.portfolio.optimization import PortfolioOptimizer, EfficientFrontier
from quantfinance.risk.var import VaRCalculator
from quantfinance.risk.metrics import RiskMetrics, PerformanceAnalyzer

# 1. Données
prices = DataLoader.generate_synthetic_prices(n_assets=6, n_days=756)
returns = prices.pct_change().dropna()

# 2. Comparer les 4 stratégies d'optimisation
opt = PortfolioOptimizer(returns, risk_free_rate=0.02)
strategies = {
 'Équipondéré': opt.equal_weight(),
 'Min Volatilité': opt.minimize_volatility(),
 'Max Sharpe': opt.maximize_sharpe(),
 'Risk Parity': opt.risk_parity(),
}
print(f"{'Stratégie':20s} {'Rendement':>10s} {'Volatilité':>12s} {'Sharpe':>8s}")
for name, res in strategies.items():
 print(f"{name:20s} {res['return']:>10.2%} {res['volatility']:>12.2%} {res['sharpe_ratio']:>8.3f}")

# 3. Analyse de risque sur le meilleur portefeuille
w = strategies['Max Sharpe']['weights']
port_ret = (returns * w).sum(axis=1)

print("
 Risque (Max Sharpe) ")
print(f"VaR 95% Historique : {VaRCalculator.historical_var(port_ret, 0.95):.2%}")
print(f"VaR 95% Paramétrique: {VaRCalculator.parametric_var(port_ret, 0.95):.2%}")
print(f"CVaR 95% : {VaRCalculator.expected_shortfall(port_ret, 0.95):.2%}")

# 4. Rapport complet
analyzer = PerformanceAnalyzer(port_ret, risk_free_rate=0.02)
print("
 Performance ")
print(analyzer.summary_statistics())
Backtest MA Crossover vs Buy & Hold
Comparaison de paramètres avec indicateurs techniques et coûts de transaction
python
from quantfinance.utils.data import DataLoader
from quantfinance.utils.indicators import TechnicalIndicators
from quantfinance.portfolio.backtesting import Backtester, MovingAverageCrossover, BuyAndHoldStrategy

# Données OHLCV sur 3 ans
data = DataLoader.generate_ohlcv_data(n_days=756)
close = data['Close']

# Indicateurs techniques pour analyse préalable
rsi = TechnicalIndicators.rsi(close)
macd = TechnicalIndicators.macd(close)
bands = TechnicalIndicators.bollinger_bands(close)
print(f"RSI: {rsi.iloc[-1]:.1f} | MACD: {macd['macd'].iloc[-1]:.4f}")
print(f"Bollinger: [{bands['lower'].iloc[-1]:.2f} — {bands['upper'].iloc[-1]:.2f}]")

# Comparer plusieurs combinaisons de MA
print(f"
{'Stratégie':18s} {'Rendement':>10s} {'Sharpe':>8s} {'Max DD':>8s}")
for short, long in [(10, 30), (20, 50), (50, 200)]:
 bt = Backtester(data, MovingAverageCrossover(short, long),
 initial_capital=10000, commission=0.001)
 res = bt.run()
 print(f"MA({short},{long}){' ':10s} {res['Total Return']:>10.2%} {res['Sharpe Ratio']:>8.3f} {res['Max Drawdown']:>8.2%}")

# Benchmark Buy & Hold
bh = Backtester(data, BuyAndHoldStrategy(), initial_capital=10000)
res = bh.run()
print(f"Buy & Hold {res['Total Return']:>10.2%} {res['Sharpe Ratio']:>8.3f} {res['Max Drawdown']:>8.2%}")
Stress testing & rééquilibrage
Tests de résistance sur crises historiques et rééquilibrage automatique
python
from quantfinance.utils.data import DataLoader
from quantfinance.portfolio.optimization import PortfolioOptimizer
from quantfinance.portfolio.rebalancing import Rebalancer
from quantfinance.risk.stress import StressTesting

# Setup
prices = DataLoader.generate_synthetic_prices(n_assets=4, n_days=756)
returns = prices.pct_change().dropna()
opt = PortfolioOptimizer(returns, risk_free_rate=0.02)
weights = opt.maximize_sharpe()['weights']

# Stress testing sur crises historiques
print(" Scénarios historiques ")
scenarios = StressTesting.historical_scenarios(returns)
print(scenarios)

# Scénario personnalisé : crash actions + hausse taux
shocks = {'equity': -0.35, 'rates': 0.025, 'credit': 0.015}
pnl = StressTesting.custom_scenario(returns, shocks)
print(f"
Scénario crise 2008 amplifié: {pnl:.2%}")

# Rééquilibrage périodique vs par seuil
rebalancer = Rebalancer(weights, prices, initial_capital=100000, transaction_cost=0.001)

res_monthly = rebalancer.periodic_rebalancing(frequency='monthly')
res_threshold = rebalancer.threshold_rebalancing(threshold=0.05)

print(f"
Rééquilibrage mensuel — transactions: {len(res_monthly)}")
print(f"Rééquilibrage seuil 5% — transactions: {len(res_threshold)}")

Tests

bash
# Tous les tests
pytest

# Avec rapport de couverture HTML
pytest --cov=quantfinance --cov-report=html

# Tests rapides uniquement
pytest -m "not slow"

# Un module spécifique
pytest tests/test_pricing.py -v

Guide de contribution

  1. Fork le dépôt sur GitHub
  2. Créez une branche : git checkout -b feature/ma-fonctionnalite
  3. Implémentez avec des tests unitaires
  4. Committez : git commit -m 'feat: description'
  5. Pushez : git push origin feature/ma-fonctionnalite
  6. Ouvrez une Pull Request sur GitHub

Auteur : Marcel ALOEKPO

Made with for quantitative finance · MIT License