Documentação1.0 (latest)
PáginasModelo de conta · 9 / 29

Modelo de conta

Toda estratégia do QuantCraft é executada contra uma conta de negociação simulada (paper) integrada. A conta acompanha o seu caixa, mantém as posições abertas, registra o lucro e a perda realizados quando você fecha operações e calcula as métricas de desempenho ao final de um backtest.

No uso normal você não a constrói: o mecanismo de backtest a cria a partir da aba Simulation da caixa Run backtest (saldo inicial, comissão / slippage) e a atribui a quantcraft.process.runtime.account antes de on_init() ser executado. SL/TP padrão opcionais e dimensionamento baseado em risco podem ser ligados por configuração / chaves .qcs quando presentes; a caixa não expõe atualmente entradas de SL/TP / risco. Os callbacks recebem os dados de mercado conforme descrito em Ciclo de vida da estratégia e Dados OHLCV e de barras; as métricas aparecem em Resultados dos testes.


Início rápido

import quantcraft.process.runtime as rt def on_init(): pass def on_bar(bar_index, bar, fundamentals=None, symbol=None): account = rt.account px = float(bar["close"]) snap = account.open_trade("AAPL", "long", 10, px) if snap["open_positions"]: pid = snap["open_positions"][0]["id"] account.close_trade(pid, px + 1.0) def on_finish(bars, symbol=None, bars_by_symbol=None): last_px = float(bars[-1]["close"]) metrics = rt.account.refresh_metrics({"AAPL": last_px})

Esse é o padrão completo: abrir uma operação, guardar o id da posição, fechá-la depois e (opcionalmente) atualizar as métricas no final.

O que você lê da instância: account.cash, account.balance, account.realized_pnl, account.closed_trades e account.open_positions_payload(). Não existe o atributo account.open_positions — as posições abertas vêm do snapshot (snap["open_positions"]) ou de open_positions_payload().

Execuções forward enviam ordens reais. Em um teste forward no gráfico ou em uma execução em massa, runtime.account é uma conta real de corretora — uma subclasse de PaperAccount com a mesma API que envia ordens reais para a sua conta de corretora conectada (paper ou real), incluindo ordens bracket de SL/TP para ações. Trate open_trade / close_trade ali como operações reais. Negociar envolve risco de perda.


O que o PaperAccount faz

A conta simula uma conta de negociação à vista simples:

  • Acompanha o saldo inicial, o caixa, o saldo e o patrimônio (equity).
  • Mantém posições compradas e vendidas na mesma conta.
  • Registra o P/L realizado quando você fecha uma operação e acompanha o P/L não realizado das posições abertas.
  • Mantém um histórico de patrimônio que recebe um ponto a cada atualização de preço.
  • Opcionalmente calcula um conjunto completo de métricas de desempenho (Sharpe, Sortino, drawdown, CAGR etc.).

O que ele não modela: margem, custo de empréstimo ou execução automática de SL/TP. Os valores de SL/TP armazenados são apenas números — o código da sua estratégia é responsável por verificar os preços e chamar close_trade quando um stop ou alvo é atingido. Comissão e slippage são aplicados quando há modelos configurados na aba Simulation — veja abaixo.

Dividendos e desdobramentos são modelados quando os dados de calendário estão ativados para a execução. Na data de vigência do desdobramento, a quantidade de ações é multiplicada pela razão e a base de custo é dividida por ela (a curva de patrimônio permanece contínua). Na data ex, qty × amount é creditado ao caixa nas posições compradas e debitado nas vendidas. O dividendo entra em balance e no patrimônio, mas não em realized_pnl — leia total_dividend_income e total_splits_applied em account.refresh_metrics(). Veja Dados de calendário.

Comissão e slippage são configurados na aba Simulation da caixa Run backtest. Quando ativados, as execuções são ajustadas automaticamente; você não os define em Python. A caixa Run usa por padrão comissão por ação de US$ 0,001 por ação com mínimo de US$ 0 e slippage por participação no volume. (Um PaperAccount que você mesmo constrói tem por padrão nenhuma comissão e nenhum slippage.)


Configurando a conta

Você configura a conta na aba Simulation de Run backtest:

ConfiguraçãoO que faz
starting_balanceCaixa com o qual a conta começa.
Modelos de comissão / slippageComo as execuções são ajustadas — veja abaixo.

SL/TP padrão opcionais e dimensionamento por risco (sl_pct / sl_dollars, tp_pct / tp_dollars, risk_pct_per_trade / risk_fixed_per_trade) ainda podem ser aplicados por auxiliares de .qcs / configuração quando presentes, mas a caixa Run backtest não expõe atualmente essas entradas. Quando definidos:

  • Campos de porcentagem aceitam valores inteiros como 2 ou frações como 0.02 — ambos significam a mesma coisa.
  • Em posições compradas, o stop fica abaixo da entrada e o alvo acima dela. Nas vendidas, é o inverso.
  • Os padrões valem para toda nova operação, a menos que você os substitua a cada chamada.

Apenas para notebooks ou testes (fora do fluxo de backtest do IDE), você pode construir uma conta manualmente:

account = PaperAccount( 100_000, sl_pct=2, tp_pct=4, risk_pct_per_trade=1, slippage_model=None, # optional; defaults to no slippage (IDE engine wires from Simulation tab) commission_model=None, # optional; defaults to zero commission (IDE engine wires from Simulation tab) equity_history_cap=None, # optional; keep only the newest N equity_history points )

Comissão e slippage (aba Simulation)

A aba Simulation da caixa Run backtest configura como as execuções são ajustadas pelos custos de negociação. As configurações se aplicam automaticamente a runtime.account — você não as configura em Python. Veja Backtests e Predefinições de execução (.qcs) para salvar essas opções.

Modelos de comissão

ModeloParâmetrosComportamento
Por ação (padrão)Custo por ação ($), mínimo por operação ($)max(qty × cost, min) quando o mínimo > 0
Por operaçãoCusto fixo por ordem ($)Mesma taxa independentemente da quantidade
Zero—Sem comissão — útil para testar alfa puro

Modelos de slippage

ModeloParâmetrosComportamento
Participação no volume (padrão)Limite de volume (padrão 0,025), impacto no preço (padrão 0,1)O impacto escala com (qty / bar_volume)²; compras são executadas mais alto e vendas mais baixo. Somente nas aberturas, volume_limit também reduz silenciosamente a quantidade para volume_limit × bar_volume — confira o snapshot retornado para ver o tamanho que você realmente obteve. (Se não sobrar nada, open_trade lança qty after volume_limit cap is not positive.) Os fechamentos nunca são limitados. Volume de barra zero / ausente → sem impacto.
Spread fixoSpread total por ação ($)Meio spread aplicado em cada perna de execução
Zero—Execução ao preço exato solicitado

Os registros de operações fechadas incluem commission, slippage_cost, raw_entry_price e raw_exit_price. Os entry_price / exit_price armazenados são os preços de execução após o slippage. account.refresh_metrics() expõe total_commission e total_slippage_cost.


Posições

Uma posição representa uma operação aberta em um único símbolo. Você não constrói posições diretamente — elas são criadas por open_trade(...) e removidas por close_trade(...), que também pode fechar parte de uma posição e deixar o restante no lugar.

Abrindo uma operação

snap = account.open_trade( symbol, # "AAPL" side, # "long" or "short" qty, # integer or float quantity entry_price, # float, the requested price (slippage/commission applied) sl_price=None, # optional stop price (overrides default sl_pct/sl_dollars) tp_price=None, # optional target price (overrides default tp_pct/tp_dollars) position_id=None, )

A chamada retorna um snapshot da conta (veja abaixo). A posição recém-aberta aparece em snap["open_positions"] — pegue o id dela se quiser fechá-la depois por id. O entry_price armazenado é a execução após o slippage; o preço solicitado é mantido como raw_entry_price.

Uma abertura comprada é rejeitada com insufficient cash for long quando qty × fill_price + commission (preço de execução após o slippage) excede o caixa. O snapshot retornado marca esse símbolo ao preço de execução.

Dimensionando pelo risco

Se você quer que a conta dimensione a operação com base no seu orçamento de risco (um entre risk_pct_per_trade ou risk_fixed_per_trade) e em uma distância de stop, use:

qty = account.position_size_for_risk(entry_price, stop_price)

Isso exige que exatamente um modo de risco esteja configurado e que a distância do stop não seja zero. Por padrão, risk_pct_per_trade usa uma porcentagem do caixa; passe equity_for_risk=... para dimensionar em relação ao patrimônio.

Fechando uma operação

account.close_trade(position_id, exit_price) # close the whole position account.close_trade(position_id, exit_price, qty=4) # scale out 4 units, keep the rest

A assinatura completa é close_trade(position_id, exit_price, *, qty=None) — qty é somente por palavra-chave.

open_trade / close_trade recebem um preço solicitado e então aplicam os modelos de slippage e comissão da execução. O exit_price armazenado é a execução; o preço solicitado é mantido como raw_exit_price. Um padrão comum de simulação de backtest: solicitar o tick sintético de fechamento da barra a partir de on_tick:

def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None): if tick_in_bar == 3: account.close_trade(pid, price)

Em execuções forward, use o price ao vivo de cada cotação da corretora em on_tick em vez de verificar tick_in_bar.

Fechamentos parciais (qty=)

Passar qty fecha apenas essa quantidade de unidades e deixa o restante aberto no lugar — mesmo id de posição, mesmo entry_price, mesmo entry_time — de modo que a posição restante mantém sua base de custo e seu período de detenção:

pid = account.open_positions_payload()[0]["id"] account.close_trade(pid, px, qty=4) # scale out 4 units, keep the rest

O registro de um fechamento parcial em closed_trades traz partial: True e remaining_qty.

  • Os custos são cobrados sobre as unidades que realmente foram negociadas, e a comissão de abertura do lote é dividida proporcionalmente, de modo que uma sequência de fechamentos parciais cobra exatamente o que um único fechamento total cobraria.
  • Omita qty — ou passe um valor igual ou maior que o tamanho da posição — para um fechamento total.
  • O restante continua sendo uma posição aberta normal: segue sendo marcado a cada passo e aparece em snap["open_positions"] com o mesmo id, então você pode reduzir novamente depois.

Você também pode fechar em bloco por símbolo. symbol= é obrigatório em execuções com vários símbolos para que você não feche por acidente posições de todos os tickers de uma vez:

account.close_all_longs(exit_price, symbol="AAPL") account.close_all_shorts(exit_price, symbol="AAPL")

Outros auxiliares

MétodoFinalidade
open_positions_payload(prices=None)Pernas abertas como dicts, cada uma com last_price e unrealized_pnl em prices (padrão: último tick).
open_position_symbols()Conjunto de símbolos com pelo menos uma perna aberta.
position_unrealized_pnl(pos, prices)P/L aberto de uma OpenPosition.
apply_split(symbol, ratio, *, when=None) / apply_dividend(symbol, amount_per_share, *, estimated=False, when=None)Eventos corporativos — o mecanismo os chama quando os dados de calendário estão ativados; veja Dados de calendário.
corporate_actionsRegistro dos desdobramentos / dividendos aplicados até agora.
set_bar_volume(symbol, volume)Volume da barra usado pelo slippage por participação no volume; o mecanismo o define a cada barra.

Preço de saída vs. indicadores: Passe o preço que você pretende atingir (por exemplo, float(bar["close"]) de on_bar); o mecanismo ainda aplica slippage/comissão. Ele não reescreve a sua solicitação para uma SMA ou outro indicador. No backtest, tick_in_bar == 3 em on_tick é um tick de fechamento sintético (simulação OHLC da barra, não dados reais de ticks). Em execuções forward, feche usando o price ao vivo de on_tick a cada atualização da corretora.


Caixa, saldo, patrimônio e PnL

Estes são os campos centrais de acompanhamento de dinheiro, disponíveis na conta e em todo snapshot:

CampoSignificado
starting_balanceO caixa com que a conta começou.
cash / balanceCaixa liquidado após aberturas/fechamentos. Aberturas compradas debitam qty × fill_price + commission; as vendidas creditam qty × fill_price − commission. Os entry_price / exit_price armazenados são preços de execução após o slippage.
equityTotal marcado a mercado: cash + Σ(long qty × mark) − Σ(short qty × mark).
unrealized_pnlSoma do P/L das posições abertas, avaliadas às marcas atuais.
realized_pnlLucro/prejuízo acumulado das operações fechadas.

Como as marcas são atualizadas:

  • O mecanismo chama tick(prices, time=...) na conta a cada passo de preço simulado, de modo que equity, unrealized_pnl e o histórico de patrimônio se mantêm atualizados automaticamente.
  • Se o preço de marca de um símbolo estiver ausente do mapa prices, essa posição é avaliada ao seu preço de entrada — o P/L não realizado dessa perna é 0 até que você passe uma cotação real.
  • close_trade carrega as marcas anteriores e marca o símbolo fechado em sua execução de saída, de modo que as posições deixadas abertas — incluindo o restante de um fechamento parcial — continuam marcadas. refresh_metrics() sem argumento reutiliza as marcas do snapshot mais recente (open_trade / close_trade / tick) e só recorre ao último patrimônio / caixa quando esse mapa está vazio.

Dentro de on_bar e on_tick, você não precisa chamar account.tick(...) por conta própria — o mecanismo cuida disso. Ele avança antes de cada on_tick, mas on_bar é executado antes dos ticks desse passo, então em on_bar as marcas mais recentes ainda são o fechamento da barra anterior. Um tick redundante acrescenta um ponto extra a equity_history. Prefira passar last_prices explícitos para refresh_metrics({symbol: last_px}) em on_finish quando algo ainda puder estar aberto.

Operações vendidas e caixa

Posições compradas e vendidas são suportadas na mesma conta:

  • Abertura vendida — o produto aumenta o caixa (menos a comissão, quando configurada).
  • Fechamento vendido (compra para cobrir) — reduz o caixa.

Não há modelo de margem nem custo de empréstimo, portanto as vendas a descoberto são uma simulação pura de caixa e marcação (mais comissão/slippage quando há modelos definidos).


Snapshots

Toda chamada a open_trade, close_trade e tick retorna o mesmo dicionário snapshot descrevendo o estado da conta logo após a chamada. Chaves úteis:

ChaveO que contém
starting_balanceCaixa inicial.
cash / balanceCaixa atual.
equityPatrimônio atual marcado a mercado.
unrealized_pnlP/L das posições abertas às marcas atuais.
realized_pnlP/L realizado acumulado.
open_positionsLista de dicts de posições abertas: id, symbol, side, qty, entry_price, sl_price, tp_price, raw_entry_price, commission_at_open, entry_time (pode ser null). Não há campo de P/L por perna nas linhas de posições abertas — leia unrealized_pnl do snapshot para o P/L flutuante agregado.
closed_tradesLista de operações fechadas: position_id, symbol, side, qty, preços de entrada/saída, P/L realizado, commission (inclui a parcela deste fechamento na comissão de abertura), slippage_cost (slippage de entrada + saída), raw_exit_price, raw_entry_price opcional e entry_time / exit_time; fechamentos parciais acrescentam partial e remaining_qty. Também disponível como account.closed_trades na instância.
tick_pricesCópia do mapa de preços usado nesse snapshot. close_trade carrega as marcas anteriores e marca o símbolo fechado em sua execução de saída, de modo que as pernas abertas continuam marcadas ao longo de um fechamento.

Não existe a API account.get_open_positions(symbol). Filtre snap["open_positions"] (ou account.open_positions_payload()) pelo symbol em maiúsculas, ou leia account.closed_trades para o histórico fechado.

Isso facilita inspecionar a conta na sua estratégia:

snap = account.open_trade("AAPL", "long", 10, px) print("equity now:", snap["equity"]) print("open positions:", len(snap["open_positions"]))

Métricas de desempenho

Quando a execução está terminando, você pode pedir à conta que calcule um conjunto completo de métricas:

def on_finish(bars, symbol=None, bars_by_symbol=None): last_px = float(bars[-1]["close"]) metrics = account.refresh_metrics({"AAPL": last_px})

O argumento é um mapa {symbol: last_price} usado como marca final, para que as posições abertas sejam avaliadas corretamente. Use a lista bars passada para on_finish (da mais antiga para a mais recente) para obter o último fechamento — essa é a série canônica de todo o histórico.

Você também pode passar dois argumentos opcionais:

  • risk_free_rate=0 — usado pelos índices do tipo Sharpe/Sortino.
  • calmar_annualized_return=None — substitui o retorno anualizado usado pelo Calmar.

Métricas disponíveis

refresh_metrics(...) retorna um dicionário que inclui:

GrupoCampos
Retornostotal_pnl, total_return, cagr
Riscomax_drawdown, volatility, risk_adjusted_return
Índicessharpe_ratio, sortino_ratio, calmar_ratio, profit_factor
Estatísticas de operaçõesaverage_trade_expectancy, average_win, average_loss, total_commission, total_slippage_cost
Exposiçãocurrent_exposure, average_exposure, max_exposure
Eventos corporativostotal_dividend_income, total_splits_applied

Observações:

  • Os índices baseados em operações usam o retorno por operação, definido como realized_pnl / starting_balance.
  • total_return e cagr usam o patrimônio atual em relação ao saldo inicial.
  • Para um CAGR / Calmar significativo, o mecanismo precisa de informações de calendário — ele já passa os timestamps quando avança a conta, então em geral você não precisa fazer nada de especial.
  • Alguns índices podem retornar inf quando o denominador é zero e o desempenho é favorável (por exemplo, nenhuma operação perdedora).

As métricas também aparecem na visão Test Results do editor e na carga estruturada do resultado da execução.


Juntando tudo

Uso típico dentro de uma estratégia:

  1. Configure o saldo inicial e a comissão / slippage na aba Simulation (SL/TP / risco opcionais via .qcs / código quando suportado).
  2. Abra operações em on_bar (ou em on_tick para execuções intrabarra) com account.open_trade(...) — guarde o id retornado se for precisar dele.
  3. Feche operações com account.close_trade(position_id, exit_price) — ou com qty= para reduzir e deixar o restante do lote aberto.
  4. Leia snap["equity"], snap["cash"], snap["unrealized_pnl"] etc. sempre que quiser inspecionar a conta.
  5. Em on_finish, chame account.refresh_metrics({symbol: last_price}) para preencher as métricas de desempenho do resultado.

Esse é todo o modelo de conta — uma superfície pequena, mas suficiente para simular posições compradas, vendidas, fluxos de caixa, P/L e um conjunto completo de métricas para qualquer estratégia que você consiga expressar com os callbacks do ciclo de vida.

15 min de leituraSugerir uma edição ↗
Modelo de conta — Documentação (1.0 (latest)) | QuantCraft