Documentação1.0 (latest)
PáginasCiclo de vida da estratégia · 8 / 29

Ciclo de vida da estratégia

Uma estratégia do QuantCraft é um arquivo Python comum que define um pequeno conjunto de funções de callback. O mecanismo de backtest chama essas funções em ordem fixa enquanto percorre os dados históricos. Você não as chama; quem faz isso é o mecanismo. Use o Test para acioná-las (executando código).

O ciclo de vida, do início ao fim:

on_init → on_bar → on_tick (×4 per bar) → account tick(close) → on_finish └─────────── repeats for every bar ───────────┘

No backtest, o on_tick dispara quatro vezes por barra sobre um OHLC sintético de cada barra (sem histórico real de ticks). Depois desses quatro ticks, o mecanismo faz uma última marcação a mercado da conta no fechamento — não um quinto on_tick. O on_timer só está disponível nas execuções forward (forward no gráfico, forward em massa / QuantCloud) quando timerIntervalMs está definido — a caixa Run backtest do IDE não tem controle de timer. Nas execuções forward, o on_tick roda a cada atualização de preço da corretora conectada; nas fronteiras intradiárias costumam disparar on_tick e depois on_bar — não presuma a ordem do backtest.

Esta página cobre cada callback, como as estratégias com vários símbolos se encaixam neles e o básico de que você precisa para escrever sua primeira estratégia.


O arquivo da estratégia

Uma estratégia mínima fica assim:

import quantcraft.process.runtime as bt # account, chart_indicators, qc_* handles from quantcraft import Timeframe import quantcraft.runtime as rt def on_init(symbols=None): pass def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None): close_px = float(bar["close"]) daily = rt.get_bar(Timeframe.DAILY, shift=1) def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None): if tick_in_bar == 3: close_px = price def on_finish(bars, symbol=None, bars_by_symbol=None): pass

Algumas regras valem para todos os callbacks:

  • Devem ser funções de nível superior no seu arquivo (não aninhadas nem dentro de uma classe).
  • O mecanismo de backtest exige on_init, on_bar, on_tick e on_finish chamáveis (caso contrário a execução falha com User script must define callable: …). Os botões Test, Compile e Debug Test do IDE aparecem quando a aba .py ativa define on_init / on_bar / on_finish; a ausência de on_tick ainda faz o backtest falhar ao iniciar. O on_timer é opcional e só é usado em execuções forward com um intervalo de timer.
  • Adicione apenas os argumentos opcionais que você usa. calendar, symbols (em on_init) e os argumentos de on_finish são casados por nome; fundamentals e symbol são casados tentando as formas comuns em sequência, então mantenha-os na ordem documentada. A assinatura completa mais segura é def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None). Um calendar declarado sempre recebe o identificador do calendário ou None — nunca o objeto de dados fundamentalistas.
  • Dentro dos callbacks você coloca ordens por meio de account e registra valores de indicadores por meio de chart_indicators (séries de indicadores) — ambos lidos de quantcraft.process.runtime.

Globais do runtime

O mecanismo expõe estes itens a partir de quantcraft.process.runtime antes de os callbacks rodarem:

NomeFinalidade
accountInstância de PaperAccount configurada na aba Simulation (saldo + comissão / slippage). Nas execuções forward é uma conta real de corretora com a mesma API que coloca ordens reais — veja Modelo de conta
chart_indicatorsColetor ChartIndicatorSeries mesclado ao resultado — colete os valores enquanto a execução percorre as barras e emita as séries em on_finish
qc_fundamentalsIdentificador QcFundamentals opcional quando dados fundamentalistas são carregados
qc_fama_frenchQcFamaFrench opcional quando fatores Fama-French globais são carregados; prefira fama_french.qc nos callbacks — veja Fatores Fama-French
qc_calendarBacktestCalendar | None — dados de calendário quando Include calendar data está ativado; cobre todos os símbolos negociados. Somente backtests — sempre None em execuções forward. Os callbacks podem receber o mesmo identificador nomeando um argumento calendar — veja Dados de calendário
qc_ohlcv_by_tfBuffers opcionais de intervalos secundários indexados pela string do intervalo (avançado; prefira get_bar)
get_bar(tf, shift=1)API recomendada para o intervalo principal e os adicionais — retorna um BarSnapshot ou None; importe com import quantcraft.runtime as rt

Prefira o argumento bars de on_finish(bars, ...) para contas de série completa — não dependa do qc_ohlcv de nível de módulo nas estratégias do IDE (ele costuma ser None no momento da importação, é None em uma execução com alvo de screener e, no restante, abrange todo o intervalo carregado incluindo o aquecimento). Leia os preços do objeto bar nos callbacks. isinstance(bar, dict) é False — OhlcvBarView é um Mapping (o acesso no estilo dict ainda funciona).

Leia-os como atributos de módulo. Cada identificador é sustentado por um valor por sessão, então bt.account / bt.chart_indicators sempre se referem a esta execução. from quantcraft.process.runtime import account vincula um instantâneo — tudo bem no arquivo raiz do algoritmo em um backtest, porque o mecanismo revincula esses nomes depois de carregá-lo (as execuções forward revinculam apenas account, qc_fundamentals e qc_fama_french, então use bt.chart_indicators e rt.qc_ohlcv* ali), mas errado em um módulo auxiliar separado, que permanece em cache em sys.modules e manteria o identificador da primeira sessão. Veja Identificadores de módulo vs nomes importados.

Intervalos de tempo secundários

O intervalo principal da execução está sempre disponível por rt.get_bar. Selecione tamanhos de barra extras no menu Additional timeframes da caixa Run backtest, em Symbol & data (Backtests), e leia-os de qualquer callback:

from quantcraft import Timeframe import quantcraft.runtime as rt def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None): daily = rt.get_bar(Timeframe.DAILY, shift=1) if daily and bar.close[0] > daily.close: account.open_trade(...)

A semântica completa de shift, a enumeração Timeframe e as ressalvas das execuções forward estão em Dados OHLCV e de barras.

Momento da execução das ordens

  • bar["close"] em on_bar é o fechamento da barra de execução atual.
  • account.open_trade / close_trade recebem um preço solicitado e então aplicam os modelos de slippage e comissão da execução. Os entry_price / exit_price armazenados são os preços de execução; os preços solicitados são mantidos como raw_entry_price / raw_exit_price. Com o slippage por participação no volume padrão, as execuções costumam diferir do preço que você passa.
  • Para mirar o fechamento da barra a partir de on_bar, passe float(bar["close"]) (ainda sujeito a slippage/comissão).
  • Para executar durante a simulação intrabarra, chame a partir de on_tick quando tick_in_bar == 3 — price é o fechamento daquele símbolo para a linha.

on_init — preparar tudo

Roda logo depois que o mecanismo preparou a conta, carregou os dados de mercado e construiu o runtime — mas antes de qualquer barra ser processada.

Use-o para:

  • Inicializar variáveis que a estratégia carrega entre barras (contadores, máquinas de estados, valores de parâmetros).
  • Configurar indicadores ou buffers de aquecimento.
  • Ler parâmetros de entrada para que conduzam o restante da execução.
def on_init(symbols=None): global lookback, position_size lookback = 20 position_size = 100

Você não tem acesso a uma barra atual em on_init — neste ponto a simulação ainda não começou.

Parâmetro symbols (somente execuções com screener): Quando o backtest usa uma fonte de símbolos Screener, on_init é disparada de novo a cada rebalanceamento com symbols=<lista de tickers> para o novo universo. Isso permite que a estratégia reaja a um novo conjunto de tickers sem reiniciar a simulação.

  • No início da execução, on_init dispara duas vezes em sequência — uma com symbols=None (a chamada de configuração padrão) e outra com symbols=<tickers iniciais>.
  • Os novos disparos não redefinem o estado de nível de módulo; qualquer variável definida na primeira chamada continua lá.
  • Estratégias que definem def on_init(): (sem parâmetro) funcionam exatamente como antes — o mecanismo casa os argumentos por nome, então o parâmetro ausente é simplesmente ignorado.

on_bar — uma vez por barra

Roda uma vez por barra de execução, antes dos quatro ticks intrabarra daquela barra. É aqui que a maioria das estratégias toma decisões.

def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None): if bar.bars_back < 1: return if bar.close[0] > bar.close[1]: account.open_trade(...)

Parâmetros:

  • bar_index — índice, base zero, da barra de execução atual.
  • bar — a visão da barra atual (OhlcvBarView). Comporta-se como um mapeamento somente leitura (bar["open"], bar["high"], bar["low"], bar["close"], bar["volume"], bar["t"]) e também expõe o histórico por deslocamentos: bar.close[0] é o fechamento da barra atual, bar.close[1] é o da barra anterior, e assim por diante. len(bar.close) e bar.bars_back dizem quanto histórico está disponível. Não é um dict simples (isinstance(bar, dict) é False).
  • fundamentals — snapshot de dados fundamentalistas para esta barra, ou None se não estiver configurado.
  • calendar — identificador BacktestCalendar quando Include calendar data está ativado; caso contrário None (sempre None em execuções forward). Veja Dados de calendário para a API completa. Como os argumentos são casados por nome, omitir este parâmetro deixa o seu callback inalterado.
  • symbol — string do ticker em maiúsculas. Útil em estratégias com vários símbolos; pode ser omitido se você carrega apenas um símbolo.

Padrões comuns:

  • Decidir entradas/saídas no fechamento da barra: passe bar.close[0] (ou float(bar["close"])) a account.open_trade / account.close_trade.
  • Calcular indicadores simples lendo deslocamentos como bar.close[0], bar.close[1], …
  • Pular as primeiras barras que não têm histórico suficiente (if bar.bars_back < N: return).

Dica: on_bar sempre roda antes de qualquer callback de tick da mesma barra.


on_tick — atualizações de preço intrabarra e ao vivo

on_tick tem a mesma assinatura no backtest e no forward, mas quando dispara e o que price significa dependem do modo de execução.

Backtest (Test)

Roda até quatro vezes por barra, depois de on_bar, para simular o movimento dentro da barra. O mecanismo percorre o OHLC de cada barra nesta ordem:

tick_in_barSignificado
0Abertura da barra
1Máxima da barra
2Mínima da barra
3Fechamento da barra

Não há dados reais de ticks no backtest. As execuções históricas não carregam o histórico de ticks da corretora. As quatro chamadas por barra usam preços sintéticos derivados apenas do OHLC de cada barra. Você pode aproximar execuções intrabarra na simulação, mas o comportamento do on_tick no backtest não corresponderá aos ticks forward ao vivo. Para decisões no fechamento da barra, on_bar basta — on_tick com tick_in_bar == 3 equivale a agir sobre o fechamento da barra na simulação.

def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None): if tick_in_bar == 3: # Backtest simulation: closing tick fill account.close_trade(position_id, price)

Use o on_tick do backtest quando o momento dentro da barra importar na simulação — por exemplo, para executar no tick de fechamento, simular um stop intrabarra ou avaliar o toque na máxima ou na mínima da barra.

Execuções forward (forward no gráfico, forward em massa / QuantCloud)

Nas execuções forward, on_tick roda toda vez que a plataforma recebe um preço da sua corretora conectada enquanto o seu algoritmo está anexado. Não há um percurso OHLC fixo de quatro ticks — cada chamada reflete uma cotação ao vivo.

def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None): # Forward run: react to each broker price update if price <= stop_level: account.close_trade(position_id, price)
  • price — a última cotação da corretora para este símbolo.
  • bar — a visão da barra atual para o histórico (mesmo formato do backtest).
  • tick_in_bar — pode continuar presente por compatibilidade de assinatura; nas execuções forward, ramifique por price, não por fases OHLC sintéticas.

Use o on_tick forward para a lógica intrabarra ao vivo — stops, saídas móveis ou qualquer reação às marcas entre fechamentos de barra.

Parâmetros (ambos os modos)

  • bar_index — igual ao de on_bar.
  • tick_in_bar — no backtest, 0, 1, 2 ou 3 (passo OHLC sintético). Nas execuções forward, não dependa dele para lógica de fase da barra.
  • price — passo OHLC sintético no backtest; cotação ao vivo da corretora nas execuções forward.
  • bar — a mesma visão de barra de on_bar.
  • fundamentals, calendar, symbol — mesmo significado de on_bar.

Se você só precisa de decisões no fechamento da barra no backtest, pode ignorar on_tick por completo e colocar a lógica em on_bar.


on_timer — callback periódico opcional (somente execuções forward)

Backtest do IDE: a caixa Run backtest não define um intervalo de timer. Mesmo que você defina on_timer no arquivo da estratégia, ele não é chamado durante os backtests do IDE. Coloque a lógica periódica ou baseada em tempo em on_bar ou on_tick nos backtests.

As execuções forward (teste forward no gráfico, forward em massa / QuantCloud) podem definir timerIntervalMs na caixa de execução. Quando configurado, on_timer roda a um intervalo de tempo fixo entre ticks, somente quando duas condições são atendidas:

  1. Você configurou timer_interval_ms / timerIntervalMs para a execução (um inteiro positivo em milissegundos).
  2. O seu arquivo define uma função on_timer.
def on_timer(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None): pass

A assinatura tem o mesmo formato de on_tick. Use on_timer para comportamentos baseados em tempo (por exemplo: atualizar um sinal lento a cada minuto, ou agendar uma verificação independente da contagem de barras). Se timer_interval_ms não estiver definido, on_timer nunca é chamado, mesmo que esteja definido.


Dados de calendário nos callbacks

Quando Include calendar data está ativado na aba Symbol & data, qualquer callback pode receber um identificador BacktestCalendar ao adicionar um parâmetro calendar:

def on_bar(bar_index, bar, calendar=None, symbol=None): if calendar is None: # always guard — not every run enables calendar data return days = calendar.days_to_earnings(bar["t"], symbol) if days is not None and days <= 2: return # avoid trading into an earnings print divs = calendar.dividends(bar["t"], symbol) if divs and (divs[-1].amount or 0) > 0.5: # amount can be None ...

O mesmo identificador está disponível em on_tick e on_timer — mas os dados de calendário são somente de backtest, então nas execuções forward calendar é sempre None. Todas as consultas são point-in-time — dividends(bar["t"], symbol) retorna apenas os dividendos que eram públicos naquela barra, nunca os futuros.

Métodos principais:

MétodoRetorna
dividends(bar_time, symbol=None)Dividendos públicos naquela barra (do mais antigo ao mais novo)
splits(bar_time, symbol=None)Desdobramentos anunciados até aquela barra (do mais antigo ao mais novo)
next_earnings(bar_time, symbol=None){date, basis, estimated, …} ou None
days_to_earnings(bar_time, symbol=None)int ou None
has(symbol=None)Se este símbolo tem dados de calendário

As listas vão do mais antigo ao mais novo — [-1] é o evento mais recente. Isso é o oposto dos dados fundamentalistas, em que o índice 0 é o período mais novo.

A global do runtime qc_calendar guarda o mesmo identificador, caso você prefira lê-lo de quantcraft.process.runtime em vez do argumento do callback.

Veja Dados de calendário para os esquemas dos campos de eventos, os detalhes da simulação de eventos corporativos e a solução de problemas.


on_finish — concluir

Roda uma única vez, depois da última barra. Use-o para calcular as saídas finais, montar indicadores de gráfico ou imprimir um resumo.

def on_finish(bars, symbol=None, bars_by_symbol=None): closes = [b["close"] for b in bars] bt.chart_indicators.add(...)

Parâmetros:

  • bars — lista cronológica de dicionários OHLC do símbolo de relógio, da mais antiga para a mais nova — a janela executada, as mesmas linhas que o gráfico do Test Results desenha. Cada entrada tem t, open, high, low, close, volume e, opcionalmente, fundamentals. É a série canônica de todo o histórico para on_finish.
  • symbol — ticker em maiúsculas do símbolo de relógio.
  • Em uma execução Screener não há símbolo de relógio: bars é [] e symbol é "", então use bars_by_symbol.
  • bars_by_symbol — para execuções com vários símbolos, um dict que mapeia cada ticker para a sua lista cronológica de barras (alinhada ao relógio de execução). Em execuções de um só símbolo você pode ignorá-lo.

Usos comuns:

  • Entregar séries de indicadores a chart_indicators.add(...) para que apareçam nos gráficos de resultados. Prefira os valores que você acumulou por símbolo enquanto a execução percorria as barras; recalcule a partir de bars apenas os indicadores que você não acompanhou por barra — veja Séries de indicadores.
  • Imprimir ou registrar um resumo final no Painel de saída.
  • Atualizar as métricas do portfólio para a carga do resultado.

Todo return em on_finish é silencioso. A execução ainda tem sucesso e o Test Results ainda é renderizado — um retorno antecipado só significa que as suas séries ou métricas nunca foram anexadas, sem erro algum. Imprima em sys.stderr em cada guarda enquanto depura; a mensagem aparece no Painel de saída.


Noções de vários símbolos

As estratégias do QuantCraft podem rodar em um símbolo ou em vários ao mesmo tempo. O ciclo de vida continua o mesmo — mudam apenas um pouco as assinaturas dos callbacks e os dados que recebem.

Configurando vários símbolos

Na aba Symbol & data do Run backtest, selecione um ou mais tickers em maiúsculas. Todos os símbolos listados são carregados e simulados juntos.

O símbolo de relógio

Quando você roda vários símbolos, um deles dita o relógio — os carimbos de tempo das suas barras decidem quando os callbacks rodam. A caixa Run backtest não tem controle de símbolo principal. Ordem de resolução:

  1. Um SYMBOL = "AAPL" de nível superior no seu arquivo de estratégia, se esse ticker estiver na lista carregada.
  2. Caso contrário, o primeiro símbolo carregado.

Com uma fonte de símbolos Screener não existe símbolo de relógio — o conjunto de símbolos muda ao longo da execução. rt.get_bar sempre lê as barras do símbolo de relógio (e retorna None em uma execução Screener), então, para o histórico de outro ticker, use os deslocamentos bar.close[i] desse ticker.

O símbolo de relógio é também o que on_finish(bars, symbol=...) usa para os argumentos bars e symbol.

Callbacks por símbolo

Para cada barra de execução, o mecanismo percorre os símbolos na ordem da lista:

  1. on_bar roda uma vez por ticker carregado no horário dessa barra (antes de qualquer tick dela).
  2. Somente backtest: para cada tick_in_bar (0 = abertura, 1 = máxima, 2 = mínima, 3 = fechamento), on_tick roda uma vez por ticker sobre OHLC sintético (sem dados reais de ticks).
  3. Somente backtest: depois das quatro rodadas de ticks, um tick(close_map) final da conta marca o patrimônio no fechamento — não um quinto on_tick.
  4. Execuções forward: on_tick roda a cada atualização de preço da corretora por símbolo, conforme as cotações chegam da sua corretora conectada (sem se limitar a quatro chamadas por barra). Nas fronteiras intradiárias costumam disparar on_tick e depois on_bar — não presuma a ordem do backtest.
  5. on_timer (somente em execuções forward com timerIntervalMs configurado — não no backtest do IDE) segue o mesmo padrão por ticker entre os ticks.

Isso significa que um on_bar com vários símbolos é invocado, por exemplo, três vezes por barra se você tiver três símbolos carregados. Use o parâmetro symbol para saber para qual ticker é a chamada:

def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None): if symbol == "AAPL": ... elif symbol == "MSFT": ...

A visão bar sempre pertence ao símbolo para o qual o callback foi invocado. O mesmo vale para price em on_tick.

Alinhamento "as-of". Para cada horário de barra de execução, cada ticker recebe a sua última barra naquele horário ou antes. Um ticker sem barra naquele horário exato (uma suspensão, um minuto ausente, um calendário de negociação diferente) recebe de novo a sua barra anterior. Se você conta barras ou sinais por ticker, ignore as repetições lembrando o último bar["t"] visto para aquele símbolo:

_last_t = {} def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None): if _last_t.get(symbol) == bar["t"]: return # same bar as last step for this ticker _last_t[symbol] = bar["t"] ...

on_finish com vários símbolos

on_finish ainda roda uma vez, no fim. Para trabalhar com o histórico de todos os símbolos, use bars_by_symbol:

def on_finish(bars, symbol=None, bars_by_symbol=None): if bars_by_symbol: for ticker, ticker_bars in bars_by_symbol.items(): ...

Juntando tudo

Uma estratégia típica de um só símbolo:

  1. Define parâmetros de entrada ou outro estado em on_init.
  2. Lê o histórico de preços (OHLCV) por meio de bar.close[0], bar.close[1], … em on_bar para tomar decisões de negociação e chama account.open_trade / account.close_trade.
  3. Opcionalmente usa on_tick — no backtest, para execuções intrabarra sintéticas (por exemplo, tick_in_bar == 3 para o tick de fechamento); nas execuções forward, para reagir aos preços ao vivo da corretora entre fechamentos de barra.
  4. Opcionalmente usa on_timer nas execuções forward quando timerIntervalMs está configurado (indisponível no Run backtest do IDE).
  5. Monta os indicadores de gráfico e finaliza os resultados em on_finish usando bars.

Uma estratégia com vários símbolos usa os mesmos callbacks, apenas ramifica pelo argumento symbol dentro deles e lê os dados por ticker por meio da bar que recebe — e por meio de bars_by_symbol em on_finish.

Precisa de mais detalhes? Veja Modelo de conta, Dados fundamentalistas, Fatores Fama-French, Dados OHLCV e de barras, Séries de indicadores, ou a aba Documentation integrada na barra lateral direita (layout do espaço de trabalho).

18 min de leituraSugerir uma edição ↗
Ciclo de vida da estratégia — Documentação (1.0 (latest)) | QuantCraft