IRAI — Plano de convergência de sinal (foco: Parte 2)

Projeto: IRAI — Intraday Risk Appetite Index

Criado: 2026-07-10 · Reescrito com ênfase na Parte 2: 2026-07-12 · Reverificado e atualizado: 2026-07-13

Revisões: fable-reasoner + codex (/codex-r) na v1; evidências de linha reverificadas contra o código em 2026-07-12, e novamente em 2026-07-13 depois que o Pacote A fechou.

Por que a ênfase mudou. A v1 deste documento era um plano de migração de frontend com um apêndice sobre backend. A migração acabou (5/5 charts, GEX entregue, recharts removido). O que sobrou — e o que separava o nosso dashboard da produção — era inteiramente a Parte 2: sinal e contrato de backend. Por isso a Parte 2 virou o corpo do plano; a Parte 1 (entregue) foi comprimida para o fim, como registro.


PARTE 2 — Backend e sinal (FOCO DE TRABALHO)

2.0 A prova visual: Mini Dólar, nós vs. produção (medição de 2026-07-12)

Comparação lado a lado do mesmo ativo, mesma sessão — este era o estado antes do Pacote A:

LeituraNosso (local)ProduçãoDiagnóstico em 12/07
Chart de movimento/NWE✅ igualconvergido (migração cumpriu o objetivo)
Sinal direcionalBAIXABAIXAconvergido
P(↑) dinâmico1,0% — linha reta32,4% — oscilandoV1 estático vs. V2/Kalman
Convicção59%21%❌ consequência do mesmo V1/V2
PAR ATIVO / DIV Zausenteisharescurrencybond+ β −1.061❌ só existe no V2
Markers P VENDA no chartausentespresentes❌ engine não emitia eventos discretos
GEX / MIDsó WINWIN e DOL❌ nosso worker só cobria IBOV

O chart não era mais o problema. O modelo servido à UI era. Essa foi a tese central deste plano — e é o que o Pacote A atacou.

2.0.1 Depois do Pacote A + parte do B verificado no código em 13/07

Reverificação estática do código-fonte, não uma nova medição ao vivo lado a lado com a produção — os valores específicos (1,0% vs 32,4%, etc.) não foram remedidos hoje.

LeituraSituação em 13/07
Chart de movimento/NWE✅ seguia convergido, sem mudanças
Sinal direcional✅ seguia convergido, sem mudanças
P(↑) dinâmico✅ mecanismo corrigido — App.jsx agora pede version=v2 igual à produção (e5f513f); volta a oscilar bar-a-bar em vez de travado
Convicção✅ mesma correção, consequência direta
PAR ATIVO / DIV Z✅ passa a existir, porque o V2 roda por padrão agora
Markers P VENDA no chart✅ engine emite eventos discretos (e235c03; endurecido por 0236f19 e 97f2cb7)
GEX / MID✅ WIN e WDO (39e6822)

2.1 O bug-raiz RESOLVIDO 13/07

Diagnóstico original (12/07): frontend/src/App.jsx:648 pedia version=both, que o engine resolvia como V1 estático. O scripts/firebase_sync.py (linhas 48/66) já pedia version=v2.

Localhost mostrava V1. Produção mostrava V2. Ambos rotulados "DINÂMICO (KALMAN)".

Consequências em cascata (tabela 2.0): P(↑) travado, convicção deslocada, PAR ATIVO/DIV Z ausentes, painel de pesos Kalman exibindo pesos estáticos.

Estado em 13/07: corrigido junto com a Fase 3, como o plano exigia (não podiam ser separadas — ver 2.3). App.jsx agora pede version=v2 explicitamente nos dois ramos de fetchSeries (commit e5f513f), com um comentário no próprio código documentando a troca.

2.2 Estado verificado das fases (evidência reconferida em 13/07)

FaseTemaStatusEvidência
2Contrato version=bothRESOLVIDOApp.jsx:656 pede version=v2 nos dois ramos, igual ao firebase_sync.py — bug-raiz fechado. Commit e5f513f
3Ghost bars / pré-mercadoRESOLVIDOGate de pré-mercado corrigido em engine.py. Commit 28ecf2a; endurecido por ceec25d (review Codex: só a sessão viva persiste o Kalman, e o gap intra-sessão passa a ser tratado como observação ausente, não retorno falso)
4Persistência Kalman monotônicaRESOLVIDOGuard monotônico implementado. Commit 01e0b9b; caso de borda do replay histórico fechado por ceec25d
1Schema divergence_configPARCIALSem mudança — init_db() (db.py:112) segue rodando só executescript(SCHEMA); migrate_divergence_config() só roda se db.py for executado como script (__main__). Confirmado ainda assim hoje
5NWE causal (backend)PENDENTESem mudança — get_center ainda soma sobre range(n) inteiro (main.py:288-295); lookahead de 1 barra confirmado ainda presente hoje
6API não-bloqueantePENDENTESem mudança — nenhum single-flight/threadpool encontrado em main.py
7Contrato Firebase completoPENDENTESem mudança — firebase_sync.py segue sem history_closes nem is_b3
8.1Acurácia no detalhe (B1)RESOLVIDOCommit e5f513f; fix adicional em 82e3727 (o card de overview rotulava P(up) como "v1" com valor hardcoded)
8.2Corridas de requestRESOLVIDO (bônus)Não estava no pacote de trabalho ativo — resolvido de graça dentro do commit 39e6822 (GEX do dólar): guard de reqId/sequence-id em fetchSeries descarta respostas fora de ordem
10Modularização App.jsxPARCIALSem mudança de fundo — 5 charts extraídos, mas App.jsx tem 1.254 linhas hoje (era ~1.200 em 12/07)

2.3 Ordem de execução (a sequência importava — histórico de como foi feito)

Pacote A — destravar o V2 (o coração do plano) 3/3 concluído

Pacote B — o que sobra da divergência 2/3 concluído

Pacote C — higiene quantitativa (sem impacto visual imediato) 0/4 — nada iniciado

2.4 Regra transversal (workflow do projeto)

Toda mudança de sinal/contrato (Fases 2, 3, 4, 5; B1; B2) entra com regressão permanente antes da correção. tests/ ainda cobre sobretudo z-score/pair e, desde o Pacote A, os cenários de gate de pré-mercado e persistência do Kalman. Mínimo que segue faltando: test_api_contract, test_nwe_causality, test_db_schema — para as Fases 5, 1, 6 e 7 do Pacote C.

2.5 Riscos (atualizado — a maioria dos riscos do Pacote A já foi consumida)


PARTE 1 — Entregue (registro, sem trabalho pendente)

Migração de charts (Recharts → lightweight-charts v5) — ✅ 5/5

Reconstrução por engenharia reversa do bundle de produção (fonte perdida), estratégia strangler-fig, um chart por vez com build/lint/deploy//codex-r:

ChartComponenteCommit
Pair-spread z-score (piloto)TVPairwiseZScoreChartb47e3b1
Probabilidade P(↑) dinâmicaTVProbabilityChart87d7fd9
Divergência-preço z-scoreTVPriceDivergeZScoreChart1f43499
Pesos dinâmicos (Kalman)TVKalmanWeightsChart552f64f
Movimento do índice (NWE)TVNweChart9cc6066, 2be2ecf

Resultado: bundle 752 kB → 419 kB (gzip 227 → 131), 591 → 30 módulos; recharts removido do package.json (eae208e).

Decisões preservadas: fidelidade > melhoria; sem abstração prematura; chart de movimento em preço absoluto; ghost bars como whitespace no centro NWE; eixo BRT âmbar secundário omitido (uma timeScale por chart — ver C4 na Parte 3).

GEX — gamma walls IBOV → WIN$N (task #8) — ✅ entregue 2026-07-12

Foi além do escopo original ("investigar fonte de dados"): virou serviço próprio, de ponta a ponta, em ~40 s/dia.

Outras entregas desde a v1 deste plano


PARTE 3 — Backlog secundário (polimento de frontend)

O plano previa que a Parte 3 só entrasse depois do Pacote A da Parte 2 — condição agora satisfeita — mas a Trilha C ainda depende da Fase 5 (NWE causal, Pacote C, ainda pendente) para não polir em cima de um sinal errado. segue bloqueada