Pivot de la série ‘close’ en DataFrame large, avec remapping des colonnes Symbol → ticker pour Trend-Following.
# Pivoter les donnéescloses = history['close'].unstack(level=0)symbol_to_ticker = {str(v): k for k, v in symbols.items()}closes.columns = [symbol_to_ticker.get(str(c), str(c)) for c in closes.columns]closes = closes.dropna()print(f"Période: {closes.index[0].date()} à {closes.index[-1].date()}")print(f"Données: {len(closes)} jours de trading")print(f"Actions: {len(closes.columns)}")print(f"\nStatistiques des prix finaux (échantillon):")for ticker inlist(closes.columns)[:5]: ret = (closes[ticker].iloc[-1] / closes[ticker].iloc[0] -1) *100print(f" {ticker}: {ret:+.1f}%")
Période: 2019-01-02 à 2025-12-31
Données: 1760 jours de trading
Actions: 2
Statistiques des prix finaux (échantillon):
AAPL: +217.2%
GOOGL: +95.6%
2. Calcul des EMA (50/200)
Le custom alpha model utilise un filtre EMA 50/200 par stock.
def compute_ema(prices, period):"""Calcule l'EMA."""return prices.ewm(span=period, adjust=False).mean()# EMA 50 et 200 pour chaque stockema_50 = closes.apply(lambda col: compute_ema(col, 50))ema_200 = closes.apply(lambda col: compute_ema(col, 200))print("EMA 50 - Derniers 5 jours (échantillon):")print(ema_50.iloc[-5:, :3])print(f"\nEMA 200 - Derniers 5 jours (échantillon):")print(ema_200.iloc[-5:, :3])
EMA 50 - Derniers 5 jours (échantillon):
AAPL GOOGL
time
2025-12-24 13:00:00 122.15 2062.52
2025-12-26 16:00:00 122.15 2062.52
2025-12-29 16:00:00 122.15 2062.52
2025-12-30 16:00:00 122.15 2062.52
2025-12-31 16:00:00 122.15 2062.52
EMA 200 - Derniers 5 jours (échantillon):
AAPL GOOGL
time
2025-12-24 13:00:00 122.149950 2062.517879
2025-12-26 16:00:00 122.149951 2062.517900
2025-12-29 16:00:00 122.149951 2062.517921
2025-12-30 16:00:00 122.149952 2062.517941
2025-12-31 16:00:00 122.149952 2062.517962
Interprétation: Filtre EMA 50/200
EMA 50 > EMA 200: Tendance haussière (signal long)
EMA 50 < EMA 200: Tendance baissière (pas de position)
Multi-stock: Chaque stock est évalué indépendamment
Insights: Les insights sont générés pour chaque stock qualifié
3. Backtest Trend Following Simplifié
Simulation de la stratégie avec: - Filtre EMA 50/200 - Equal-weight portfolio - Max 10 positions (10% chacune) - Rebalance quotidien
def backtest_trend_following(closes, ema_50, ema_200, max_positions=10):""" Backtest Trend Following simplifié. Retourne les métriques de performance. """ portfolio_values = [1.0] current_positions = {} # ticker -> weight warmup =200for i inrange(warmup, len(closes)):# Find stocks with bullish EMA cross bullish = []for ticker in closes.columns:if pd.isna(ema_50[ticker].iloc[i]) or pd.isna(ema_200[ticker].iloc[i]):continueif ema_50[ticker].iloc[i] > ema_200[ticker].iloc[i]: bullish.append(ticker)# Select top N (simplified: just take first N) selected = bullish[:max_positions]ifnot selected: current_positions = {} portfolio_values.append(portfolio_values[-1])continue# Equal weight allocation weight =0.95/len(selected) current_positions = {ticker: weight for ticker in selected}# Calculate return daily_returns = closes.iloc[i] / closes.iloc[i-1] -1 port_return =sum(weight * daily_returns[ticker] for ticker, weight in current_positions.items()) portfolio_values.append(portfolio_values[-1] * (1+ port_return))# Métriques returns = np.diff(portfolio_values) / np.array(portfolio_values[:-1]) cum_returns = pd.Series(portfolio_values[1:], index=closes.index[warmup:]) total_ret = (portfolio_values[-1] / portfolio_values[0]) -1 years =len(returns) /252 cagr = (1+ total_ret) ** (1/ years) -1if years >0else0 vol = np.std(returns) * np.sqrt(252) iflen(returns) >1else0 sharpe = (cagr -0.03) / vol if vol >0.001else0 running_max = cum_returns.expanding().max() drawdown = (cum_returns - running_max) / running_max max_dd = drawdown.min()return {'cum': cum_returns,'sharpe': sharpe,'cagr': cagr,'max_dd': max_dd,'vol': vol,'final_value': portfolio_values[-1] }result = backtest_trend_following(closes, ema_50, ema_200)print(f"Performance Trend Following:")print(f" Sharpe: {result['sharpe']:.3f}")print(f" CAGR: {result['cagr']:.1%}")print(f" Max DD: {result['max_dd']:.1%}")print(f" Vol: {result['vol']:.1%}")