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 dePaperAccountcom 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. Trateopen_trade/close_tradeali 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ção | O que faz |
|---|---|
starting_balance | Caixa com o qual a conta começa. |
| Modelos de comissão / slippage | Como 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
2ou frações como0.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
| Modelo | Parâmetros | Comportamento |
|---|---|---|
| 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ção | Custo fixo por ordem ($) | Mesma taxa independentemente da quantidade |
| Zero | — | Sem comissão — útil para testar alfa puro |
Modelos de slippage
| Modelo | Parâmetros | Comportamento |
|---|---|---|
| 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 fixo | Spread 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 restA 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 restO 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étodo | Finalidade |
|---|---|
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_actions | Registro 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"])deon_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 == 3emon_tické um tick de fechamento sintético (simulação OHLC da barra, não dados reais de ticks). Em execuções forward, feche usando opriceao vivo deon_ticka 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:
| Campo | Significado |
|---|---|
starting_balance | O caixa com que a conta começou. |
cash / balance | Caixa 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. |
equity | Total marcado a mercado: cash + Σ(long qty × mark) − Σ(short qty × mark). |
unrealized_pnl | Soma do P/L das posições abertas, avaliadas às marcas atuais. |
realized_pnl | Lucro/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 queequity,unrealized_pnle 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_tradecarrega 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_bareon_tick, você não precisa chamaraccount.tick(...)por conta própria — o mecanismo cuida disso. Ele avança antes de cadaon_tick, mason_baré executado antes dos ticks desse passo, então emon_baras marcas mais recentes ainda são o fechamento da barra anterior. Umtickredundante acrescenta um ponto extra aequity_history. Prefira passarlast_pricesexplícitos pararefresh_metrics({symbol: last_px})emon_finishquando 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:
| Chave | O que contém |
|---|---|
starting_balance | Caixa inicial. |
cash / balance | Caixa atual. |
equity | Patrimônio atual marcado a mercado. |
unrealized_pnl | P/L das posições abertas às marcas atuais. |
realized_pnl | P/L realizado acumulado. |
open_positions | Lista 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_trades | Lista 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_prices | Có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:
| Grupo | Campos |
|---|---|
| Retornos | total_pnl, total_return, cagr |
| Risco | max_drawdown, volatility, risk_adjusted_return |
| Índices | sharpe_ratio, sortino_ratio, calmar_ratio, profit_factor |
| Estatísticas de operações | average_trade_expectancy, average_win, average_loss, total_commission, total_slippage_cost |
| Exposição | current_exposure, average_exposure, max_exposure |
| Eventos corporativos | total_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_returnecagrusam 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
infquando 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:
- Configure o saldo inicial e a comissão / slippage na aba Simulation (SL/TP / risco opcionais via
.qcs/ código quando suportado). - Abra operações em
on_bar(ou emon_tickpara execuções intrabarra) comaccount.open_trade(...)— guarde oidretornado se for precisar dele. - Feche operações com
account.close_trade(position_id, exit_price)— ou comqty=para reduzir e deixar o restante do lote aberto. - Leia
snap["equity"],snap["cash"],snap["unrealized_pnl"]etc. sempre que quiser inspecionar a conta. - Em
on_finish, chameaccount.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.

