0Pricing
AI Agents · Leçon

Rétrosimulation des décisions de l’agent

Simuler les stratégies de l’agent sur des données historiques afin de valider ses performances.

Rétrosimulation des décisions de l’agent est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.

Pourquoi tester rétrospectivement les décisions de l'agent ?

Vous ne pouvez pas savoir si votre agent financier produit de bonnes recommandations sans le confronter à des données historiques. Le test rétrospectif rejoue les conditions de marché passées dans la logique de décision de l'agent et mesure les performances qu'auraient obtenues ces décisions.

C'est l'outil principal pour valider la qualité d'une stratégie avant sa mise en production.

Le piège du biais d'anticipation

Le biais d'anticipation est l'erreur la plus courante et la plus dangereuse dans les tests rétrospectifs : utiliser des données futures pour prendre une décision « passée ». Par exemple, utiliser le cours de clôture de la journée pour décider d'acheter à l'ouverture de cette même journée.

Chaque décision historique doit utiliser uniquement les données disponibles au moment simulé de la décision.

import pandas as pd
import yfinance as yf

# WRONG — uses today's close to decide today's trade
def bad_signal(history: pd.DataFrame, date: str) -> str:
    today_close = history.loc[date, 'Close']  # look-ahead!
    if today_close > history['Close'].mean():
        return 'BUY'
    return 'HOLD'

# CORRECT — uses only data available BEFORE the decision date
def good_signal(history: pd.DataFrame, date: str) -> str:
    past_data = history.loc[:date].iloc[:-1]  # exclude today
    if past_data.empty:
        return 'HOLD'
    today_open = history.loc[date, 'Open']    # open price known at market open
    if today_open > past_data['Close'].mean():
        return 'BUY'
    return 'HOLD'

Construire la boucle de relecture historique

Parcourez chaque date historique. À chaque étape, l'agent ne voit que les données disponibles jusqu'à cette date. Consignez la décision, puis avancez jusqu'à la date suivante pour observer le résultat.

import yfinance as yf
import pandas as pd

def backtest_simple(ticker: str, start: str, end: str) -> list[dict]:
    hist = yf.Ticker(ticker).history(start=start, end=end)
    results = []

    for i in range(20, len(hist)):  # need 20 days of history for signals
        window   = hist.iloc[:i]           # data available on day i
        today    = hist.iloc[i]
        decision = good_signal(window, str(hist.index[i].date()))

        results.append({
            'date':     str(hist.index[i].date()),
            'decision': decision,
            'open':     float(today['Open']),
            'close':    float(today['Close']),
            'next_day_return': None  # filled in next iteration
        })
    return results

Simuler la logique de l'agent à chaque étape

Pour un test rétrospectif plus réaliste, simulez l'appel complet aux outils de l'agent à chaque date — y compris la construction de l'invite, l'appel au LLM et l'analyse de la décision. Cela teste l'ensemble du système, et pas seulement la logique du signal.

import openai, json
import yfinance as yf

client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')

def agent_decision_at_date(ticker: str, hist_up_to: pd.DataFrame) -> str:
    recent = hist_up_to.tail(20)
    closes = recent['Close'].tolist()
    returns = [round((closes[i] - closes[i-1]) / closes[i-1], 4)
               for i in range(1, len(closes))]
    prompt = (
        f'Ticker: {ticker}\n'
        f'Last 20 closing prices: {closes}\n'
        f'Daily returns: {returns}\n'
        f'Based on this data, recommend: BUY, SELL, or HOLD.\n'
        f'Return JSON: {{"action": "BUY/SELL/HOLD", "reason": "..."}}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    return json.loads(resp.choices[0].message.content).get('action', 'HOLD')

Calculer le taux de réussite

Le taux de réussite mesure le pourcentage de décisions BUY qui ont été suivies d'un rendement positif le jour suivant. Un taux de réussite supérieur à 50 % signifie que l'agent prédit correctement la direction plus souvent que le hasard.

def compute_hit_rate(results: list[dict]) -> dict:
    buy_decisions = [r for r in results if r['decision'] == 'BUY'
                     and r.get('next_day_return') is not None]

    if not buy_decisions:
        return {'hit_rate': None, 'sample_size': 0}

    correct = sum(1 for r in buy_decisions if r['next_day_return'] > 0)
    return {
        'hit_rate':     round(correct / len(buy_decisions), 4),
        'sample_size':  len(buy_decisions),
        'correct':      correct,
        'interpretation': 'above_chance' if correct / len(buy_decisions) > 0.52 else 'at_or_below_chance'
    }

if __name__ == '__main__':
    results = [
        {'decision': 'BUY', 'next_day_return': 0.012},
        {'decision': 'BUY', 'next_day_return': -0.004},
        {'decision': 'HOLD', 'next_day_return': None},
        {'decision': 'BUY', 'next_day_return': 0.007},
    ]
    print('Hit rate:', compute_hit_rate(results))

Calculer le rendement par rapport à l'indice de référence

Comparez le rendement de la stratégie à celui d'une stratégie de référence consistant à acheter puis conserver. Si l'agent fait moins bien qu'une simple conservation de l'indice, sa complexité n'est pas justifiée.

def compare_to_benchmark(results: list[dict], ticker: str, period: str = '1y') -> dict:
    import yfinance as yf

    # Strategy return: only invested on BUY days
    buy_days = [r for r in results if r['decision'] == 'BUY'
                and r.get('next_day_return') is not None]
    strategy_return = sum(r['next_day_return'] for r in buy_days)

    # Benchmark: buy-and-hold
    hist = yf.Ticker(ticker).history(period=period)['Close']
    bh_return = float((hist.iloc[-1] - hist.iloc[0]) / hist.iloc[0])

    return {
        'strategy_return_pct':  round(strategy_return * 100, 2),
        'benchmark_return_pct': round(bh_return * 100, 2),
        'outperformed':         strategy_return > bh_return,
        'alpha_pct':            round((strategy_return - bh_return) * 100, 2)
    }

Impact des coûts de transaction

Sans tenir compte des coûts de transaction (commissions, écart entre cours acheteur et vendeur, glissement), une stratégie peut sembler rentable alors qu'elle fait en réalité perdre de l'argent en pratique. Même 0,1 % par transaction s'accumule considérablement sur un grand nombre de transactions.

COMMISSION_PCT = 0.001   # 0.1% per trade (buy + sell)
SLIPPAGE_PCT   = 0.0005  # 0.05% per trade

def returns_after_costs(decisions: list[dict]) -> dict:
    total_return = 0.0
    total_cost   = 0.0

    for d in decisions:
        if d['decision'] == 'BUY' and d.get('next_day_return') is not None:
            gross = d['next_day_return']
            cost  = COMMISSION_PCT + SLIPPAGE_PCT
            net   = gross - cost
            total_return += net
            total_cost   += cost

    return {
        'gross_return_pct': round((total_return + total_cost) * 100, 2),
        'net_return_pct':   round(total_return * 100, 2),
        'total_costs_pct':  round(total_cost * 100, 2),
        'num_trades':       sum(1 for d in decisions if d['decision'] == 'BUY')
    }

if __name__ == '__main__':
    decisions = [
        {'decision': 'BUY', 'next_day_return': 0.012},
        {'decision': 'BUY', 'next_day_return': -0.004},
        {'decision': 'HOLD', 'next_day_return': None},
    ]
    print('Returns after trading costs:', returns_after_costs(decisions))

Validation glissante

L'optimisation sur l'échantillon (entraînement et évaluation sur la même période) entraîne un surajustement. La validation glissante utilise une fenêtre d'entraînement mobile suivie d'une nouvelle période d'évaluation — comme en conditions réelles de mise en production.

def walk_forward_test(ticker: str, total_years: int = 3,
                      train_months: int = 12, test_months: int = 3) -> list[dict]:
    import yfinance as yf
    from dateutil.relativedelta import relativedelta
    from datetime import date

    results = []
    start = date(date.today().year - total_years, 1, 1)
    end   = date.today()

    window_start = start
    while True:
        train_end = window_start + relativedelta(months=train_months)
        test_end  = train_end + relativedelta(months=test_months)
        if test_end > end:
            break

        # In practice: train your signal on window_start->train_end
        # then evaluate on train_end->test_end
        results.append({
            'train_period': f'{window_start} to {train_end}',
            'test_period':  f'{train_end} to {test_end}'
        })
        window_start += relativedelta(months=test_months)

    return results

Rapport de test rétrospectif

Résumez les résultats du test rétrospectif dans un rapport structuré : taux de réussite, rendement total par rapport à l'indice de référence, alpha, ratio de Sharpe, perte maximale, nombre de transactions et rendement net des coûts.

def backtest_report(ticker: str, results: list[dict]) -> dict:
    hit = compute_hit_rate(results)
    benchmark = compare_to_benchmark(results, ticker)
    costs = returns_after_costs(results)

    return {
        'ticker':                  ticker,
        'total_decisions':         len(results),
        'buy_signals':             costs['num_trades'],
        'hit_rate':                hit.get('hit_rate'),
        'strategy_return_gross':   f'{costs["gross_return_pct"]}%',
        'strategy_return_net':     f'{costs["net_return_pct"]}%',
        'benchmark_return':        f'{benchmark["benchmark_return_pct"]}%',
        'alpha':                   f'{benchmark["alpha_pct"]}%',
        'outperformed_benchmark':  benchmark['outperformed'],
        'transaction_costs':       f'{costs["total_costs_pct"]}%'
    }

Interpréter les résultats du test rétrospectif

De bons résultats rétrospectifs ne garantissent pas les performances futures, mais de mauvais résultats permettent bien d'identifier les stratégies qui ne devraient pas être déployées. Signaux d'alerte importants : taux de réussite inférieur à 50 %, alpha négatif après déduction des coûts, ratio de Sharpe inférieur à 0,5 ou perte maximale dépassant votre tolérance au risque.

def interpret_backtest(report: dict) -> list[str]:
    warnings = []

    hit_rate = report.get('hit_rate')
    if hit_rate and hit_rate < 0.50:
        warnings.append(f'Hit rate {hit_rate:.1%} is below chance — strategy predicts direction poorly')

    alpha_str = report.get('alpha', '0%')
    alpha = float(alpha_str.replace('%', '')) / 100
    if alpha < 0:
        warnings.append(f'Negative alpha {alpha_str}: strategy underperforms buy-and-hold')

    if not warnings:
        return ['Backtest results look acceptable — proceed with paper trading before live deployment']
    return warnings

if __name__ == '__main__':
    report = {'hit_rate': 0.45, 'alpha': '-1.2%'}
    for warning in interpret_backtest(report):
        print('-', warning)

Simulation de Monte Carlo pour évaluer la robustesse

Un seul test rétrospectif dépend fortement de la période choisie. La simulation de Monte Carlo randomise l'ordre des rendements historiques pour vérifier si les performances se maintiennent avec différentes séquences, révélant ainsi si la stratégie est robuste ou simplement chanceuse.

import numpy as np

def monte_carlo_backtest(daily_returns: list[float],
                         simulations: int = 1000) -> dict:
    returns_arr = np.array(daily_returns)
    final_returns = []

    for _ in range(simulations):
        shuffled = np.random.choice(returns_arr, size=len(returns_arr), replace=True)
        cumulative = float((1 + shuffled).prod() - 1)
        final_returns.append(cumulative)

    final_returns = sorted(final_returns)
    return {
        'median_return':      round(float(np.median(final_returns)) * 100, 2),
        'percentile_5':       round(float(np.percentile(final_returns, 5)) * 100, 2),
        'percentile_95':      round(float(np.percentile(final_returns, 95)) * 100, 2),
        'prob_positive':      round(sum(1 for r in final_returns if r > 0) / simulations, 3)
    }

Qu'est-ce que le biais d'anticipation dans un test rétrospectif ?

Le biais d'anticipation est la façon la plus courante de surestimer accidentellement les performances d'un test rétrospectif. Comprendre sa définition précise permet d'éviter de construire des tests rétrospectifs défaillants.

Récapitulatif des décisions de l'agent testées rétrospectivement

Un test rétrospectif fiable exige : d'éviter le biais d'anticipation (les décisions utilisent uniquement les données passées), de simuler une logique réaliste de l'agent à chaque étape historique, de mesurer le taux de réussite et l'alpha par rapport à l'indice de référence, de tenir compte des coûts de transaction et de valider au moyen de découpages glissants pour détecter le surajustement.

Questions Fréquemment Posées

La leçon « Rétrosimulation des décisions de l’agent » est-elle gratuite ?

Oui — le texte complet de « Rétrosimulation des décisions de l’agent » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Rétrosimulation des décisions de l’agent » ?

Simuler les stratégies de l’agent sur des données historiques afin de valider ses performances. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Agents ?

Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Rétrosimulation des décisions de l’agent » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?

Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Intégration d’API de données de marché
  2. Outils d’agent pour l’analyse de portefeuille
  3. Garde-fous de risque et de conformité
  4. Rétrosimulation des décisions de l’agent
← Retour à AI Agents