diff --git a/docs/architecture/arquitetura.md b/docs/architecture/arquitetura.md new file mode 100644 index 0000000..ba16233 --- /dev/null +++ b/docs/architecture/arquitetura.md @@ -0,0 +1,665 @@ +# Arquitetura — Criptotrade (visão completa deste repositório) + +> Conjunto completo de **diagramas de arquitetura** de **todo este repositório**, no modelo **C4** (Contexto → Contêineres → Componentes) + vistas de apoio (dados/ERD, fluxo de dados, runtime, implantação, CI/CD, observabilidade, segurança) e **recomendações de arquitetura** com um estado-alvo proposto. +> +> Complementa `docs/uml/arquitetura-uml.md` (que detalha as **classes**). Aqui o foco é o **nível arquitetural**: sistemas, contêineres, integrações, dados, deploy e evolução. +> +> **Composição do repositório:** Python (backend, ~168 arq.), React/JSX (console em `docs/design/pages/`), 1 arquivo TypeScript gerado (`openapi.d.ts`, o contrato), infra (Docker/nginx/Prometheus/Grafana). Sem Rust. + +## Índice +1. [C4 L1 — Contexto de Sistema](#1-c4-nível-1--contexto-de-sistema) +2. [C4 L2 — Contêineres](#2-c4-nível-2--contêineres) +3. [C4 L3 — Componentes: API](#3-c4-nível-3--componentes-do-contêiner-api) +4. [C4 L3 — Componentes: Loop Orquestrador](#4-c4-nível-3--componentes-do-contêiner-loop-orquestrador) +5. [Arquitetura de Dados (ERD + stores)](#5-arquitetura-de-dados) +6. [Fluxo de Dados (DFD)](#6-fluxo-de-dados-dfd) +7. [Vista de Runtime / Processos](#7-vista-de-runtime--processos) +8. [Implantação (3 topologias)](#8-implantação) +9. [Pipeline CI/CD](#9-pipeline-cicd) +10. [Arquitetura de Observabilidade](#10-arquitetura-de-observabilidade) +11. [Arquitetura de Segurança](#11-arquitetura-de-segurança) +12. [Recomendações e Arquitetura-Alvo](#12-recomendações-de-arquitetura-e-estado-alvo) + +**Legenda de estereótipos (C4):** `«person»` ator humano · `«system»` sistema externo · `«container»` unidade executável/deployável (processo, app, banco) · `«component»` agrupamento lógico dentro de um contêiner. Setas = fluxo/dependência rotulada com protocolo. + +--- + +## 1. C4 Nível 1 — Contexto de Sistema + +**Objetivo:** o sistema Criptotrade como uma caixa preta, seus usuários e sistemas externos. + +```mermaid +graph TB + Op(["👤 Operador / Trader
«person»
aprova ordens (HITL), ajusta risco,
observa KPIs"]) + + subgraph SYSBND["Criptotrade — Plataforma de Trading com IA + HITL «system»"] + SYS["Backend Python + Console Web
gera sinais, valida risco, executa
ordens (paper), audita tudo"] + end + + EX(["🏦 Exchange / Market Data
«system» (CCXT)
OHLCV, ordens (paper por padrão)"]) + LLM(["🧠 LLM Provider
«system» (Gemini/OpenAI/Anthropic)
Chain-of-Thought / reflexão (opcional)"]) + PROM(["📈 Prometheus + Grafana
«system»
métricas técnicas e de negócio"]) + SENTRY(["🐞 Sentry
«system» (opcional)
captura de erros 5xx"]) + + Op -->|"HTTPS: console React + dashboard Streamlit"| SYS + SYS -->|"CCXT (async); dry-run offline por padrão"| EX + SYS -.->|"HTTPS (opcional, degradação graciosa)"| LLM + PROM -->|"scrape /metrics (HTTP, 15s)"| SYS + SYS -.->|"eventos 5xx (opcional)"| SENTRY +``` + +**Notas de arquitetura:** +- **HITL é primeira-classe:** o operador não é observador passivo — é parte do fluxo de execução (aprova/rejeita ordens acima do limiar de autonomia). +- **Fail-safe por padrão:** o único caminho para a exchange roda em `EXCHANGE_DRY_RUN=true` (mercado sintético offline); trading real exige override explícito no ambiente de deploy. +- **Dependências externas são todas opcionais/degradáveis:** LLM, Sentry e até a exchange (dry-run) podem faltar sem derrubar o núcleo. + +--- + +## 2. C4 Nível 2 — Contêineres + +**Objetivo:** as unidades executáveis/deployáveis e como se comunicam. Este é o diagrama de arquitetura central. + +```mermaid +graph TB + Op(["👤 Operador «person»"]) + + subgraph Boundary["Criptotrade «system»"] + NGINX["nginx «container»
reverse proxy + TLS
serve console estático · proxy /v1"] + CONSOLE["Console React «container»
docs/design/pages (dist estático)
SPA hash-routing"] + API["API «container»
FastAPI/uvicorn :8000
REST /v1 + SSE + /metrics"] + LOOP["Loop Orquestrador «container»
python -m src.orchestration.main_loop
ciclo de trading (sem porta)"] + DASH["Dashboard «container»
Streamlit :8501
console operacional alternativo"] + SQLITE[("SQLite (WAL) «container»
orders · cycle_events · open_positions
circuit_breaker · backtest_jobs · journal · ledger_events")] + JSONL[("Arquivos append-only «container»
ledger JSONL · alerts JSONL · XES · memória")] + PROM["Prometheus «container» :9090"] + GRAF["Grafana «container» :3000"] + PG[("Postgres «container» (perfil scale)")] + REDIS[("Redis «container» (perfil scale)")] + end + + EX(["Exchange (CCXT) «system»"]) + LLM(["LLM «system»"]) + + Op -->|HTTPS| NGINX + NGINX -->|serve estático| CONSOLE + CONSOLE -->|"REST /v1 + SSE (envelope APIResponse, X-API-Key)"| NGINX + NGINX -->|"HTTP proxy /v1 /health"| API + Op -.->|HTTP alternativo| DASH + DASH -->|"httpx REST /v1"| API + + API -->|read/write| SQLITE + API -->|read/write| JSONL + LOOP -->|read/write| SQLITE + LOOP -->|append| JSONL + LOOP -->|analyze_and_trade| EX + API -->|market data| EX + LOOP -.->|CoT/reflexão| LLM + API -.->|CoT| LLM + + PROM -->|scrape /metrics| API + GRAF -->|query| PROM + API -. "scale: DATABASE_URL" .-> PG + API -. "scale: REDIS_URL (rate limit)" .-> REDIS + + classDef store fill:#e8eef7,stroke:#4a6fa5; + class SQLITE,JSONL,PG,REDIS store; +``` + +**Decisões arquiteturais-chave (o "porquê"):** + +| Decisão | Racional | ADR | +|---|---|---| +| **API e Loop como contêineres separados** | Lifecycle independente: restart da API não para o trading, e vice-versa | ADR-002 | +| **Estado compartilhado via SQLite WAL** (sem RPC/fila) | Cross-process simples e durável; dois processos no mesmo host/volume | ADR-001/003 | +| **Paper trading first (`EXCHANGE_DRY_RUN`)** | Segurança: zero conexão real por padrão | ADR-001 | +| **Uma imagem Python para API/Loop/Dashboard** | Diferem só pelo `command`; simplifica build/deploy | — | +| **Console React estático (esbuild + nginx)** | Sem servidor Node em produção; SPA servida como arquivos | — | +| **Postgres/Redis opt-in (perfil `scale`)** | Escala horizontal sem mudar código (abstração de DB + rate limiter) | ADR-005 | + +--- + +## 3. C4 Nível 3 — Componentes do Contêiner API + +**Objetivo:** organização interna da FastAPI (`src/api/*`). + +```mermaid +graph TB + subgraph API["Contêiner API (FastAPI) «container»"] + direction TB + MW["Cadeia de Middleware «component»
RequestId → Prometheus → RateLimit →
SecurityHeaders → CORS → APIKey"] + subgraph Routers["Routers /v1 «component»"] + R1[metrics · trades · risk] + R2[orders · hitl] + R3[agents · config · process] + R4[market · backtest · journal · alerts] + end + DEPS["deps.py «component»
providers @lru_cache:
ledger · order_store · alert_store/bus ·
registry · exchange · metrics_calc · hitl_store"] + SCHEMAS["schemas.py «component»
DTOs Pydantic + envelope APIResponse[T]"] + OBS["observability «component»
PrometheusMiddleware · DomainMetricsCollector"] + end + + subgraph Domain["Serviços de domínio/infra (fora da API)"] + OS["OrderStore (hitl)"] + LG["TradingLedger (core)"] + MC["PortfolioMetricsCalculator"] + AR["AgentRegistry"] + AX["ExchangeClient + analysis/*"] + BT["BacktestEngine/MC/WF"] + AB["AlertStore + AlertBus"] + end + DB[("SQLite/Postgres")] + + MW --> Routers + Routers --> DEPS + Routers --> SCHEMAS + DEPS --> OS + DEPS --> LG + DEPS --> MC + DEPS --> AR + DEPS --> AX + R2 --> OS + R4 --> BT + R4 --> AB + R4 --> AX + R1 --> MC + OS --> DB + LG --> DB + OBS --> LG +``` + +**Notas:** +- **Middleware em cadeia (fail-closed em prod):** `_enforce_prod_security()` recusa boot em produção sem `API_KEYS` + `CORS_ORIGINS` explícito. Rate limit por IP real (nginx repassa `X-Forwarded-*`). +- **Injeção via `deps.py`:** providers `@lru_cache(maxsize=1)` (singletons por processo), exceto `get_metrics_calculator` (fresco a cada request para refletir o ledger). `reset_singletons()` para testes. +- **Contrato tipado:** `schemas.py` → OpenAPI → `openapi.d.ts` (gate de drift no CI). + +--- + +## 4. C4 Nível 3 — Componentes do Contêiner Loop Orquestrador + +**Objetivo:** organização interna do processo de trading (`src/orchestration/*` + agentes). + +```mermaid +graph TB + subgraph LOOP["Contêiner Loop «container»"] + MAIN["main_loop «component»
init_db · from_env · sinais SIGTERM"] + OL["OrchestratorLoop «component»
run_forever/run_cycle · intervalo 60s ·
heartbeat · registry.record_cycle"] + SQ["SquadOrchestrator «component»
pipeline analyze_and_trade"] + CB["CircuitBreaker «component»
perda diária 4% / 3 losses → pausa 24h"] + subgraph Agents["Agentes de trading «component»"] + SA[StrategyAgent
CoT + TA + regime] + RA[RiskAgent
reflexão + guardrails] + EA[ExecutionAgent
ReAct paper fill] + end + PS["PositionStore «component»
book de posições paper"] + HB["approval_handler «component»
OrderStore.wait_for_decision"] + end + DB[("SQLite compartilhado")] + EX(["Exchange (CCXT)"]) + LG["TradingLedger + XES"] + + MAIN --> OL + OL --> SQ + SQ --> CB + SQ --> SA + SQ --> RA + SQ --> EA + SQ --> PS + SQ --> HB + SA --> EX + EA --> EX + HB --> DB + PS --> DB + CB --> DB + OL --> LG + SQ --> LG +``` + +**Notas:** +- **Pipeline invariante:** `Strategy → (fecha SL/TP) → Risk → Guardrails → HITL → Execution → fill/ledger`, com circuit breaker no início. Fail-soft: falha de um símbolo emite `agent_cycle_failed` e o loop continua. +- **Recuperação de restart:** `reload_open_positions()` restaura book + breaker do SQLite (evita posições zumbi / streak esquecida). +- Detalhe de classes e sequência: ver `docs/uml/arquitetura-uml.md` §5.2 e §6.1. + +--- + +## 5. Arquitetura de Dados + +**Objetivo:** o modelo de persistência. O sistema usa **dois estilos**: (a) tabelas SQLite/Postgres para estado cross-process, (b) arquivos append-only (JSONL/JSON) para auditoria e memória. + +### 5.1 ERD — estado relacional (SQLite WAL / Postgres) + +```mermaid +erDiagram + orders { + TEXT id PK "ord_xxxx" + TEXT pair + TEXT side "buy|sell (CHECK)" + REAL quantity "> 0" + REAL price "> 0" + TEXT strategy + TEXT agent_id + REAL confidence + INTEGER critical "0/1" + REAL position_size_pct + REAL stop_loss + REAL take_profit + TEXT status "pending|approved|filled|rejected|cancelled" + INTEGER auto_approved "0/1" + TEXT created_at + TEXT resolved_at + TEXT filled_at + } + open_positions { + TEXT order_id PK + TEXT symbol + TEXT side + REAL entry_price + REAL quantity + REAL stop_loss + REAL take_profit + TEXT opened_at + } + circuit_breaker_state { + INTEGER id PK "singleton (id=1)" + REAL tripped_at + INTEGER consecutive_losses + REAL daily_loss_pct + } + cycle_events { + INTEGER id PK "autoincrement" + TEXT agent_id + TEXT cycled_at "idx(agent_id,cycled_at)" + } + backtest_jobs { + TEXT id PK + TEXT status "running|done|error" + TEXT config_json + TEXT result_json + TEXT error + TEXT created_at + TEXT completed_at + } + journal_entries { + INTEGER id PK "autoincrement" + TEXT setup + INTEGER emotion_before "1..10" + INTEGER emotion_after "1..10" + INTEGER stop_defined "0/1" + INTEGER plan_followed "0/1" + REAL pnl_pct + TEXT created_at + } + ledger_events { + INTEGER id PK "autoincrement" + TEXT timestamp + TEXT event_type "idx" + TEXT data "JSON" + } + + orders ||..o| open_positions : "id ≈ order_id (lógico, sem FK)" + orders ||..o{ ledger_events : "gera eventos (lógico)" + cycle_events }o..|| orders : "agent_id (lógico)" +``` + +> **Nota de design importante:** as tabelas **não têm foreign keys** entre si. É deliberado — são datasets de coordenação cross-process (bridge HITL, contadores, book) desenhados para escrita concorrente por dois processos sem contenção de lock. As relações acima são **lógicas** (tracejadas). O `ledger_events` é o *event store* de onde métricas são derivadas. + +### 5.2 Data stores completos (relacional + arquivos) + +```mermaid +graph LR + subgraph Relacional["SQLite WAL (default) / Postgres (scale)"] + T1[orders] + T2[open_positions] + T3[circuit_breaker_state] + T4[cycle_events] + T5[backtest_jobs] + T6[journal_entries] + T7[ledger_events] + end + subgraph Arquivos["Append-only (volume ./data)"] + F1[["ledger JSONL
auditoria"]] + F2[["event log XES
process mining"]] + F3[["alerts JSONL"]] + F4[["agent_memories.jsonl
+ Chroma opcional"]] + F5[["trade_journal.json"]] + F6[["loop_heartbeat.json"]] + end + Writers["API · Loop · Agentes"] --> Relacional + Writers --> Arquivos + Metrics["PortfolioMetricsCalculator"] -->|deriva| T7 + ProcessAPI["/v1/process/events"] -->|lê| F2 +``` + +**Notas:** +- **Event Sourcing leve:** métricas de portfólio (Sharpe, win rate, drawdown, P&L) **não são armazenadas** — são recalculadas relendo `ledger_events` (`position_closed`, `order_fill`). Fonte única de verdade. +- **Migração de portabilidade:** `src/core/db.py` abstrai `connection()`; migrations em `migrations/` (+ `migrations/postgres/`) rodadas por `init_db()` (idempotente, ambos processos). +- **Dívida conhecida (ADR-003):** o XES/alerts ainda em JSONL; a migração completa para SQLite está deferida. + +--- + +## 6. Fluxo de Dados (DFD) + +**Objetivo:** como um sinal vira ordem, fill e métrica — o caminho do dado ponta a ponta. + +```mermaid +flowchart LR + EX([Exchange OHLCV]) -->|fetch_ohlcv| SA[StrategyAgent
TA + regime + CoT] + SA -->|signal + confidence| RA[RiskAgent + Guardrails] + RA -->|approved signal| OSUB[OrderStore.submit] + OSUB -->|"notional ≤ threshold?"| AUTO{auto?} + AUTO -->|sim| FILL[fill local] + AUTO -->|não| PEND[(orders: pending)] + PEND -->|"PATCH /v1/orders (operador)"| APPR[(orders: approved)] + APPR -->|loop wait_for_decision| EXEC[ExecutionAgent paper fill] + FILL --> LED[(ledger_events)] + EXEC --> LED + LED -->|position_closed / order_fill| MC[PortfolioMetricsCalculator] + MC -->|APIResponse| UI[Console / Dashboard] + RA -.->|rejeição| AL[(alerts)] + AL -.->|SSE /v1/alerts| UI + LED -.->|XES| PM["/v1/process/events → process mining"] +``` + +**Notas:** dois "portões" no fluxo — **guardrails** (risco por ordem) e **HITL** (aprovação por valor). Todo evento econômico passa pelo ledger, do qual métricas e process mining derivam. + +--- + +## 7. Vista de Runtime / Processos + +**Objetivo:** o que roda concorrentemente e como sincronizam. + +```mermaid +graph TB + subgraph P1["Processo: API (uvicorn) — N workers possíveis"] + A1[event loop asyncio] + A2[middleware + routers] + A3[backtest asyncio.create_task] + end + subgraph P2["Processo: Loop — SINGLE INSTANCE"] + B1[run_forever: ciclo a cada 60s] + B2[wait_for_decision: poll 2s] + end + subgraph P3["Processo: Dashboard (Streamlit)"] + C1[httpx → API] + end + SHARED[("SQLite WAL
(única fonte de sincronização)")] + + A2 -->|resolve/decide| SHARED + B2 -->|poll status| SHARED + B1 -->|record_cycle / positions| SHARED + A3 -->|persist jobs| SHARED + C1 -->|HTTP| A2 + + note1["Sincronização = estado no SQLite.
Sem fila/mensageria.
Latência de aprovação ≤ poll_interval (2s).
Timeout 300s → cancel (fail-closed)."] + P2 -.-> note1 +``` + +**Ponto de atenção arquitetural:** o **loop é single-instance por design** (ADR-005). O modelo HITL por *polling* de SQLite não é trivialmente replicável para N loops (precisaria de leader-election ou locks de linha). A API, sim, escala horizontalmente (estado externo + rate limiter Redis). + +--- + +## 8. Implantação + +**Objetivo:** as três topologias versionadas. Nós = contêineres; setas rotuladas com protocolo/porta. + +### 8.1 Dev — `docker-compose.yml` + +```mermaid +graph TB + Host["🖥️ Host dev (todas as portas publicadas)"] + app["app :8000
EXCHANGE_DRY_RUN=true"] + orch["orchestrator (sem porta)
INTERVAL=60s"] + dash["dashboard :8501"] + prom["prometheus :9090"] + graf["grafana :3000"] + pg["postgres :5432
(perfil scale)"] + rd["redis :6379
(perfil scale)"] + vol[("volume ./data + ./logs
compartilhado app↔orchestrator")] + + Host --- app & orch & dash & prom & graf & pg & rd + app --- vol + orch --- vol + dash -->|API_URL=http://app:8000| app + prom -->|scrape| app + graf -->|query| prom + app -. scale .- pg + app -. scale .- rd + orch -->|healthcheck| hc["scripts/healthcheck_loop.py
(lê loop_heartbeat.json)"] +``` + +### 8.2 Prod self-contained — `docker-compose.prod.yml` + +```mermaid +graph TB + Net["rede edge 172.28.0.0/24 (nginx = .2)"] + subgraph Public["Exposto (host)"] + nginx["nginx :80/:443
console baked + proxy /v1
reload a cada 6h"] + certbot["certbot sidecar
renova certs 2x/dia (HTTP-01)"] + end + subgraph Internal["Interno (sem porta de host)"] + appp["app
--proxy-headers
--forwarded-allow-ips 172.28.0.2
APP_ENV=production (fail-closed)"] + orchp["orchestrator"] + promp["prometheus"] + end + volp[("volume ./data")] + Browser(["Browser"]) + + Browser -->|HTTPS 443| nginx + nginx -->|HTTP proxy| appp + certbot -.-> nginx + appp --- volp + orchp --- volp + promp -->|scrape| appp + Net --- nginx & appp & orchp & promp +``` + +### 8.3 VPS atrás de gateway compartilhado — `docker-compose.vps.yml` + +```mermaid +graph LR + GW["btv-nginx-prod (global-ingress)
rede externa btv-prod-net"] + subgraph CT["rede interna criptotrade_internal (expose only)"] + va["criptotrade-app:8000"] + vf["criptotrade-frontend:80
(console.Dockerfile)"] + vd["criptotrade-dashboard:8501"] + vo["criptotrade-orchestrator"] + vp["criptotrade-prometheus"] + end + GW -->|"/v1/ ·/health → app:8000"| va + GW -->|"/ → frontend:80"| vf + vf -->|"same-origin /v1 (API_BASE='')"| GW + vd --> va + vp --> va +``` + +**Notas:** +- **Superfície pública mínima em prod:** só nginx/gateway publica 80/443; app/loop/prometheus ficam internos. +- **Confiança de proxy explícita:** app só aceita `X-Forwarded-*` de `172.28.0.2` → rate-limit por IP real. +- **Uma imagem Python** reusada; console em imagem nginx separada (`deploy/console.Dockerfile`, multi-stage node→nginx). + +--- + +## 9. Pipeline CI/CD + +**Objetivo:** os gates automáticos (GitHub Actions) que protegem `master`. + +```mermaid +flowchart TB + TRIG["push master / claude/** · PR → master"] + + subgraph WF1["workflow: Python CI"] + SS["secret-scan
gitleaks"] + TEST["test
ruff (gate correção) + ruff full (info)
pytest --cov (fail-under 72%)"] + DB2["docker-build (needs: test)
build imagem Python · compose config -q (prod+vps)
build console image · nginx -t (certs efêmeros)"] + CB["console-build
npm ci · npm run build (esbuild)
gen:types + git diff --exit-code (drift gate)
upload console-dist"] + E2E["console-e2e
build · playwright chromium (mock data)"] + end + subgraph WF2["workflow: Phase 1-3 Validation (rápido)"] + PV["validate-phases
lean deps · ruff módulos novos
smoke import src.api.main:app
pytest (metrics/alerts/db/api/orders)"] + end + + TRIG --> SS & TEST & CB & E2E & PV + TEST --> DB2 + + style DB2 fill:#eef7ee,stroke:#4a7 + style CB fill:#eef7ee,stroke:#4a7 +``` + +**Gates notáveis:** +- **Drift do contrato** (`gen:types` + `git diff --exit-code openapi.d.ts`): backend Python e front JS **não podem divergir**. +- **Compose/nginx validados no CI** com certs auto-assinados descartáveis. +- **Cobertura com piso** (`--cov-fail-under=72`, `pyproject.toml`), com meta de subir para 80%. +- **Dois workflows**: um completo (pesado) e um rápido por fases (deps enxutas). + +--- + +## 10. Arquitetura de Observabilidade + +**Objetivo:** os quatro pilares — métricas, logs, traces, e o diferencial de **process mining**. + +```mermaid +graph TB + subgraph App["API + Loop"] + PM["PrometheusMiddleware
http_requests_total · duration histogram
(label = template de rota, cardinalidade limitada)"] + DM["DomainMetricsCollector
criptotrade_* gauges do ledger
(open_positions, pnl, win_rate, sharpe)"] + LOG["Logs JSON estruturados
python-json-logger + request_id
(RequestIdMiddleware + ContextVar)"] + TR["AgentObserver / SpanRecord
trajectory export (reasoning log)"] + XES["TradingLedger.log_process_event
event log XES (case_id, activity, actor)"] + HB["loop_heartbeat.json
liveness do loop (sem HTTP)"] + SEN["Sentry (opcional)
captura 5xx"] + end + + PROM["Prometheus :9090
scrape /metrics (15s)"] + GRAF["Grafana :3000
dashboards provisionados
(performance/availability/business/ai)"] + PMINE["/v1/process/events
→ análise de processo"] + SSE["/v1/alerts (SSE)
AlertBus fan-out"] + + PM --> PROM + DM --> PROM + PROM --> GRAF + XES --> PMINE + App --> SSE + HB --> HCK["scripts/healthcheck_loop.py"] + SEN -.-> SentryCloud([Sentry]) +``` + +**Diferencial:** o **event log XES** (`log_process_event` emite `agent_cycle_started/completed/failed`, `order_submitted/approved/rejected/filled/cancelled`) habilita *process mining* real do fluxo de trading — raro em sistemas deste porte. Correlação por `request_id` liga logs a requests HTTP. + +--- + +## 11. Arquitetura de Segurança + +**Objetivo:** as camadas de defesa (rede, autenticação, risco, execução, segredos). + +```mermaid +graph TB + subgraph Perimetro["Perímetro"] + N["nginx/gateway
TLS 1.2/1.3 · HSTS · único público"] + end + subgraph Rede["Isolamento de rede"] + NET["app/loop/prom internos
(edge 172.28.0.0/24 ou criptotrade_internal)
--forwarded-allow-ips (confia só no proxy)"] + end + subgraph AppSec["Segurança da aplicação (middleware)"] + AUTH["APIKeyMiddleware (X-API-Key)
dev: fail-open · prod: fail-closed
(_enforce_prod_security no boot)"] + RL["RateLimitMiddleware
30/min write · 200/min read (por IP)"] + SH["SecurityHeadersMiddleware
CSP · X-Frame-Options DENY · nosniff · HSTS"] + CORS["CORS: '*' dev / allowlist explícito prod"] + end + subgraph Trading["Segurança de trading"] + DRY["EXCHANGE_DRY_RUN obrigatório
(ExchangeClient recusa iniciar sem)"] + GR["GuardrailSystem
position size · stop loss · risk-reward"] + HITL["HITL fail-closed
(sem handler → nega)"] + CBK["CircuitBreaker (perda diária/streak)"] + end + subgraph Exec["Segurança de execução (tools)"] + SC["SecurityConfig
FORBIDDEN_PATTERNS · validate_tool_call"] + SB["SecureToolSandbox
DockerSandbox (network off, timeouts)"] + end + subgraph Segredos["Gestão de segredos"] + GL["gitleaks (CI)"] + ENV[".env / .env.prod (host, fora do git)"] + end + + N --> NET --> AppSec --> Trading + AppSec -.-> Exec + Segredos -.-> AppSec +``` + +**Notas:** +- **Fail-closed onde importa:** produção recusa boot com auth aberto; HITL nega sem handler; exchange recusa sem `EXCHANGE_DRY_RUN`. +- **Defesa em profundidade:** perímetro (TLS) → rede (isolamento) → app (auth/rate/headers) → domínio (guardrails/HITL/breaker) → execução (sandbox). +- **Pontos a consolidar** (ver §12): duas políticas de validação de ordem (`GuardrailSystem` vs `SecurityConfig.validate_order`) e dois modelos de autonomia. + +--- + +## 12. Recomendações de Arquitetura e Estado-Alvo + +### 12.1 Achados priorizados + +| # | 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. | +| 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`. | +| 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. | + +### 12.2 Métricas qualitativas de acoplamento +- **Núcleo estável correto:** `core` é dependência de quase tudo e depende de pouco (config/db) — *Stable Dependencies Principle* respeitado. +- **God Object no cluster genérico:** `UnifiedOrchestrator` compõe 9 subsistemas — acoplamento eferente alto, reforça R1. +- **Bom uso de inversão de dependência:** callbacks (`alert_sink`, `state_db_provider`, `fill_callback`, `approval_handler`) quebram ciclos de import consistentemente. + +### 12.3 Arquitetura-alvo proposta (evolução incremental) + +```mermaid +graph TB + Op(["👤 Operador"]) + subgraph Target["Estado-alvo"] + NGINX["nginx/gateway (TLS)"] + FE["Console (ES modules + bundler)"] + API["API FastAPI (N réplicas)"] + subgraph Loops["Loop(s) de trading"] + L1["Loop primário (leader)"] + L2["Loop standby (leader-election)"] + end + BUS{{"Coordenação:
Postgres LISTEN/NOTIFY
ou Redis pub/sub"}} + PGDB[("Postgres
estado + event store XES unificado")] + RISK["Serviço de Risco único
(GuardrailSystem + Kelly + proteções)"] + OBS["Observabilidade
Prometheus + Grafana + traces"] + end + EX(["Exchange"]) + LLM(["LLM (opcional)"]) + + Op -->|HTTPS| NGINX --> FE + FE -->|REST+SSE| API + API --> BUS + L1 --> BUS + L2 -. standby .-> BUS + API --> PGDB + L1 --> PGDB + L1 --> RISK + API --> RISK + L1 --> EX + L1 -.-> LLM + API --> OBS + L1 --> OBS + + style BUS fill:#fff3cd,stroke:#c90 + style RISK fill:#d4edda,stroke:#4a7 + style Loops fill:#e8eef7,stroke:#4a6fa5 +``` + +**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. +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. + +Cada passo é independente e preserva o invariante central: **paper-trading-first, HITL fail-closed, tudo auditável no ledger/XES**. + +--- + +> **Diagramas em Mermaid** (renderizam nativamente no GitHub). Nomes de contêineres/componentes/tabelas preservados conforme o código-fonte e migrations. Para detalhe de **classes** e sequências, ver `docs/uml/arquitetura-uml.md`. diff --git "a/docs/documenta\303\247\303\243o/mapeamento-dados.md" "b/docs/documenta\303\247\303\243o/mapeamento-dados.md" new file mode 100644 index 0000000..7802073 --- /dev/null +++ "b/docs/documenta\303\247\303\243o/mapeamento-dados.md" @@ -0,0 +1,446 @@ +# Mapeamento Completo de Dados — Criptotrade + +> **Dicionário de dados exaustivo de todo o repositório.** Mapeia **todos os dados que circulam no sistema** — entradas, saídas, processamento intermediário e estado — em cada módulo e em cada linguagem (Python, JavaScript/JSX, TypeScript, HTML, SQL, YAML, `.env`, Docker). Inclui deliberadamente os dados **"perdidos na própria classe"** (atribuídos e nunca lidos). +> +> Produzido por análise estática dos scripts do repositório, com referências `arquivo:linha`. Complementa `docs/uml/arquitetura-uml.md` (classes) e `docs/architecture/arquitetura.md` (C4). + +## Nota sobre linguagens +Varredura recursiva do repositório: **Python** 168 arquivos (núcleo), **React/JSX + JS** ~24 (console em `docs/design/pages/`), **TypeScript** 1 (`openapi.d.ts`, contrato gerado), **HTML** 2, **SQL** 4 (migrations), **YAML** vários (config/monitoring). **Rust: 0 arquivos** — não existe código Rust neste repositório; onde a metodologia pediria mapeamento Rust, registra-se a ausência. + +## Legenda de classificação de dado +Cada dado é marcado por seu papel no fluxo: + +| Marca | Papel | +|---|---| +| **E** | Entrada (input externo: parâmetro, env, HTTP, arquivo, exchange, LLM) | +| **S** | Saída (retorno de função, resposta HTTP, escrita em store, alerta) | +| **I** | Intermediário (variável local, atributo de estado, dict transformado) | +| **P** | Persistido (SQLite/Postgres, JSONL/JSON, arquivo) | +| **X** | Dado morto / "perdido na classe" (atribuído e nunca lido depois) | + +--- + +## Índice +- [1. Dados de entrada — configuração e ambiente](#1-dados-de-entrada--configuração-e-ambiente) +- [2. Dados persistidos — bancos e arquivos](#2-dados-persistidos--bancos-e-arquivos) +- [3. Estruturas de dados canônicas em trânsito](#3-estruturas-de-dados-canônicas-em-trânsito) +- [4. Catálogo por módulo — backend Python](#4-catálogo-por-módulo--backend-python) +- [5. Dados da camada API (DTOs, rotas, middleware)](#5-dados-da-camada-api) +- [6. Dados do frontend (React/JS) e contrato TS](#6-dados-do-frontend-reactjs-e-contrato-ts) +- [7. Ciclo de vida ponta-a-ponta de um dado](#7-ciclo-de-vida-ponta-a-ponta-de-um-dado) +- [8. Dados "perdidos na classe" / declarados-mas-não-usados](#8-dados-perdidos-na-classe--declarados-mas-não-usados) +- [9. Anomalias e inconsistências de dados](#9-anomalias-e-inconsistências-de-dados) + +--- + +## 1. Dados de entrada — configuração e ambiente + +### 1.1 Variáveis de ambiente (todas as lidas no código) + +| Var | Default | Tipo/coerção | Consumido em | +|---|---|---|---| +| `APP_ENV` | `"development"` | str, compara `"production"` | `config.py:16`, `api/main.py:67,195`, `routes/config.py:62` | +| `LOG_LEVEL` | `"INFO"` | str | `config.py:17` | +| `LOG_FORMAT` | `"text"` | text\|json | `config.py:18` | +| `GOOGLE_API_KEY` / `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` | `None` | str | `config.py:21-22`, `llm_client.py:51-54` | +| `LLM_ENABLED` | `false` | truthy (`1/true/yes/on`) | `llm_client.py:61` | +| `LLM_PROVIDER` | `"google"` | str | `llm_client.py:47` | +| `LLM_MODEL` | (default por provider) | str | `llm_client.py:76` | +| `EXCHANGE` | `"binance"` | str | `config.py:25`, `deps.py:83` | +| `EXCHANGE_API_KEY` / `EXCHANGE_API_SECRET` | `None` | str | `config.py:26-27`, `deps.py:85-86` | +| `EXCHANGE_TESTNET` | `true` | bool | `config.py:28`, `deps.py:84` | +| `EXCHANGE_DRY_RUN` | **obrigatório (sem default)** | bool; unset → `RuntimeError` | `exchange_client.py:41-48`, `routes/config.py:58` | +| `ORDER_ROUTING` | `"paper"` | paper\|live | `exchange_client.py:62-64` | +| `DRY_RUN_BASE_PRICE` | `"50000"` | float | `exchange_client.py:50` | +| `DRY_RUN_BASE_PRICES` | `""` | CSV `PAIR=PRICE` | `exchange_client.py:53` | +| `MARKET_PAIRS` | `BTC/USDT,ETH/USDT,SOL/USDT,BNB/USDT,XRP/USDT` | CSV | `pairs.py:21` | +| `SYMBOLS` | (unset → `BTC/USDT`) | CSV (subset de MARKET_PAIRS) | `orchestrator_loop.py:50` | +| `INITIAL_CAPITAL` | `10000` | float | `config.py:31`, `deps.py:24`, `hitl/orders.py:392` | +| `MAX_POSITION_SIZE_PCT` | `5.0` | float | `config.py:32`, `risk_agent.py:20`, `guardrails.py:13` | +| `STOP_LOSS_PCT` | `3.0` | float | `config.py:33`, `risk_agent.py:21` | +| `MAX_DAILY_LOSS_PCT` | `5.0` | float | `config.py:34`, `risk_agent.py:22` (**X** no RiskAgent) | +| `MAX_CONCURRENT_POSITIONS` | `3` | int | `config.py:35` | +| `AUTONOMY_LEVEL` | `1` (config) / `2` (hitl) | int (0-3 no hitl) | `config.py:38`, `hitl/config.py:64` | +| `HITL_APPROVAL_REQUIRED` | `true` | bool | `config.py:39` | +| `ORCHESTRATOR_INTERVAL_SECONDS` | `60` | int [10,3600] | `orchestrator_loop.py:74`, `routes/config.py:60` | +| `DATABASE_URL` | `sqlite:///./data/trading.db` | str; `postgres*`→PG | `config.py:42`, `db.py:35` | +| `CHROMA_PERSIST_DIR` | `./data/chroma` | str | `config.py:48` | +| `PROMETHEUS_PORT` | `9090` | int | `config.py:54` | +| `LEDGER_DIR` | `.buildtovalue/ledger` | path | `config.py:89`, `ledger.py:30`, `alerts.py:57` | +| `JOURNAL_PATH` | `data/trade_journal.json` | path | `journal/trade_journal.py:106` | +| `API_KEYS` | `""` (fail-open) | CSV allowlist | `api/main.py:53` | +| `CORS_ORIGINS` | `"*"` | CSV | `api/main.py:72,218` | +| `SENTRY_DSN` / `SENTRY_TRACES_SAMPLE_RATE` | `""` / `0.0` | str/float | `api/main.py:185,196` | +| `REDIS_URL` | (unset → in-memory) | str | `ratelimit.py:88` | +| `API_URL` / `API_KEY` / `REFRESH_INTERVAL` (dashboard) | `http://localhost:8000` / `""` / `5` | str/str/int | `dashboard/app.py:16-18` | +| Infra: `POSTGRES_DB/USER/PASSWORD`, `GF_SECURITY_ADMIN_USER/PASSWORD` | (compose) | str | `docker-compose.yml` | + +> Constantes de caminho (`config.py:85-90`): `PROJECT_ROOT` (=`src/`), `DATA_DIR`, `LOGS_DIR`, `LEDGER_DIR`, `CONFIG_DIR`. Settings inner `Config`: `env_file=".env"`, `extra="ignore"`. + +### 1.2 Configuração YAML (dados de política) + +**`config/strategies/risk_params.yaml`** — lido por `routes/risk.py:28`. Seções e chaves-dado: +- `position_limits`: `max_position_size_pct 5.0`, `min_position_size_pct 1.0`, `max_concurrent_positions 3`, `max_exposure_per_asset 15.0` +- `stop_loss`: `mandatory true`, `default_pct 3.0`, `max_allowed_pct 5.0`, `trailing_enabled false` +- `take_profit`: `mandatory false`, `default_pct 9.0`, `min_risk_reward_ratio 1.5` +- `loss_limits`: `max_loss_per_trade_pct 3.0`, `max_daily_loss_pct 5.0`, `max_weekly_loss_pct 10.0`, `max_monthly_loss_pct 15.0`; `circuit_breaker{enabled, trigger_daily_loss_pct 4.0, trigger_consecutive_losses 3, cooldown_period_hours 24}` +- `diversification`, `leverage{allowed false, max 1.0}`, `order_types{allowed[limit], prohibited[market,stop_market], slippage{max 0.5, simulated_in_paper 0.2}}` +- `market_conditions{volatility.max 80, liquidity.min_24h_volume_usd 1e8, trend.min_indicators_aligned 2}` +- `capital{initial 10000, reserve_pct 20, max_deployed_pct 80}`, `performance_targets{sharpe 1.5, max_drawdown 15, win_rate 55, profit_factor 1.8}` +- `signal_validation{min_confidence 0.6, filters[volume_surge,spread_check,momentum_confirmation]}`, `fees{exchange_fee_pct 0.1, slippage 0.2, break_even 0.6}` +- `strategies{dca_optimized(size 2.0,entries 3,spacing 1.0), grid_trading(disabled), momentum(disabled)}`, `mode{current paper_trading, transition_criteria...}` + +**`config/agents/constitution.yaml`** (v6.1): `agents.{strategy(autonomy 2, chain_of_thought),risk(autonomy 3, reflection),execution(autonomy 1, react)}` cada com role/goals/capabilities/constraints/tools/performance_targets; `collaboration.flow: strategy→risk→hitl→execution→ledger`; `global_guardrails.{security,risk_management,operational}`; `audit.ledger_path .buildtovalue/ledger/trades.jsonl`. + +**`config/prometheus.yml`**: `scrape_interval 15s`, job `app` → `app:8000`. +**`metrics.yaml` / `metrics-advanced.yaml`**: catálogos `{metric,target,alert,dashboard}` (technical/business/ai_squad/agent). **`config/prompts/advanced_agent_prompts.py`**: `AGENT_PROMPTS: dict[str,str]` com 4 prompts (`architect_with_cot`, `developer_with_react`, `auditor_with_reflection`, `consensus_facilitator`). + +### 1.3 Dados de entrada externos (runtime) +- **Exchange (CCXT)** — OHLCV `[ts_ms,o,h,l,c,v]`, ticker, order book, balance, ordens (ver §4.8 `exchange_client`). Em dry-run vêm de `synthetic_market` (determinístico). +- **LLM** — entrada `(system, user)` strings; saída `str` ou JSON `{confidence, thesis}` (Strategy) / `{note, hidden_risk}` (Risk). +- **HTTP** — corpo/query/headers das rotas `/v1/*` (ver §5). + +--- + +## 2. Dados persistidos — bancos e arquivos + +### 2.1 Tabelas SQLite/Postgres (migrations) + +| Tabela | Colunas (tipo · constraint) | Índices | Origem | +|---|---|---|---| +| **orders** (`001`) | `id TEXT PK`, `pair TEXT NN`, `side TEXT NN CHECK(buy\|sell)`, `quantity REAL NN >0`, `price REAL NN >0`, `strategy TEXT`, `agent_id TEXT`, `confidence REAL`, `reason TEXT`, `critical INT NN=0`, `position_size_pct REAL`, `stop_loss REAL`, `take_profit REAL`, `status TEXT NN='pending' CHECK(pending\|approved\|rejected\|cancelled\|filled)`, `operator_note TEXT`, `operator_id TEXT`, `auto_approved INT NN=0`, `created_at TEXT NN`, `resolved_at TEXT`, `filled_at TEXT` | `status`, `created_at` | HITL bridge | +| **cycle_events** (`001`) | `id INT PK AUTOINC`, `agent_id TEXT NN`, `cycled_at TEXT NN` | `(agent_id,cycled_at)` | contador de ciclos | +| **journal_entries** (`002`) | `id INT PK AUTOINC`, `setup TEXT NN`, `emotion_before INT NN CHECK 1..10`, `emotion_after INT CHECK 1..10`, `stop_defined INT NN=0`, `plan_followed INT NN=0`, `pnl_pct REAL`, `note TEXT`, `created_at TEXT NN=now` | `created_at`, `emotion_before` | diário | +| **backtest_jobs** (`003`) | `id TEXT PK`, `status TEXT NN CHECK(running\|done\|error)`, `config_json TEXT`, `result_json TEXT`, `error TEXT`, `created_at TEXT NN`, `completed_at TEXT` | `status`, `created_at` | jobs async | +| **open_positions** (`004`) | `order_id TEXT PK`, `symbol TEXT NN`, `side TEXT NN`, `entry_price REAL NN`, `quantity REAL NN`, `stop_loss REAL`, `take_profit REAL`, `opened_at TEXT NN` | `symbol` | book paper | +| **circuit_breaker_state** (`004`) | `id INT PK CHECK(id=1)` (singleton), `tripped_at REAL`, `consecutive_losses INT NN=0`, `daily_loss_pct REAL NN=0.0` | — | breaker | +| **ledger_events** (criada em `ledger.py:48`) | `id {autoincrement_pk}`, `timestamp TEXT NN`, `event_type TEXT NN`, `data TEXT NN` (JSON) | `event_type` | event store | +| **schema_migrations** (`db.py:205`) | `version TEXT PK`, `applied_at TEXT NN` | — | migrations | + +> **Sem foreign keys** entre tabelas (design de coordenação cross-process). `db.py` abstrai `connection()`; PG traduz `?`→`%s` (`db.py:102`); `_Row`/`_PgConn` adaptam PG à API sqlite3. + +### 2.2 Event store — payloads do `ledger_events.data` (JSON) + +Cada `log_*` de `TradingLedger` grava `(timestamp, event_type, data)`: + +| Método | `event_type` | chaves de `data` | linha | +|---|---|---|---| +| `log_signal(agent,signal)` | `signal_generated` | `agent`, `signal`(dict) | `ledger.py:74` | +| `log_validation(agent,validation)` | `risk_validation` | `agent`, `validation`(dict) | `:78` | +| `log_execution(agent,execution)` | `order_executed` | `agent`, `execution`(dict) | `:82` | +| `log_hitl_approval(approved,order,user)` | `hitl_approval` | `approved`(bool), `order`(dict), `user`(str="default") | `:86` | +| `log_process_event(case_id,activity,actor,attributes)` | `process_event` | `case_id`, `activity`, `actor`, `attributes`(dict) | `:90` | +| `log_fill(...)` | `order_fill` | `order_id`, `symbol`, `side`(lower), `price`, `quantity`, `notional`(=p·q), `fee`, `strategy`, `agent` | `:116` | +| `log_position_closed(...)` | `position_closed` | `order_id`, `symbol`, `side`(lower), `entry_price`, `exit_price`, `quantity`, `fee`, `gross_pnl`, `pnl`(=gross−fee), `pnl_pct`, `opened_at` | `:149` | +| `log_decision(event_type,data)` | arbitrário | ex.: CircuitBreaker → `circuit_breaker_tripped`/`_reset` com `{reason}` | `:60` | + +**Atividades XES** (via `log_process_event`): `agent_cycle_started/completed/failed` (loop), `order_submitted/approved/rejected/cancelled/filled` (OrderStore), `hitl_level_changed` (HITLConfigStore). + +### 2.3 Arquivos append-only (JSONL/JSON) + +| Arquivo | Registro (chaves) | Escrito por | +|---|---|---| +| `trades.jsonl` (legado) | — (histórico) | `ledger.py` | +| `alerts.jsonl` | `severity, type, message, agent_id, pair, auto_action, id, occurred_at` | `AlertStore.append` (`alerts.py`) | +| `agent_memories.jsonl` | `agent, decision`(dict), `timestamp` | `AgentMemorySystem.remember_decision` (`agent_memory.py:39`) | +| `trade_journal.json` | `{order_id: asdict(TradeEntry)}` (objeto único) | `TradeJournal._save` (`trade_journal.py:211`) | +| `ab_tests.jsonl` | `{timestamp, winner, scores, statistical_significance}` — **`str()` repr, não JSON** | `ab_testing.py:60` | +| `loop_heartbeat.json` | `{ts, cycle_id}` | `heartbeat.write_heartbeat` | + +--- + +## 3. Estruturas de dados canônicas em trânsito + +Estes são os **dicts que fluem entre módulos** — o dado "vivo" do sistema. Chaves exatas e origem. + +### 3.1 `task` (entrada de cada `agent.execute`) +- **Strategy** `{symbol, timeframe}` — `squad_orchestrator.py:207` +- **Risk** `{signal, portfolio:{available_capital, capital_base}}` — `:241` +- **Execution** `{signal, human_approved, quantity}` — `:272` + +### 3.2 `analysis` (StrategyAgent._analyze_market → strategy_agent.py:160) +`symbol, timeframe, current_price, trend`(bullish\|bearish\|None)`, regime, eligible_strategies:list[str], indicators:TechnicalIndicators, support_resistance, fibonacci_levels:dict, volume_profile, rsi_divergence, macd_divergence, market_extreme, stub_used:bool, _ohlcv:list` (I; `_ohlcv` removido antes de logar). Versão stub (`:446`) espelha as chaves com valores fixos (`regime="sideways"`, `eligible_strategies=["grid","dca"]`). + +### 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.5 Resultados dos agentes +- **strategy_result** (`strategy_agent.py:89`): `success, agent, signal, confidence, analysis, llm_used, llm_thesis, stub_used` +- **risk_result** (`risk_agent.py:64`): `success, agent, approved, validation:{approved,issues,warnings,confidence,[requires_review],refined,reflection_applied}, confidence` +- **execution_result** (`execution_agent.py:50`): `success, agent, order_id, executed_price, fee, confidence` +- **analyze_and_trade** (`squad_orchestrator.py:297`): `success, order_id, signal, confidence` — ou `{success:False, reason, ...}` nos caminhos de rejeição +- **run_cycle** (`orchestrator_loop.py:149`): `{cycle_id, ran:list[str], failures:list[{agent_id,error}]}` + +### 3.6 `Order` (dataclass HITL — hitl/orders.py:48) +`pair, side, quantity, price, strategy, agent_id, confidence, reason, critical=False, position_size_pct=0.0, stop_loss=None, take_profit=None, status=pending, operator_note=None, operator_id=None, auto_approved=False, id="ord_"+hex8, created_at, resolved_at=None, filled_at=None`; property `notional=price*quantity`; `guardrail_view()→{position_size_pct,action=side.upper(),entry_price=price,stop_loss,take_profit}`. + +### 3.7 `_open_positions[order_id]` (book paper — squad_orchestrator.py:374) +`{symbol, side(lower), entry_price, quantity, stop_loss|None, take_profit|None, opened_at(iso)}` — espelhado em SQLite via `PositionStore`. + +--- + +## 4. Catálogo por módulo — backend Python + +> Formato: por arquivo/classe, os dados com marca **E/S/I/P/X**. Atributos de estado listados no `__init__`. + +### 4.1 `src/agents/` + +**`base_agent.py` — BaseAgent** (`:17`) +- **I** `self.agent_type`(:21), `self.agent_id`(uuid,:22), `self.created_at`(:23), `self.confidence_threshold=0.6`(:24), `self.memory`(:29), `self.tools=[]`(:35) +- `log_decision` → **I/S** `entry={timestamp,agent,agent_id,decision}`(:48) → grava em memory + retorna + +**`strategy_agent.py` — StrategyAgent** (detalhado em §3.2–3.5) +- **I** `_sr_detector`, `_div_detector`, `_strategy_cache:dict`(key→instância), `_llm` +- `_llm_assess` **I** `context`{symbol,regime,trend,action,strategy,entry_price,stop_loss,take_profit,deterministic_confidence,rsi,macd_hist,bb_percent,volume_ratio}(:344) → LLM → `(blended,thesis)` +- `_stub_analysis` **I** `stub_ind` TechnicalIndicators hardcoded (current_price=50000, rsi=45, bb_percent=0.25, atr=50, volume_ratio=1.2...)(:434) +- confiança: blend `0.6·strategy + 0.4·agent` (:57), com LLM `0.5/0.5` (:373), clamp [0.10,0.95] + +**`risk_agent.py` — RiskAgent** +- **I** `guardrails=GuardrailSystem()`(:18), `max_position_size_pct`(:20), `stop_loss_pct`(:21), **X** `max_daily_loss_pct`(:22 — nunca lido), `_llm` +- `_validate_signal` **E** signal.{position_size_pct,entry_price,stop_loss}, portfolio.{available_capital,capital_base} → **S** `{approved,issues,warnings,confidence}`(:119) +- `_reflect_on_validation` **S** `{missed_anything,too_strict,suggestions,[llm_note]}` +- `_refine_validation` **S** `final` = validation + `{[requires_review],refined,reflection_applied}` + +**`execution_agent.py` — ExecutionAgent** +- **I** `exchange`, `paper_trading=True` +- `_simulate_order` **E** signal.{symbol,action}, quantity → exchange.create_order → **S** `{success,order_id,executed_price,fee,status,message}`(:112) + +**`registry.py`** +- **I** `AGENT_REGISTRY` (5 AgentInfo: strategy/risk/execution implemented, recovery/exploration stub) (:38) +- **I** `AGENT_PARAMS` catálogo estático por agente (confidence_threshold, tools, reasoning_pattern, autonomy_level, e p/ risk: max_position_size_pct/stop_loss_pct/max_daily_loss_pct/min_risk_reward_ratio) (:52) +- **I** `self._cycles:dict`, `self._last_action:dict`, `self._cycles_date` — **P** grava `cycle_events` +- `status()` **S** `{id,domain,implemented,description,status,cycles,last_action_at,params}` + +**`behavioral_guard.py`** — constantes REVENGE_LOSS_STREAK=2, REVENGE_SIZE_MULTIPLIER=1.50, EUPHORIA_WIN_STREAK=3, EUPHORIA_SIZE_MULTIPLIER=1.20, OVERCONFIDENCE_MARGIN=0.15. `BehavioralAlert{detected,kind,message,action,recommended_size_multiplier,recommended_confidence_cap}`. `check` **E** new_trade.{position_size_pct,confidence}, trade_history[].{position_size_pct,pnl}, win_rate. + +**Agentes de engenharia** (não no pipeline de trading; executados por `UnifiedOrchestrator`): +- `architect_agent` **S** `{reasoning{problem_analysis,constraints,applicable_patterns,trade_offs,recommendation,confidence=0.85,reasoning_steps},plan{goal,architecture,components,patterns,risks,mitigations},adr:str}` +- `developer_agent` **I** `history[{iteration,thought,action,observation}]`, max_iterations=5 → **S** `{status,history,final_output,confidence}` +- `auditor_agent` **S** audit `{issues,warnings,passed,confidence}`; validate_results `{approved,confidence,issues,agent_scores}` +- `designer_agent` **S** `{pattern,theme,components,colors{primary,secondary,background},typography,responsive,accessibility,confidence}` +- `ops_agent` **S** deploy `{strategy,stages,rollback_plan,health_checks,scaling{min_instances=2,max_instances=10,target_cpu=70}}`, monitoring `{metrics,alerts,dashboards,logging}` +- `supervisor_agent` `SupervisorAgent{orchestrator:Agent, specialists:list[Agent]}` → `run(task:str)→str` +- `recovery_agent`/`exploration_agent` dataclasses `{remediation_tool}`/`{scanner_tool}` → `arun→str` + +### 4.2 `src/orchestration/` + +**`squad_orchestrator.py`** +- **CircuitBreaker** — constantes DAILY_LOSS_LIMIT_PCT=4.0, CONSECUTIVE_LOSS_LIMIT=3, COOLDOWN_SECONDS=86400. **I/P** `_tripped_at`, `_consecutive_losses`, `_daily_loss_pct` (persistidos em `circuit_breaker_state`). `record_trade_result(pnl_pct)` acumula; `_trip/_reset` gravam ledger `circuit_breaker_tripped/_reset{reason}`. +- **SquadOrchestrator** — **I** agentes, ledger, circuit_breaker, `_last_order_ref`, `_open_positions`, `_positions` (§3.7). Pipeline `analyze_and_trade` (§3.5, §7). `_available_capital = initial + _realized_pnl − open_notional`. `_position_quantity = (capital·size_pct/100)/price`. Emite `Alert` type=`data_fallback`/`risk_rejection` (severity high). + +**`orchestrator_loop.py`** — constantes MIN/MAX/DEFAULT_INTERVAL=10/3600/60. `run_cycle` **I** `cycle_id`, `ran_agents`, `failures`; grava XES `agent_cycle_started/completed/failed`. `from_env` monta ExchangeClient/ledger/registry/OrderStore/handler/SquadOrchestrator. `AgentExecutionError.agent_id`. + +**`position_store.py`** — `PositionStore.load_all()→{order_id:{symbol,side,entry_price,quantity,stop_loss,take_profit,opened_at}}`; `save/load_circuit_state→{tripped_at,consecutive_losses,daily_loss_pct}`. + +**`heartbeat.py`** — `{ts,cycle_id}` (HEARTBEAT_FILENAME="loop_heartbeat.json"). + +**`unified_orchestrator.py`** (squad de engenharia, fora do trading) — **I** planner/router/consensus/parallel/evaluator/autonomy/memory + **X** `sandbox`, `chain_manager` (criados, nunca usados). `execute_complex_task` **S** `{success,task_id,plan,consensus,results,validation,confidence}`. + +### 4.3 `src/strategies/` +- `STRATEGY_REGISTRY = {"dca":DCA, "grid":Grid, "mean_reversion":MeanReversion}` (`__init__.py:17`) +- **MeanReversion** params `{rsi_oversold=30, rsi_overbought=70, risk_reward=2.0, atr_mult=2.0}`. **E** `market_data.{indicators(rsi,bb_lower/upper/middle,atr,volume_ratio,stochastic_k),current_price,regime}`. **S** `{action,direction,entry,stop_loss,take_profit,position_size_pct=2.0,confidence,reason}` ou HOLD `{action,confidence=0.05,reason}`. +- **Grid** params `{grid_levels=10,grid_spacing_pct,total_size_pct,size_per_level}`. **E** `indicators.{ema_fast,ema_slow,atr,bb_middle,volume_ratio}, volume_profile.poc, regime`. **S** `{action,direction,entry,stop_loss,take_profit=None,position_size_pct,total_position_size_pct,grid_levels:{buy:[],sell:[]},confidence,reason}`. +- **DCA** params `{position_size_pct=2.0,num_entries=3,spacing_pct=1.0,stop_loss_pct=3.0,rsi_oversold=35,min_volume_ratio=0.8}`. **E** flat `market_data.{symbol,current_price,ma_20,ma_50,rsi,macd_histogram,at_bollinger_lower,volume_24h,avg_volume}`. **S** `{action:"DCA_ENTRY"|"WAIT",signal:{symbol,strategy,action,entries:[{entry_number,price,size_pct}],avg_entry_price,stop_loss,take_profit,total_position_size_pct,risk_reward_ratio=3.0,timestamp},confidence,reasoning}`. + +### 4.4 `src/analysis/` (dataclasses = DTOs de análise) +- **TechnicalIndicators** (20 campos `float|None`): sma_20/50/200, ema_fast, ema_slow, rsi, stochastic_k/d, macd_line/signal/hist, bb_upper/middle/lower, bb_percent, atr, volume_ratio, obv, current_price (`indicators.py:18`). OHLCV in = `[ts_ms,o,h,l,c,v]`. `MIN_CANDLES=50`. +- **DivergenceResult** `{detected,kind,description}`; **TrendAlignment** `{primary,secondary,minor,aligned,direction}`; MultiTimeframeTrend TIMEFRAMES `{primary:1w,secondary:1d,minor:1h}`. +- **SRLevel** `{price,kind,strength,last_touch}`; **SRLevels** `{support,resistance,zones:[SRLevel]}`; `fibonacci_levels→{"0.0%".."100.0%":price}`. +- **VolumeProfileResult** `{poc,value_area_high,value_area_low,low_volume_nodes:[]}`. +- **PatternResult** `{pattern,confidence,direction,target_price,candle_index,description}`. +- **regime_detector** (funções): `detect_regime→"unknown"|"chaotic"|"strong_uptrend"|"strong_downtrend"|"sideways"`; `_REGIME_STRATEGY_MAP{strong_uptrend:[dca],strong_downtrend:[],sideways:[grid,dca],chaotic:[],unknown:[dca]}`; `detect_market_extreme→"EUFORIA…"|"PÂNICO…"|None`. + +### 4.5 `src/backtest/` +- **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. + +### 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. +- **PositionSizer** `compute(entry,stop)→size%` clamp [0.5,5.0]. +- **CapitalProtections** `check(daily,weekly,monthly)→ProtectionResult{status:DrawdownStatus,message,size_multiplier(1.0/0.5/0.0),can_trade}`. Limites DAILY=3.0/WEEKLY=6.0/MONTHLY=15.0/WARN=2.4. **DrawdownStatus** = OK/WARN/DAILY_PAUSE/WEEKLY_REDUCED/MONTHLY_SUSPEND. + +### 4.7 `src/safety/` +- **GuardrailSystem** `validate_order(order)→(bool,list[str])`. **E** order.{position_size_pct,stop_loss,entry_price,action,take_profit,market_context:{atr,bb_middle,volume_ratio}}. Regras: position≤max, stop obrigatório e do lado certo, risk_reward≥2.5, market atr/bb_middle≤0.10 e volume_ratio≥0.3. `alert_sink:Callable[[str],None]`. +- **SecurityConfig** (dataclass) `{MAX_POSITION_SIZE_PCT=5.0,MAX_STOP_LOSS_PCT=3.0,MAX_DAILY_LOSS_PCT=5.0,MAX_CONCURRENT_POSITIONS=3,MAX_EXECUTION_TIME_SECONDS=30,FORBIDDEN_PATTERNS[leverage.*10x,margin.*call,liquidation,all.*in,100%.*position]}`; ClassVars FORBIDDEN_TOOL_NAMES(rm,delete_resource,format_disk,drop_database), SENSITIVE_PARAM_PATTERNS(rm -rf,drop table,delete from,format), ALLOWED_EXCHANGES{binance,coinbase,kraken}, HIGH_RISK_ACTIONS. `validate_order` **E** order.{position_size_pct,notes,exchange}; `validate_tool_call(tool_name,params)`. + +### 4.8 `src/core/` (resumo — detalhe completo em §1, §2) +- **config.Settings** (23 campos env, §1.1); funções `get_risk_params/get_resource_limits/get_autonomy_config`. +- **db** `connection()`, `upsert`, `init_db`, `_Row`, `_PgConn` (§2.1). +- **ledger.TradingLedger** — event store (§2.2); read `_row_to_entry→{timestamp,event_type,data}`. +- **metrics.PortfolioMetrics** (14 campos §5) derivado de `position_closed`+`order_fill`. `_PERIOD_DAYS{1d,7d,30d,90d,all}`. +- **exchange_client.ExchangeClient** — `fetch_ticker→{symbol,last,close,bid,ask,timestamp,info}`, `fetch_ohlcv→[[ts,o,h,l,c,v]]`, `fetch_order_book→{bids,asks}`, `fetch_balance→{USDT:{free,used,total},BTC:{...}}`, `create_order/_create_paper_order→{id="PAPER_"+hex8,symbol,type,side,amount,price,average,filled,remaining,status,fee:{cost,currency},timestamp,datetime,info}`. `simulated_orders:dict`. Slippage 0.002, fee 0.001. +- **alerts** — Alert `{severity,type,message,agent_id,pair,auto_action,id,occurred_at}`; AlertStore JSONL; AlertBus fila `asyncio.Queue(maxsize=100)`; `make_guardrail_sink`. SEVERITIES=(low,medium,high,critical). +- **llm_client.LLMClient** `reason(system,user)→str?`, `reason_json→dict?`; `_DEFAULT_MODELS{google:gemini-1.5-flash,openai:gpt-4o-mini,anthropic:claude-haiku-4-5-...}`. +- **ratelimit** InMemory `_buckets:dict[str,list[float]]` / Redis `INCR+pexpire` (key `ns:key`); janela 60s. +- **pairs** DEFAULT_PAIRS; `parse_pairs`/`allowed_pairs`/`is_allowed`. +- **synthetic_market** `_DEFAULT_BASES{ETH:3000,SOL:150,BNB:600,XRP:0.5}`; `synthetic_ticker/ohlcv/order_book`. +- **request_context** `request_id_var:ContextVar(default="-")`; RequestIdLogFilter. +- **safe_agent_base** — Plan/PlanCreation/AgentExecution dataclasses; GuardrailSuite + 4 guardrails; MemoryManager. + +### 4.9 `src/hitl/` +- **OrderStore** (§3.6, §5.6): `submit/resolve/mark_filled/cancel/wait_for_decision`. Auto-approve quando `notional≤threshold & !critical & risk_ok`. `make_approval_handler` mapeia signal→Order (§7). +- **config.HITLConfigStore** `snapshot()→{current_level,threshold_usdt,level_description,min_level,max_level,pending_orders_count,human_approved_today,human_rejected_today,last_changed_at,last_changed_by,levels:[{level,threshold_usdt,description}]}`. AUTONOMY_LEVELS {0:0.0,1:500,2:1000,3:5000}, DEFAULT=2. +- **progressive_autonomy.ProgressiveAutonomyManager** (não ligado às rotas): `autonomy_levels{0..3}`, `agent_trust_scores:dict`, `action_history:[{timestamp,agent,action,success,trust_score,autonomy_level}]`; score +0.02/−0.1; result `{approved,feedback,modifications}`. + +### 4.10 `src/memory/`, `src/journal/`, `src/tools/`, `src/evaluation/`, misc +- **AgentMemorySystem** — **X** `short_term:dict` (nunca escrito); episodic JSONL `{agent,decision,timestamp}`; Chroma opcional. `recall/recall_similar`. +- **IntelligentForgetting** — `MemoryStore.data:{key:{relevance}}`; remove relevance<0.1. +- **TradeJournal** — TradeEntry (identidade+setup+psicologia+resultado, §4 detalhe); JournalStats (9 campos); JSON `{order_id:asdict}`. +- **MCPToolRegistry** — `tools:dict[str,callable]`; core tools `analyze_code_quality→{file,complexity,coverage,issues}`, `generate_tests→str`, `optimize_performance→{suggestions,estimated_gain}`. +- **rag_tool** VectorDBClient `_collection_name="btf-default"`; RAGTool.retrieve. +- **sandbox** DockerSandbox `{image=python:3.11-slim,network_disabled=True}`; SecureToolSandbox `{docker_sandbox,execution_timeout=30,` **X** `memory_limit_mb=512,` **X** `cpu_quota=0.5,allow_unsandboxed=False}`; result `{tool,output|params,sandboxed,[message]}`. +- **evaluation** — `continuous_eval.ContinuousEvaluator` (**X** `baseline`) `evaluate_trajectory→{task_completion,efficiency,safety}` (lê trajectory.{completed,resource_usage,guardrail_violations}); `continuous_evaluator.ContinuousEvaluator` (dataclass) metrics `{task_success_rate,average_confidence,` **X** `user_satisfaction,error_rate,response_time_p95}`; `ab_testing` result `{winner,scores{A,B},statistical_significance}`. +- **consensus.WeightedConsensusEngine** `agent_weights{architect,developer,auditor,designer,ops:{domínio:peso}}`; `reach_consensus→{decision,consensus_strength,decision_maker,dissenting_opinions:[{agent,score}]}`. +- **routing.LearningRouter** `route_performance{route_hash:{attempts,successes,total_latency,success_rate,avg_latency}}`; `smart_route→request["preferred_route"]|"default"`. +- **planning** — `hierarchical_planner` plan `{goal,steps,alternatives,confidence}`; `adaptive_planner` plan `{plan_id,task_id,goal,steps:[{step,action,description,estimated_time,dependencies,can_fail}],estimated_duration,created_at,adaptive,confidence}`; `adaptive_replanner` (dup) `{plan,result,attempts}`. +- **chains.ChainStep** `{name,execute}` / ResilientPromptChain `{steps,max_retries=3}`. **parallel.ParallelResourceManager** `limits{max_concurrent:5}`. +- **protocols.MCPServer** card `{name,skills,endpoint}`, handle_request(type discover|execute). **protocols.SquadOrchestrator** `delegate_task→{plan,implementation}`. +- **utils.observability** SpanRecord `{operation,metadata,start_time,reasoning_log,end_time}`; `export_trajectory→{trace_id,spans:[...]}`. **utils.MemoryStore** key/value. **tool_utils.wrap_sync_tool**. +- **main.py** `MemoryStore.set("usuario","João")`; **orchestrator.py** AgentOrchestrator (base_agent+evaluator+rag_tool+observer+mcp_server) `execute_with_monitoring→(AgentExecution,evaluation)`. + +--- + +## 5. Dados da camada API + +### 5.1 Middleware (dados lidos/escritos, em ordem de execução) +| Middleware | Dado | `arquivo:linha` | +|---|---|---| +| RequestId | lê/escreve header `X-Request-ID`; grava `request_id_var` | `request_id.py:23-29` | +| Prometheus | Counter `http_requests_total{method,path,status}`, Histogram `http_request_duration_seconds{method,path}`; gauges de domínio `criptotrade_{open_positions,total_trades,portfolio_value_usdt,realized_pnl_usdt,win_rate,sharpe_ratio}` | `observability.py:18-89` | +| RateLimit | `request.client.host` → key `ip:w|r`; WRITE=30/READ=200/min; 429 `{error,message,retry_after,docs}` | `main.py:128-164` | +| SecurityHeaders | escreve CSP, X-Frame-Options=DENY, X-Content-Type-Options=nosniff, Referrer-Policy, HSTS max-age=300, X-XSS-Protection=0 | `main.py:109-125` | +| CORS | `CORS_ORIGINS`; métodos GET,POST,PATCH; headers X-API-Key,Content-Type | `main.py:216` | +| APIKey | lê header `X-API-Key`; PUBLIC_PATHS isentos; 401 `{error,message,docs}` | `main.py:84-106` | + +**Handlers de exceção**: 422 `{error:validation_error,message,field,docs}`; 404 `{error:not_found,message,docs}`; 500 `{error:internal_error,message,docs}` (`main.py:265-313`). + +### 5.2 Envelope e DTOs (schemas.py) — contrato de saída/entrada +- **APIResponse[T]** `{data:T, meta:Meta?, links:Links?(alias _links)}`; **Meta** `{total,page=1,per_page=20,timestamp}`; **Links** `{self,related?}`; **ErrorResponse** `{error,message,field?,docs="/v1/docs"}`. +- **PortfolioMetricsOut** (14): sharpe_ratio?, win_rate?, max_drawdown, profit_factor?, total_trades, open_positions, portfolio_value_usdt, pnl_period_usdt, pnl_period_pct, exposure_pct, initial_capital_usdt, period, calculated_at, has_data. +- **OrderCreate** (E): pair(regex `^[A-Z]{2,10}/[A-Z]{2,10}$`), side(OrderSide), quantity(>0), price(>0), strategy(min1), agent_id(min1), confidence(0-1), reason(min10), critical=False, position_size_pct(0-100], stop_loss(>0, **obrigatório**), take_profit?(>0). +- **OrderDecisionPatch** (E): decision(`^(approve|reject)$`), operator_note?(**obrigatório em reject**), operator="operator". +- **OrderOut** (S): id,pair,side,quantity,price,notional,status,strategy,agent_id,confidence,reason,critical,auto_approved,operator_note?,operator_id?,position_size_pct?,stop_loss?,take_profit?,rr?,created_at,resolved_at?,filled_at?. +- **HITLConfigOut**, **AutonomyLevelOut/Patch**, **AgentStatusOut/ConfigOut**, **ProcessEventOut**{case_id,activity,actor,timestamp,attributes}, **AlertOut**, **ClosedTradeOut**, **EquityPoint**{t,equity,drawdown}. +- Mercado: **CandleOut**{t,o,h,lo,c,v} (nota: `lo`, não `l`), **TickerOut**, **MacdOut/StochOut/BollingerOut**, **IndicatorsOut** (rsi,macd,stoch,bb,atr,atr_pct,ema9/21,sma20/50/200,obv_trend,volume_ratio,current_price,as_of), **RegimeOut**, **SRLevelOut/LevelsOut**, **VolumeProfileBin/Out**, **PatternOut**, **ConfidenceFactor**, **SignalOut**{action,entry,stop,take_profit,position_size_pct,rr,strategy,confidence,reason,confidence_factors,valid_until,as_of}, **TFSnapshot**, **ConfluenceOut**. +- Risco: **ProtectionOut**{scope,value,limit,status,action}, **CircuitBreakerOut**{status,triggers,cooldown_hours,cooldown_remaining}, **KellyOut** (9 campos), **RiskConfigOut** (14 campos), **RiskConfigPatch** (7 opcionais + confirm). +- Backtest: **BacktestConfigIn**{strategy=dca,pair=BTC/USDT,initial_capital=10000,commission_pct=0.1,slippage_bps=5,monte_carlo_sims=1000}, **BacktestResultOut**, **MonteCarloOut**, **WalkForwardFold/Out**, **BacktestJobOut**{job_id,status,result?,error?}. +- Journal: **JournalEntryCreate**{setup(min3),emotion_before(1-10),emotion_after?(1-10),stop_defined,plan_followed,pnl_pct?,note?}, **JournalEntryOut** (+id,created_at), **EmotionBand**, **JournalMetricsOut**. +- Config: **ConfigOut**{exchange,dry_run,initial_capital,orchestrator_interval_seconds,autonomy_level,app_env}, **ConfigPatch**, **AlertsConfigPatch**{revenge_size_multiplier,euphoria_size_multiplier,overconfidence_margin,risk_of_ruin_alert_pct}. + +### 5.3 Rotas `/v1/*` — entradas (query/path/body) → saída +| Rota | Entrada | Saída | +|---|---|---| +| `GET /v1/metrics` | period(1d\|7d\|30d\|90d\|all), symbol? | APIResponse[PortfolioMetricsOut] | +| `GET /v1/metrics/equity` | period, symbol? | List[EquityPoint] | +| `GET/PATCH /v1/hitl/config` | (PATCH) AutonomyLevelPatch; level 3 requer confirm | HITLConfigOut | +| `GET /v1/orders` | status?,pair?,limit(1-500),offset | List[OrderOut] + Meta | +| `POST /v1/orders` | OrderCreate | OrderOut (201 filled/202 pending/422 rejected) | +| `PATCH /v1/orders/{id}/status` | OrderDecisionPatch | OrderOut (404/409) | +| `GET /v1/agents[/{id}][/config]` | path id | AgentStatusOut/ConfigOut (404/501) | +| `GET /v1/process/events` | case_id?,limit(1-1000) | List[ProcessEventOut] | +| `GET /v1/alerts` (SSE) | severity?,replay(0-200) | EventSource {alert,heartbeat} | +| `GET /v1/alerts/history` | severity?,since?,limit,page | List[AlertOut] | +| `GET /v1/market/pairs` | — | List[str] | +| `GET /v1/market/{pair}/{candles,ticker,indicators,regime,levels,volume-profile,patterns,signal,confluence}` | tf,limit,bins? | DTO respectivo (503 se dados indisponíveis) | +| `GET /v1/risk/{protections,circuit-breaker,kelly,config}` · `PATCH /v1/risk/config` | RiskConfigPatch (confirm) | DTOs de risco | +| `POST /v1/backtest/{run,montecarlo,walkforward}` · `GET /v1/backtest/jobs/{id}` | BacktestConfigIn | BacktestJobOut/MonteCarloOut/WalkForwardOut | +| `GET/POST /v1/journal` · `GET /v1/journal/metrics` | JournalEntryCreate | JournalEntryOut / JournalMetricsOut | +| `GET /v1/trades/closed` | symbol?,limit,offset | List[ClosedTradeOut] | +| `GET/PATCH /v1/config` · `PATCH /v1/agents/{id}/config` · `PATCH /v1/alerts/config` | ConfigPatch / params / AlertsConfigPatch | ConfigOut / AgentConfigOut / Dict | + +### 5.4 Providers de dependência (`deps.py`, `@lru_cache`) +`get_ledger`→TradingLedger, `get_alert_store/bus`, `get_hitl_store`(wire pending_orders_provider), `get_order_store`(threshold+guardrails+alert_sink), `get_agent_registry(db_path)`, `get_exchange_client`, `get_metrics_calculator`(fresco). `_runtime_overrides:{INITIAL_CAPITAL,ORCHESTRATOR_INTERVAL_SECONDS}` (in-memory, `routes/config.py:24`). + +--- + +## 6. Dados do frontend (React/JS) e contrato TS + +### 6.1 `apiClient.js` — CT_API (window.CT_API) +Globais: `window.API_BASE`(default localhost:8000), `window.API_KEY`(header X-API-Key), `window.USE_MOCK_DATA`. `req()` desembrulha `j.data ?? j`. ~40 métodos mapeando 1:1 as rotas `/v1` (getMetrics, getHITL/patchHITL, getOrders/createOrder/decideOrder, getAgents/getAgentConfig/patchAgentConfig, get{Ticker,Candles,Indicators,Regime,Levels,VolumeProfile,Patterns,Signal,Confluence}, get{Protections,CircuitBreaker,Kelly,RiskConfig}/patchRiskConfig, getEquity, getProcessEvents, run{Backtest,MonteCarlo,WalkForward}/getBacktestJob, getJournal/addJournalEntry/getJournalMetrics, getConfig/patchConfig, patchAlertsConfig, getAlertHistory, subscribeAlerts=SSE). Mercado reescreve `BTC/USDT`→`BTC-USDT`. +**CT_PAIR** store: localStorage `'ct.pair'` (default BTC/USDT); CustomEvent `'ct:pair'`. (Market usa `'ct.alerts'` para alertas de preço client-side.) + +### 6.2 `data.js` — CT.* (mock, quando USE_MOCK_DATA) +~35 entidades mock espelhando os DTOs: `symbol,candles,bb,regime,indicators,sr,volumeProfile,patterns,signal,confluence,confidenceBreakdown,capital,drawdown,equity,circuitBreaker,kelly,riskConfig,guardrails,behavioral,agents(6),strategies,orders(22),pendingOrders,hitl,journal(16),journalMetrics,journalScatter,heatmap,backtest,monteCarlo,walkForward,backtestConfig,alerts(6),alertThresholds`. (Campos detalhados por `data.js:64-403`.) + +### 6.3 Estado por tela (screen_*.jsx) — dado → CT_API +- **overview**: metrics(period),equity,orders — `getMetrics/getEquity/getOrders`. +- **market**: pair,tf,candles,bb,indicators,regime,levels,volumeProfile,patterns,signal,confluence,ticker — Promise.all; `submitOrder`→OrderCreate{pair,side,quantity,price,strategy,agent_id:'manual-ui',confidence,reason,position_size_pct,stop_loss,take_profit?}. +- **orders**: orders,statusF,sideF; mapeia mock stop→stop_loss etc. +- **risk**: protections,cb,kelly,equity. +- **hitl**: config,orders(pending); decide→{action,operator_note,operator_id}; setLevel→{level,reason,operator}. +- **agents**: agents,configAgent; getAgentConfig→params, patchAgentConfig(draft). +- **observability**: events agrupados por case_id; lê activity,timestamp,attributes{failures,duration_ms,ran}. +- **journal**: entries,metrics; form{setup,emotion_before,stop_defined,plan_followed,pnl_pct,note}. +- **backtest**: config,result,mc,wf,jobId; polling getBacktestJob 1500ms. +- **settings**: sysConfig,riskConfig,alertConfig,agents; patchConfig/patchRiskConfig/patchAlertsConfig. +- **app.jsx**: routing por hash; polling pendingCount 15s; SSE subscribeAlerts→toast (severity critical/high). AlertDrawer: getAlertHistory(50)+SSE, slice 100. + +### 6.4 `openapi.d.ts` — contrato transversal (TS gerado) +34 paths + `components.schemas` com ~50 DTOs espelhando 1:1 os Pydantic (envelopes `APIResponse_*_{_links?,data,meta?}`; DTOs OrderCreate/OrderOut/PortfolioMetricsOut/IndicatorsOut/RiskConfigOut/... + enums OrderSide, OrderStatus). Gerado por `npm run gen:types`; CI barra drift (`git diff --exit-code`). + +--- + +## 7. Ciclo de vida ponta-a-ponta de um dado + +Rastreamento de um sinal de trading, do mercado à métrica (todas as transformações do dado): + +1. **Exchange OHLCV** `[ts,o,h,l,c,v]` → `StrategyAgent._analyze_market` → **analysis** (TechnicalIndicators + regime + S/R + volume). +2. analysis → `_build_market_data` → **market_data** (nested/flat) → `strategy.analyze` → resultado da estratégia (`entry,stop_loss,take_profit,position_size_pct,confidence`). +3. `_generate_signal` normaliza → **signal** `{action,entry_price,stop_loss,take_profit,position_size_pct,strategy,regime,market_context}`; orquestrador adiciona `symbol`. +4. **ledger** `log_signal{agent,signal}` (P). Gate de confiança <0.6 aborta. +5. signal → `RiskAgent.execute` → guardrails.validate_order(signal) → **validation** `{approved,issues,warnings,confidence}`; `log_validation` (P). Rejeição → **Alert** `risk_rejection` (P + SSE). +6. signal → `make_approval_handler` mapeia para **Order** (nota: `entry_price`→`price`, deriva `quantity` de `position_size_pct`). `OrderStore.submit` → INSERT `orders` (P) + XES `order_submitted`. +7. Se `notional≤threshold` → auto-fill (`order_fill` P); senão `wait_for_decision` (polling SQLite 2s). API `PATCH` grava `approved` (P). `log_hitl_approval` (P). +8. signal + quantity → `ExecutionAgent.execute` → exchange.create_order (paper) → **execution_result** `{order_id,executed_price,fee}`; `log_execution` + `log_fill{order_id,symbol,side,price,quantity,notional,fee}` (P); `_open_positions[order_id]` (P via PositionStore); `mark_filled` → `filled` (P). +9. Ciclo futuro: `_check_open_positions` detecta SL/TP → `log_position_closed{...,pnl,pnl_pct}` (P); `circuit_breaker.record_trade_result(pnl_pct)` (P em circuit_breaker_state). +10. `PortfolioMetricsCalculator.compute` relê `position_closed`+`order_fill` → **PortfolioMetrics** → `GET /v1/metrics` → **APIResponse[PortfolioMetricsOut]** → CT_API desembrulha `data` → tela overview. + +Em paralelo: cada ciclo emite XES `agent_cycle_started/completed/failed` → `GET /v1/process/events` → tela observability (process mining). + +--- + +## 8. Dados "perdidos na classe" / declarados-mas-não-usados + +Dados atribuídos e **nunca lidos depois** (marca **X**), conforme pedido: + +| 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`). + +--- + +## 9. Anomalias e inconsistências de dados + +Achados relevantes para refatoramento (não alteram código — apenas documentados): + +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. +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). + +--- + +> **Documento gerado por análise estática dos scripts do repositório.** Referências `arquivo:linha` preservam rastreabilidade. Nenhum código foi alterado. Complementa `docs/uml/arquitetura-uml.md` e `docs/architecture/arquitetura.md`. diff --git a/docs/uml/arquitetura-uml.md b/docs/uml/arquitetura-uml.md new file mode 100644 index 0000000..8d03652 --- /dev/null +++ b/docs/uml/arquitetura-uml.md @@ -0,0 +1,1330 @@ +# Documentação UML — Criptotrade + +> Análise arquitetural completa da plataforma **Criptotrade** (crypto AI trading com Human-in-the-Loop), gerada a partir da leitura recursiva de todos os arquivos-fonte do repositório. +> +> **Objetivo:** mapear como cada script/módulo/pacote se conecta ao todo — dependências, fluxos de dados e responsabilidades — como base para estudo de arquitetura e futuros refatoramentos. + +--- + +## 0. Nota sobre o escopo multi-linguagem + +O prompt original pedia análise de **Python + Rust + TypeScript**. A composição real do repositório, medida por varredura recursiva, é: + +| Linguagem / tipo | Arquivos | Situação | +|---|---:|---| +| **Python** (`.py`) | 168 (~18,7k LOC) | **Núcleo do sistema** — backend, agentes, orquestração, API, análise, backtest | +| **React/JSX** (`.jsx`, `.js`, `.mjs`) | ~24 | Console operacional em `docs/design/pages/` (React "classic scripts" + build esbuild) | +| **TypeScript** (`.d.ts`) | 1 | `docs/design/pages/openapi.d.ts` — **contrato gerado** a partir do OpenAPI da FastAPI | +| **Rust** (`.rs`) | 0 | **Ausente** — não há código Rust no repositório | +| SQL / YAML / Dockerfile / conf | ~30 | Migrations, config, infra | + +**Decisões de adaptação:** + +- **Rust:** a seção específica é omitida por inexistência de código Rust. A metodologia de ownership/traits não se aplica. +- **TypeScript:** existe um único arquivo `.d.ts`, que **não é código de aplicação** e sim o **contrato transversal** gerado (`openapi-typescript` a partir do `openapi.json` emitido pela FastAPI). Ele é tratado na seção de *entidades transversais* (§ 4 e § 5.8), por ser exatamente o elo tipado entre backend Python e frontend JS. +- **Frontend:** o console React (JSX) é o equivalente prático da camada "TypeScript/front-end" da metodologia — é analisado como componente e pacote. + +O restante deste documento segue a metodologia pedida (mapeamento estrutural → análise estática → padrões → diagramas), aplicada à realidade Python-cêntrica do projeto. + +--- + +## 1. Mapeamento Estrutural (Top-down) + +### 1.1 Mapa de diretórios e responsabilidades + +``` +Criptotrade/ +├── src/ # ── BACKEND (Python) ────────────────────── +│ ├── agents/ # Agentes de IA (BaseAgent + especialistas) +│ ├── orchestration/ # Loop contínuo + SquadOrchestrator (pipeline de trading) +│ ├── strategies/ # Estratégias de trading (Strategy pattern) +│ ├── analysis/ # Indicadores, S/R, volume, regime, padrões +│ ├── backtest/ # Engine, Monte Carlo, walk-forward +│ ├── evaluation/ # Avaliação contínua, A/B testing +│ ├── consensus/ # Votação ponderada entre agentes +│ ├── routing/ # Roteamento adaptativo (learning router) +│ ├── planning/ # Planejamento hierárquico/adaptativo +│ ├── chains/ · parallel/ # Cadeias resilientes · execução paralela +│ ├── risk/ # Kelly, sizing, proteções de capital +│ ├── safety/ # GuardrailSystem, SecurityConfig +│ ├── hitl/ # Human-in-the-Loop (OrderStore, níveis de autonomia) +│ ├── core/ # Infra: config, db, ledger, métricas, exchange, alerts, llm +│ ├── memory/ # Memória de agentes (JSONL + Chroma opcional) +│ ├── journal/ # Diário de trades (psicologia/plano) +│ ├── tools/ # MCP registry, RAG, sandbox seguro +│ ├── protocols/ # MCP server, squad (variante A2A) +│ ├── api/ # FastAPI gateway /v1 (routes, schemas, deps, middleware) +│ ├── dashboard/ # Console Streamlit (consumidor da API) +│ └── utils/ # Observabilidade, memória utilitária +├── docs/design/pages/ # ── FRONTEND (React/JSX) + openapi.d.ts (TS) ── +├── migrations/ # DDL SQLite/Postgres +├── config/ · monitoring/ # YAML de risco/prompts · Prometheus/Grafana +├── deploy/ · docker-compose*.yml# nginx, Dockerfiles, topologias dev/prod/vps +└── tests/ # unit · integration · api · hitl · emergent +``` + +### 1.2 Fronteiras de sistema e mecanismos de comunicação + +O sistema roda em **dois processos que não compartilham lifecycle** (um restart da API não para o trading), comunicando-se por **estado compartilhado em SQLite (WAL)** + volume de arquivos append-only: + +``` +Browser / Operador + │ HTTPS (REST + SSE) + ▼ +[nginx] ──proxy HTTP──► [API FastAPI :8000] ◄──HTTP── [Dashboard Streamlit :8501] + │ escreve/lê (consumidor puro da API) + ▼ + ┌───────────────────────────┐ + │ SQLite WAL (orders, │◄── shared state (cross-process) + │ cycle_events, ledger_...) │ + │ + JSONL (ledger, alerts) │ + └───────────────────────────┘ + ▲ + │ escreve/lê (mesmo volume ./data) + [Orchestrator loop process] ── python -m src.orchestration.main_loop + │ + └─► ExchangeClient (dry-run/paper por padrão) +``` + +| Fronteira | Mecanismo | Detalhe | +|---|---|---| +| Browser ↔ nginx | **HTTPS** | TLS 1.2/1.3, HSTS | +| Console/Dashboard ↔ API | **REST `/v1` + SSE** | envelope `APIResponse`, header `X-API-Key`, SSE em `/v1/alerts` | +| API ↔ Loop | **SQLite WAL** (sem RPC) | HITL bridge: API *decide* (`approved`/`rejected`), loop *executa* (`wait_for_decision` → `mark_filled`) | +| Backend ↔ Exchange | **CCXT** (async) | por padrão `EXCHANGE_DRY_RUN=true` → mercado sintético offline | +| Backend ↔ LLM | **LangChain** (opcional) | Gemini/OpenAI/Anthropic; ausência de chave = desabilitado (fail-safe) | +| Backend Python ↔ Frontend JS | **`openapi.d.ts`** (contrato) | gerado do OpenAPI; drift barrado no CI (`git diff --exit-code`) | +| Métricas | **Prometheus scrape** | `/metrics`; Grafana provisionado | + +### 1.3 Dependências externas por linguagem + +**Python (`requirements.txt`):** +- Núcleo: `pydantic`, `pydantic-settings`, `PyYAML`, `python-dotenv` +- API/Web: `fastapi`, `uvicorn`, `streamlit`, `httpx`, `sse-starlette`, `slowapi` +- IA/Agentes: `langchain`, `langchain-core`, `langchain-google-genai` (Chroma opcional) +- Trading/dados: `ccxt`, `pandas`, `numpy`, `ta` +- Persistência: `sqlalchemy`, `psycopg[binary]` (Postgres opcional) +- Observabilidade: `prometheus-client`, `python-json-logger`, `sentry-sdk[fastapi]` +- Segurança: `cryptography`, `slowapi`; async: `aiohttp` + +**Frontend (`docs/design/pages/package.json`):** +- Runtime: `react` / `react-dom` 18.3.1 (UMD globals, sem bundler em dev) +- Build/test: `esbuild`, `openapi-typescript`, `@playwright/test`, `typescript` + +--- + +## 2. Diagrama de Casos de Uso + +**Objetivo:** identificar atores e as funcionalidades principais visíveis nas fronteiras do sistema (API `/v1` + dashboard + loop). + +**Escopo:** operação do sistema pelo operador humano (HITL), automação do loop de trading, e integrações externas (exchange, LLM, monitoramento). + +```plantuml +@startuml +left to right direction +skinparam actorStyle awesome + +actor "Operador\n(Trader)" as Op +actor "Sistema\nOrquestrador\n(Loop)" as Loop +actor "Exchange\n(CCXT)" as Ex +actor "LLM\n(Gemini/OpenAI)" as LLM +actor "Prometheus\n/Grafana" as Mon + +rectangle "Criptotrade" { + usecase "Aprovar/Rejeitar ordem (HITL)" as UC_HITL + usecase "Ajustar nível de autonomia" as UC_Auto + usecase "Visualizar KPIs do portfólio" as UC_KPI + usecase "Consultar agentes e ciclos" as UC_Agents + usecase "Analisar mercado (indicadores/regime)" as UC_Market + usecase "Rodar backtest / Monte Carlo / walk-forward" as UC_BT + usecase "Registrar/consultar diário de trades" as UC_Journal + usecase "Receber alertas (SSE)" as UC_Alerts + usecase "Ajustar parâmetros de risco" as UC_Risk + + usecase "Gerar sinal de trading" as UC_Signal + usecase "Validar risco (guardrails)" as UC_Validate + usecase "Executar ordem (paper)" as UC_Exec + usecase "Auditar via ledger/XES" as UC_Audit + usecase "Disparar circuit breaker" as UC_CB +} + +Op --> UC_HITL +Op --> UC_Auto +Op --> UC_KPI +Op --> UC_Agents +Op --> UC_Market +Op --> UC_BT +Op --> UC_Journal +Op --> UC_Alerts +Op --> UC_Risk + +Loop --> UC_Signal +Loop --> UC_Validate +Loop --> UC_Exec + +UC_Signal ..> UC_Market : «include» +UC_Signal ..> LLM : «include» (CoT opcional) +UC_Validate ..> UC_CB : «extend» (perda/streak) +UC_Exec ..> UC_HITL : «include» (gate humano) +UC_Exec ..> Ex : «include» +UC_Signal ..> UC_Audit : «include» +UC_Validate ..> UC_Audit : «include» +UC_Exec ..> UC_Audit : «include» +UC_Validate ..> UC_Alerts : «extend» (rejeição) + +Ex --> UC_Market +LLM --> UC_Signal +Mon --> UC_KPI +@enduml +``` + +**Legenda:** `«include»` = comportamento sempre executado como parte do caso base; `«extend»` = comportamento condicional; atores à esquerda (humano/sistemas) disparam os casos. + +**Notas de design:** +- O **gate HITL** é obrigatório na execução (`«include»`), mas o modo *auto-approval* (Model B) fila-e-preenche ordens abaixo do limiar de valor sem intervenção — o operador só é chamado acima do threshold. +- O **circuit breaker** é uma extensão do fluxo de validação/resultado: dispara com perda diária ≥ 4% ou 3 perdas consecutivas. +- O **LLM é opcional**: Chain-of-Thought no `StrategyAgent` e reflexão no `RiskAgent` degradam graciosamente para o score determinístico quando não há chave de API. + +--- + +## 3. Diagrama de Pacotes + +**Objetivo:** organização lógica do código Python e dependências direcionadas entre pacotes. + +**Escopo:** todos os subpacotes de `src/` + frontend + contrato. + +```mermaid +graph TD + subgraph Apresentacao["Apresentação"] + API[api
FastAPI /v1] + DASH[dashboard
Streamlit] + FE[docs/design/pages
React console] + end + subgraph Aplicacao["Aplicação / Orquestração"] + ORCH[orchestration] + HITL[hitl] + CONS[consensus] + ROUT[routing] + PLAN[planning] + CHAIN[chains] + PAR[parallel] + EVAL[evaluation] + end + subgraph Dominio["Domínio"] + AG[agents] + STRAT[strategies] + ANAL[analysis] + BT[backtest] + RISK[risk] + JOUR[journal] + end + subgraph Infra["Infraestrutura"] + CORE[core
config·db·ledger·metrics·exchange·alerts·llm] + SAFE[safety] + MEM[memory] + TOOLS[tools] + PROTO[protocols] + UTIL[utils] + end + CONTRACT[["openapi.d.ts
(contrato TS gerado)"]] + + FE -->|REST+SSE| API + DASH -->|httpx| API + API --> CONTRACT + FE -.consome.-> CONTRACT + + API --> HITL + API --> AG + API --> CORE + API --> ANAL + API --> BT + API --> RISK + + ORCH --> AG + ORCH --> HITL + ORCH --> CORE + HITL --> SAFE + HITL --> CORE + + AG --> ANAL + AG --> STRAT + AG --> SAFE + AG --> CORE + AG --> MEM + + STRAT --> ANAL + BT --> STRAT + RISK --> CORE + JOUR --> CORE + + ORCH --> PLAN + ORCH --> ROUT + ORCH --> CONS + ORCH --> CHAIN + ORCH --> PAR + ORCH --> EVAL + + CORE --> SAFE + TOOLS --> SAFE + PROTO --> AG +``` + +**Legenda:** seta cheia = dependência de import/uso; seta tracejada = consumo do contrato; caixa `[[...]]` = artefato gerado. Camadas de cima (apresentação) dependem das de baixo (infra), não o contrário. + +**Notas de design:** +- **Direção de dependência saudável no eixo principal:** apresentação → aplicação → domínio → infra. `core` é o núcleo estável do qual quase tudo depende (config, db, ledger). +- **Dois "clusters" de orquestração convivem** (ver § 5.2): o cluster de **trading** (`orchestration.squad_orchestrator` + `agents.{strategy,risk,execution}`) e o cluster **genérico "BuildToValue"** (`orchestration.unified_orchestrator` + `planning/routing/consensus/chains/parallel` + agentes de engenharia). O segundo **não está no caminho de trading** — é acoplamento potencialmente removível. +- **Acoplamento fraco via callbacks:** `safety.guardrails` não importa `core.alerts`; recebe um `alert_sink: Callable[[str], None]`. Boa inversão de dependência. + +--- + +## 4. Diagrama de Componentes + +**Objetivo:** componentes de runtime (processos/serviços) e interfaces de comunicação. + +**Escopo:** deploy lógico — API, loop, dashboard, console, persistência, atores externos. + +```mermaid +graph LR + subgraph Browser + Console["React Console
«component»"] + end + Operator(("Operador")) + + subgraph Edge + NGINX["nginx
«reverse proxy»
TLS, static, /v1 proxy"] + end + + subgraph AppNode["Nó de aplicação (imagem única Python)"] + API["API FastAPI
«service» :8000
REST /v1 + SSE"] + LOOP["Orchestrator Loop
«process»
main_loop"] + DASH["Dashboard
«service» Streamlit :8501"] + end + + subgraph State["Estado compartilhado (volume ./data)"] + DB[("SQLite WAL
orders · cycle_events
ledger_events")] + JSONL[("JSONL
ledger · alerts · XES")] + end + + subgraph External + EX["Exchange (CCXT)
«external»"] + LLM["LLM Provider
«external»"] + end + subgraph Obs + PROM["Prometheus :9090"] + GRAF["Grafana :3000"] + end + + Operator -->|HTTPS| Console + Console -->|REST+SSE| NGINX + NGINX -->|HTTP /v1,/health| API + Operator -.->|opção operacional| DASH + DASH -->|httpx REST| API + + API -->|read/write| DB + API -->|read/write| JSONL + LOOP -->|read/write| DB + LOOP -->|append| JSONL + LOOP -->|analyze_and_trade| EX + API -->|market data| EX + LOOP -.->|CoT/reflection opcional| LLM + API -.->|CoT opcional| LLM + + PROM -->|scrape /metrics| API + GRAF -->|query| PROM +``` + +**Legenda:** `«service»` = processo servido em porta; `«process»` = processo sem porta; `«external»` = dependência fora do deploy; cilindro = repositório de dados; seta tracejada = dependência opcional. + +**Notas de design:** +- **Uma única imagem Python** serve API, loop e dashboard (diferem só pelo `command`). Simplifica build; separa lifecycle por processo. +- **Ponto de integração crítico (e frágil):** a coordenação API↔loop é feita **exclusivamente via polling do SQLite** (`wait_for_decision`, `poll_interval=2s`). Não há fila/mensageria — é simples e cross-process, mas o loop só percebe uma aprovação com latência de até `poll_interval`, e um timeout de 300s auto-cancela (fail-closed). +- **Console é 100% estático** (React "classic scripts" transpilado por esbuild, servido por nginx). Não há servidor Node em produção. + +--- + +## 5. Diagramas de Classes (por camada) + +> Dado o volume (>150 classes), o diagrama de classes é **quebrado por camada** com referência cruzada, conforme o critério de clareza da metodologia. + +### Legenda comum (todas as subseções) + +| Notação | Significado | +|---|---| +| `<|--` | herança / implementação | +| `*--` | composição (dono cria/possui) | +| `o--` | agregação (referência injetada) | +| `-->` | associação / uso | +| `..>` | dependência pontual (import lazy, factory) | +| `«abstract»` `«dataclass»` `«enum»` `«protocol»` `«service»` `«repository»` `«dto»` `«factory»` | estereótipos | + +--- + +### 5.1 Camada de Agentes + +**Objetivo:** hierarquia dos agentes e suas dependências para o domínio de análise/risco/execução. +**Escopo:** `src/agents/*` + `src/core/safe_agent_base.py`. + +```mermaid +classDiagram + class BaseAgent { + <> + +agent_type: str + +agent_id: str + +confidence_threshold: float = 0.6 + +memory: AgentMemorySystem? + +tools: list~str~ + +execute(task) dict* + +validate_input(task) bool + +log_decision(decision) dict + +validate_confidence(c) bool + } + class StrategyAgent { + +exchange_client + -_sr_detector: SupportResistanceDetector + -_div_detector: DivergenceDetector + -_llm + +execute(task) dict + } + class RiskAgent { + +guardrails: GuardrailSystem + +max_position_size_pct + +stop_loss_pct + +execute(task) dict + } + class ExecutionAgent { + +exchange: ExchangeClient + +paper_trading = True + +execute(task) dict + } + class AuditorAgent + class ArchitectAgent + class DeveloperAgent + class DesignerAgent + class OpsAgent + + BaseAgent <|-- StrategyAgent + BaseAgent <|-- RiskAgent + BaseAgent <|-- ExecutionAgent + BaseAgent <|-- AuditorAgent + BaseAgent <|-- ArchitectAgent + BaseAgent <|-- DeveloperAgent + BaseAgent <|-- DesignerAgent + BaseAgent <|-- OpsAgent + + class Agent { + <> + +name: str + +arun(task) str + } + class SupervisorAgent { + <> + +orchestrator: Agent + +specialists: list~Agent~ + +run(task) str + } + class RecoveryAgent { + <> + +remediation_tool + +arun(report) str + } + class ExplorationAgent { + <> + +scanner_tool + +arun(instruction) str + } + SupervisorAgent o-- Agent + Agent <|.. RecoveryAgent + Agent <|.. ExplorationAgent + + class AgentRegistry { + <> + -_db_path + +record_cycle(agent_id) + +cycles_today(agent_id) int + +status(agent_id) dict + +all_statuses() list + } + class AgentInfo { + <> + +id · domain · implemented · description + } + AgentRegistry --> AgentInfo + + class BehavioralGuard { + +check(trade, history) BehavioralAlert + } + class BehavioralAlert { <> } + BehavioralGuard --> BehavioralAlert + + StrategyAgent ..> GuardrailSystem + RiskAgent *-- GuardrailSystem + ExecutionAgent o-- ExchangeClient + BaseAgent ..> AgentMemorySystem +``` + +**Notas de design:** +- **Padrão Template Method + async:** `BaseAgent.execute` é `@abstractmethod async`; cada especialista implementa seu raciocínio (CoT no Strategy, Reflection no Risk, ReAct no Execution). +- **Duas hierarquias de "agente base" desconectadas:** `BaseAgent` (ABC, `execute` async, em `src/agents/`) e `SafeAgentBase` (em `src/core/`, `execute` **síncrono**, composição de guardrails — ver § 5.6). **Nenhum arquivo faz a ponte** — é um sintoma de duas fundações concebidas em momentos diferentes; candidato a unificação. +- **Registry não é factory:** `AgentRegistry` só reporta metadados/status/ciclos (cross-process via `cycle_events`); não instancia agentes. Apenas `strategy`/`risk`/`execution` são `implemented=True`; `recovery`/`exploration` são stubs (`implemented=False` → API retorna 501). Os agentes de engenharia (architect/auditor/…) são subclasses de `BaseAgent` **não registradas** no domínio de trading. +- **Protocolo estrutural:** `SupervisorAgent` depende do `Agent` Protocol; `Recovery/Exploration` o satisfazem (duck typing), embora ainda não estejam ligados ao trading. + +--- + +### 5.2 Camada de Orquestração e Pipeline de Trading + +**Objetivo:** o coração do sistema — como o loop dirige o `SquadOrchestrator` e como este encadeia os agentes. +**Escopo:** `src/orchestration/*`. + +```mermaid +classDiagram + class OrchestratorLoop { + <> + +interval: int + +symbols: list~str~ + -_stop_event: asyncio.Event + +run_cycle() dict + +run_forever() + +stop() + +from_env() OrchestratorLoop$ + } + class SquadOrchestrator { + <> + +strategy_agent: StrategyAgent + +risk_agent: RiskAgent + +execution_agent: ExecutionAgent + +ledger: TradingLedger + +circuit_breaker: CircuitBreaker + +approval_handler + +fill_callback + -_open_positions: dict + +analyze_and_trade(symbol, tf) dict + +reload_open_positions() + } + class CircuitBreaker { + +DAILY_LOSS_LIMIT_PCT = 4.0 + +CONSECUTIVE_LOSS_LIMIT = 3 + +is_open: bool + +record_trade_result(pnl_pct) + +reset_daily() + } + class PositionStore { + <> + +upsert(id, pos) + +delete(id) + +load_all() dict + } + class AgentRegistry + class TradingLedger + + OrchestratorLoop o-- SquadOrchestrator + OrchestratorLoop o-- AgentRegistry + OrchestratorLoop o-- TradingLedger + SquadOrchestrator *-- CircuitBreaker + SquadOrchestrator *-- PositionStore + SquadOrchestrator *-- StrategyAgent + SquadOrchestrator *-- RiskAgent + SquadOrchestrator *-- ExecutionAgent + SquadOrchestrator *-- TradingLedger + SquadOrchestrator ..> OrderStore : approval_handler + CircuitBreaker --> TradingLedger + + class UnifiedOrchestrator { + <> + +planner · router · consensus + +parallel · evaluator · autonomy + +agents: dict + +execute_complex_task(task) dict + } + class AdaptivePlanner + class LearningRouter + class WeightedConsensusEngine + class ParallelResourceManager + class ContinuousEvaluator + class ProgressiveAutonomyManager + UnifiedOrchestrator *-- AdaptivePlanner + UnifiedOrchestrator *-- LearningRouter + UnifiedOrchestrator *-- WeightedConsensusEngine + UnifiedOrchestrator *-- ParallelResourceManager + UnifiedOrchestrator *-- ContinuousEvaluator + UnifiedOrchestrator *-- ProgressiveAutonomyManager +``` + +**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). + - 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. + +--- + +### 5.3 Camada de Estratégias e Análise + +**Objetivo:** o Strategy pattern e os DTOs de análise técnica que alimentam as estratégias. +**Escopo:** `src/strategies/*`, `src/analysis/*`. + +```mermaid +classDiagram + class BaseStrategy { + <> + +analyze(market_data) dict* + +get_parameters() dict + } + class MeanReversionStrategy { + +rsi_oversold = 30 + +rsi_overbought = 70 + +analyze(md) dict + } + class GridTradingStrategy { + +grid_levels = 10 + +analyze(md) dict + } + class DCAOptimizedStrategy { + +num_entries = 3 + +analyze(md) dict + } + BaseStrategy <|-- MeanReversionStrategy + BaseStrategy <|-- GridTradingStrategy + BaseStrategy <|-- DCAOptimizedStrategy + + class STRATEGY_REGISTRY { + <> + +dca : DCAOptimizedStrategy + +grid : GridTradingStrategy + +mean_reversion : MeanReversionStrategy + } + STRATEGY_REGISTRY ..> BaseStrategy + + class TechnicalAnalyzer { + +MIN_CANDLES = 50 + +get_latest() TechnicalIndicators + +get_series(col) Series + } + class TechnicalIndicators { + <> + rsi · macd_hist · bb_percent + ema_fast · atr · volume_ratio · ... + } + class SupportResistanceDetector { + +detect(ohlcv) SRLevels + +fibonacci_levels() dict$ + } + class SRLevels { <> } + class VolumeProfile { +analyze() VolumeProfileResult } + class VolumeProfileResult { <> +poc } + class DivergenceDetector { +check_rsi_price() DivergenceResult } + class PatternScanner { +scan(ohlcv) list~PatternResult~ } + + TechnicalAnalyzer --> TechnicalIndicators + SupportResistanceDetector --> SRLevels + VolumeProfile --> VolumeProfileResult + MeanReversionStrategy ..> TechnicalIndicators + GridTradingStrategy ..> TechnicalIndicators + GridTradingStrategy ..> VolumeProfileResult + + class regime_detector { + <> + detect_regime() str + strategies_for_regime() list + detect_market_extreme() + } + StrategyAgent ..> TechnicalAnalyzer + StrategyAgent ..> regime_detector + StrategyAgent ..> STRATEGY_REGISTRY + regime_detector ..> STRATEGY_REGISTRY : keys alinhadas +``` + +**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. +- **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. +- **`regime_detector` é módulo de funções** (sem classe) — ponte funcional entre análise e o factory de estratégias. + +--- + +### 5.4 Camada de Backtest e Avaliação + +**Objetivo:** validação de estratégias fora do caminho de produção. +**Escopo:** `src/backtest/*`, `src/evaluation/*`. + +```mermaid +classDiagram + class BacktestEngine { + +initial_capital = 10_000 + +commission_pct · slippage_pct + +run(strategy, ohlcv) BacktestResult + } + class BacktestResult { + <> + +total_trades · win_rate + +sharpe_ratio · max_drawdown_pct + +trades: list~BacktestTrade~ + +expectancy: float + } + class BacktestTrade { <> } + class WalkForwardValidator { + +window_size = 252 + +validate(strategy, ohlcv) WalkForwardResult + } + class WalkForwardResult { <> +window_results } + class WindowResult { <> } + class MonteCarloSimulator { + +n_simulations = 1000 + +simulate(pnl_list) MonteCarloResult + } + class MonteCarloResult { <> +rejected } + + BacktestEngine --> BacktestResult + BacktestResult *-- BacktestTrade + WalkForwardValidator ..> BacktestEngine + WalkForwardValidator --> WalkForwardResult + WalkForwardResult *-- WindowResult + WindowResult *-- BacktestResult + MonteCarloSimulator --> MonteCarloResult + BacktestEngine ..> BaseStrategy : duck-typed analyze() + + class AgentABTestingFramework { <> +run_ab_test() } + class ContinuousEvaluator_eval { <> +evaluate_trajectory() } + class ContinuousEvaluator_dc { <> +evaluate_agent_performance() } + note for ContinuousEvaluator_dc "Colisão de nome:\ncontinuous_eval.py vs\ncontinuous_evaluator.py" +``` + +**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. + +--- + +### 5.5 Camada Core — Persistência, Métricas e Integrações + +**Objetivo:** a "espinha dorsal" de infraestrutura (ledger append-only, abstração de DB, métricas, exchange, alertas, LLM). +**Escopo:** `src/core/*`. + +```mermaid +classDiagram + class Settings { + <> + app_env · exchange · initial_capital + max_position_size_pct · autonomy_level ... + } + class TradingLedger { + <> + +ledger_path · db_path + +log_signal() + +log_validation() + +log_execution() + +log_hitl_approval() + +log_process_event() XES + +log_fill() + +log_position_closed() + +get_events(type) list + +get_process_events(case) list + } + class PortfolioMetricsCalculator { + <> + +compute(period, symbol) PortfolioMetrics + } + class PortfolioMetrics { <> +sharpe · win_rate · drawdown } + class ExchangeClient { + <> + +dry_run: bool + +fetch_ohlcv() + +fetch_ticker() + +create_order() + +_create_paper_order() + } + class Alert { <> +severity · type · message } + class AlertStore { + <> + +append() + +history() + } + class AlertBus { + +publish() + +register() Queue + } + class LLMClient { + <> + +reason(sys, user) str? + +reason_json(sys, user) dict? + } + class db { + <> + +connection() ctx + +init_db() + +upsert() + +is_postgres() bool + +autoincrement_pk() str + } + class RateLimiter { <> +allow(key, limit) bool } + class InMemoryRateLimiter + class RedisFixedWindowLimiter + RateLimiter <|.. InMemoryRateLimiter + RateLimiter <|.. RedisFixedWindowLimiter + + TradingLedger ..> db + PortfolioMetricsCalculator o-- TradingLedger + PortfolioMetricsCalculator --> PortfolioMetrics + ExchangeClient ..> synthetic_market + AlertStore --> Alert + AlertBus --> Alert + Settings ..> RequestIdLogFilter +``` + +**Notas de design:** +- **Ledger como *event store* (Event Sourcing leve):** `TradingLedger` grava eventos append-only em SQLite (`ledger_events`) e emite um **event log XES** (`log_process_event`) para *process mining* (`/v1/process/events`). O `PortfolioMetricsCalculator` **deriva** todas as métricas relendo esses eventos (`position_closed`, `order_fill`) — fonte única de verdade, sem estado mutável duplicado. +- **Abstração de DB portável:** `db.connection()` é o único ponto de acesso; `_PgConn`/`_Row` adaptam Postgres à API do `sqlite3`. Migrations são backend-aware (`migrations/` + `migrations/postgres/`). Permite escalar de SQLite (default) a Postgres (ADR-005) sem tocar nos repositórios. +- **Fail-safe por design:** `ExchangeClient` exige `EXCHANGE_DRY_RUN` (recusa iniciar sem) e, em dry-run, usa `synthetic_market` offline. `LLMClient` retorna `None` sem chave. `RedisFixedWindowLimiter` faz *fail-open* para memória. +- **Pub/Sub para SSE:** `AlertBus` faz fan-out in-process para `asyncio.Queue` (assinantes do `/v1/alerts`); `AlertStore` persiste em JSONL. `make_guardrail_sink` conecta guardrails → alertas por callback (sem import cruzado). + +--- + +### 5.6 Camada de Risco e Segurança + +**Objetivo:** as regras que protegem capital e execução. +**Escopo:** `src/risk/*`, `src/safety/*`, `src/core/safe_agent_base.py`. + +```mermaid +classDiagram + class GuardrailSystem { + <> + +rules: list~Guardrail~ + +alert_sink: Callable? + +validate_order(order) tuple~bool,list~ + +check_position_size() + +check_stop_loss() + +check_risk_reward() + +check_market_conditions() + } + class SecurityConfig { + <> + +MAX_POSITION_SIZE_PCT = 5.0 + +FORBIDDEN_PATTERNS + +validate_order() tuple$ + +validate_tool_call() tuple$ + } + class CapitalProtections { + +DAILY_LIMIT_PCT = 3.0 + +WEEKLY_LIMIT_PCT = 6.0 + +check(daily, weekly, monthly) ProtectionResult + } + class ProtectionResult { <> +status +size_multiplier +can_trade } + class DrawdownStatus { <> OK·WARN·DAILY_PAUSE·... } + class KellyCriterion { + <> + +full_kelly() float? + +fractional_kelly() float + +ruin_risk() float + } + class PositionSizer { + +kelly: KellyCriterion? + +compute(entry, stop) float + } + CapitalProtections --> ProtectionResult + ProtectionResult --> DrawdownStatus + PositionSizer o-- KellyCriterion + RiskAgent *-- GuardrailSystem + GuardrailSystem ..> AlertStore : alert_sink + + class SafeAgentBase { + +guardrails: GuardrailSuite + +memory: MemoryManager + +execute(task, ctx) AgentExecution + } + class GuardrailSuite { <> +validate_pattern_safety() } + class Guardrail_proto { <> +validate(pattern) bool } + class InputSanitizer + class OutputValidator + class EthicalBoundaryChecker + class ResourceLimiter + SafeAgentBase *-- GuardrailSuite + GuardrailSuite o-- Guardrail_proto + Guardrail_proto <|.. InputSanitizer + Guardrail_proto <|.. OutputValidator + Guardrail_proto <|.. EthicalBoundaryChecker + Guardrail_proto <|.. ResourceLimiter +``` + +**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). + +--- + +### 5.7 Camada HITL, DTOs da API e Contrato + +**Objetivo:** o gate humano cross-process e o contrato de dados da API. +**Escopo:** `src/hitl/*`, `src/api/schemas.py`, `openapi.d.ts`. + +```mermaid +classDiagram + class OrderStore { + <> + +submit(order) Order + +resolve(id, approved, op) Order + +mark_filled(id) Order + +cancel(id, reason) Order + +wait_for_decision(id, timeout) bool + +list() + +count() + +pending_count() + } + class Order { + <> + +id · pair · side · quantity · price + +status: OrderStatus + +auto_approved · critical + +notional: float + +guardrail_view() dict + } + class OrderStatus { <> pending·approved·filled·rejected·cancelled } + class OrderConflictError { <> } + class HITLConfigStore { + +level: int + +set_level(level, reason, op) + +snapshot() dict + } + class AutonomyLevel { <> +level +threshold_usdt } + class GuardrailSystem + class TradingLedger + + OrderStore *-- Order + Order --> OrderStatus + OrderStore o-- TradingLedger + OrderStore o-- GuardrailSystem + OrderStore ..> OrderConflictError + HITLConfigStore ..> AutonomyLevel + HITLConfigStore ..> OrderStore : pending_count + + class APIResponse~T~ { + <> + +data: T + +meta: Meta? + +links: Links? + } + class OrderCreate { <> pair·side·qty·price·stop_loss(obrig.) } + class OrderOut { <> +id +status +notional +rr } + class OrderDecisionPatch { <> decision: approve|reject } + class PortfolioMetricsOut { <> } + APIResponse ..> OrderOut + APIResponse ..> PortfolioMetricsOut + OrderCreate ..> Order : route → OrderStore.submit +``` + +**Máquina de estados do `Order` (HITL):** + +```mermaid +stateDiagram-v2 + [*] --> pending: submit() + pending --> filled: auto (notional≤threshold\n& not critical & risk_ok) + pending --> rejected: guardrail violation + pending --> approved: resolve(approve)\n[API/operador] + pending --> rejected: resolve(reject) + pending --> cancelled: wait_for_decision timeout (300s) + approved --> filled: mark_filled()\n[loop pós-execução] + filled --> [*] + rejected --> [*] + cancelled --> [*] +``` + +**Notas de design:** +- **Contrato tipado ponta-a-ponta:** os DTOs Pydantic em `schemas.py` são embrulhados no envelope genérico `APIResponse[T]` (`data`/`meta`/`_links`, HATEOAS). O `openapi-typescript` gera `openapi.d.ts` a partir do OpenAPI; o CI roda `gen:types` + `git diff --exit-code` — **impossível o backend Python e o front JS divergirem** sem quebrar o build. Essa é a *entidade transversal* que a metodologia pedia. +- **Validações de negócio no schema:** `OrderCreate` exige `stop_loss` (>0) e `reason` (≥10 chars); `OrderDecisionPatch` exige `operator_note` ao rejeitar. Regras de risco codificadas já na fronteira. +- **Modelo dois-processos explícito:** a **API decide** (`resolve` grava `approved`/`rejected`), o **loop executa** (`wait_for_decision` vê `approved` → roda `ExecutionAgent` → `mark_filled`). `mark_filled` é atômico e idempotente (`WHERE status='approved'`) — sem duplo-preenchimento. +- **Ponto de atenção (namespace):** `PATCH /v1/agents/{id}/config` (router `config`) coexiste com `GET /v1/agents/{id}/config` e `GET /v1/agents/{id}` (router `agents`) — mesma raiz `/v1/agents` servida por dois módulos. Além disso, há dois modelos de autonomia (`hitl.config` por valor US$ vs `hitl.progressive_autonomy` por trust-score) — só o primeiro está ligado às rotas. + +--- + +### 5.8 Camada de Apresentação (Frontend React) + +**Objetivo:** estrutura de componentes do console e o único ponto de I/O com o backend. +**Escopo:** `docs/design/pages/*`. + +```mermaid +classDiagram + class App { + +screen : hashRoute + +SCREENS : registry + } + class Sidebar + class Header + class ErrorBoundary + class AlertDrawer + class ToastContainer + class ScreenOverview + class ScreenMarket + class ScreenOrders + class ScreenHITL + class ScreenAgents + class ScreenRisk + class ScreenBacktest + class ScreenJournal + class ScreenObservability + class ScreenSettings + class CT_API { + +getMetrics() + +getOrders() + +createOrder() + +decideOrder() + +subscribeAlerts() SSE + } + class UIKit { + +Card_Badge_KPI_Meter_PairSelect + } + class Charts { + +CandleChart_EquityChart_Gauge + } + + App *-- Sidebar + App *-- Header + App *-- ErrorBoundary + App *-- AlertDrawer + App *-- ToastContainer + App --> ScreenOverview + App --> ScreenMarket + App --> ScreenOrders + App --> ScreenHITL + App --> ScreenAgents + App --> ScreenRisk + App --> ScreenBacktest + App --> ScreenJournal + App --> ScreenObservability + App --> ScreenSettings + ScreenOverview ..> CT_API + ScreenMarket ..> CT_API + ScreenOrders ..> CT_API + ScreenHITL ..> CT_API + ScreenOverview ..> Charts + ScreenOverview ..> UIKit + CT_API ..> openapi_d_ts : contrato +``` + +**Notas de design:** +- **Arquitetura "classic scripts":** React/ReactDOM como UMD globals; cada arquivo compartilha o escopo global e publica símbolos em `window.*` (`window.Badge`, `window.CT_API`). Sem sistema de módulos em dev; o `build.mjs` (esbuild, `bundle:false`) transpila cada `.jsx` isoladamente em IIFE e auto-hospeda o React de produção. Simples, mas frágil quanto a ordem de carga e colisões globais — candidato natural a migrar para ES modules/bundler se o console crescer. +- **`CT_API` é o único ponto de I/O:** `fetch` REST + `EventSource` (SSE) em `/v1/alerts`; desembrulha `j.data ?? j` (o envelope `APIResponse`); reescreve `BTC/USDT`→`BTC-USDT` nas rotas de mercado. Cada `Screen` possui suas chamadas — bom isolamento por tela. +- **Roteamento por hash** (`#overview`, `#market`…) → SPA fallback no nginx (`try_files … /index.html`). E2E Playwright roda sobre mock (`USE_MOCK_DATA`), sem backend. + +--- + +## 6. Diagramas de Sequência + +### 6.1 Ciclo de trading contínuo (fluxo de negócio principal) + +**Objetivo:** ordem temporal exata do pipeline `analyze_and_trade` dentro de um ciclo do loop. +**Escopo:** loop → squad → agentes → guardrails → HITL → execução → ledger. + +```mermaid +sequenceDiagram + autonumber + participant Loop as OrchestratorLoop + participant Squad as SquadOrchestrator + participant CB as CircuitBreaker + participant SA as StrategyAgent + participant EX as ExchangeClient + participant RA as RiskAgent + participant GS as GuardrailSystem + participant HITL as approval_handler
(OrderStore) + participant EA as ExecutionAgent + participant L as TradingLedger + + Loop->>L: log_process_event(agent_cycle_started) + Loop->>Squad: await analyze_and_trade(symbol) + Squad->>CB: is_open? + alt breaker aberto + CB-->>Squad: true + Squad-->>Loop: {success:false, "trading paused"} + else breaker fechado + Squad->>SA: await execute({symbol, timeframe}) + SA->>EX: await fetch_ohlcv(symbol) + EX-->>SA: OHLCV (ou stub sintético) + SA->>SA: TA + regime + confidence (CoT LLM opcional) + SA-->>Squad: {signal, confidence, stub_used} + Squad->>Squad: _check_open_positions() (fecha SL/TP) + Squad->>L: log_signal() + alt confidence < 0.6 + Squad-->>Loop: {success:false, "low confidence"} + else + Squad->>RA: await execute({signal, portfolio}) + RA->>GS: validate_order(signal) + GS-->>RA: (approved, issues) + RA-->>Squad: {approved, validation} + Squad->>L: log_validation() + alt rejeitado + Squad->>L: log + emit_alert() + Squad-->>Loop: {success:false, "risk failed"} + else aprovado + Squad->>HITL: await approval_handler(signal) + Note over HITL: submete Order; auto-fill se ≤ threshold,
senão wait_for_decision (cross-process) + HITL-->>Squad: order_id | None + Squad->>L: log_hitl_approval() + alt humano aprovou + Squad->>EA: await execute({signal, quantity}) + EA->>EX: await create_order() (paper) + EX-->>EA: fill (preço+fee) + EA-->>Squad: {success, order_id, executed_price} + Squad->>L: log_execution() + log_fill() + Squad->>HITL: fill_callback(order_id) → mark_filled + else rejeitado + Squad-->>Loop: {success:false} + end + end + end + end + Loop->>L: log_process_event(agent_cycle_completed) + heartbeat +``` + +**Notas:** todos os pontos `await` estão em `run_cycle`/`analyze_and_trade`/agentes; falha de um símbolo é *fail-soft* (o loop emite `agent_cycle_failed` e segue). A ordem `Strategy → posições → Risk → HITL → Execution` é invariante. + +### 6.2 Aprovação HITL cross-process (API decide, loop executa) + +**Objetivo:** mostrar a coordenação dos dois processos via SQLite compartilhado. + +```mermaid +sequenceDiagram + autonumber + actor Op as Operador (Console/Dashboard) + participant API as API FastAPI + participant DB as SQLite (orders) + participant Store as OrderStore + participant Loop as OrchestratorLoop + + Loop->>Store: submit(Order) [notional > threshold] + Store->>DB: INSERT status='pending' + Store->>Loop: wait_for_decision(id) inicia polling + loop a cada poll_interval (2s) + Loop->>DB: SELECT status WHERE id=? + DB-->>Loop: 'pending' + end + Op->>API: PATCH /v1/orders/{id}/status {approve} + API->>Store: resolve(id, approved=true) + Store->>DB: UPDATE status='approved' + Note over Loop,DB: próximo poll vê 'approved' + Loop->>DB: SELECT status + DB-->>Loop: 'approved' + Store-->>Loop: True + Loop->>Loop: ExecutionAgent.execute() (paper fill) + Loop->>Store: mark_filled(id) + Store->>DB: UPDATE status='filled' WHERE status='approved' + Note over Op,DB: timeout de 300s sem decisão → cancel() (fail-closed) +``` + +**Notas de design:** não há mensageria — o acoplamento é o **estado no SQLite (WAL)**. Latência de aprovação ≤ `poll_interval`. Idempotência garantida pelo `WHERE status='approved'`. + +### 6.3 Backtest assíncrono (job) + +```mermaid +sequenceDiagram + autonumber + actor Op + participant API as /v1/backtest + participant DB as SQLite (backtest_jobs) + participant Task as asyncio Task + participant Eng as BacktestEngine/MC/WF + + Op->>API: POST /v1/backtest/run (config) + API->>DB: INSERT job status='running' + API->>Task: asyncio.create_task(_run_job) + API-->>Op: 202 {job_id, running} + Task->>Eng: run(strategy, ohlcv) + Eng-->>Task: BacktestResult + Task->>DB: UPDATE status='done', result=... + Op->>API: GET /v1/backtest/jobs/{id} (polling) + API->>DB: SELECT + DB-->>API: status/result + API-->>Op: APIResponse[BacktestJobOut] + Note over API,DB: no startup, _reconcile_orphans marca
jobs 'running' interrompidos como errored +``` + +--- + +## 7. Diagramas de Atividades + +### 7.1 Geração de sinal + blend de confiança (StrategyAgent) + +**Objetivo:** o workflow com múltiplos caminhos de decisão dentro de `StrategyAgent.execute`. + +```mermaid +flowchart TD + A([execute task]) --> B{exchange_client?} + B -->|não| S[stub_analysis sintético] + B -->|sim| F[fetch_ohlcv] + F --> G{candles ≥ MIN_CANDLES?} + G -->|não| S + G -->|sim| TA[TA: indicators, S/R, volume, regime, divergência] + S --> R[detect_regime → eligible_strategies] + TA --> R + R --> SEL{estratégia elegível?} + SEL -->|não| HOLD[signal = HOLD] + SEL -->|sim| STRAT[STRATEGY_REGISTRY get → strategy.analyze] + STRAT --> CONF[confidence determinística
trend+confluência+S/R+volume+divergência] + CONF --> BLEND[blend 60% estratégia + 40% agente] + BLEND --> LLM{LLM ativo & ação≠HOLD?} + LLM -->|sim| COT[reason_json → blend 50/50 + tese] + LLM -->|não| SKIP[mantém score] + COT --> LOG + SKIP --> LOG + HOLD --> LOG[log_decision] + LOG --> OUT([retorna signal + confidence + stub_used]) +``` + +### 7.2 Decisão de execução com circuit breaker e proteções (visão de negócio) + +```mermaid +flowchart TD + Start([cycle]) --> CB{circuit_breaker.is_open?} + CB -->|sim| Pause[[pausa 24h — skip]] + CB -->|não| Sig[gera sinal] + Sig --> Conf{confidence ≥ 0.6?} + Conf -->|não| Skip1[skip] + Conf -->|sim| Risk{guardrails ok?} + Risk -->|não| Alert[emit alert + reject] + Risk -->|sim| Thr{notional ≤ threshold
& not critical?} + Thr -->|sim| Auto[auto-approve → fill] + Thr -->|não| Wait{humano aprova
em 300s?} + Wait -->|não| Cancel[cancel/reject] + Wait -->|sim| Exec[ExecutionAgent paper fill] + Auto --> Ledger[log fill] + Exec --> Ledger + Ledger --> Close{preço atinge SL/TP
em ciclo futuro?} + Close -->|sim| PnL[registra pnl → circuit_breaker.record_trade_result] + PnL --> Trip{perda diária ≥4%
ou 3 losses seguidas?} + Trip -->|sim| Pause + Trip -->|não| Start +``` + +**Notas de design:** há **paralelismo real** apenas no `UnifiedOrchestrator` (`ParallelResourceManager` com `asyncio.Semaphore` + `gather`) e na rota `/v1/market/{pair}/confluence` (`asyncio.gather` sobre timeframes 1h/4h/1d). O pipeline de trading em si é **sequencial e determinístico** por ciclo — decisão consciente para auditabilidade. + +--- + +## 8. Diagrama de Implantação + +**Objetivo:** nós físicos/containers, redes e protocolos. +**Escopo:** `docker-compose.prod.yml` (topologia self-contained) + `deploy/`. + +```plantuml +@startuml +node "Browser do Operador" as browser { + artifact "React Console (dist estático)" as console +} + +node "Host de Produção (Docker)" { + node "nginx container\n(deploy/console.Dockerfile)" as nginx { + artifact "console dist (baked)" + portin "443/tcp (HTTPS)" + portin "80/tcp (ACME + 301)" + } + node "app container\n(uvicorn/FastAPI)" as app { + component "API /v1 + SSE" + portin "8000 (interno)" + } + node "orchestrator container\n(main_loop)" as loop { + component "OrchestratorLoop" + } + node "prometheus container" as prom + node "certbot sidecar" as certbot + database "Volume ./data" as data { + artifact "SQLite WAL (orders, cycle_events, ledger_events)" + artifact "JSONL (ledger, alerts, XES)" + } +} + +cloud "Exchange (CCXT)" as ex +cloud "LLM Provider" as llm + +browser --> nginx : HTTPS (REST + SSE)\nAPIResponse envelope, X-API-Key +nginx --> app : HTTP proxy /v1 /health\n(--forwarded-allow-ips 172.28.0.2) +app --> data : read/write +loop --> data : read/write (mesmo volume) +loop ..> ex : dry-run/paper (offline por padrão) +app ..> ex : market data +loop ..> llm : CoT/reflection (opcional) +prom --> app : scrape /metrics (HTTP) +certbot ..> nginx : renova certs (HTTP-01) + +note bottom of loop + Sem porta publicada. + Compartilha lifecycle=NENHUM com app: + restart da API não para o trading. +end note +@enduml +``` + +**Notas de design:** +- **Três topologias** no repo: `docker-compose.yml` (dev, todas as portas expostas + Prometheus/Grafana, perfis `scale` com Postgres/Redis), `docker-compose.prod.yml` (só nginx publica 80/443; app/loop/prometheus internos em rede `edge` 172.28.0.0/24) e `docker-compose.vps.yml` (sem portas de host, atrás de gateway `btv-nginx-prod` compartilhado). +- **Isolamento de rede em prod:** `app` confia apenas no nginx para `X-Forwarded-*` (rate-limit por IP real). `APP_ENV=production` força `API_KEYS` + `CORS_ORIGINS` explícito (fail-closed no boot). +- **Escala horizontal (ADR-005):** trocar SQLite→Postgres e in-memory rate limiter→Redis é opt-in via env, sem mudança de código (graças à abstração `db.connection` e `build_rate_limiter`). **Ressalva:** o loop é *single-instance* — o modelo de HITL por polling de SQLite não é trivialmente replicável para N loops. + +--- + +## 9. Análise Crítica: Coesão, Acoplamento e Refatoramento + +### 9.1 Pontos fortes +- **Fluxo de trading coeso e auditável:** o pipeline `Strategy→Risk→HITL→Execution` é linear, testado e totalmente registrado no ledger/XES. Fail-safe em todas as fronteiras (dry-run, LLM opcional, fail-closed no HITL, circuit breaker). +- **Separação de processos correta:** API e loop desacoplados por lifecycle, comunicando via estado compartilhado — resiliente a restart. +- **Contrato tipado ponta-a-ponta:** `openapi.d.ts` + drift-gate no CI eliminam divergência backend↔frontend. Exemplar. +- **Infra portável:** abstração de DB (SQLite↔Postgres) e rate limiter (memória↔Redis) por configuração. +- **Padrões bem aplicados:** Strategy (estratégias), Repository (ledger/order/journal/alert stores), Event Sourcing leve (ledger→métricas derivadas), Registry/Factory (`STRATEGY_REGISTRY`, `MCPToolRegistry`), Observer/PubSub (`AlertBus`→SSE), Template Method (`BaseAgent`). + +### 9.2 Pontos de atenção (dívida técnica / refatoramento) + +| # | Achado | Impacto | Recomendação | +|---|---|---|---| +| 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 | +| 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` | +| 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 | + +### 9.3 Métricas qualitativas de acoplamento +- **Núcleo estável correto:** `core` é dependência de quase tudo, mas depende de pouco (config/db) — *stable dependencies principle* respeitado. +- **Acoplamento aferente alto e saudável** em `TradingLedger` e `db.connection` (são seams intencionais). +- **Acoplamento eferente problemático** no `UnifiedOrchestrator` (compõe 9 subsistemas) — sintoma de God Object no cluster genérico, reforçando o achado #2. +- **Desacoplamento por callback** (`GuardrailSystem.alert_sink`, `state_db_provider`, `fill_callback`, `approval_handler`) é usado consistentemente — bom uso de inversão de dependência para evitar imports cíclicos. + +--- + +## Apêndice — Rastreabilidade (entidade → arquivo) + +| Entidade | Arquivo-fonte | +|---|---| +| `BaseAgent` | `src/agents/base_agent.py` | +| `StrategyAgent` / `RiskAgent` / `ExecutionAgent` | `src/agents/{strategy,risk,execution}_agent.py` | +| `AgentRegistry` / `AGENT_REGISTRY` | `src/agents/registry.py` | +| `SquadOrchestrator` / `CircuitBreaker` | `src/orchestration/squad_orchestrator.py` | +| `OrchestratorLoop` | `src/orchestration/orchestrator_loop.py` | +| `UnifiedOrchestrator` | `src/orchestration/unified_orchestrator.py` | +| `OrderStore` / `Order` / `OrderStatus` | `src/hitl/orders.py` | +| `HITLConfigStore` / `AutonomyLevel` | `src/hitl/config.py` | +| `GuardrailSystem` | `src/safety/guardrails.py` | +| `SecurityConfig` | `src/safety/security_config.py` | +| `SafeAgentBase` + guardrails | `src/core/safe_agent_base.py` | +| `TradingLedger` | `src/core/ledger.py` | +| `PortfolioMetricsCalculator` / `PortfolioMetrics` | `src/core/metrics.py` | +| `ExchangeClient` | `src/core/exchange_client.py` | +| `Alert` / `AlertStore` / `AlertBus` | `src/core/alerts.py` | +| `db.connection` / backends | `src/core/db.py` | +| `Settings` | `src/core/config.py` | +| `BaseStrategy` / `STRATEGY_REGISTRY` | `src/strategies/` | +| `TechnicalAnalyzer` / `TechnicalIndicators` | `src/analysis/indicators.py` | +| `BacktestEngine` / `WalkForwardValidator` / `MonteCarloSimulator` | `src/backtest/` | +| `KellyCriterion` / `PositionSizer` / `CapitalProtections` | `src/risk/` | +| API gateway + middleware | `src/api/main.py` | +| DTOs / contrato Pydantic | `src/api/schemas.py` | +| Rotas `/v1/*` | `src/api/routes/*.py` | +| Providers de dependência | `src/api/deps.py` | +| Dashboard | `src/dashboard/app.py` | +| Console React + `CT_API` | `docs/design/pages/*.jsx`, `apiClient.js` | +| **Contrato TS gerado** | `docs/design/pages/openapi.d.ts` | +| Topologias de deploy | `docker-compose*.yml`, `deploy/`, `Dockerfile` | + +--- + +> **Documento gerado por análise estática recursiva de todo o repositório.** Diagramas em **Mermaid** (renderizam nativamente no GitHub) e **PlantUML** (casos de uso e implantação — requerem renderizador PlantUML). Nomes de classes/módulos preservados *case-sensitive* conforme o código-fonte.