Indonesia Momentum — Auditoria Hostil
Data: 2026-08-25 · Auditor: agente adversarial independente (2ª auditoria, sem confiança em auditorias anteriores)
Objecto: generate_indonesia_backtest.py / generate_indonesia_sweep.py; config alegada 12M skip=0, top 15 equal-weight, mcap ≥ 1T IDR, regime ^JKSE vs MA200, mensal, 2004-01→2026-07; resultado alegado CAGR +28.0%, Sharpe 0.99, MaxDD -41.8%, Alpha +18.2pp.
Artefactos da auditoria: ~/eodhd_data/audit2/ (scripts audit2_*.py, CSVs mensais, caches). Originais não modificados.
A. Veredicto executivo
Não investiria capital real nesta estratégia na sua forma actual, porque os números-manchete (28.0%/0.99) estão inflacionados por quatro camadas cumulativas — (1) universo sem QUALQUER delisting antes de 2017 e com <50% dos delistings reais depois de 2017 (survivorship estrutural do fornecedor EODHD), (2) configuração escolhida in-sample de uma grelha multi-parâmetro numa pesquisa multi-mercado (winner's curse), (3) zero custos de transacção com turnover de 32%/mês num mercado onde 28-41% das posições excedem 100% do volume diário, e (4) convenções favoráveis (benchmark price-only vs estratégia total-return; rf=0) — e porque a janela mais limpa e verificável (2017-2026), com custos e rf realistas, entrega Sharpe ≈ 0.4-0.7 e CAGR ≈ 15-16%, não 0.99/28%. O sinal cross-sectional de momentum é genuíno (o motor distingue-o claramente de placebos), mas o produto investável implícito é uma estratégia média com drawdowns de -35/-45%, não a máquina de Sharpe ~1 anunciada.
Confiança no veredicto: 85/100. (Os 15 restantes: a magnitude exacta do survivorship é inquantificável com estes dados; é concebível — mas improvável — que os delistings em falta fossem neutros.)
O que sobrevive à auditoria: a aritmética do motor (reproduzi 28.0/0.99/-41.8/+18.2 ao mês exacto com uma reimplementação 100% independente), a ausência de lookahead mecânico, a assimetria sinal-vs-inverso, o plateau de parâmetros e a persistência de algum alpha em todas as sub-janelas.
B. Tabela de riscos
| # | Problema | Severidade | Evidência | Impacto estimado | Resolvido? |
|---|---|---|---|---|---|
| 1 | Survivorship estrutural: 0/1014 históricos de preços terminam antes de 2017; apenas 26 mortes reais de acções (2017-25) vs ~40-60 delistings reais no período (só 2025: 15 oficiais; 2009-2016 dezenas documentadas, todas ausentes) | CRÍTICA | scan completo audit2_data_forensics.py; log do downloader ("53 delisted", 27 dos quais são warrants); fontes IDX/Databoks |
2004-2016 inquantificável (RISCO NÃO RESOLVIDO); proxy: Sharpe cai de 1.36 (2004-16) para 0.66 (2017-26) | ❌ Não resolúvel com estes dados |
| 2 | Selecção in-sample da config (12-0/1T/MA200 = melhor célula de grelha 5×7 + 6 MAs; Indonésia = melhor de pesquisa multi-mercado desde Aug-02) | ALTA | generate_indonesia_sweep.py STEP1/2; backtest_asia_latam.py (grelha JK anterior com outros parâmetros) |
Winner's curse; expectativa live < backtest mesmo sem mais nenhum bug. Mitigante: plateau suave (12-0:0.99, 6-0:1.06, 9-0:0.96) | ⚠️ Mitigado por plateau, não eliminado |
| 3 | Custos = 0 com turnover médio 32%/mês (~7.7× ano só de compras) | ALTA | battery B5-B7 | 50bps/lado → 24.8%/0.90; 100bps → 21.7%/0.82; 200bps → 15.6%/0.64 | ❌ Não incluídos no original |
| 4 | Investibilidade: a $1M de capital, 28% das posições >100% do ADV20; a $5M, 41%; top contributors (MGLV, PGUN, POLU, TFAS) negociavam $10-40k/dia quando "detidos" | ALTA | análise posição/ADV com FX anual; forensics dos top contributors | O backtest tal como especificado é inexecutável à escala; variante com filtro ADV≥10B IDR mantém Sharpe 1.15 (0.87 em 2017+) — o alpha não morre, mas a estratégia auditada não é a executável | ⚠️ Alpha sobrevive ao filtro; números-manchete não |
| 5 | Benchmark price-only vs estratégia total-return | MÉDIA | C2: bench TR (+3% yield) → alpha +14.9pp, não +18.2pp | -3.3pp de alpha | ❌ no original |
| 6 | Sharpe com rf=0 em moeda com rf≈6% | MÉDIA | C1: excess-Sharpe 0.85 (cash a render rf) | Sharpe 0.99 → 0.85 | ❌ no original |
| 7 | Limpeza de dados full-sample: 50 tickers excluídos a história inteira com base em eventos futuros; ~metade das exclusões são marginais (HERO, MAPI, ISAT, AKRA, TOWR, DILD, TMAS: 1 dia suspeito em 22 anos); UNVR excluída por um critério de dominância do índice sintético | MÉDIA | exclusion_reasons.json; A2-A4 |
Limpeza vale +0.4 a +1.0pp CAGR e +0.04-0.07 Sharpe vs limpeza mínima com princípios (27.6/0.95) ou nenhuma (27.0/0.92) | ⚠️ Direcção favorável, magnitude pequena |
| 8 | Concentração: top-5 acções = 79% do P&L$; remover top-5 meses → 19.6%/0.89; 2016 +167%, 2025 +132%, 2026 YTD -28% | MÉDIA | análise de atribuição | Cauda direita domina (típico de momentum, mas agrava o risco de survivorship/pump) | ⚠️ Sobrevive parcialmente (top-5 zeradas: 22.3%/0.91) |
| 9 | Ano de 2004 morto (warm-up EMPTY) dentro das estatísticas "n=270" | BAIXA | A5/A6: com sinal pré-2004 real: 29.7%/1.03; a começar em 2005: 29.5%/1.02 | Subestima o período investido (~+1.5pp); rotulagem enganosa, direcção anti-conservadora nula | ✔️ quantificado |
| 10 | mcap_daily com níveis historicamente duvidosos (UNVR 2004 implica 0.7B acções vs 7.63B reais; TLKM idem) | MÉDIA | teste de acções implícitas por ano | O corte de 1T IDR pode classificar mal empresas no passado; stress 0.5T/2T/5T mantém Sharpe 0.93-0.97 → efeito limitado, mas níveis não são fiáveis | ⚠️ RISCO NÃO RESOLVIDO (nível), mitigado (robustez ao corte) |
| 11 | Warrants classificados como "Common Stock" no tickers.parquet (27 de 53 "delisted") | BAIXA | forensics | Excluídos do universo apenas pelo gate lateral de fundamentais (por sorte, não por design); contaminam a alegação "inclui delisted" | ✔️ identificado |
| 12 | Convenção delisting=0%: 0 posições em 270 meses atingiram morte terminal | BAIXA (mas sintoma do #1) | teste directo | Impacto 0.0pp — a alegação anterior de "7 tickers afectados" está errada para esta config; a ausência total de mortes em 22 anos × 15 posições é em si evidência de survivorship | ✔️ testado |
C. Todos os bugs/vícios encontrados (localização exacta)
- Warm-up cego dentro das estatísticas —
generate_indonesia_sweep.pylinhas 253-255 + 323-324:price_matrixsó começa em 2004-01, logomom_matrix.shift(12)é NaN durante 2004 inteiro → 12 meses "EMPTY" com ret=0 enquanto o benchmark corre, dentro dos n=270. Havia dados de 2003 disponíveis e não usados. Correcção (sinal pré-2004 real): 29.7%/1.03. Efeito: entendimento errado do período (efectivamente 2005-2026), direcção conservadora. - Limpeza informada pelo futuro —
generate_indonesia_backtest.pySTEP 2 (linhas 92-210): um ticker é excluído da história INTEIRA se em qualquer dia (mesmo 2025) disparar um check. Cheques (d)/(e) capturam também eventos reais. 50 exclusões (incl. UNVR por dominância, linha 250-255). Antes/depois: limpeza original 28.0/0.99; limpeza mínima só-corrupção-dura (24 tickers: sentinela, >2000%/mês, broken-adjust) 27.6/0.95; sem limpeza 27.0/0.92. - Gate de fundamentais escondido no universo — STEP 4 (linha 323): o universo exige ≥4 trimestres de EBIT mesmo com
use_ebit=False(842 vs 936 tickers). Não enviesa materialmente (A2: 27.4/0.95) mas é um filtro não declarado na especificação da estratégia. (Testei e rejeitei a hipótese de exclusão dos bancos — EODHD preencheebitpara bancos JK.) - Bug na auditoria anterior —
audit_indonesia_independent.pylinhas 56-58: o comentário afirma "null mcap on sentinel-close days" mas o código não o faz; execução é zero-lag (compra ao próprio fecho do sinal); warrants só parcialmente excluídos. Os seus números (32.4/1.03, reproduzi exactamente) validam menos do que o alegado. - Alegação "verificação de lag 270/270" — o código só faz
asserta 3 meses (backtest linhas 540-562, sweep 559-572). A minha comparação painel-completo confirmou que as datas M_j/E_j e os modos CASH/MOM coincidem 270/270 — a lógica está certa; a alegação de verificação exaustiva era falsa. - tickers.parquet: 27 warrants com
Type='Common Stock'— bug de classificação do fornecedor/downloader que infla a contagem de "delisted names" citada como defesa contra survivorship. - Sem custos, benchmark PR, rf=0 — ver riscos #3, #5, #6 (não são "bugs" de código, são convenções que empolam os manchetes).
Sem bugs encontrados em: construção do calendário (M_j = último dia de negociação real; E_j = dia seguinte real — 270/270 idênticos na reimplementação); lag sinal→execução; lógica as-of do regime (JKSE e MA200 avaliados a M_j, executados a E_j — implementável); ffill limitado a 5 dias (apenas passado); ordenação/desempate do ranking; aritmética de CAGR/Sharpe/MaxDD. A reprodução independente convergiu ao mês exacto (corr. mensal 1.000, mesmos 4 decimais nos agregados) quando alimentada com o mesmo universo — toda a divergência inicial (overlap médio 13.4/15 picks) foi rastreada à lista de exclusões, e a nada mais.
D. Resultados auditados
| Variante | CAGR | Sharpe | MaxDD | Bench | Alpha |
|---|---|---|---|---|---|
| Original (alegado e reproduzido ao decimal) | +28.0% | 0.99 | -41.8% | +9.9% (PR) | +18.2pp |
| Reimplementação independente, mesmo universo | +28.0% | 0.99 | -41.8% | +9.9% | +18.2pp |
| Reimpl., limpeza mínima com princípios | +27.6% | 0.95 | -43.4% | +9.9% | +17.7pp |
| Reimpl., universo sujo (sem exclusões) | +27.0% | 0.92 | -46.1% | +9.9% | +17.1pp |
| Benchmark comparável TR (JKSE +3% yield, pressuposto declarado) | +28.0% | 0.99 | -41.8% | +13.2% | +14.9pp |
| rf=6% (cash rende rf; excess-Sharpe) | +30.2% | 0.85 | -39.4% | +9.9% | +20.3pp |
| Execução: zero-lag / next-open / +2 dias | +29.3 / +28.6 / +26.5% | 0.98 / 0.97 / 0.93 | — | — | — |
| Custos 50 / 100 / 200 bps por lado | +24.8 / +21.7 / +15.6% | 0.90 / 0.82 / 0.64 | -46/-50/-56% | +9.9% | +15.0/+11.8/+5.7pp |
| 2004-2016 (janela suja) | +34.7% | 1.36 | -24.8% | +16.3% | +18.4pp |
| 2017-2026 standalone (janela mais limpa) | +19.1% | 0.66 | -41.8% | +1.7% | +17.4pp |
| 2017-2026 + 100bps + rf6% | +15.1% | 0.56 (0.38 excess) | -47.5% | +1.7% | +13.4pp |
| Executável: filtro ADV20≥10B IDR, full / 2017+ / 2017+ c/100bps | +28.6 / +23.1 / +16.2% | 1.15 / 0.87 / 0.67 | -32/-32/-34% | — | — |
| Placebo: aleatório ×20 (mesma pipeline) | +12.6% [9.6-15.5] | 0.76 [0.63-0.88] | -28.5% | 9.9% | +2.7pp |
| Placebo: aleatório sem regime ×10 | +13.4% | 0.62 | — | ≈ JCI (0.60) | ≈0 |
| Placebo: ranking invertido (bottom-15) | +1.5% | 0.18 | -80.5% | +9.9% | -8.3pp |
| Placebo: sinal com +1 mês de atraso | +21.4% | 0.89 | -38.4% | +9.9% | +11.6pp |
Leitura dos placebos (secção 9): a pipeline NÃO fabrica Sharpe do nada — aleatório sem regime ≈ mercado (0.62 vs 0.60). O regime MA200 acrescenta ~+0.14 de Sharpe a qualquer carteira; o momentum acrescenta ~+0.23 acima do aleatório-com-regime e o inverso colapsa (-80% DD). O sinal é real; os manchetes é que não são.
Subperíodos (cortes fixos): 2004-08: 30.1%/1.34 · 2009-12: 56.2%/1.64 · 2013-16: 30.1%/1.22 · 2017-20: 12.5%/0.61 · 2021-26: 25.0%/0.72. Primeira metade 1.29, segunda metade 0.85. Anos: 2016 +167%, 2025 +132%, 2026 YTD -28%. Hit rate global 63%; turnover 32%/mês.
Concentração: top-5 acções = 79% do P&L$ (MGLV +70.6, BBHI +42.3, MLPT +31.5, BUVA +30.8, PGUN +28.9 por unidade inicial); zerar top-5 → 22.3%/0.91; remover top 5 meses → 19.6%/0.89; remover top 10% meses → 3.4%/0.28 (assimetria de cauda esperada em momentum, mas concentrada em micro-caps recentes de perfil "saham gorengan").
E. O que continua incerto (explicitamente)
- Magnitude do survivorship 2004-2016 — INQUANTIFICÁVEL com estes dados. Zero delistings pré-2017 no dataset; os mortos em falta (BORN, DAVO, TRUB, SOBI, BAEK, etc.) eram exactamente do tipo que o momentum compra antes do colapso. O diferencial 1.36→0.66 entre janelas mistura survivorship com mudança de regime — não é separável. RISCO NÃO RESOLVIDO.
- Survivorship residual 2017-2026: o dataset tem ~26 mortes reais vs ~40-60 delistings oficiais no período (2025 sozinho: 15 oficiais vs 10 no dataset) — até a "janela limpa" está incompleta. Os números 2017+ desta auditoria são tectos, não estimativas centrais.
- Fiabilidade dos níveis de mcap histórico (acções implícitas de UNVR/TLKM incompatíveis com valores conhecidos nos anos 2000). Robustez ao corte mitiga; não elimina.
- PR da estratégia tecnicamente irrecuperável com EODHD (adjusted_close mistura splits+dividendos sem factores separáveis); o TR do benchmark usa o pressuposto declarado de 3% de yield.
- Custos reais IDX (spread, impacto, levy de venda 0.1%): não medidos com dados de mercado; 50-200bps/lado é um intervalo plausível, não uma medição. IBKR não testado para os nomes IDX.
- ^JKSE via yfinance: fonte não auditada; corroborada apenas pela correlação com o índice sintético interno.
- Preços de fecho de nomes com $10-40k/dia de volume: mesmo "sem custos", o próprio print de fecho pode não ser transaccionável em tamanho algum relevante.
Secção 11 — Julgamento das alegações anteriores
| Alegação | Veredicto |
|---|---|
| "Verificação exaustiva de lag 270/270" | FALSA-EXAGERADA (código só assevera 3 meses; a lógica em si está certa — verifiquei eu os 270) |
| "Sentinela 999999.9999 encontrada e neutralizada na price_cache" | CONFIRMADA (4 tickers com sentinela exacta, BCIC com 5044 dias; canal derivado via close cru tratado; reproduzi a neutralização de forma independente) |
| "50 tickers estruturalmente excluídos" | CONFIRMADA COM RESSALVAS (50 = 49 checks + UNVR por dominância; ~metade das exclusões assenta em 1-2 dias suspeitos em 22 anos e usa informação futura; vale +0.4-1.0pp) |
| "tickers.parquet inclui delisted names" | CONFIRMADA COM RESSALVAS → materialmente enganosa (53 flags, mas 27 são warrants; só 26 acções mortas, todas ≥2017) |
| "Reimplementação independente anterior concordou em 32.4%/1.03" | CONFIRMADA (reexecutei: 32.4/1.03) mas o script tem execução zero-lag e um fix de mcap prometido em comentário e não implementado — valida menos do que anuncia |
| "Survivorship: zero delistings pré-2017 nos dados" | CONFIRMADA — e agravada: também faltam >50% dos delistings pós-2017 |
| "Alpha sobrestimado 2-3pp por benchmark price-only" | CONFIRMADA (3.3pp com yield 3%) |
| "Sharpe com rf≈6% é ~0.90" | CONFIRMADA COM RESSALVAS (o meu cálculo estrito: 0.85; 0.85-0.90 conforme convenção de cash) |
| "Plateau suave, não pico isolado" | CONFIRMADA (grelha própria: 6-0:1.06, 9-0:0.96, 12-0:0.99, 12-1:0.89; cortes 0.5/1/2/5T: 0.93-0.97) — mas não anula o winner's curse multi-mercado |
| "2016/2025 são empresas reais identificáveis" | CONFIRMADA COM RESSALVAS (BBHI/ARTO/PANI reais e líquidas; MGLV/PGUN/POLU/TFAS negociavam $10-40k/dia — reais mas inexecutáveis) |
| "Delisting exit 0% afecta 7 tickers, efeito pequeno" | NÃO CONFIRMADA (nesta config: 0 posições atingem morte terminal; efeito 0.0pp — o número "7" não corresponde a nada verificável aqui) |
Secção 10 — Arqueologia de código
Sem repositório git. Todos os scripts Indonésia criados em lote a 2026-08-25 07:13-07:14 (mesmo template dos motores AU/TH/MY, gerados no mesmo minuto); auditoria anterior às 07:53. Não existem versões antigas nem outputs contraditórios para JK — sem incidente de versões. Existe porém backtest_asia_latam.py (Aug-02) com uma grelha JK anterior (mcap 4.8T, N∈{5..30}, vários MAs) — prova de que o espaço de pesquisa total sobre este mercado é muito maior do que o declarado no sweep, reforçando o risco #2.
F. Próximas 3 auditorias mais valiosas
- Reconstruir o universo com a lista oficial de delistings do IDX/KSEI (2004-2026) — obter a lista pública de emitentes delistados (IDX publica; academia cobre 2009-2016), cruzar com o dataset, e para 2017-2026 injectar os mortos em falta com retorno terminal -50/-100% num re-run: transforma o maior risco (survivorship) de "inquantificável" em "medido" pelo menos na janela recente.
- Medição real de executabilidade via IBKR — para os 15 nomes do sinal actual e uma amostra histórica: spreads, profundidade, disponibilidade dos tickers IDX, custo total estimado por escalão de AUM ($100k/$1M/$5M); substituir a curva 50-200bps por números medidos.
- Paper-trading de 6-12 meses com a regra exacta congelada (incluindo o filtro de liquidez executável), comparando fills reais vs fechos do backtest — o único teste que nenhum dataset histórico pode falsificar. (O -28% de 2026 YTD já é o primeiro dado quase-live e é negativo.)