Backtesting das decisões dos agentes
Simulando estratégias de agentes em dados históricos para validar o desempenho.
Backtesting das decisões dos agentes é uma aula grátis de AI Agents no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Agents, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Agents inclui 4 aulas no total.
Por que testar retrospectivamente as decisões do agente?
Não é possível saber se o seu agente financeiro está gerando boas recomendações sem testá-lo com dados históricos. O teste retrospectivo reproduz condições de mercado passadas por meio da lógica de decisão do agente e mede como essas decisões teriam se saído.
Ele é a principal ferramenta para validar a qualidade da estratégia antes da implantação em produção.
A armadilha do viés de antecipação
O viés de antecipação é o erro mais comum e perigoso em testes retrospectivos: usar dados futuros para tomar uma decisão do “passado”. Por exemplo, usar o preço de fechamento do dia para decidir se deve comprar na abertura desse mesmo dia.
Cada decisão histórica deve usar somente os dados disponíveis no momento simulado da decisão.
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'Construindo o ciclo de reprodução histórica
Percorra cada data histórica. A cada etapa, o agente vê somente os dados disponíveis até aquele momento. Registre a decisão e avance para a data seguinte para observar o resultado.
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 resultsSimulando a lógica do agente em cada etapa
Para obter um teste retrospectivo mais realista, simule a chamada completa à ferramenta do agente em cada data — incluindo a construção do prompt, a chamada ao LLM e a análise da decisão. Isso testa o sistema inteiro, não apenas a lógica do sinal.
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')Calculando a taxa de acerto
A taxa de acerto mede qual porcentagem das decisões BUY foi seguida por um retorno positivo no dia seguinte. Uma taxa de acerto acima de 50% significa que o agente prevê corretamente a direção com mais frequência do que o acaso.
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))
Calculando o retorno em comparação com o índice de referência
Compare o retorno da estratégia com o índice de referência de comprar e manter. Se o agente tiver um desempenho inferior ao de simplesmente manter o índice, sua complexidade não se justifica.
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)
}Impacto dos custos de transação
Sem considerar os custos de transação (comissões, diferença entre compra e venda e deslizamento), uma estratégia pode parecer lucrativa, mas na prática perder dinheiro. Até mesmo 0,1% por operação se acumula significativamente ao longo de muitas operações.
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))
Validação progressiva
A otimização dentro da amostra (treinar no mesmo período usado para testar) causa sobreajuste. A validação progressiva usa uma janela de treinamento móvel seguida por um novo período de teste — assim como na implantação real.
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 resultsRelatório do teste retrospectivo
Resuma os resultados do teste retrospectivo em um relatório estruturado: taxa de acerto, retorno total em comparação com o índice de referência, alfa, índice de Sharpe, perda máxima, número de operações e retorno líquido dos custos.
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"]}%'
}Interpretando os resultados do teste retrospectivo
Bons resultados em um teste retrospectivo não garantem o desempenho futuro, mas resultados ruins identificam estratégias que não devem ser implantadas. Principais sinais de alerta: taxa de acerto abaixo de 50%, alfa negativo após os custos, índice de Sharpe abaixo de 0,5 ou perda máxima superior à sua tolerância ao risco.
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)
Simulação de Monte Carlo para avaliar a robustez
Um único teste retrospectivo é sensível ao período específico escolhido. A simulação de Monte Carlo randomiza a ordem dos retornos históricos para testar se o desempenho se mantém em diferentes sequências, revelando se a estratégia é robusta ou apenas teve sorte.
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)
}O que é viés de antecipação em testes retrospectivos?
O viés de antecipação é a maneira mais comum de superestimar acidentalmente o desempenho de um teste retrospectivo. Entender a definição precisa evita a construção de testes retrospectivos incorretos.
Recapitulação das decisões do agente em testes retrospectivos
Um teste retrospectivo sólido exige: evitar o viés de antecipação (as decisões usam somente dados passados), simular uma lógica realista do agente em cada etapa histórica, medir a taxa de acerto e o alfa em comparação com o índice de referência, considerar os custos de transação e validar com divisões progressivas para detectar sobreajuste.
Perguntas Frequentes
A aula “Backtesting das decisões dos agentes” é grátis?
Sim — o texto completo de “Backtesting das decisões dos agentes” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Agents, atualize para CoddyKit PRO. O curso de AI Agents inclui 4 aulas no total.
O que vou aprender em “Backtesting das decisões dos agentes”?
Simulando estratégias de agentes em dados históricos para validar o desempenho. Você pratica AI Agents com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar AI Agents?
Nenhuma experiência prévia é necessária. AI Agents no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Backtesting das decisões dos agentes”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de AI Agents?
Sim. Cada aula de AI Agents inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Integração com APIs de dados de mercado
- Ferramentas de agentes para análise de portfólios
- Barreiras de proteção para riscos e conformidade
- Backtesting das decisões dos agentes