From 3a2650f0e1cdbb26232288b3c66d21eb05d32c95 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 01:50:37 +0000 Subject: [PATCH] =?UTF-8?q?refactor+docs:=20Onda=202=20=E2=80=94=20higiene?= =?UTF-8?q?=20estrutural=20(R2/R5/market=5Fdata)=20+=20refresh=20de=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - R5 (ADR-006): fórmula central do Kelly extraída para src/risk/position_sizing.full_kelly_fraction (fonte única), consumida pelo endpoint GET /v1/risk/kelly (antes reimplementava inline) e por KellyCriterion. src/risk/ deixa de ser código morto. Contrato da API inalterado; plugar no sizing real fica como cauda do R5. - R2: colisões de nome de classe renomeadas por propósito — protocols.SquadOrchestrator->A2ASquad, adaptive_replanner.AdaptivePlanner-> AdaptiveReplanner, continuous_evaluator.ContinuousEvaluator-> AgentPerformanceEvaluator, intelligent_forgetting.MemoryStore-> RelevanceMemoryStore. - market_data unificado: engine._build_market_data computa TechnicalIndicators real + regime (guarda de warmup) — Grid/MeanReversion exercitam o mesmo caminho do live no backtest, em vez de placeholders. - Removida dependência não usada `ta` (indicadores usam numpy/pandas direto). Frontend (docs/design/pages): - R8 (fatia): utilitário .kpi-row (auto-fit/reflow) nas linhas de KPI das telas + sidebar responsiva < 960px (lacuna do briefing). Docs (refresh completo): - ADR-006; CHANGELOG.md, CONTRIBUTING.md, SECURITY.md criados; mapeamento-dados §8/§9/§3.4, arquitetura §12 (R2/R5), uml e plano-melhorias sincronizados; nova seção §7.7 na auditoria de junho. Verificação: 408 passed / 8 skipped; ruff limpo; console build OK; deploy-config OK. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01QehSjYY6mpa1yBhqZxsY6X --- CHANGELOG.md | 54 ++++++++++++++++++++ CONTRIBUTING.md | 59 ++++++++++++++++++++++ SECURITY.md | 48 ++++++++++++++++++ docs/adr/006-kelly-sizing-policy.md | 57 +++++++++++++++++++++ docs/adr/README.md | 3 +- docs/architecture/arquitetura.md | 10 ++-- docs/auditoria-criptotrade-2026-06.md | 34 +++++++++++++ docs/data/mapeamento-dados.md | 57 ++++++++++++--------- docs/design/pages/screen_agents.jsx | 4 +- docs/design/pages/screen_backtest.jsx | 2 +- docs/design/pages/screen_hitl.jsx | 2 +- docs/design/pages/screen_journal.jsx | 2 +- docs/design/pages/screen_observability.jsx | 2 +- docs/design/pages/screen_orders.jsx | 2 +- docs/design/pages/screen_overview.jsx | 4 +- docs/design/pages/screen_risk.jsx | 2 +- docs/design/pages/styles.css | 40 +++++++++++++++ docs/plano-melhorias.md | 52 ++++++++++--------- docs/uml/arquitetura-uml.md | 20 ++++---- requirements.txt | 5 +- src/analysis/indicators.py | 2 +- src/api/routes/risk.py | 11 ++-- src/backtest/engine.py | 41 ++++++++++++--- src/evaluation/continuous_evaluator.py | 8 ++- src/memory/intelligent_forgetting.py | 10 +++- src/orchestration/unified_orchestrator.py | 4 +- src/planning/adaptive_replanner.py | 8 ++- src/protocols/squad_orchestrator.py | 9 +++- src/risk/position_sizing.py | 25 ++++++--- 29 files changed, 470 insertions(+), 107 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 docs/adr/006-kelly-sizing-policy.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4b680f3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog + +Todas as mudanças notáveis deste projeto são documentadas aqui. +Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); +o projeto segue versionamento pragmático (paper-trading-first, ver ADR-001). + +> **Nota sobre histórico:** o histórico detalhado por sprint vive nos +> `docs/roadmap_v1..v6.md` e nas auditorias datadas (`docs/auditoria-*.md`) — +> esses arquivos são **registros históricos** e preservam os números da época +> (ex.: contagens de testes por sprint). A contagem de testes **canônica atual** +> está no README e no TESTING.md. + +## [Não lançado] + +### Onda 2 — Higiene estrutural (backend + frontend + docs) +- **R5 (Kelly):** fórmula central do Kelly extraída para + `src/risk/position_sizing.full_kelly_fraction`, agora **fonte única** consumida + pelo endpoint `GET /v1/risk/kelly` (que antes reimplementava inline) e por + `KellyCriterion`. `src/risk/` deixa de ser código morto. Contrato da API + inalterado. *Plugar Kelly no sizing real (`SquadOrchestrator._position_quantity`) + permanece como cauda do R5 — ver ADR-006.* +- **R2 (colisões de nome):** classes duplicadas renomeadas por propósito — + `protocols.SquadOrchestrator`→`A2ASquad`, `adaptive_replanner.AdaptivePlanner`→ + `AdaptiveReplanner`, `continuous_evaluator.ContinuousEvaluator`→ + `AgentPerformanceEvaluator`, `intelligent_forgetting.MemoryStore`→ + `RelevanceMemoryStore`. +- **market_data unificado:** o backtest (`engine._build_market_data`) agora computa + um `TechnicalIndicators` real + regime (guarda de warmup), em vez de placeholders + — estratégias dirigidas por indicadores (Grid, MeanReversion) exercitam o mesmo + caminho do live. +- **Frontend (R8, fatia):** utilitário `.kpi-row` (auto-reflow via `auto-fit`) + aplicado às linhas de KPI das telas; **sidebar responsiva** < 960px (lacuna do + briefing). Rollout completo de responsividade/a11y permanece aberto. +- **Docs:** ADR-006 (política de sizing Kelly); `mapeamento-dados.md` §8/§9 e + `arquitetura.md` §12 sincronizados; `CHANGELOG.md`/`CONTRIBUTING.md`/`SECURITY.md` + adicionados; `plano-melhorias.md` atualizado. + +### Onda 1 — Correções seguras (mergeada, PR #68) +- **Backend:** `ab_tests.jsonl` agora é JSON válido (`json.dumps`); + `mean_reversion` roteável no regime `sideways`; remoção de dados mortos + (`RiskAgent.max_daily_loss_pct`, `UnifiedOrchestrator.sandbox`/`chain_manager`, + `AgentMemorySystem.short_term`); log em vez de `except: pass` silencioso. +- **Frontend:** erros de mutação → toast (HITL/journal/settings/backtest); + confirmação em 2 etapas ao aprovar/rejeitar ordem; fim da colisão + `STATUS_VARIANT`/`STATUS_LABEL`; limpeza de arquivos/CSS mortos. +- **Docs:** contagem de testes reconciliada (**416 coletados / 408 passam / + 8 pulados** sem `DATABASE_URL`); ADR-004 documentado + índice corrigido; + `docs/documentação/` → `docs/data/`; `docs/plano-melhorias.md` criado como fonte + única do backlog. + +## Histórico anterior (v1–v6) +Ver `docs/roadmap_v1.md` … `docs/roadmap_v6.md` (correção & verdade → produção +Docker → observabilidade → métricas de domínio → Grafana → backend PostgreSQL +opcional) e `docs/auditoria-criptotrade-2026-06.md` (remediação P0–P3 + Fase 5b). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..46cda5a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contribuindo com o Criptotrade + +Obrigado por contribuir! Este guia resume o fluxo de desenvolvimento. Para a +arquitetura, ver `docs/architecture/arquitetura.md` (C4) e +`docs/uml/arquitetura-uml.md` (classes); para o backlog priorizado, ver +`docs/plano-melhorias.md`. + +## Princípios inegociáveis + +Toda mudança **deve preservar** os invariantes centrais: + +1. **Paper-trading-first** — `EXCHANGE_DRY_RUN` é obrigatório; o único caminho + para a exchange roda offline por padrão (ADR-001). Nunca introduza um caminho + de ordem real que rode sem override explícito de ambiente. +2. **HITL fail-closed** — ordens acima do limiar de autonomia exigem aprovação + humana; em produção a API sobe fail-closed sem `API_KEYS`. +3. **Tudo auditável** — decisões e execuções vão para o ledger append-only / event + log XES. Não remova trilha de auditoria. + +## Setup + +```bash +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +pytest -q # suíte completa (ver contagem no README/TESTING.md) +``` + +Console React (opcional): `cd docs/design/pages && npm install && npm run build`. + +## Fluxo + +1. **Branch** a partir de `master`. +2. **Commits** em [Conventional Commits](https://www.conventionalcommits.org/) + (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `build:`, `chore:`). +3. **Testes**: adicione/estenda testes em `tests/`. O gate de cobertura da CI é + **72%** (`pyproject.toml`); não o reduza. +4. **Lint**: `ruff check src` (regras E4/E7/E9/F). +5. **CI verde** antes do merge — jobs `test`, `console-build`, `console-e2e`, + `docker-build`, `validate-phases`, `secret-scan`. +6. **PR** contra `master`. Decisões arquiteturais entram como **ADR** em + `docs/adr/` (use `ADR-Template.md`; próximo número livre indicado no + `docs/adr/README.md`). + +## Convenções + +- **Sem segredos no repo.** `.env` é gitignored; use `.env.example` como + referência. A CI roda `secret-scan` e `scripts/validate_deploy_config.py`. +- **Nomes de classe únicos por propósito** (ver R2 em `arquitetura.md §12`): + evite reintroduzir colisões como dois `SquadOrchestrator`. +- **Reuse antes de criar**: cheque utilitários existentes em `src/core`, + `src/utils` e (no front) `components.jsx` antes de escrever novos. +- **Documentação viva vs. histórica**: atualize os docs de estado atual + (`README`, `TESTING.md`, `mapeamento-dados.md`, `arquitetura.md`, + `plano-melhorias.md`, `CHANGELOG.md`). **Não** reescreva registros datados + (roadmaps por sprint, auditorias) — eles são snapshots históricos. + +## Reportando vulnerabilidades + +Ver `SECURITY.md`. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a4004a6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,48 @@ +# Política de Segurança + +O Criptotrade é um sistema financeiro (trading de cripto). Levamos segurança a +sério — mesmo em modo paper-trading, uma configuração insegura pode expor a API, +segredos ou, no futuro com trading real, capital. + +## Versões suportadas + +O projeto está em estágio **paper-trading / alpha**. Apenas o `master` recebe +correções de segurança. + +## Reportando uma vulnerabilidade + +**Não** abra uma issue pública para vulnerabilidades. Em vez disso: + +- Envie um relatório privado por **[GitHub Security Advisories]** + (`Security` → `Report a vulnerability` no repositório), ou +- contate o mantenedor do repositório diretamente. + +Inclua: descrição, passos de reprodução, impacto potencial e, se possível, uma +sugestão de correção. Faremos o possível para responder rapidamente e coordenar +uma divulgação responsável após a correção. + +[GitHub Security Advisories]: https://docs.github.com/pt/code-security/security-advisories + +## Superfície e controles atuais + +Controles já implementados (ver `docs/architecture/arquitetura.md §11` e a +auditoria `docs/auditoria-criptotrade-2026-06.md`): + +- **Autenticação** por `X-API-Key` (`API_KEYS`), timing-safe + (`secrets.compare_digest`), **fail-closed em produção** (a API recusa subir sem + `API_KEYS` quando `APP_ENV=production`). +- **CORS** travável por `CORS_ORIGINS` (sem `*` em produção — validado na CI por + `scripts/validate_deploy_config.py`). +- **Rate limiting** (`RateLimitMiddleware`, Redis opcional). +- **Headers de segurança** (CSP, HSTS, X-Frame-Options, etc.) via middleware + nginx. +- **Confirmação explícita** (`confirm: true`) para mutações de alto impacto. +- **Sem segredos no repo**: `.env` gitignored; CI roda `secret-scan`. +- **Paper-trading-first**: `EXCHANGE_DRY_RUN` obrigatório; zero rede de corretora + por padrão. + +## Boas práticas para operadores + +- Gere um `API_KEYS` forte (`openssl rand -hex 32`) e nunca o commite. +- Só vire `EXCHANGE_DRY_RUN=false` como decisão deliberada, após validar a stack + (ver `docs/acaoPendenteDono.md`). +- Mantenha `CORS_ORIGINS` restrito à origem real do console. diff --git a/docs/adr/006-kelly-sizing-policy.md b/docs/adr/006-kelly-sizing-policy.md new file mode 100644 index 0000000..461b5d3 --- /dev/null +++ b/docs/adr/006-kelly-sizing-policy.md @@ -0,0 +1,57 @@ +# ADR-006: Política de sizing por Kelly (fonte única + cauda de integração) + +## Status +🟡 Aceita parcialmente (2026-07-15) — fonte única entregue; sizing ao vivo diferido. + +## Contexto +O módulo `src/risk/` (`KellyCriterion`, `PositionSizer`, `CapitalProtections`) +existia **completo e testado, mas morto**: nenhum código de aplicação o +importava. Pior, o endpoint `GET /v1/risk/kelly` **reimplementava o Kelly inline** +(`src/api/routes/risk.py`), com constantes e fórmulas divergentes da biblioteca — +dando a falsa impressão de que o sistema dimensionava posições por Kelly quando, na +verdade, o pipeline de trading (`SquadOrchestrator._position_quantity`) usa +`position_size_pct` fixo. Isto está registrado como **R5** em +`docs/architecture/arquitetura.md §12` e no §8 de `docs/data/mapeamento-dados.md`. + +Havia três caminhos possíveis: (a) **deletar** o módulo morto; (b) **unificar** +tornando a biblioteca a fonte única e o endpoint um consumidor; (c) **plugar** o +Kelly no sizing real das ordens. + +## Decisão +Adotar (b) agora, de forma **preservadora de contrato**, e diferir (c): + +1. A fórmula central do Kelly foi extraída para + `src/risk/position_sizing.full_kelly_fraction(win_rate, avg_win_pct, + avg_loss_pct)` — **fonte única de verdade**. É algebricamente idêntica à forma + anterior (`(p·b − q)/b ≡ p − (1−p)/b`), então **nenhum número da API muda**. +2. `GET /v1/risk/kelly` passa a **delegar** a essa função (removendo a + reimplementação inline). `KellyCriterion.full_kelly()` também a consome. Assim + `src/risk/` deixa de ser código morto. +3. A apresentação do endpoint (Kelly fracionado × 0.25, risco de ruína, limiar de + 10 trades para `data_quality:"insufficient"`) é **mantida como está** — é um + endpoint **consultivo** (display), não de execução. + +## Diferido (cauda do R5) +- **Plugar `PositionSizer`/`KellyCriterion` no sizing real** de ordens + (`SquadOrchestrator._position_quantity`) muda **o que é negociado** — é uma + mudança de comportamento que exige validação dedicada (backtest comparativo, + revisão de risco) e **não** entra junto de higiene de código. +- **Unificar a apresentação fracionada/ruína** entre endpoint e biblioteca (hoje o + endpoint usa fração crua não-clampada; a lib usa percentual clampado [0.5, 5.0] e + a fórmula clássica de ruína). Decisão de produto sobre qual semântica é canônica. +- **`CapitalProtections`** (limites diário/semanal/mensal com `size_multiplier`) + permanece disponível, ainda não alimentado por P&L do período. + +## Consequências +- **Positivas:** remove a duplicação e a falsa impressão; `src/risk/` passa a ser + exercitado pela API; caminho claro para o sizing por Kelly quando houver + validação. Zero mudança observável de contrato/comportamento. +- **Negativas:** o sistema **ainda não dimensiona por Kelly** — o valor pleno do + R5 (sizing dinâmico por edge) fica para a etapa (c). O módulo continua + parcialmente subutilizado (`PositionSizer`/`CapitalProtections`). + +## Referências +- `docs/architecture/arquitetura.md §12` (R5), `docs/data/mapeamento-dados.md §8`. +- `src/risk/position_sizing.py`, `src/api/routes/risk.py`, + `src/orchestration/squad_orchestrator.py` (`_position_quantity`). +- `docs/plano-melhorias.md` (R5 — backlog estrutural). diff --git a/docs/adr/README.md b/docs/adr/README.md index a5ae47b..86e93b9 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -12,6 +12,7 @@ o boilerplate de metodologia que não descrevia o sistema. | [003](003-persistence-sqlite-wal.md) | Persistência — JSONL (ledger/XES) + SQLite WAL (estado cross-process) | Aceito | | [004](004-reserved.md) | *Número reservado — não utilizado* (a sequência salta de 003 para 005) | Vago | | [005](005-scaling-path.md) | Caminho de Escalabilidade — single-host → horizontal (Redis/Postgres opt-in) | Aceita | +| [006](006-kelly-sizing-policy.md) | Política de sizing por Kelly — fonte única (`src/risk`) + endpoint delega; sizing ao vivo diferido | Aceita parcial | Novos ADRs: copie [`ADR-Template.md`](ADR-Template.md), use o próximo número -sequencial livre (**006** em diante — ver ADR-004) e adicione a linha na tabela acima. +sequencial livre (**007** em diante) e adicione a linha na tabela acima. diff --git a/docs/architecture/arquitetura.md b/docs/architecture/arquitetura.md index ba16233..2607506 100644 --- a/docs/architecture/arquitetura.md +++ b/docs/architecture/arquitetura.md @@ -598,11 +598,11 @@ graph TB | # | Severidade | Achado | Recomendação | |---|---|---|---| | R1 | 🔴 Alta | **Cluster "BuildToValue" paralelo** (`UnifiedOrchestrator` + planning/routing/consensus/chains/parallel + agentes de engenharia) **não exercitado pelo trading** | Extrair para pacote opcional `src/experimental/` ou remover; medir cobertura real. Reduz superfície e confusão. | -| R2 | 🔴 Alta | **Colisões de nome** (`SquadOrchestrator`×2, `AdaptivePlanner`×2, `ContinuousEvaluator`×2, `MemoryStore`×2, `Guardrail`×2) | Renomear por propósito (ex.: `TradingSquad` vs `A2ASquad`). Evita imports errados. | +| R2 | 🟡 Parcial | **Colisões de nome** (`SquadOrchestrator`×2, `AdaptivePlanner`×2, `ContinuousEvaluator`×2, `MemoryStore`×2, `Guardrail`×2) | 🟢 **Feito (Onda 2):** duplicatas renomeadas por propósito — `A2ASquad`, `AdaptiveReplanner`, `AgentPerformanceEvaluator`, `RelevanceMemoryStore`. **Resta** `Guardrail`×2 (ver R2b/R3). | | R2b | 🟠 Média | **Duas fundações de agente** (`BaseAgent` async vs `SafeAgentBase` sync) sem ponte | Escolher uma base única ou documentar explicitamente os dois papéis. | | R3 | 🟠 Média | **Duas políticas de validação de ordem** e **dois modelos de autonomia** (US$ threshold vs trust-score) | Eleger fonte única de política de risco/autonomia. | | R4 | 🟠 Média | **HITL por polling de SQLite** acopla loop↔API ao arquivo; loop single-instance | Migrar coordenação para **Postgres `LISTEN/NOTIFY`** ou Redis pub/sub → menor latência + caminho para multi-loop. | -| R5 | 🟡 Baixa | **Kelly/proteções prontos mas não plugados** no sizing do pipeline | Ligar `PositionSizer`/`KellyCriterion`/`CapitalProtections` no `SquadOrchestrator._position_quantity`. | +| R5 | 🟡 Parcial | **Kelly/proteções prontos mas não plugados** no sizing do pipeline | 🟢 **Feito (Onda 2, ADR-006):** fórmula central do Kelly virou fonte única (`src/risk.full_kelly_fraction`), consumida pelo endpoint `/v1/risk/kelly` — `src/risk/` não é mais morto. **Resta** plugar no `SquadOrchestrator._position_quantity` (muda o trading — validação dedicada). | | R6 | 🟡 Baixa | **Namespace `/v1/agents/...` servido por dois routers** (`agents` e `config`) | Consolidar num router único. | | R7 | 🟡 Baixa | **XES/alerts ainda em JSONL** (ADR-003 deferido) | Concluir migração para SQLite/Postgres quando o volume justificar. | | R8 | 🟡 Baixa | **Frontend "classic scripts" com globals `window.*`** | Migrar para ES modules + bundler quando o console crescer. | @@ -653,10 +653,10 @@ graph TB ``` **Roteiro incremental (sem big-bang):** -1. **Higiene** (R1, R2): isolar/remover o cluster genérico e renomear colisões — baixo risco, alto ganho de clareza. -2. **Consolidação de política** (R3, R5, R6): uma fonte de risco/autonomia; plugar Kelly; unificar rotas de agentes. +1. **Higiene** (R1, R2): 🟡 **R2 renomeações feitas (Onda 2)**; R1 (isolar cluster genérico) ainda aberto. +2. **Consolidação de política** (R3, R5, R6): 🟡 **R5 fonte única do Kelly feita (Onda 2, ADR-006)**; resta plugar no sizing, unificar política de risco/autonomia (R3) e rotas de agentes (R6). 3. **Coordenação** (R4, R7): trocar polling SQLite por `LISTEN/NOTIFY` no Postgres (já suportado pela abstração de DB) e unificar o event store — habilita multi-loop com leader-election. -4. **Frontend** (R8): migrar para ES modules/bundler. +4. **Frontend** (R8): 🟡 **fatia feita (Onda 2)** — `.kpi-row` responsivo + sidebar colapsável < 960px; migração para ES modules/bundler ainda aberta. Cada passo é independente e preserva o invariante central: **paper-trading-first, HITL fail-closed, tudo auditável no ledger/XES**. diff --git a/docs/auditoria-criptotrade-2026-06.md b/docs/auditoria-criptotrade-2026-06.md index 045db76..fe30e94 100644 --- a/docs/auditoria-criptotrade-2026-06.md +++ b/docs/auditoria-criptotrade-2026-06.md @@ -509,6 +509,40 @@ Revisão do código após Fase 5b. **4 achados corrigidos em `remediacao/audit-f | A-03 | `scripts/migrate_ledger.py:43` | `json.loads()` sem `try/except` abortava migração em linha corrompida, deixando histórico parcialmente importado sem aviso | Baixo | Adicionado `try/except JSONDecodeError` com skip+warn por linha | | A-04 | `README.md` | Contagem de testes desatualizada (273 → 324, duas ocorrências) | Doc | Atualizado | +### 7.7 Análise & Melhorias — Ondas 1–2 (2026-07-15) + +Análise profunda partindo da documentação de mapeamento (`docs/data/mapeamento-dados.md`), +que catalogou anomalias §9 e dados mortos §8 **sem corrigir**. As 9 anomalias foram +verificadas (todas verdadeiras) e uma primeira leva foi corrigida. Fonte única do +backlog: `docs/plano-melhorias.md`. + +**Onda 1** (PR #68, baixo risco): + +| # | Arquivo | Correção | Sev. | +|---|---------|----------|------| +| O1-1 | `evaluation/ab_testing.py` | `ab_tests.jsonl` gravado como JSON válido (`json.dumps`, não `repr`) | Baixo | +| O1-2 | `analysis/regime_detector.py` | `mean_reversion` roteável no regime `sideways` (era registrada mas nunca emitida) + teste de consistência | Médio | +| O1-3 | `agents/risk_agent.py`, `orchestration/unified_orchestrator.py`, `memory/agent_memory.py` | Remoção de dados mortos (§8): `max_daily_loss_pct`, `sandbox`/`chain_manager`, `short_term`; log em vez de `except: pass` | Baixo | +| O1-4 | `docs/design/pages/*.jsx` | Erros de mutação → toast; confirmação em aprovar/rejeitar; fim da colisão `STATUS_*`; limpeza morta | Médio | +| O1-5 | README/TESTING + ADR-004 + `docs/documentação`→`docs/data` | Contagem de testes reconciliada (**416/408/8**); índice ADR corrigido | Doc | + +**Onda 2** (higiene estrutural): + +| # | Arquivo | Correção | Sev. | +|---|---------|----------|------| +| O2-1 | `risk/position_sizing.py`, `api/routes/risk.py` | **R5**: fórmula central do Kelly vira fonte única (`full_kelly_fraction`); endpoint delega; `src/risk/` deixa de ser morto (contrato inalterado — ADR-006) | Médio | +| O2-2 | `protocols/`, `planning/`, `evaluation/`, `memory/` | **R2**: colisões de nome renomeadas (`A2ASquad`, `AdaptiveReplanner`, `AgentPerformanceEvaluator`, `RelevanceMemoryStore`) | Baixo | +| O2-3 | `backtest/engine.py` | `market_data` unificado: backtest computa `TechnicalIndicators` real + regime (guarda de warmup) — Grid/MeanReversion não ficam mais inertes | Médio | +| O2-4 | `docs/design/pages/styles.css` + `screen_*.jsx` | **R8 (fatia)**: `.kpi-row` auto-reflow + sidebar responsiva < 960px | Baixo | +| O2-5 | `CHANGELOG.md`, `CONTRIBUTING.md`, `SECURITY.md`, ADR-006, sync de `mapeamento`/`arquitetura`/`uml` | Docs de projeto ausentes criadas; docs vivas sincronizadas | Doc | + +**Verificação:** `408 passed / 8 skipped` (cobertura ≥ gate 72%); console `npm run build` OK; +`scripts/validate_deploy_config.py` OK. Invariante central preservado. + +**Aberto (backlog em `plano-melhorias.md`):** R1 (isolar cluster BuildToValue), +cauda do R5 (plugar Kelly no sizing real), R3 (unificar política de risco/autonomia), +R6 (rotas de agentes), R8 completo (ES modules + a11y), CandleOut `lo`→`l`. + --- ## 8. Anexos diff --git a/docs/data/mapeamento-dados.md b/docs/data/mapeamento-dados.md index 00adcc2..8c2aee3 100644 --- a/docs/data/mapeamento-dados.md +++ b/docs/data/mapeamento-dados.md @@ -170,9 +170,9 @@ Estes são os **dicts que fluem entre módulos** — o dado "vivo" do sistema. C ### 3.3 `signal` (StrategyAgent._generate_signal → strategy_agent.py:218) — **o dado central do sistema** `action`(BUY\|SELL\|HOLD)`, entry_price, stop_loss, take_profit, position_size_pct`(default 2.0)`, strategy, regime, market_context:{atr,bb_middle,volume_ratio}|None`. `symbol` é adicionado depois pelo orquestrador via `setdefault` (`:214`); `reason` só existe nos caminhos HOLD. -### 3.4 `market_data` — **DOIS formatos distintos** -- **Nested** (Grid/MeanReversion) — `_build_market_data` (`strategy_agent.py:383`): `symbol, current_price, trend, regime, rsi, macd_histogram, at_bollinger_lower, ma_20, ma_50, volume_24h, avg_volume, indicators:TechnicalIndicators, support_resistance, volume_profile, _raw_ohlcv` -- **Flat** (DCA e backtest) — `engine.py:234`: `current_price, rsi(=50), macd_histogram(=0), at_bollinger_lower(=False), ma_20, ma_50, volume_24h, avg_volume(=1), _raw_ohlcv` (sem `indicators`) +### 3.4 `market_data` — formato unificado (ver nota) +- **Nested** (StrategyAgent e backtest) — `_build_market_data` (`strategy_agent.py:383`): `symbol, current_price, trend, regime, rsi, macd_histogram, at_bollinger_lower, ma_20, ma_50, volume_24h, avg_volume, indicators:TechnicalIndicators, support_resistance, volume_profile, _raw_ohlcv` +- **Backtest** — `engine._build_market_data`: 🟢 **desde Onda 2** computa `indicators:TechnicalIndicators` real + `regime` (guarda de warmup `WARMUP_CANDLES=50`), mantendo os campos flat (`rsi/ma_20/ma_50/...`) que DCA lê. Fallback a placeholders só se os indicadores não puderem ser computados (janela curta / numpy ausente em CI mínima). Antes emitia flat com `rsi=50`/`indicators` ausente — o que deixava Grid/MeanReversion inertes no backtest. ### 3.5 Resultados dos agentes - **strategy_result** (`strategy_agent.py:89`): `success, agent, signal, confidence, analysis, llm_used, llm_thesis, stub_used` @@ -264,7 +264,7 @@ Estes são os **dicts que fluem entre módulos** — o dado "vivo" do sistema. C - **BacktestTrade** `{candle_index,action,entry_price,exit_price,position_size_pct,pnl_usdt,pnl_pct,stop_loss,take_profit,exit_reason}`. - **BacktestResult** `{total_trades,win_rate,total_pnl_usdt,total_pnl_pct,max_drawdown_pct,sharpe_ratio,profit_factor,avg_win_pct,avg_loss_pct,trades:[BacktestTrade]}` + prop `expectancy`. Constantes commission 0.001, slippage 5bps, warmup 50. - **WindowResult**/`WalkForwardResult` (validação walk-forward; MAX_SHARPE_DEVIATION=0.30). **MonteCarloResult** `{n_simulations,median/p5/p95_pnl_pct,max_simulated_drawdown,pct_profitable,rejected}`. -- `_build_market_data` emite o formato **flat** (§3.4) → só DCA o consome plenamente. +- `_build_market_data` (desde Onda 2) computa `indicators` real + `regime` (§3.4) → estratégias dirigidas por indicadores (Grid/MeanReversion) exercitam o mesmo caminho que o live, não só DCA. ### 4.6 `src/risk/` - **KellyCriterion** `{win_rate,avg_win_pct,avg_loss_pct,capital=10000,n_trades}` → `full_kelly()|None` (min 30 trades), `fractional_kelly(0.25)` clamp [0.5,5.0], `ruin_risk()`. Constantes KELLY_FRACTION=0.25, MIN/MAX_POSITION_PCT=0.5/5.0, MIN_SAMPLE_FOR_KELLY=30. @@ -413,36 +413,43 @@ Em paralelo: cada ciclo emite XES `agent_cycle_started/completed/failed` → `GE ## 8. Dados "perdidos na classe" / declarados-mas-não-usados -Dados atribuídos e **nunca lidos depois** (marca **X**), conforme pedido: +Dados atribuídos e **nunca lidos depois** (marca **X**), conforme pedido. +> **Estado (atualizado após Ondas 1–2, PR #68 + seguinte):** vários itens foram +> resolvidos — marcados ✅ abaixo. Ver `docs/plano-melhorias.md` e `CHANGELOG.md`. | Dado | Local | Observação | |---|---|---| -| `RiskAgent.max_daily_loss_pct` | `risk_agent.py:22` | lido do env, nunca referenciado na classe | -| `UnifiedOrchestrator.sandbox` | `unified_orchestrator.py:38` | instanciado, nunca usado | -| `UnifiedOrchestrator.chain_manager` | `unified_orchestrator.py:39` | instanciado, nunca usado | -| `AgentMemorySystem.short_term` | `agent_memory.py:21` | dict criado, nunca escrito | -| `SecureToolSandbox.memory_limit_mb` | `secure_executor.py:26` | declarado, não aplicado na execução | -| `SecureToolSandbox.cpu_quota` | `secure_executor.py:27` | declarado, não aplicado | -| `ContinuousEvaluator.baseline` (continuous_eval) | `continuous_eval.py:11` | atribuído None, nunca usado | -| metrics `user_satisfaction`,`error_rate`,`response_time_p95` | `continuous_evaluator.py:12` | chaves declaradas, nunca alimentadas | -| `signal["market_context"]` | `strategy_agent.py:232` | construído mas o pipeline nunca lê (RiskAgent passa o signal inteiro) | -| `analysis["fibonacci_levels"]` | `strategy_agent.py:126` | computado, só carregado/sanitizado, nunca consumido a jusante | -| `strategy_result.{analysis,llm_used,llm_thesis,reasoning}` | `strategy_agent.py:89` | retornados; orquestrador só lê signal/confidence/stub_used | -| `OrchestratorLoop.order_store` | `orchestrator_loop.py:220` | exposto "para inspeção/futuro" | - -Dados **carregados mas subutilizados**: `KellyCriterion`/`PositionSizer`/`CapitalProtections` existem completos mas o pipeline dimensiona por `position_size_pct` simples (o Kelly só aparece em `GET /v1/risk/kelly`). +| ~~`RiskAgent.max_daily_loss_pct`~~ | `risk_agent.py` | ✅ **REMOVIDO (Onda 1)** — a perda-diária vive no `CircuitBreaker`, que tem o P&L; atributo era morto/enganoso | +| ~~`UnifiedOrchestrator.sandbox`~~ | `unified_orchestrator.py` | ✅ **REMOVIDO (Onda 1)** | +| ~~`UnifiedOrchestrator.chain_manager`~~ | `unified_orchestrator.py` | ✅ **REMOVIDO (Onda 1)** | +| ~~`AgentMemorySystem.short_term`~~ | `agent_memory.py` | ✅ **REMOVIDO (Onda 1)** | +| `SecureToolSandbox.memory_limit_mb` | `secure_executor.py:26` | declarado, não aplicado na execução (aberto) | +| `SecureToolSandbox.cpu_quota` | `secure_executor.py:27` | declarado, não aplicado (aberto) | +| `ContinuousEvaluator.baseline` (continuous_eval) | `continuous_eval.py:11` | atribuído None, nunca usado (aberto) | +| metrics `user_satisfaction`,`error_rate`,`response_time_p95` | `continuous_evaluator.py` (`AgentPerformanceEvaluator`) | chaves declaradas, nunca alimentadas (aberto) | +| `signal["market_context"]` | `strategy_agent.py:232` | construído mas o pipeline nunca lê (RiskAgent passa o signal inteiro) (aberto) | +| `analysis["fibonacci_levels"]` | `strategy_agent.py:126` | computado, só carregado/sanitizado, nunca consumido a jusante (aberto) | +| `strategy_result.{analysis,llm_used,llm_thesis,reasoning}` | `strategy_agent.py:89` | retornados; orquestrador só lê signal/confidence/stub_used (aberto) | +| `OrchestratorLoop.order_store` | `orchestrator_loop.py:220` | exposto "para inspeção/futuro" (aberto) | + +Dados **carregados mas subutilizados**: `KellyCriterion`/`PositionSizer`/`CapitalProtections`. +🟡 **Parcial (Onda 2, ADR-006):** a fórmula central do Kelly virou fonte única +(`src/risk/position_sizing.full_kelly_fraction`) e o endpoint `GET /v1/risk/kelly` +agora a consome — `src/risk/` não é mais código morto. **Resta:** o pipeline ainda +dimensiona por `position_size_pct` simples (plugar Kelly/proteções no sizing é a +cauda do R5). --- ## 9. Anomalias e inconsistências de dados -Achados relevantes para refatoramento (não alteram código — apenas documentados): +Achados relevantes para refatoramento. **Estado atualizado após Ondas 1–2** (✅ = resolvido): -1. **Dois formatos de `market_data`** (§3.4): `_build_market_data` do backtest emite o formato **flat** sem `indicators`; se Grid/MeanReversion forem usadas no backtest, `indicators=None` — só DCA consome o flat plenamente (`engine.py:234` vs `strategy_agent.py:383`). -2. **Ponte de nome `entry`→`entry_price`**: estratégias emitem `entry` (`mean_reversion.py:68`), mas guardrails/Order esperam `entry_price` — o `StrategyAgent._generate_signal` faz a normalização; um consumo direto do output da estratégia quebraria a validação. -3. **Mismatch de chave no registry de estratégias**: `STRATEGY_REGISTRY` tem `"mean_reversion"` (`__init__.py:23`) mas `_REGIME_STRATEGY_MAP` só emite `"dca"`/`"grid"` — **mean_reversion nunca é selecionada** por regime. -4. **`ab_tests.jsonl` não é JSON válido**: gravado via `str(payload)` (repr Python), não `json.dumps` (`ab_testing.py:60`). -5. **Colisões de nome de tipo** (mesmos dados, classes distintas): `ContinuousEvaluator`×2, `AdaptivePlanner`×2, `MemoryStore`×2, `SquadOrchestrator`×2, `Guardrail`×2. +1. ✅ **Dois formatos de `market_data`** (§3.4) — **RESOLVIDO (Onda 2)**: `engine._build_market_data` agora computa um `TechnicalIndicators` real + `regime` (guarda de warmup), exercitando o mesmo caminho que o live; Grid/MeanReversion não ficam mais inertes no backtest. +2. **Ponte de nome `entry`→`entry_price`** (aberto): estratégias emitem `entry` (`mean_reversion.py:68`), mas guardrails/Order esperam `entry_price` — o `StrategyAgent._generate_signal` faz a normalização; um consumo direto do output da estratégia quebraria a validação. +3. ✅ **Mismatch de chave no registry de estratégias** — **RESOLVIDO (Onda 1)**: `mean_reversion` agora é emitida no regime `sideways` por `_REGIME_STRATEGY_MAP`; teste de consistência registry↔roteamento adicionado. +4. ✅ **`ab_tests.jsonl` não é JSON válido** — **RESOLVIDO (Onda 1)**: gravado via `json.dumps` (`ab_testing.py`). +5. 🟡 **Colisões de nome de tipo** — **PARCIAL (Onda 2, R2)**: renomeados por propósito — `SquadOrchestrator`(protocols)→`A2ASquad`, `AdaptivePlanner`(replanner)→`AdaptiveReplanner`, `ContinuousEvaluator`(evaluator)→`AgentPerformanceEvaluator`, `MemoryStore`(forgetting)→`RelevanceMemoryStore`. **Resta** `Guardrail`×2 (ligado a R2b/R3 — duas fundações de agente/política). 6. **Duas políticas de validação de ordem** com campos diferentes: `GuardrailSystem` lê `market_context/action/entry_price`; `SecurityConfig.validate_order` lê `notes/exchange/position_size_pct`. 7. **`CandleOut` usa `lo`** (não `l`) para low (`schemas.py:216`) — divergência de convenção vs OHLCV interno `[o,h,l,c]`. 8. **Namespace `/v1/agents/{id}/config`** servido por dois routers (`agents` GET, `config` PATCH). diff --git a/docs/design/pages/screen_agents.jsx b/docs/design/pages/screen_agents.jsx index 0fdd677..c0ebd10 100644 --- a/docs/design/pages/screen_agents.jsx +++ b/docs/design/pages/screen_agents.jsx @@ -190,13 +190,13 @@ function ScreenAgents() { -
+
-
+
{agents.map(agent => ( ))} diff --git a/docs/design/pages/screen_backtest.jsx b/docs/design/pages/screen_backtest.jsx index b507c3e..ba70c36 100644 --- a/docs/design/pages/screen_backtest.jsx +++ b/docs/design/pages/screen_backtest.jsx @@ -266,7 +266,7 @@ function ScreenBacktest({ addToast }) { {tab === 'result' && result && ( <> -
+
diff --git a/docs/design/pages/screen_hitl.jsx b/docs/design/pages/screen_hitl.jsx index ed760bd..51b9fc7 100644 --- a/docs/design/pages/screen_hitl.jsx +++ b/docs/design/pages/screen_hitl.jsx @@ -226,7 +226,7 @@ function ScreenHITL({ addToast }) {
-
+
diff --git a/docs/design/pages/screen_journal.jsx b/docs/design/pages/screen_journal.jsx index 2a62c06..9654f50 100644 --- a/docs/design/pages/screen_journal.jsx +++ b/docs/design/pages/screen_journal.jsx @@ -225,7 +225,7 @@ function ScreenJournal({ addToast }) { {/* Metrics row */} {metrics && ( -
+
diff --git a/docs/design/pages/screen_observability.jsx b/docs/design/pages/screen_observability.jsx index 5cb4282..7fd5fcb 100644 --- a/docs/design/pages/screen_observability.jsx +++ b/docs/design/pages/screen_observability.jsx @@ -73,7 +73,7 @@ function ScreenObservability() { } else { body = ( <> -
+
r.status === 'failed').length} format="int" icon="alert" />
diff --git a/docs/design/pages/screen_orders.jsx b/docs/design/pages/screen_orders.jsx index c48c81b..fdfcf12 100644 --- a/docs/design/pages/screen_orders.jsx +++ b/docs/design/pages/screen_orders.jsx @@ -74,7 +74,7 @@ function ScreenOrders() {
-
+
diff --git a/docs/design/pages/screen_overview.jsx b/docs/design/pages/screen_overview.jsx index 690256d..55abfd3 100644 --- a/docs/design/pages/screen_overview.jsx +++ b/docs/design/pages/screen_overview.jsx @@ -67,13 +67,13 @@ function ScreenOverview() { } else if (metrics) { body = ( <> -
+
-
+
diff --git a/docs/design/pages/screen_risk.jsx b/docs/design/pages/screen_risk.jsx index 3420c82..75dbe3c 100644 --- a/docs/design/pages/screen_risk.jsx +++ b/docs/design/pages/screen_risk.jsx @@ -100,7 +100,7 @@ function ScreenRisk() {
{/* Capital KPIs */} -
+
diff --git a/docs/design/pages/styles.css b/docs/design/pages/styles.css index 79dd1d7..036dca7 100644 --- a/docs/design/pages/styles.css +++ b/docs/design/pages/styles.css @@ -361,3 +361,43 @@ input, select, textarea { font-family: inherit; } .grid-3, .grid-regime4 { grid-template-columns: 1fr; } .grid-kpi5 { grid-template-columns: repeat(2, 1fr); } } + +/* ---- Reusable auto-reflowing KPI/stat-tile row (R8) ---- + Replaces per-screen inline `gridTemplateColumns: repeat(N,1fr)` on KPI rows. + auto-fit + minmax reflows at any width with NO media query: N tiles stay in + one row on desktop and wrap on narrow viewports (mobile/tablet). */ +.kpi-row { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); } + +/* ---- Responsive sidebar (R8) ---- + The briefing promises a collapsible sidebar < ~960px, but `.app` was a fixed + `232px 1fr` that never reflowed. Below 960px the shell stacks: the sidebar + becomes a horizontal, scrollable top bar and the content takes full width. */ +@media (max-width: 960px) { + .app { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + .sidebar { + flex-direction: row; + align-items: center; + height: auto; + overflow-x: auto; + border-right: none; + border-bottom: 1px solid var(--border); + } + .brand { + border-bottom: none; + border-right: 1px solid var(--border); + flex-shrink: 0; + } + .nav { + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + overflow-x: auto; + padding: 8px 10px; + } + .nav-item { white-space: nowrap; flex-shrink: 0; } + .nav-badge { margin-left: 6px; } +} diff --git a/docs/plano-melhorias.md b/docs/plano-melhorias.md index a75fa3a..64c73f3 100644 --- a/docs/plano-melhorias.md +++ b/docs/plano-melhorias.md @@ -11,7 +11,11 @@ > severidade e critério de aceite. Este arquivo é um **tracker vivo** — atualize os > checkboxes por commit, no estilo da auditoria de junho. > -> Última atualização: **2026-07-14**. +> Última atualização: **2026-07-15** (Onda 2). +> +> **Progresso:** Onda 1 ✅ (PR #68) · Onda 2 ✅ higiene estrutural — R2 (renomeações), +> R5 parcial (fonte única do Kelly, ADR-006), market_data unificado, R8 fatia +> (responsividade), docs de projeto (CONTRIBUTING/CHANGELOG/SECURITY). Ver `CHANGELOG.md`. --- @@ -76,36 +80,38 @@ revisáveis. Numeração **R#** espelha `arquitetura.md §12`. - [ ] **R1 🔴 · Cluster "BuildToValue" não exercitado pelo trading** — `UnifiedOrchestrator` + `planning/routing/consensus/chains/parallel` + agentes de engenharia. Isolar em `src/experimental/` ou remover; medir cobertura real. Reduz superfície e confusão. -- [ ] **R2 🔴 · Colisões de nome de classe** — `SquadOrchestrator`×2 - (`orchestration/` vs `protocols/`), `AdaptivePlanner`×2, `ContinuousEvaluator`×2, - `MemoryStore`×2, `Guardrail`. Renomear por propósito (ex.: `TradingSquad` vs - `A2ASquad`). *(mapeamento §9.5)* -- [ ] **R5 🟠 · `src/risk/` é código morto** — `KellyCriterion`/`PositionSizer`/ - `CapitalProtections` não são usados por ninguém; até o `GET /v1/risk/kelly` - (`routes/risk.py`) **reimplementa Kelly inline**. Decidir: **plugar** no - `SquadOrchestrator._position_quantity` (`squad_orchestrator.py:326`) + delegar o - endpoint à classe, **ou deletar** o módulo. Alto valor. *(mapeamento §8, §9)* +- [x] **R2 🔴 · Colisões de nome de classe** — 🟢 **FEITO (Onda 2)**: duplicatas + mortas renomeadas por propósito — `protocols.SquadOrchestrator`→`A2ASquad`, + `adaptive_replanner.AdaptivePlanner`→`AdaptiveReplanner`, + `continuous_evaluator.ContinuousEvaluator`→`AgentPerformanceEvaluator`, + `intelligent_forgetting.MemoryStore`→`RelevanceMemoryStore`. **Resta** `Guardrail`×2 + (ver R3/R2b). *(mapeamento §9.5)* +- [x] **R5 🟠 · `src/risk/` era código morto** — 🟡 **PARCIAL (Onda 2, ADR-006)**: + fórmula central do Kelly extraída para `full_kelly_fraction` (fonte única), consumida + pelo `GET /v1/risk/kelly` (não reimplementa mais inline) e por `KellyCriterion` — + `src/risk/` deixa de ser morto, contrato inalterado. **Resta (cauda R5):** plugar no + `SquadOrchestrator._position_quantity` (muda o trading — exige validação). *(mapeamento §8, §9)* - [ ] **R3 🟠 · Política de risco/autonomia duplicada** — duas validações de ordem (`GuardrailSystem` vs `SecurityConfig`) e dois modelos de autonomia (limiar US$ vs trust-score). Eleger fonte única. *(mapeamento §9.6)* -- [ ] **market_data 🟠 · Dois formatos** — flat (backtest, `engine.py:234`, sem - `indicators`) vs nested (`strategy_agent.py:401`). `MeanReversion` fica **inerte em - backtest** (HOLD permanente). Unificar o builder para o backtest exercitar o mesmo - caminho do live. *(mapeamento §9.1)* +- [x] **market_data 🟠 · Dois formatos** — 🟢 **FEITO (Onda 2)**: `engine._build_market_data` + computa `TechnicalIndicators` real + `regime` (guarda de warmup), mantendo os campos + flat do DCA; Grid/MeanReversion não ficam mais inertes no backtest. *(mapeamento §9.1)* - [ ] **R6 🟡 · `/v1/agents/{id}/config` servido por dois routers** (`agents` GET, `config` PATCH). Consolidar num router. *(mapeamento §9.8)* - [ ] **CandleOut 🟡 · campo `lo` em vez de `l`** — `schemas.py:216` diverge da convenção OHLCV. Renomear com alias de compat. *(mapeamento §9.7)* - [ ] **R7 🟡 · XES/alerts ainda em JSONL** (ADR-003 deferido) — concluir migração para SQLite/Postgres quando o volume justificar. -- [ ] **Limpeza 🟡 · dependência `ta` não usada** — em `requirements.txt` mas sem - nenhum `import ta` no código (e falha ao buildar wheel). Remover. +- [x] **Limpeza 🟡 · dependência `ta` não usada** — 🟢 **FEITO (Onda 2)**: removida do + `requirements.txt` (nenhum `import ta` no código; indicadores usam numpy/pandas + direto). Docstring de `indicators.py` corrigida. ### Frontend (trilha própria — `docs/design/pages/`) -- [ ] **R8 🟠 · Responsividade real** — várias telas fixam `gridTemplateColumns` - inline (que media queries não conseguem sobrescrever); só `screen_market` reflui. - Trocar por classes de grid + breakpoints e implementar a **sidebar colapsável - < 960px** prometida no briefing. +- [x] **R8 🟠 · Responsividade real** — 🟡 **PARCIAL (Onda 2)**: utilitário `.kpi-row` + (auto-fit/reflow) aplicado às linhas de KPI das telas + **sidebar responsiva < 960px** + (a lacuna do briefing). **Resta** os layouts de 2 colunas restantes e a migração ES + modules/bundler. - [ ] **FE 🟠 · Camada de normalização de dados** — mata os objetos `mock*` repetidos por tela e o coalescing defensivo `a ?? b` (mock↔API). Centraliza no `data.js`/mapper. - [ ] **FE 🟡 · a11y das charts** — `Donut/BarChart/ScatterChart/Heatmap/MonteCarloChart/ @@ -118,9 +124,9 @@ revisáveis. Numeração **R#** espelha `arquitetura.md §12`. de `components.jsx` (remover `_fmt` local de `charts.jsx` e `toLocaleString` inline). ### Docs / Infra -- [ ] **DOC 🟡 · Arquivos de projeto ausentes** — `CONTRIBUTING.md`, `CHANGELOG.md` - (hoje disperso em 6 roadmaps), `SECURITY.md` (sistema financeiro sem política de - disclosure), runbook de deploy único (hoje entre README e `acaoPendenteDono`). +- [x] **DOC 🟡 · Arquivos de projeto ausentes** — 🟢 **FEITO (Onda 2)**: `CONTRIBUTING.md`, + `CHANGELOG.md`, `SECURITY.md` criados na raiz. *(runbook de deploy único ainda aberto — + hoje entre README e `acaoPendenteDono`.)* - [ ] **DOC 🟡 · Onboarding específico** — `docs/tutorials/*` são boilerplate genérico "BuildToValue", não ensinam a trabalhar no Criptotrade. - [ ] **DOC 🟡 · Critérios de go-live divergentes** — `discovery-consensus.v6.json` diff --git a/docs/uml/arquitetura-uml.md b/docs/uml/arquitetura-uml.md index 8d03652..2f0c79a 100644 --- a/docs/uml/arquitetura-uml.md +++ b/docs/uml/arquitetura-uml.md @@ -553,10 +553,10 @@ classDiagram **Notas de design:** - **Pipeline central (`SquadOrchestrator.analyze_and_trade`):** `Strategy → (checagem de posições SL/TP) → Risk → Guardrails → HITL → Execution → fill/ledger`, com circuit breaker no início. Tudo `async`; helpers de sizing/PnL são síncronos. Ver sequência em § 6.1. - **Recuperação de restart:** `reload_open_positions()` restaura o *position book* e o estado do breaker do SQLite (evita "posições zumbi"). -- **Dois orquestradores + colisões de nome (atenção para refatoramento):** - - `SquadOrchestrator` existe **duas vezes** (`orchestration/` — trading real; `protocols/` — variante A2A architect+developer). - - `AdaptivePlanner` existe **duas vezes** (`planning/adaptive_planner.py` vs `planning/adaptive_replanner.py`). - - `ContinuousEvaluator` existe **duas vezes** (`evaluation/continuous_eval.py` plain vs `evaluation/continuous_evaluator.py` dataclass). +- **Dois orquestradores + colisões de nome** — 🟢 **colisões resolvidas (Onda 2, R2):** as duplicatas mortas foram renomeadas por propósito: + - `SquadOrchestrator` (trading real, `orchestration/`) permanece; a variante A2A em `protocols/` virou **`A2ASquad`**. + - `AdaptivePlanner` (`planning/adaptive_planner.py`) permanece; o duplicado virou **`AdaptiveReplanner`** (`adaptive_replanner.py`). + - `ContinuousEvaluator` (`continuous_eval.py`, plain) permanece; o dataclass virou **`AgentPerformanceEvaluator`** (`continuous_evaluator.py`). - O `UnifiedOrchestrator` e todo o cluster planning/routing/consensus **não são exercitados pelo trading** — é infraestrutura genérica "BuildToValue" paralela. Alto risco de código morto/confusão. --- @@ -638,9 +638,9 @@ classDiagram ``` **Notas de design:** -- **Strategy pattern completo:** `BaseStrategy` (ABC) ← 3 concretas, selecionadas via `STRATEGY_REGISTRY` (registro plugin, com import condicional). O `StrategyAgent` escolhe a estratégia pelo **regime de mercado detectado** (`regime_detector.strategies_for_regime`), cujas chaves espelham as do registry. +- **Strategy pattern completo:** `BaseStrategy` (ABC) ← 3 concretas, selecionadas via `STRATEGY_REGISTRY` (registro plugin, com import condicional). O `StrategyAgent` escolhe a estratégia pelo **regime de mercado detectado** (`regime_detector.strategies_for_regime`). 🟢 **Desde Onda 1** as chaves realmente espelham o registry — `mean_reversion` (antes registrada mas nunca emitida) agora é roteada no regime `sideways`. - **DTO central:** `TechnicalIndicators` (`@dataclass`, ~20 campos) é produzido por `TechnicalAnalyzer` e consumido polimorficamente pelas estratégias via `market_data["indicators"]`. Bom desacoplamento por dados. -- **Consumo duck-typed:** `BacktestEngine` (§ 5.4) também chama `strategy.analyze(...)`, tratando qualquer `BaseStrategy` como *context* — reuso limpo entre trading e backtest. +- **Consumo duck-typed:** `BacktestEngine` (§ 5.4) também chama `strategy.analyze(...)`, tratando qualquer `BaseStrategy` como *context* — reuso limpo entre trading e backtest. 🟢 **Desde Onda 2** o backtest constrói o mesmo `TechnicalIndicators` real (não placeholders), então estratégias dirigidas por indicadores exercitam o mesmo caminho. - **`regime_detector` é módulo de funções** (sem classe) — ponte funcional entre análise e o factory de estratégias. --- @@ -695,7 +695,7 @@ classDiagram **Notas de design:** - **Composição em árvore de resultados:** `WalkForwardResult ◆ WindowResult ◆ BacktestResult ◆ BacktestTrade` — DTOs imutáveis, fáceis de serializar para a API (`/v1/backtest/*`). - **Validação anti-overfitting:** o `WalkForwardValidator` (`MAX_SHARPE_DEVIATION=0.30`) e o `MonteCarloSimulator` (percentis 5/95, `rejected`) formam um filtro de robustez estatística antes de promover uma estratégia. -- **`ContinuousEvaluator` duplicado:** duas classes homônimas com contratos diferentes — só a versão dataclass é usada pelo `UnifiedOrchestrator`. Consolidar. +- **`ContinuousEvaluator` duplicado:** 🟢 **resolvido (Onda 2, R2)** — a versão dataclass usada pelo `UnifiedOrchestrator` foi renomeada para `AgentPerformanceEvaluator`; a plain (`continuous_eval.py`) mantém o nome. --- @@ -852,7 +852,7 @@ classDiagram **Notas de design:** - **Dois conceitos de "guardrail" coexistem** (colisão semântica a esclarecer): (1) `safety.guardrails.GuardrailSystem` — regras de **risco de trading** por ordem (position size, stop, risk-reward), usado pelo `RiskAgent` e pelo `OrderStore`; (2) `core.safe_agent_base` — **guardrails de segurança de execução** (sanitização, ética, limites de recurso) sobre o `SafeAgentBase` (que não é usado no trading). Nomes iguais, propósitos distintos. - **Duas validações de ordem redundantes:** `GuardrailSystem.validate_order` (instância, com regras dinâmicas + alertas) e `SecurityConfig.validate_order` (classmethod estático). Convém eleger uma única fonte de política. -- **Sizing avançado disponível, mas subutilizado:** `KellyCriterion`/`PositionSizer`/`CapitalProtections` são módulos ricos e testáveis, porém o `SquadOrchestrator` ainda dimensiona posição por `position_size_pct` simples — oportunidade de plugar o Kelly no pipeline (o endpoint `/v1/risk/kelly` já o expõe). +- **Sizing avançado disponível, mas subutilizado:** 🟡 **parcial (Onda 2, ADR-006)** — a fórmula central do Kelly virou fonte única (`src/risk.full_kelly_fraction`), consumida pelo endpoint `/v1/risk/kelly` (não reimplementa mais inline). **Resta:** o `SquadOrchestrator` ainda dimensiona por `position_size_pct` simples — plugar `KellyCriterion`/`PositionSizer`/`CapitalProtections` no sizing é a cauda do R5. --- @@ -1276,9 +1276,9 @@ end note |---|---|---|---| | 1 | **Duas fundações de agente** (`BaseAgent` async vs `SafeAgentBase` sync) sem ponte | Confusão conceitual; `SafeAgentBase` não usado no trading | Decidir uma base única ou documentar claramente os dois propósitos | | 2 | **Cluster "BuildToValue" paralelo** (`UnifiedOrchestrator` + planning/routing/consensus/chains/parallel + agentes de engenharia) não exercitado pelo trading | Código potencialmente morto; ~⅓ dos módulos de orquestração | Isolar em pacote opcional ou remover; medir cobertura real | -| 3 | **Colisões de nome:** `SquadOrchestrator`×2, `AdaptivePlanner`×2, `ContinuousEvaluator`×2, `MemoryStore`×2, `Guardrail`×2 | Erros de import, ambiguidade em revisões | Renomear para nomes qualificados por propósito | +| 3 | **Colisões de nome:** `SquadOrchestrator`×2, `AdaptivePlanner`×2, `ContinuousEvaluator`×2, `MemoryStore`×2, `Guardrail`×2 | Erros de import, ambiguidade em revisões | 🟢 **Feito (Onda 2, R2):** renomeadas → `A2ASquad`, `AdaptiveReplanner`, `AgentPerformanceEvaluator`, `RelevanceMemoryStore` (resta `Guardrail`×2) | | 4 | **Duas políticas de validação de ordem** (`GuardrailSystem` vs `SecurityConfig.validate_order`) e **dois modelos de autonomia** (US$ threshold vs trust-score) | Regra de negócio duplicada; risco de divergência | Eleger fonte única de política de risco/autonomia | -| 5 | **Sizing por `position_size_pct` fixo** enquanto `KellyCriterion`/`PositionSizer`/`CapitalProtections` existem prontos | Subaproveitamento de gestão de risco | Plugar Kelly/proteções no `SquadOrchestrator._position_quantity` | +| 5 | **Sizing por `position_size_pct` fixo** enquanto `KellyCriterion`/`PositionSizer`/`CapitalProtections` existem prontos | Subaproveitamento de gestão de risco | 🟡 **Parcial (Onda 2, ADR-006):** Kelly virou fonte única no endpoint; **resta** plugar no `SquadOrchestrator._position_quantity` | | 6 | **Namespace `/v1/agents/...` servido por dois routers** (`agents` e `config`) | Manutenção confusa; risco de conflito de rota | Consolidar num único router | | 7 | **HITL por polling de SQLite** acopla loop e API ao arquivo | Latência de até `poll_interval`; loop single-instance | Considerar `LISTEN/NOTIFY` (Postgres) ou fila se escalar | | 8 | **Frontend "classic scripts" com globals `window.*`** | Frágil a ordem de carga/colisões conforme cresce | Migrar para ES modules + bundler quando justificar | diff --git a/requirements.txt b/requirements.txt index ef67d8f..4db498b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,9 +24,8 @@ langchain-google-genai==0.0.6 ccxt==4.2.25 pandas==2.1.4 numpy==1.26.3 - -# Technical Analysis -ta==0.11.0 +# Technical indicators are computed with pandas/numpy directly +# (src/analysis/indicators.py) — no third-party `ta` package needed. # Database & Storage sqlalchemy==2.0.25 diff --git a/src/analysis/indicators.py b/src/analysis/indicators.py index 758ffb5..00782a2 100644 --- a/src/analysis/indicators.py +++ b/src/analysis/indicators.py @@ -65,7 +65,7 @@ class TechnicalAnalyzer: """Compute indicators from raw OHLCV data. Requires at least 50 candles for meaningful indicator values. - Uses the `ta` library (already in requirements.txt) plus numpy/pandas. + Indicators are computed directly with numpy/pandas (no third-party `ta`). """ MIN_CANDLES = 50 diff --git a/src/api/routes/risk.py b/src/api/routes/risk.py index 722693c..a54755a 100644 --- a/src/api/routes/risk.py +++ b/src/api/routes/risk.py @@ -22,6 +22,7 @@ ) from src.core.ledger import TradingLedger from src.core.metrics import PortfolioMetricsCalculator +from src.risk.position_sizing import full_kelly_fraction router = APIRouter(prefix="/risk", tags=["risk"]) @@ -181,12 +182,10 @@ async def get_kelly( avg_win = sum(wins) / len(wins) if wins else 1.0 avg_loss = abs(sum(losses) / len(losses)) if losses else 1.0 - if avg_loss > 0: - b = avg_win / avg_loss - full_kelly = win_rate - ((1 - win_rate) / b) if b > 0 else 0.0 - full_kelly = max(0.0, round(full_kelly, 4)) - else: - full_kelly = 0.0 + # Core Kelly formula is shared with src/risk (single source of truth); the + # advisory endpoint keeps its own fractional/ruin presentation for now + # (full consolidation + live-sizing is the R5 tail — see plano-melhorias). + full_kelly = max(0.0, round(full_kelly_fraction(win_rate, avg_win, avg_loss), 4)) fraction = 0.25 fractional_kelly = round(full_kelly * fraction, 4) diff --git a/src/backtest/engine.py b/src/backtest/engine.py index a615798..bf1da1b 100644 --- a/src/backtest/engine.py +++ b/src/backtest/engine.py @@ -233,15 +233,42 @@ def _close_trade( @staticmethod def _build_market_data(window: list[list[float]], close: float) -> dict[str, Any]: + """Build the market_data dict strategies expect. + + Computes a real ``TechnicalIndicators`` snapshot (and derived regime) + from the window — the same rich shape the live ``StrategyAgent`` + produces — so indicator-driven strategies (Grid, MeanReversion) + exercise the same code path in backtests as in production instead of + receiving neutral placeholders. Falls back to placeholders when + indicators can't be computed (short window / numpy unavailable in + minimal CI). Keeps the flat keys DCA and legacy strategies read. + """ + ind = None + regime = "unknown" + if len(window) >= WARMUP_CANDLES: + try: # lazy import — numpy optional in minimal CI (mirrors market.py) + from src.analysis.indicators import TechnicalAnalyzer + from src.analysis.regime_detector import detect_regime + + ind = TechnicalAnalyzer(window).get_latest() + regime = detect_regime(ind.ema_fast, ind.ema_slow, ind.atr, ind.current_price) + except Exception as exc: # pragma: no cover - defensive + logger.debug("Backtest indicator computation failed: %s", exc) + ind = None + vol_last = float(window[-1][5]) if window else 0.0 return { "current_price": close, - "rsi": 50, - "macd_histogram": 0, - "at_bollinger_lower": False, - "ma_20": close, - "ma_50": close, - "volume_24h": float(window[-1][5]) if window else 0, - "avg_volume": 1, + "regime": regime, + "rsi": ind.rsi if (ind and ind.rsi is not None) else 50, + "macd_histogram": ind.macd_hist if (ind and ind.macd_hist is not None) else 0, + "at_bollinger_lower": ( + ind.bb_percent is not None and ind.bb_percent < 0.05 + ) if ind else False, + "ma_20": ind.sma_20 if (ind and ind.sma_20 is not None) else close, + "ma_50": ind.sma_50 if (ind and ind.sma_50 is not None) else close, + "volume_24h": vol_last, + "avg_volume": (vol_last / max(ind.volume_ratio, 0.001)) if (ind and ind.volume_ratio) else 1, + "indicators": ind, "_raw_ohlcv": window, } diff --git a/src/evaluation/continuous_evaluator.py b/src/evaluation/continuous_evaluator.py index 569bfbc..79b53da 100644 --- a/src/evaluation/continuous_evaluator.py +++ b/src/evaluation/continuous_evaluator.py @@ -6,8 +6,12 @@ @dataclass -class ContinuousEvaluator: - """Collect rolling metrics for agent executions.""" +class AgentPerformanceEvaluator: + """Collect rolling metrics for agent executions. + + Renamed from ``ContinuousEvaluator`` to disambiguate from + :class:`src.evaluation.continuous_eval.ContinuousEvaluator` (R2). + """ metrics: Dict[str, List[float]] = field( default_factory=lambda: { diff --git a/src/memory/intelligent_forgetting.py b/src/memory/intelligent_forgetting.py index 48b3c62..5177ee4 100644 --- a/src/memory/intelligent_forgetting.py +++ b/src/memory/intelligent_forgetting.py @@ -6,7 +6,13 @@ @dataclass -class MemoryStore: +class RelevanceMemoryStore: + """Relevance-scored memory store used by :class:`IntelligentForgetting`. + + Renamed from ``MemoryStore`` to disambiguate from + :class:`src.utils.memory_utils.MemoryStore` (R2). + """ + data: Dict[str, Dict] def get_all_memories(self) -> Dict[str, Dict]: @@ -24,7 +30,7 @@ def execute_cleanup(self) -> int: @dataclass class IntelligentForgetting: - memory: MemoryStore + memory: RelevanceMemoryStore def adaptive_forget(self) -> int: removed = 0 diff --git a/src/orchestration/unified_orchestrator.py b/src/orchestration/unified_orchestrator.py index b1f6468..bdd7b74 100644 --- a/src/orchestration/unified_orchestrator.py +++ b/src/orchestration/unified_orchestrator.py @@ -12,7 +12,7 @@ from src.agents.designer_agent import DesignerAgent from src.agents.ops_agent import OpsAgent from src.consensus.weighted_voting import WeightedConsensusEngine -from src.evaluation.continuous_evaluator import ContinuousEvaluator +from src.evaluation.continuous_evaluator import AgentPerformanceEvaluator from src.hitl.progressive_autonomy import ProgressiveAutonomyManager from src.memory.agent_memory import AgentMemorySystem from src.parallel.resource_manager import ParallelResourceManager @@ -30,7 +30,7 @@ def __init__(self) -> None: self.router = LearningRouter() self.parallel = ParallelResourceManager() self.memory = AgentMemorySystem() - self.evaluator = ContinuousEvaluator() + self.evaluator = AgentPerformanceEvaluator() self.consensus = WeightedConsensusEngine() self.autonomy = ProgressiveAutonomyManager() diff --git a/src/planning/adaptive_replanner.py b/src/planning/adaptive_replanner.py index 6434b05..ce94b9a 100644 --- a/src/planning/adaptive_replanner.py +++ b/src/planning/adaptive_replanner.py @@ -7,8 +7,12 @@ @dataclass -class AdaptivePlanner: - """Execute plans with the ability to replan after failures.""" +class AdaptiveReplanner: + """Execute plans with the ability to replan after failures. + + Renamed from ``AdaptivePlanner`` to disambiguate from + :class:`src.planning.adaptive_planner.AdaptivePlanner` (R2). + """ max_replanning_attempts: int = 3 failure_history: List[Dict[str, Any]] = field(default_factory=list) diff --git a/src/protocols/squad_orchestrator.py b/src/protocols/squad_orchestrator.py index 28aa935..ef35e95 100644 --- a/src/protocols/squad_orchestrator.py +++ b/src/protocols/squad_orchestrator.py @@ -9,8 +9,13 @@ @dataclass -class SquadOrchestrator: - """Coordinate tasks between multiple specialised agents.""" +class A2ASquad: + """Coordinate tasks between multiple specialised agents (agent-to-agent). + + Renamed from ``SquadOrchestrator`` to disambiguate from the trading + pipeline's :class:`src.orchestration.squad_orchestrator.SquadOrchestrator` + (R2). This is part of the non-trading "BuildToValue" agent cluster. + """ architect: ArchitectAgent = field(default_factory=ArchitectAgent) developer: DeveloperAgent = field(default_factory=DeveloperAgent) diff --git a/src/risk/position_sizing.py b/src/risk/position_sizing.py index dfb3d42..279d4e8 100644 --- a/src/risk/position_sizing.py +++ b/src/risk/position_sizing.py @@ -48,6 +48,24 @@ def risk_of_ruin(win_rate: float, bet_fraction: float) -> float: return 1.0 +def full_kelly_fraction(win_rate: float, avg_win_pct: float, avg_loss_pct: float) -> float: + """Full Kelly fraction f* = p − q/b, with b = avg_win/avg_loss. + + Single source of truth for the core Kelly formula, shared by + :class:`KellyCriterion` and the ``GET /v1/risk/kelly`` endpoint (which + previously reimplemented this inline). Algebraically identical to the + ``(p*b - q) / b`` form. Returns 0.0 when the win/loss ratio is undefined + (non-positive average loss), i.e. no computable edge. + """ + if avg_loss_pct <= 0: + return 0.0 + b = avg_win_pct / avg_loss_pct + if b <= 0: + return 0.0 + q = 1.0 - win_rate + return win_rate - (q / b) + + @dataclass class KellyCriterion: """Kelly Criterion position sizer. @@ -80,12 +98,7 @@ def full_kelly(self) -> float | None: if self.avg_loss_pct <= 0: return None - b = self.avg_win_pct / self.avg_loss_pct - p = self.win_rate - q = 1.0 - p - - f = (p * b - q) / b - return f + return full_kelly_fraction(self.win_rate, self.avg_win_pct, self.avg_loss_pct) def fractional_kelly(self, fraction: float = KELLY_FRACTION) -> float: """Fractional Kelly: fraction × f*, clamped to [MIN, MAX] percent.