Cada número da nota, e de onde ele veio
Método suficiente para conferir a aritmética à mão, ou para reconstruir um estudo equivalente a partir das mesmas fontes públicas e licenciadas, incluindo o que o adendo muda e o hash sob o qual cada execução foi congelada.
Dados e universo
Cripto. Dez perpétuos da Binance com margem em USDT, selecionados em 31 de dezembro de 2022 pelo volume nocional diário mediano entre outubro e dezembro de 2022: BTC, ETH, DOGE, XRP, BNB, SOL, MATIC, LTC, CHZ e ETC. As klines e o histórico completo de funding vêm dos arquivos públicos da Binance.
Ações. Dez ações americanas individuais de grande capitalização selecionadas da mesma forma: TSLA, AAPL, NVDA, AMZN, MSFT, AMD, META, GOOGL, NFLX e XOM. A fonte são dados de negócio e cotação de 1 minuto elegíveis para NBBO, chaveados por um identificador de segurança estável com fatores de ajuste encadeados entre mudanças de identificador, para que splits e reuso de ticker não corrompam os retornos.
Escopo. Duas frequências de barra, 15 minutos e 1 hora, dando quatro células: crypto:15m, crypto:1h, equity:15m e equity:1h. O histórico de input começa em 1º de janeiro de 2021 e a avaliação fora da amostra vai de 1º de janeiro de 2023 até 31 de julho de 2026.
Todo arquivo de input tem hash registrado em um manifesto de dados que a execução verifica antes de tocar em uma única barra. Funding, spreads, taxas, slippage, timestamps de máxima e mínima, campos de ordem do caminho, retornos futuros e resultados de trades são metadados de execução, e estão proibidos nas definições de sinal base e nos inputs de estado da curva.
A biblioteca de estratégias, e como ela foi calibrada
Oitenta estratégias mecânicas em oito famílias, todas simétricas em sinal. A biblioteca foi calibrada uma única vez, apenas sobre janeiro de 2021 a dezembro de 2022, para que os períodos de holding se pareçam com os de um trader discricionário de intraday e swing: a maioria das posições fechando em horas, com uma cauda chegando a cerca de duas semanas. A calibração lê contagem de candidatos, períodos de holding e cobertura de instrumentos, e não lê retorno, Sharpe, taxa de acerto ou drawdown de nenhum tipo. Ela convergiu em três rodadas com cinco substituições, todas na família de reversão de RSI, todas por cobertura rala.
Um candidato só dispara quando a visão direcional de uma estratégia realmente se inverte, de comprado para vendido ou de vendido para comprado. Uma barra neutra não é um sinal e não zera a visão:
# The candidate grid: genuine inversions only, never a re-entry from neutral. view = raw_view.replace(0, method="ffill") # neutral holds the previous view signal = view != view.shift(1) # a transition, not a level
Isso importa mais do que parece. Tratar cada reentrada a partir do neutro como sinal novo faz com que uma regra cuja condição oscila em torno de uma banda emita um candidato a cada vez, e contar apenas inversões genuínas cortou a contagem de candidatos em cerca de três vezes.
Essas transições de sinal são também a grade comum de candidatos sobre a qual toda política é comparada. A cada transição, cada política fecha o que estiver segurando e abre uma posição na nova direção sinalizada, então as fronteiras e as contagens de trades são idênticas entre políticas por construção, e a única coisa que difere é como cada trade terminou e a que preço.
Barreiras, e o baseline que os tratamentos precisam superar
A largura da barreira e o reward-to-risk são parâmetros da estratégia, não constantes universais, então são ajustados dentro da amostra por par estratégia-janela sobre o objetivo registrado e contam no mesmo orçamento de busca dos overlays.
stop in {3, 6} ATR x reward-to-risk in {1.5, 3} = 4 base configurationsIsso dá pares de alvo e stop de (4.5, 3), (9, 3), (9, 6) e (18, 6) ATR. O ATR é o ATR(14) de Wilder calculado até a barra de sinal concluída e congelado por toda a vida do trade, e uma unidade de risco é um movimento de um ATR do momento da entrada no tamanho base. Calibrar o baseline assim eleva o nível que os tratamentos precisam cruzar: a comparação honesta é contra gestão estática já calibrada, não contra um espantalho universal de dois para um, que é fácil de melhorar.
Variáveis de estado da curva de capital
Oito variáveis orientadas, cada uma definida de modo que um valor maior sempre signifique desempenho recente melhor: resultado líquido acumulado nos 10 trades anteriores; o mesmo em 50; o capital menos sua média móvel de 20 trades dividido pelo desvio padrão dos 20 incrementos anteriores; o drawdown máximo nos 50 trades concluídos anteriores, armazenado como valor não positivo; o comprimento sinalizado da sequência; a taxa de acerto em 20 trades; a taxa de acerto em 50; e o resultado líquido médio em 20 trades.
state = {
"cum_r_10": net_r.rolling(10).sum(),
"cum_r_50": net_r.rolling(50).sum(),
"equity_vs_ma": (equity - equity.rolling(20).mean()) / net_r.rolling(20).std(),
"max_dd_50": (equity - equity.rolling(50).max()).rolling(50).min(), # <= 0
"streak": signed_streak(net_r > 0),
"win_rate_20": (net_r > 0).rolling(20).mean(),
"win_rate_50": (net_r > 0).rolling(50).mean(),
"mean_r_20": net_r.rolling(20).mean(),
}Uma nona candidata, o drawdown corrente contra o pico histórico acumulado, foi removida por motivos estruturais antes da execução. Para uma estratégia com expectativa negativa ela é ilimitada e deriva monotonicamente para baixo, então limiares ajustados dentro da amostra ficam permanentemente ultrapassados fora da amostra: em um exemplo medido, toda a faixa fora da amostra caiu abaixo do menor ponto de corte in-sample e o filtro executou zero trades. Isso é uma falha de estacionariedade, observável sem referência a lucratividade, e é por isso que se usa o drawdown de 50 trades em janela.
Até o lookback de uma variável estar completo, a gestão fica no baseline selecionado e o rastro de decisão registra insufficient_history. Existe uma curva de capital por estratégia base, mercado, timeframe e política, e os dez instrumentos de uma célula a compartilham.
A biblioteca de gestão e o orçamento de busca
As ações de overlay são multiplicadores sobre a base selecionada, então uma família mantém seu significado enquanto a base flutua. A resposta de reversão à média inverte o mapeamento de estado para ação.
| Estado | Só TP | Só SL | TP + SL | Tamanho da posição | Filtro de trade |
|---|---|---|---|---|---|
| 1 | 0.5x TP | 0.5x SL | 0.5x ambos | 0.5R | pular |
| 2 | 0.75x TP | 0.75x SL | 0.75x ambos | 1R | pular |
| 3 | 1x | 1x | 1x | 1R | entrar |
| 4 | 1.5x TP | 1.5x SL | 1.5x ambos | 1.5R | entrar |
| 5 | 2x TP | 2x SL | 2x ambos | 2R | entrar |
Oito variáveis de estado vezes duas direções dá 16 overlays por família, e 16 overlays vezes 4 configurações base dá 64 tentativas por família por janela. Toda família recebe exatamente o mesmo orçamento, que é o que busca igual significa aqui: igualdade de configurações tentadas, não igualdade no número de botões.
As fontes de estado são escolhidas para que nenhuma família consiga fabricar o próprio estado. As famílias de barreira leem o próprio resultado líquido realizado; o sizing de posição lê o próprio resultado realizado em tamanho unitário, para que a alavancagem não infle a variável que a governa; o filtro de trade lê um book sombra de baseline atualizado continuamente, para que o estado desligado nunca possa ser absorvente.
Walk-forward
Quatorze janelas trimestrais completas fora da amostra, de 2023Q1 até 2026Q2, mais uma janela parcial registrada cobrindo apenas julho de 2026, que é pontuada, sinalizada e divulgada onde quer que seja incluída. A amostra de treino são sempre os 24 meses calendário anteriores, e as fronteiras de calendário são idênticas entre os mercados.
for window in quarterly_windows(oos_start="2023-01-01", oos_end="2026-08-01"):
grid = candidate_grid(strategy, window) # no equity state, no OOS values
is_candidates = [c for c in grid if c.entry_ts < window.oos_start
and c.exit_ts < window.oos_start]
thresholds = state_thresholds(baseline_replay(is_candidates))
attempts = [simulate(cfg, is_candidates, thresholds) for cfg in family_grid] # all 64
persist(attempts) # including degenerate and inactive
winner = max(attempts, key=lambda a: (a.net_r_per_common_candidate,
a.executed_trades, -a.canonical_id))
winner.freeze() # config and thresholds fixed
score(winner, window.oos_slice) # OOS starts flat, state from IS replayA seleção é cronológica, toda tentativa é persistida, incluindo as que não fizeram nada, e o estado fora da amostra do overlay selecionado é inicializado a partir do seu replay in-sample concluído.
Execução intrabarra
Os painéis preparados carregam o timestamp da máxima e da mínima finais de cada barra pai, então o caminho intrabarra é reconstruído como abertura, primeiro extremo registrado, segundo extremo registrado, fechamento, com os cruzamentos de barreira avaliados ao longo desses segmentos monótonos.
Um gap que atravessa um stop sai na primeira abertura disponível, enquanto um gap que atravessa um alvo executa no alvo, que é o que uma ordem limite descansando faz. Se apenas uma barreira é alcançável, ela executa; se as duas são, a ordem dos extremos registrados decide; e se essa ordem é desconhecida, o stop executa primeiro, com os trades que dependeram dessa convenção contados e sua contribuição econômica divulgada.
Modelo de custos
| Execução | Taxa | Slippage | Spread |
|---|---|---|---|
| Entrada, por qualquer motivo | taker 5 bps | 2 bps | metade do medido |
| Saída: take-profit, ou gap através do alvo | maker 2 bps | nenhum | nenhum |
| Saída: stop-loss, ou gap através do stop | taker 5 bps | 2 bps | metade do medido |
| Saída: inversão por sinal contrário | taker 5 bps | 2 bps | metade do medido |
Ações pagam uma provisão fixa de 0.5 bps de comissão e taxas por execução, sem distinção entre maker e taker. O funding de cripto é cobrado em cada settlement que uma posição aberta atravessa, com o sinal observado. Entre 2021 e 2026 o funding foi positivo em 74.5% do tempo e ficou em média em +0.87 bp por oito horas, cerca de 9.5% ao ano contra um book permanentemente comprado, e é por isso que ele é reportado decomposto por direção do trade em vez de embutido em um único número líquido.
Todo ledger mantém resultado bruto de preço, spread de entrada e de saída, taxa de entrada e de saída, slippage de entrada e de saída, funding sinalizado e resultado líquido como colunas separadas, e a identidade precisa reconciliar abaixo de 1e-9 unidades de risco:
net_r = (gross_price_r
- entry_spread_r - exit_spread_r
- entry_fee_r - exit_fee_r
- entry_slip_r - exit_slip_r
+ funding_r) # signed: debits longs when funding is positive
assert abs(net_r - ledger.net_r).max() < 1e-9Inferência
O bootstrap pareado reamostra semanas calendário inteiras, de segunda a domingo, levando junto todo instrumento e estratégia de dentro de uma semana, com 10,000 reamostragens e seed fixa. Reamostrar linha a linha, tirar média de Sharpes por janela e multiplicar a contagem de janelas pela contagem de estratégias são todos proibidos.
weeks = pd.Grouper(key="ts", freq="W-MON") blocks = [df for _, df in paired_deltas.groupby(weeks)] # whole weeks move together draws = [np.concatenate(rng.choice(blocks, len(blocks))).mean() for _ in range(10_000)] p = 2 * min((np.array(draws) <= 0).mean(), (np.array(draws) >= 0).mean()) q = benjamini_hochberg(p_values, alpha=0.05) # 20 contrasts in the declared family
A multiplicidade é corrigida dentro da família declarada de 2 mercados por 2 timeframes por 5 famílias de gestão, ou seja, 20 contrastes, falhas incluídas.
O placebo de rotação de ações pega a mesma política e rotaciona suas ações sobre os mesmos candidatos em 1,000 sorteios com seed, com uma checagem bloqueante de correspondência de exposição: o nulo rotacionado precisa reproduzir exatamente o turnover, a contagem de execuções e a exposição nominal de ações do overlay real, de modo que a única coisa destruída seja o alinhamento entre o estado da curva de capital e o trade.
A suíte de perturbação roda 20 sorteios de dropout de candidatos a 1% e 20 choques de execução com custos escalados entre 0.75x e 1.25x, e exige que pelo menos 80% dos sorteios mantenham o sinal do efeito, que a concordância mediana de decisão fique acima de 80% e que a correlação mediana entre curvas de capital fique acima de 0.9.
As oito condições de aprovação
- o resultado líquido pooled por candidato comum supera o baseline
- a expectativa por trade executado melhora, sendo que uma expectativa indefinida conta como falha
- o resultado líquido por unidade de risco bruto nominal médio não piora
- a diferença pareada com clusterização semanal tem q ajustado por BH abaixo de 0.05
- taxa de acerto por janela completa acima de 50% e reward-to-risk acima de 1
- a diferença continua positiva depois de remover a melhor janela fora da amostra
- o resultado supera o percentil 95 do placebo pareado de rotação de ações
- o gate de estabilidade de dependência de caminho passa
A condição 4 falhou nas 20 comparações do estudo e nas 24 do adendo. A condição 2 é escrita de modo que uma política com turnover zero falhe nela, porque essa política pode superar um baseline perdedor na condição 1 sem fazer nada, e a suíte de análise testa exatamente esse caso.
O que o adendo muda
Universo, dados, timeframes, cronograma walk-forward, a grade de barreiras ajustável dentro da amostra, as oito variáveis de estado da curva de capital, o orçamento de 64 tentativas, a regra de seleção, a elegibilidade, o bootstrap, o placebo, a suíte de perturbação e as oito condições de aprovação são todos herdados sem mudança. Quatro coisas diferem.
Saídas
Uma posição fecha somente quando seu take-profit ou seu stop-loss executa. Se nenhum dos dois executou até o fim do histórico do instrumento, o trade é truncado na última barra observada e sinalizado, e a taxa de truncamento é uma estatística de reporte obrigatório, já que uma taxa alta significaria que as barreiras não se resolvem dentro da amostra e que o protocolo não entregou o teste justo que promete.
Larguras das barreiras
Remover a saída por reversão alonga os holdings, então a grade de stops foi recalibrada por célula antes da execução, lendo apenas o período de holding e a resolução:
| Célula | Stops (ATR) | Holding mediano nas quatro bases | Holdings de duas semanas ou mais |
|---|---|---|---|
| crypto:15m | 3.0, 6.0 | 6.2h a 38.5h | 0.0% a 4.4% |
| equity:15m | 3.0, 6.0 | 6.0h a 28.9h | 0.6% a 24.3% |
| crypto:1h | 1.5, 3.0 | 6.5h a 38.0h | 0.0% a 3.8% |
| equity:1h | 1.5, 2.25 | 7.0h a 21.0h | 0.3% a 13.7% |
Quinze das dezesseis combinações de célula e base ficam dentro da faixa acordada. A exceção é equity:15m na sua base mais larga, reportada nas limitações da nota em vez de removida.
Ocupação
Cada ramo resolve um sinal que chega com uma posição aberta, e os dois são avaliados sobre o mesmo denominador, que é toda transição de sinal na janela, fixada pela regra e pela série de preços:
if branch == "single" and book.holds(instrument):
decision = "declined_as_occupied" # earns nothing, still counts in the denominator
else:
book.open(instrument, side, size, tp, sl)Uma política que está ocupada e pula um sinal simplesmente não ganha nada com ele. As contagens de trades passam a diferir entre políticas, o que é um resultado a reportar e não uma comparação quebrada, e o turnover é reportado ao lado.
Multiplicidade
O adendo é corrigido dentro da sua própria família declarada de 3 famílias por 4 células por 2 ramos, ou seja, 24 contrastes. Os dois ramos são corrigidos juntos porque ambos foram rodados e ambos são reportados, então pontuá-los separadamente subestimaria quanto foi buscado. Os contrastes do adendo nunca são agrupados com os do estudo, e nenhum dos dois reavalia o outro.
Verificação, e os hashes congelados
O engine de execução em Rust foi conferido linha a linha contra uma implementação de referência em Python escrita de forma independente. No engine do adendo, os dois concordam exatamente, com tolerância de 1e-9, em toda barra de saída, preço de saída e resultado líquido ao longo de 362,100 linhas. A paridade é a suíte que mais importa ali, porque remover a saída por reversão mudou o próprio núcleo de execução, e o congelamento recusa um veredito registrado antes do código que ele certifica, então nenhum veredito é herdado do engine do estudo.
| Suíte | Estudo | Adendo |
|---|---|---|
| Paridade de execução, engine Rust contra a referência em Python | passou | passou |
| Calibração pré-congelamento, apenas período de holding | passou | passou |
| Primitivas de inferência | passou | passou |
| Checagens de análise | passou | passou |
| Estado, seleção, robustez e paridade de artefatos | não reexecutada | não reexecutada |
| Detecção de defeito plantado, causalidade e gates de vazamento | não reexecutada | não reexecutada |
Os dois foram especificados por escrito e congelados antes que qualquer número fora da amostra fosse produzido ou aberto, e todo número reportado é gerado a partir de artefatos armazenados em vez de digitado à mão. O adendo carrega seus próprios hashes de overlay e de implementação, que é o que faz dele um acréscimo e não uma reavaliação:
| Artefato | SHA-256 |
|---|---|
| strategy_configs.json, compartilhado pelos dois | d5c1fabbd22fcf20907e24025c6868f739afb11a2a952fc71241630f318cf68e |
| overlay_configs.json, estudo | c0eaaaedd74800e2e973dda23a23053ee7966a07762803e7b2592cabfed5320f |
| analysis_config.json, estudo | 1afcb713478d37d234268038280d9a83f38dfc535c8b1e3fbb9b6acc649e4fef |
| data_manifest.json, estudo | 93aefeaa5ad78d920fdaf48d36cc61794921eaaaa92cbf52f3b7f5119c855a59 |
| strategy_selection.json, estudo | a37718869d539141b64c492f1d221a7a9ba8395f7e9ca323d2c536028a21df4c |
| Implementação da análise, estudo | e46041ab30b8f44ad1029a798e1d0bb58aa97959ba88bfb0558bf4468393600f |
| overlay_configs.json, adendo | 7d575275961bee72f5f6557ae5c253545f69cc352d5b9e35a216643714b7331e |
| Implementação da análise, adendo | 667f6ab6e65c2421c670179c6a5de8405209e2d8db35347e40fd71acb5fb4105 |
Pedindo o pipeline completo
Esta página é suficiente para conferir cada número da nota à mão, ou para reconstruir um estudo equivalente a partir das mesmas fontes de dados públicas e licenciadas. Os scripts de produção, ou seja, a coleta de dados, a construção do painel, o engine em Rust, o runner do experimento e a análise, não são publicados aqui. Envie um e-mail para daniel@daru.finance e eu envio para você.

